bills_controller.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /**
  2. * Created by jimiz on 2017/4/7.
  3. */
  4. let mongoose = require('mongoose');
  5. var billsData = require('../models/bills');
  6. let ration_model = require('../models/ration');
  7. let ProjectsData = require('../../pm/models/project_model').project;
  8. let logger = require("../../../logs/log_helper").logger;
  9. let quantity_detail = require("../facade/quantity_detail_facade");
  10. let bill_detail = require("../facade/bill_facade");
  11. let ration_glj = mongoose.model('ration_glj');
  12. let ration_coe = mongoose.model('ration_coe');
  13. let rationInstallationModel = mongoose.model('ration_installation');
  14. import fixedFlag from '../../common/const/bills_fixed';
  15. const uuidV1 = require('uuid/v1');
  16. const billType ={
  17. DXFY:1,//大项费用
  18. FB:2,//分部
  19. FX:3,//分项
  20. BILL:4,//清单
  21. BX:5//补项
  22. };
  23. // 上传控件
  24. const multiparty = require("multiparty");
  25. const fs = require("fs");
  26. // excel解析
  27. const excel = require("node-xlsx");
  28. //统一回调函数
  29. var callback = function(req, res, err, message, data){
  30. res.json({error: err, message: message, data: data});
  31. };
  32. module.exports = {
  33. getData: function(req, res){
  34. var data = JSON.parse(req.body.data);
  35. billsData.getData(data.projectId, function(err, message, billsList){
  36. if (err === 0) {
  37. callback(req, res, err, message, billsList);
  38. } else {
  39. callback(req, res, err, message, null);
  40. }
  41. });
  42. },
  43. getItemTemplate: function(req, res){
  44. //var data = JSON.parse(req.body.data);
  45. billsData.getItemTemplate(function(err, message, billsItem){
  46. if (billsItem) {
  47. callback(req, res, err, message, billsItem);
  48. } else {
  49. callback(req, res, err, message, null);
  50. }
  51. });
  52. },
  53. allocIDs: function(req, res){
  54. billsData.allocIDs(function(err, message, data){
  55. if (err) {
  56. callback(req, res, err, message, data);
  57. } else {
  58. callback(req, res, err, message, null);
  59. }
  60. });
  61. },
  62. //zhong 2017-9-1
  63. updateCharacterContent: function (req, res) {
  64. let data = JSON.parse(req.body.data);
  65. let findSet = data.findSet,
  66. updateObj = data.updateObj,
  67. txtObj = data.txtObj;
  68. billsData.updateCharacterContent(findSet, updateObj, txtObj, function (err, message) {
  69. callback(req, res, err, message, null);
  70. });
  71. },
  72. updateBill: async function(request, response) {
  73. const data = JSON.parse(request.body.data);
  74. const findSet = data.findSet;
  75. const updateData = data.updateData;
  76. let settingData = {};
  77. // 筛选出要保存在项目属性的设置
  78. for (const index in updateData) {
  79. if (updateData[index].field === 'addRule') {
  80. settingData = updateData[index].value;
  81. delete updateData[index];
  82. }
  83. }
  84. // 更新项目属性
  85. const propertyUpdateData = {
  86. property: 'addRule',
  87. data: settingData
  88. };
  89. const projectResult = await ProjectsData.updateProjectProperty(findSet.projectID, propertyUpdateData);
  90. const result = await billsData.updateBill(findSet, updateData);
  91. const message = !result || !projectResult ? '修改失败' : '修改成功';
  92. const err = !result || !projectResult ? 1 : 0;
  93. callback(request, response, err, message, null);
  94. },
  95. singleDelete:async function(req, res){
  96. let result={
  97. error:0
  98. }
  99. try {
  100. let data = req.body.data;
  101. data = JSON.parse(data);
  102. let tasks = generateSingleDeleteTasks(data);
  103. let resultData= await billsData.model.bulkWrite(tasks);
  104. //删除工程量明细
  105. await quantity_detail.deleteByQuery({projectID: data.projectID, billID: data.ID}) ;
  106. result.data=resultData;
  107. }catch (err){
  108. logger.err(err);
  109. result.error=1;
  110. result.message = err.message;
  111. }
  112. res.json(result);
  113. },
  114. multiDelete:async function(req, res){
  115. let result={
  116. error:0
  117. };
  118. try {
  119. let data = req.body.data;
  120. data = JSON.parse(data);
  121. result.data=await doBillsOrRationsDelete(data);
  122. }catch (err){
  123. logger.err(err);
  124. result.error=1;
  125. result.message = err.message;
  126. }
  127. res.json(result);
  128. },
  129. getSectionInfo:async function(req, res){
  130. let result={
  131. error:0
  132. }
  133. try {
  134. let data = req.body.data;
  135. data = JSON.parse(data);
  136. let sectionInfo= await bill_detail.getSectionInfo(data);
  137. result.data=sectionInfo;
  138. }catch (err){
  139. logger.err(err);
  140. result.error=1;
  141. result.message = err.message;
  142. }
  143. res.json(result);
  144. },
  145. reorganizeFBFX:async function(req,res){
  146. let result={
  147. error:0
  148. }
  149. try {
  150. let data = req.body.data;
  151. data = JSON.parse(data);
  152. let reorganizeResult= await bill_detail.reorganizeFBFX(data);
  153. result.data=reorganizeResult;
  154. }catch (err){
  155. logger.err(err);
  156. result.error=1;
  157. result.message = err.message;
  158. }
  159. res.json(result);
  160. },
  161. upload: async function(req, res){
  162. let responseData = {
  163. err: 0,
  164. msg: '',
  165. data: null
  166. };
  167. const allowHeader = ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
  168. const uploadOption = {
  169. uploadDir: './public'
  170. };
  171. const form = new multiparty.Form(uploadOption);
  172. form.parse(req, async function(err, fields, files) {
  173. try{
  174. const projectID = fields.projectID !== undefined && fields.projectID.length > 0 ?
  175. parseInt(fields.projectID[0]) : 0;
  176. if (projectID <= 0) {
  177. throw '参数错误';
  178. }
  179. const file = files.file !== undefined ? files.file[0] : null;
  180. if (err || file === null) {
  181. throw '上传失败';
  182. }
  183. // 判断类型
  184. if (file.headers['content-type'] === undefined || allowHeader.indexOf(file.headers['content-type']) < 0) {
  185. throw '不支持该类型';
  186. }
  187. // 重命名文件名
  188. const uploadFullName = uploadOption.uploadDir + '/' + file.originalFilename;
  189. fs.renameSync(file.path, uploadFullName);
  190. const sheet = excel.parse(uploadFullName);
  191. if (sheet[0] === undefined || sheet[0].data === undefined) {
  192. throw 'excel没有对应数据';
  193. }
  194. //导入的数据是否含有固定行(分部分项、施工技术措施项目、施工组织措施项目)
  195. let flag = getImportFlag(file.originalFilename);
  196. if(!flag){
  197. throw 'excel数据错误';
  198. }
  199. let fixedBill = await billsData.model.findOne({projectID: projectID, 'flags.flag': flag, deleteInfo: null});
  200. let insertFixedBill = null;
  201. //导入xx措施项目,若不存在此固定清单,则先插入相关固定清单
  202. if(!fixedBill){
  203. //分部分项工程(不可删除)应存在
  204. if(flag === fixedFlag.SUB_ENGINERRING){
  205. throw '项目不存在分部分项工程'
  206. }
  207. //措施项目是否存在
  208. let csxm = await billsData.model.findOne({projectID: projectID, 'flags.flag': fixedFlag.MEASURE, deleteInfo: null});
  209. if(!csxm){
  210. throw '项目不存在措施项目'
  211. }
  212. //插入清单固定行(施工技术措施项目、施工组织措施项目可删除)
  213. insertFixedBill = {projectID: projectID, name: flag === fixedFlag.CONSTRUCTION_TECH ? '施工技术措施项目' : '施工组织措施项目', code: '1',
  214. ID: uuidV1(), NextSiblingID: -1, ParentID: csxm.ID, flags: [{fieldName: 'fixed', flag: flag}], type: billType.BILL};
  215. //更新前节点
  216. let preDatas = await billsData.model.find({projectID: projectID, ParentID: csxm.ID, deleteInfo: null});
  217. for(let preData of preDatas){
  218. if(preData.NextSiblingID == -1){
  219. await billsData.model.update({ID: preData.ID}, {$set: {NextSiblingID: insertFixedBill.ID}});
  220. break;
  221. }
  222. }
  223. await billsData.model.create(insertFixedBill);
  224. fixedBill = insertFixedBill;
  225. }
  226. //将excel数据转换成清单树结构数据
  227. let insertDatas = parseToBillData(getValidImportData(sheet[0].data), getColMapping(sheet[0].data), fixedBill, projectID);
  228. //删除相关数据
  229. let deleteDatas = await billsData.deepDeleteBill([fixedBill], req.session.sessionUser.ssoId);
  230. //新增清单数据
  231. await billsData.importBills(insertDatas);
  232. //返回数据以更新前端
  233. if(insertFixedBill){
  234. insertDatas.push(insertFixedBill);
  235. }
  236. responseData.data = {fixedBill: fixedBill, insert: {bill: insertDatas, ration: []}, remove: {bill: deleteDatas.bill, ration: deleteDatas.ration}};
  237. //删除暂存文件
  238. fs.unlink(uploadFullName);
  239. res.json(responseData);
  240. }
  241. catch (error){
  242. responseData.err = 1;
  243. responseData.msg = error;
  244. res.json(responseData);
  245. }
  246. });
  247. }
  248. };
  249. //提取excel表头列对应数据
  250. function getColMapping(sheetData){
  251. //获取表头
  252. let headRow = [];
  253. for(let rData of sheetData) {
  254. if (rData[0] && rData[0].toString().replace(/\s/g, '') === '序号') {
  255. headRow = rData;
  256. break;
  257. }
  258. }
  259. //获取表头列与列号对应关系
  260. let colMapping = {};
  261. for(let c = 0; c < headRow.length; c++){
  262. if(headRow[c]){
  263. headRow[c] = headRow[c].toString().replace(/\s/g, '');
  264. switch(headRow[c]){
  265. case '序号': colMapping.serialNo = c; break;
  266. case '项目编码': colMapping.code = c; break;
  267. case '项目名称': colMapping.name = c; break;
  268. case '项目特征': colMapping.itemCharacterText = c; break;
  269. case '计量单位': colMapping.unit = c; break;
  270. case '工程量': colMapping.quantity = c; break;
  271. }
  272. }
  273. }
  274. return colMapping;
  275. }
  276. function rowExistData(rowData){
  277. for(let cData of rowData){
  278. if(cData !== undefined && cData !== ''){
  279. return true;
  280. }
  281. }
  282. return false;
  283. }
  284. //提取excel表数据中的有效数据(去表头表尾,提取其中的excel数据)
  285. function getValidImportData(sheetData){
  286. let withingD = false;
  287. let validData = [];
  288. for(let r = 0; r < sheetData.length; r++){
  289. let rData = sheetData[r];
  290. if(rData[0]){
  291. //首列去空格
  292. rData[0] = rData[0].toString().replace(/\s/g, '');
  293. //表头
  294. if(rData[0] === '序号'){
  295. withingD = true;
  296. r++;
  297. continue;
  298. }
  299. //表尾
  300. else if(rData[0] === '本页小计' || rData[0] === '合计'){
  301. withingD = false;
  302. }
  303. }
  304. if(withingD && rowExistData(rData)){
  305. validData.push(rData);
  306. }
  307. }
  308. return validData;
  309. }
  310. function getImportFlag(sheetName){
  311. const fixedItem = {'分部分项': fixedFlag.SUB_ENGINERRING, '施工技术措施项目': fixedFlag.CONSTRUCTION_TECH, '施工组织措施项目': fixedFlag.CONSTRUCTION_ORGANIZATION};
  312. for(let flag in fixedItem){
  313. if(sheetName.indexOf(flag) > 0){
  314. return fixedItem[flag];
  315. }
  316. }
  317. return null;
  318. }
  319. //excel数据转换成清单数据
  320. function parseToBillData(validData, colMapping, fixedBill, projectID){
  321. let rst = [];
  322. let billIdx = {};
  323. let preRootID = -1,
  324. preLeafID = -1,
  325. preID = -1;
  326. //合并了项目特征,且行有数据
  327. function isRoot(rData){
  328. return rData[colMapping.itemCharacterText] !== undefined && rData[colMapping.itemCharacterText] === '' && rowExistData(rData);
  329. }
  330. //不合并且有序号
  331. function isLeaf(rData){
  332. return (rData[colMapping] === undefined || rData[colMapping] !== '') && rData[colMapping.serialNo] && rData[colMapping.serialNo] !== '';
  333. }
  334. //续数据,上一行数据是有效节点且无序号
  335. function isExtend(preData, rData){
  336. return preData && (isRoot(preData) || isLeaf(preData)) && (rData[colMapping.serialNo] === undefined || rData[colMapping.serialNo] === '');
  337. }
  338. function getBillType(rData, flag){
  339. if(flag === fixedFlag.CONSTRUCTION_TECH || flag === fixedFlag.CONSTRUCTION_ORGANIZATION){
  340. return billType.BILL;
  341. }
  342. else if(flag === fixedFlag.SUB_ENGINERRING){
  343. return isLeaf(rData) ? billType.FX : billType.FB;
  344. }
  345. return null;
  346. }
  347. for(let r = 0; r < validData.length; r++){
  348. let preData = validData[r-1],
  349. rData = validData[r];
  350. if(fixedBill.flags[0].flag == fixedFlag.CONSTRUCTION_TECH && rData[colMapping.name] === '施工技术措施项目'
  351. || fixedBill.flags[0].flag == fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] === '施工组织措施项目'){
  352. continue;
  353. }
  354. //过滤无效数据
  355. if(!isRoot(rData) && !isLeaf(rData) && !isExtend(preData, rData)){
  356. continue;
  357. }
  358. if(isExtend(preData, rData)){
  359. let preBill = billIdx[preID];
  360. //合并续数据
  361. if(preBill){
  362. preBill.code += rData[colMapping.code] ? rData[colMapping.code] : '';
  363. preBill.name += rData[colMapping.name] ? rData[colMapping.name] : '';
  364. preBill.itemCharacterText += rData[colMapping.itemCharacterText] ? rData[colMapping.itemCharacterText] : '';
  365. preBill.unit += rData[colMapping.unit] ? rData[colMapping.unit] : '';
  366. preBill.quantity += rData[colMapping.quantity] ? rData[colMapping.quantity] : '';
  367. }
  368. }
  369. else {
  370. let newID = uuidV1();
  371. let pID = -1;
  372. let preBill = null;
  373. if(isRoot(rData)){
  374. pID = fixedBill.ID;
  375. preBill = billIdx[preRootID];
  376. }
  377. else if(isLeaf(rData)){
  378. pID = preRootID !== -1 ? preRootID : fixedBill.ID;
  379. preBill = billIdx[preLeafID];
  380. }
  381. //set bill data
  382. billIdx[newID] = {
  383. ID: newID, ParentID: pID, NextSiblingID: -1,
  384. code: rData[colMapping.code] ? rData[colMapping.code] : '',
  385. name: rData[colMapping.name] ? rData[colMapping.name] : '',
  386. itemCharacterText: rData[colMapping.itemCharacterText] ? rData[colMapping.itemCharacterText] : '',
  387. itemCharacter: [],
  388. jobContentText: '',
  389. jobContent: [],
  390. unit: rData[colMapping.unit] ? rData[colMapping.unit] : '',
  391. quantity: rData[colMapping.quantity] ? rData[colMapping.quantity] : '',
  392. flags: fixedBill.flags[0].flag === fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] === '安全文明施工专项费用' ?
  393. [{fieldName: 'fixed', flag: fixedBill.flags[0].flag}] : [],
  394. fees: [],
  395. projectID: projectID,
  396. type: getBillType(rData, fixedBill.flags[0].flag)};
  397. //update preBill NextSibling
  398. if(preBill){
  399. preBill.NextSiblingID = newID;
  400. }
  401. //set new preID
  402. preID = newID;
  403. preRootID = isRoot(rData) ? newID : preRootID;
  404. preLeafID = isLeaf(rData) ? newID : preLeafID;
  405. }
  406. }
  407. for(let i in billIdx){
  408. rst.push(billIdx[i]);
  409. }
  410. return rst;
  411. }
  412. async function doBillsOrRationsDelete(data) {
  413. let billTask = [];
  414. let deleteBillIDs = [];
  415. let rationTask=[];
  416. let deleteRationIDs=[];
  417. let qd_query=null;
  418. let sub_query=null;
  419. if(data['bills']){
  420. billTask = generateUpdateTasks(data['bills'],data.projectID,data.user_id);
  421. for(let b_key in data['bills']){
  422. if(data['bills'][b_key]===true){
  423. deleteBillIDs.push(b_key+'');
  424. }
  425. }
  426. if(deleteBillIDs.length>0){
  427. qd_query={projectID: data.projectID, billID: {"$in": deleteBillIDs}};
  428. }
  429. }
  430. if(data['ration']){
  431. rationTask = generateUpdateTasks(data['ration'],data.projectID,data.user_id);
  432. for(let r_key in data['ration']){
  433. if(data['ration'][r_key]===true){
  434. deleteRationIDs.push(r_key+'');
  435. }
  436. }
  437. if(deleteRationIDs.length>0){
  438. if(qd_query==null){//说明没删除清单
  439. qd_query={projectID: data.projectID, rationID: {"$in": deleteRationIDs}};
  440. }else {
  441. qd_query={
  442. "$or":[
  443. {projectID: data.projectID, billID: {"$in": deleteBillIDs}},
  444. {projectID: data.projectID, rationID: {"$in": deleteRationIDs}}
  445. ]
  446. }
  447. }
  448. sub_query={projectID: data.projectID, rationID: {"$in": deleteRationIDs}};
  449. }
  450. }
  451. //先删除工程量明细
  452. if(qd_query!=null){
  453. await quantity_detail.deleteByQuery(qd_query) ;
  454. }
  455. if(sub_query!=null){
  456. await ration_coe.deleteMany(sub_query);//删除附注条件
  457. await ration_glj.deleteMany(sub_query);//删除定额工料机
  458. await rationInstallationModel.deleteMany(sub_query);//删除安装增加费
  459. }
  460. if(rationTask.length>0){
  461. await ration_model.model.bulkWrite(rationTask);//删除定额
  462. }
  463. if(billTask.length>0){
  464. await billsData.model.bulkWrite(billTask);//删除清单
  465. }
  466. return 'success';
  467. }
  468. function generateSingleDeleteTasks(data) {
  469. let updateData = data.updateData;
  470. updateData[data.ID]=true;
  471. let tasks = generateUpdateTasks(updateData,data.projectID,data.user_id);
  472. return tasks;
  473. }
  474. function generateUpdateTasks(data,projectID,user_id) {
  475. let tasks=[];
  476. let updateData = data;
  477. let deleteInfo={deleted: true, deleteDateTime: new Date(), deleteBy: user_id};
  478. for(let key in updateData){
  479. let task={
  480. updateOne:{
  481. filter:{
  482. ID:key,
  483. projectID:projectID
  484. }
  485. }
  486. };
  487. if(updateData[key]===true){
  488. task.updateOne.update={
  489. deleteInfo:deleteInfo
  490. };
  491. }else {
  492. task.updateOne.update=updateData[key];
  493. }
  494. tasks.push(task);
  495. }
  496. return tasks;
  497. }