bills_controller.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  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_facade = require("../facade/bill_facade");
  11. let raiton_facade = require("../facade/ration_facade");
  12. let stdBillsModel = mongoose.model('std_bills_lib_bills');
  13. let stdBillJobsModel = mongoose.model('std_bills_lib_jobContent');
  14. let stdBillCharacterModel = mongoose.model('std_bills_lib_itemCharacter');
  15. const { fixedFlag, billType, rationType } = require('../../../public/common_constants');
  16. let LZString = require('lz-string');
  17. const uuidV1 = require('uuid/v1');
  18. //上传的09表、广联达表
  19. const uploadType = {lj: 'lj', gld: 'gld'};
  20. // 上传控件
  21. const multiparty = require("multiparty");
  22. const fs = require("fs");
  23. // excel解析
  24. const excel = require("node-xlsx");
  25. //统一回调函数
  26. var callback = function(req, res, err, message, data){
  27. res.json({error: err, message: message, data: data});
  28. };
  29. module.exports = {
  30. getData: function(req, res){
  31. var data = JSON.parse(req.body.data);
  32. billsData.getData(data.projectId, function(err, message, billsList){
  33. if (err === 0) {
  34. callback(req, res, err, message, billsList);
  35. } else {
  36. callback(req, res, err, message, null);
  37. }
  38. });
  39. },
  40. getItemTemplate: function(req, res){
  41. //var data = JSON.parse(req.body.data);
  42. billsData.getItemTemplate(function(err, message, billsItem){
  43. if (billsItem) {
  44. callback(req, res, err, message, billsItem);
  45. } else {
  46. callback(req, res, err, message, null);
  47. }
  48. });
  49. },
  50. allocIDs: function(req, res){
  51. billsData.allocIDs(function(err, message, data){
  52. if (err) {
  53. callback(req, res, err, message, data);
  54. } else {
  55. callback(req, res, err, message, null);
  56. }
  57. });
  58. },
  59. //zhong 2017-9-1
  60. updateCharacterContent: function (req, res) {
  61. let data = JSON.parse(req.body.data);
  62. let findSet = data.findSet,
  63. updateObj = data.updateObj,
  64. txtObj = data.txtObj;
  65. billsData.updateCharacterContent(findSet, updateObj, txtObj, function (err, message) {
  66. callback(req, res, err, message, null);
  67. });
  68. },
  69. updateBills: async function (req, res) {
  70. try{
  71. let data = JSON.parse(req.body.data);
  72. await billsData.updateBills(data.updateDatas);
  73. callback(req, res, 0, 'success', null);
  74. }
  75. catch (err){
  76. callback(req, res, 1, err, null);
  77. }
  78. },
  79. updateBill: async function(request, response) {
  80. const data = JSON.parse(request.body.data);
  81. const findSet = data.findSet;
  82. const updateData = data.updateData;
  83. let settingData = {};
  84. // 筛选出要保存在项目属性的设置
  85. for (const index in updateData) {
  86. if (updateData[index].field === 'addRule') {
  87. settingData = updateData[index].value;
  88. delete updateData[index];
  89. }
  90. }
  91. /* // 更新项目属性
  92. const propertyUpdateData = {
  93. property: 'addRule',
  94. data: settingData
  95. };*/
  96. //const projectResult = await ProjectsData.updateProjectProperty(findSet.projectID, propertyUpdateData);
  97. const result = await billsData.updateBill(findSet, updateData);
  98. const message = !result ? '修改失败' : '修改成功';
  99. const err = !result ? 1 : 0;
  100. callback(request, response, err, message, null);
  101. },
  102. singleDelete:async function(req, res){
  103. let result={
  104. error:0
  105. }
  106. try {
  107. let data = req.body.data;
  108. data = JSON.parse(data);
  109. let tasks = generateSingleDeleteTasks(data);
  110. let resultData= await billsData.model.bulkWrite(tasks);
  111. //删除工程量明细
  112. await quantity_detail.deleteByQuery({projectID: data.projectID, billID: data.ID}) ;
  113. result.data=resultData;
  114. }catch (err){
  115. logger.err(err);
  116. result.error=1;
  117. result.message = err.message;
  118. }
  119. res.json(result);
  120. },
  121. multiDelete:async function(req, res){
  122. let result={
  123. error:0
  124. };
  125. try {
  126. let data = req.body.data;
  127. data = JSON.parse(data);
  128. result.data=await doBillsOrRationsDelete(data);
  129. }catch (err){
  130. logger.err(err);
  131. result.error=1;
  132. result.message = err.message;
  133. }
  134. res.json(result);
  135. },
  136. getSectionInfo:async function(req, res){
  137. let result={
  138. error:0
  139. }
  140. try {
  141. let data = req.body.data;
  142. data = JSON.parse(data);
  143. let sectionInfo= await bill_facade.getSectionInfo(data);
  144. result.data=sectionInfo;
  145. }catch (err){
  146. logger.err(err);
  147. result.error=1;
  148. result.message = err.message;
  149. }
  150. res.json(result);
  151. },
  152. reorganizeFBFX:async function(req,res){
  153. let result={
  154. error:0
  155. }
  156. try {
  157. let data = req.body.data;
  158. data = JSON.parse(data);
  159. let reorganizeResult= await bill_facade.reorganizeFBFX(data);
  160. result.data=reorganizeResult;
  161. }catch (err){
  162. logger.err(err);
  163. result.error=1;
  164. result.message = err.message;
  165. }
  166. res.json(result);
  167. },
  168. pasteBlock:async function(req,res){
  169. let result={
  170. error:0
  171. };
  172. try {
  173. let data = req.body.data;
  174. data = JSON.parse(data);
  175. let pasteResult = await bill_facade.pasteBlock(data,req.session.sessionCompilation);
  176. result.data = pasteResult;
  177. }catch (err){
  178. logger.err(err);
  179. result.error=1;
  180. result.message = err.message;
  181. }
  182. res.json(result);
  183. },
  184. //下载导入清单示例
  185. downloadExample: function(request, response) {
  186. try {
  187. //导入类型(09表、广联达)对应的示例文件名
  188. const uploadTypeMap = { general: '示例1-分部分项工程项目清单.xlsx', lj: '示例2-分部分项工程项目清单计价表.xlsx', gld: '算量示例-清单汇总表.xlsx' };
  189. const type = request.query.type;
  190. const fileName = uploadTypeMap[type];
  191. const filePath = `./public/static/${fileName}`;
  192. const stats = fs.statSync(filePath);
  193. // 下载相关header
  194. response.set({
  195. 'Content-Type': 'application/octet-stream',
  196. 'Content-Disposition': `attachment; filename=${encodeURI(fileName)}`,
  197. 'Content-Length': stats.size
  198. });
  199. fs.createReadStream(filePath).pipe(response);
  200. } catch (error) {
  201. response.end(error);
  202. }
  203. },
  204. //导入清单
  205. import: async function(req, res){
  206. let responseData = {
  207. err: 0,
  208. msg: '',
  209. data: []
  210. };
  211. const form = new multiparty.Form();
  212. form.parse(req, async function(err, fields, files) {
  213. try{
  214. const projectID = fields.projectID !== undefined && fields.projectID.length > 0 ?
  215. parseInt(fields.projectID[0]) : 0;
  216. if (projectID <= 0) {
  217. throw '参数错误';
  218. }
  219. //导入清单数据
  220. let compressData = fields.compressData !== undefined && fields.compressData.length > 0 ?
  221. fields.compressData[0] : null;
  222. if(compressData === null){
  223. throw 'excel没有对应数据'
  224. }
  225. const date1 = Date.now();
  226. let importData = JSON.parse(LZString.decompressFromUTF16(compressData));
  227. //匹配的清单库
  228. const date2 = Date.now();
  229. console.log(date2 - date1, 'decompressFromUTF16');
  230. const billsLibId = fields.billsLibId !== undefined && fields.billsLibId.length > 0 && fields.billsLibId[0]? parseInt(fields.billsLibId[0]) : null;
  231. let stdBills = [], stdJobs = [], stdCharacters = [];
  232. const stdBillMap = {};
  233. const stdJobMap = {};
  234. const stdCharacterMap = {};
  235. if(billsLibId){
  236. stdBills = await stdBillsModel.find({billsLibId: billsLibId}, '-_id code jobs items engineering billsLibId ruleText');
  237. stdJobs = await stdBillJobsModel.find({billsLibId: billsLibId});
  238. stdCharacters = await stdBillCharacterModel.find({billsLibId: billsLibId});
  239. // 标准数据映射
  240. stdBills.forEach(bill => {
  241. stdBillMap[bill.code] = bill;
  242. });
  243. stdJobs.forEach(job => {
  244. stdJobMap[job.id] = job;
  245. });
  246. stdCharacters.forEach(character => {
  247. stdCharacterMap[character.id] = character;
  248. });
  249. }
  250. let stdData = {stdBills: stdBills, stdJobs: stdJobs, stdCharacters: stdCharacters, stdBillMap, stdJobMap, stdCharacterMap};
  251. console.log(Date.now() - date2, 'getStd');
  252. //导入表
  253. let importDateA = +new Date();
  254. for(let position in importData){
  255. if(importData[position] && importData[position].insert && importData[position].insert.length > 0){
  256. let updateFrontData = await importSheet(position, importData[position].insert, importData[position].remove, projectID, stdData);
  257. if(updateFrontData){
  258. responseData.data.push(updateFrontData);
  259. }
  260. }
  261. }
  262. let importDateB = +new Date();
  263. console.log(`导入时间: ${importDateB - importDateA}=========================================================================`);
  264. res.json(responseData);
  265. }
  266. catch (error){
  267. responseData.err = 1;
  268. console.log(error);
  269. responseData.msg = typeof error === 'object' ? '上传失败' : error;
  270. res.json(responseData);
  271. }
  272. });
  273. },
  274. insertBills: async function (req, res) {
  275. let data = JSON.parse(req.body.data);
  276. try {
  277. await bill_facade.insertBills(data.postData);
  278. callback(req, res, 0, 'success', null);
  279. } catch (err) {
  280. callback(req, res, 1, err, null);
  281. }
  282. }
  283. };
  284. async function importSheet(position, excelBills, removeData, projectID, stdData){
  285. //导入位置的有固定行
  286. let flag = getImportFlag(position);
  287. if(!flag){
  288. throw 'excel数据错误';
  289. }
  290. let fixedBill = await billsData.model.findOne({projectID: projectID, 'flags.flag': flag, deleteInfo: null});
  291. let insertFixedBill = null;
  292. //导入xx措施项目,若不存在此固定清单,则先插入相关固定清单
  293. if(!fixedBill){
  294. //分部分项工程(不可删除)应存在
  295. if(flag === fixedFlag.SUB_ENGINERRING){
  296. throw '项目不存在分部分项工程'
  297. }
  298. //措施项目是否存在
  299. let csxm = await billsData.model.findOne({projectID: projectID, 'flags.flag': fixedFlag.MEASURE, deleteInfo: null});
  300. if(!csxm){
  301. throw '项目不存在措施项目'
  302. }
  303. //插入清单固定行(施工技术措施项目、施工组织措施项目可删除)
  304. insertFixedBill = {projectID: projectID, name: flag === fixedFlag.CONSTRUCTION_TECH ? '施工技术措施项目' : '施工组织措施项目', code: '1',
  305. ID: uuidV1(), NextSiblingID: -1, ParentID: csxm.ID, flags: [{fieldName: 'fixed', flag: flag}], type: billType.BILL};
  306. //更新前节点
  307. let preDatas = await billsData.model.find({projectID: projectID, ParentID: csxm.ID, deleteInfo: null});
  308. for(let preData of preDatas){
  309. if(preData.NextSiblingID == -1){
  310. await billsData.model.update({ID: preData.ID}, {$set: {NextSiblingID: insertFixedBill.ID}});
  311. break;
  312. }
  313. }
  314. await billsData.model.create(insertFixedBill);
  315. fixedBill = insertFixedBill;
  316. }
  317. //将excel清单数据转换成完整清单数据(设置ParentID、匹配标准清单库)
  318. parseToCompleteBills(excelBills, fixedBill, stdData);
  319. //删除相关数据
  320. const date = Date.now();
  321. let deleteDatas = await billsData.deepDeleteBill(removeData.rBillIDs, removeData.rRationIDs);
  322. const date1 = Date.now();
  323. console.log(date1 - date, 'deepDeleteBill');
  324. //新增清单数据
  325. await billsData.importBills(excelBills);
  326. console.log(Date.now() - date1, 'importBills');
  327. // 如果导入招标、控制价文件、每个分项底下自动生成一条空定额
  328. const rations = [];
  329. excelBills.forEach(bills => {
  330. if (bills.type === billType.FX) {
  331. const emptyRation = {
  332. projectID: bills.projectID,
  333. ID: uuidV1(),
  334. billsItemID: bills.ID,
  335. serialNo: 1,
  336. type: rationType.ration,
  337. };
  338. rations.push(emptyRation);
  339. }
  340. });
  341. if (rations.length) {
  342. await ration_model.model.insertMany(rations);
  343. }
  344. //返回数据以更新前端
  345. if(insertFixedBill){
  346. excelBills.push(insertFixedBill);
  347. }
  348. return {fixedBill: fixedBill, insert: {bill: excelBills, ration: rations}, remove: {bill: deleteDatas.bill, ration: deleteDatas.ration}};
  349. }
  350. function getImportFlag(position){
  351. const fixedItem = {'fbfx': fixedFlag.SUB_ENGINERRING, 'jscsxm': fixedFlag.CONSTRUCTION_TECH, 'zzcsxm': fixedFlag.CONSTRUCTION_ORGANIZATION};
  352. return fixedItem[position] ? fixedItem[position] : null;
  353. }
  354. function isDef(data){
  355. return typeof data !== 'undefined' && data !== null && data !== '';
  356. }
  357. //将前端解析生成的清单节点数据完善(ParentID、匹配标准清单)
  358. function parseToCompleteBills(excelBills, fixedBills, stdData){
  359. //设置清单ParentID
  360. let rootID = fixedBills ? fixedBills.ID: -1;
  361. for(let bills of excelBills){
  362. if(bills.nodeType === 'root'){
  363. rootID = bills.ID;
  364. bills.ParentID = fixedBills.ID;
  365. }
  366. else {
  367. bills.ParentID = rootID;
  368. }
  369. delete bills.nodeType;
  370. const date = Date.now();
  371. matchStdBill(bills, stdData);
  372. console.log(Date.now() - date, 'matchStdBill');
  373. }
  374. //excel数据与标准库数据匹配,根据清单前九位编码匹配,匹配成功则获取标准清单对应的工程专业、特征及内容
  375. function matchStdBill(excelBill, stdData){
  376. let isMatch = false;
  377. if(excelBill.code.length >8){
  378. let nineCode = excelBill.code.substr(0, 9);
  379. const stdBill = stdData.stdBillMap[nineCode];
  380. if (stdBill) {
  381. isMatch = true;
  382. excelBill.programID = stdBill.engineering ? stdBill.engineering : null;
  383. excelBill.billsLibId = stdBill.billsLibId ? stdBill.billsLibId : null;
  384. excelBill.ruleText = stdBill.ruleText ? stdBill.ruleText : '';
  385. //set jobContent and itemCharacter
  386. let tempJob = [], tempCharacter = [];
  387. for(let billJob of stdBill.jobs){
  388. const stdJob = stdData.stdJobMap[billJob.id];
  389. if (stdJob) {
  390. tempJob.push({isChecked: false, serialNo: billJob.serialNo, content: stdJob.content});
  391. }
  392. }
  393. for(let billCharacter of stdBill.items){
  394. const stdCharacter = stdData.stdCharacterMap[billCharacter.id];
  395. if (stdCharacter) {
  396. let eigenvalue = [];
  397. for(let eValue of stdCharacter.itemValue){
  398. eigenvalue.push({isSelected: false, value: eValue.value});
  399. }
  400. tempCharacter.push({isChecked: false, serialNo: billCharacter.serialNo, character: stdCharacter.content, eigenvalue: eigenvalue});
  401. }
  402. }
  403. excelBill.jobContent = tempJob;
  404. excelBill.itemCharacter = tempCharacter;
  405. }
  406. }
  407. if(!isMatch && excelBill.type === billType.FX){//分项不为空,同时在标准清单中不匹配,则识别为补项
  408. excelBill.type = billType.BX;
  409. }
  410. }
  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 raiton_facade.deleteSubListByQuery(sub_query);
  457. }
  458. if(rationTask.length>0){
  459. await ration_model.model.bulkWrite(rationTask);//删除定额
  460. }
  461. if(billTask.length>0){
  462. await billsData.model.bulkWrite(billTask);//删除清单
  463. }
  464. return 'success';
  465. }
  466. function generateSingleDeleteTasks(data) {
  467. let updateData = data.updateData;
  468. updateData[data.ID]=true;
  469. let tasks = generateUpdateTasks(updateData,data.projectID,data.user_id);
  470. return tasks;
  471. }
  472. function generateUpdateTasks(data,projectID,user_id) {
  473. let tasks=[];
  474. let updateData = data;
  475. let deleteInfo={deleted: true, deleteDateTime: new Date(), deleteBy: user_id};
  476. for(let key in updateData){
  477. let task={
  478. updateOne:{
  479. filter:{
  480. ID:key,
  481. projectID:projectID
  482. }
  483. }
  484. };
  485. if(updateData[key]===true){
  486. //原先是假删除,现在改成真删除
  487. task = {
  488. deleteOne:{
  489. filter:{
  490. ID:key,
  491. projectID:projectID
  492. }
  493. }
  494. }
  495. /* task.updateOne.update={
  496. deleteInfo:deleteInfo
  497. };*/
  498. }else {
  499. task.updateOne.update=updateData[key];
  500. }
  501. tasks.push(task);
  502. }
  503. return tasks;
  504. }