ledger_controller.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086
  1. 'use strict';
  2. /**
  3. * 台账相关控制器
  4. *
  5. * @author CaiAoLin
  6. * @date 2017/11/30
  7. * @version
  8. */
  9. const stdDataAddType = {
  10. withParent: 1,
  11. child: 2,
  12. next: 3,
  13. };
  14. const audit = require('../const/audit');
  15. const moment = require('moment');
  16. const auditConst = audit.ledger;
  17. const tenderMenu = require('../../config/menu').tenderMenu;
  18. const measureType = require('../const/tender').measureType;
  19. const spreadConst = require('../const/spread');
  20. const shenpiConst = require('../const/shenpi');
  21. const externalDataConst = require('../const/external_data.js');
  22. const fs = require('fs');
  23. const LzString = require('lz-string');
  24. const accountGroup = require('../const/account_group').group;
  25. const path = require('path');
  26. const exportExcel = require('../lib/export_excel');
  27. const billsPosConvert = require('../lib/bills_pos_convert');
  28. const xlsx = require('js-xlsx');
  29. const stdConst = require('../const/standard');
  30. const sendToWormhole = require('stream-wormhole');
  31. module.exports = app => {
  32. class LedgerController extends app.BaseController {
  33. /**
  34. * 构造函数
  35. *
  36. * @param {Object} ctx - egg全局变量
  37. * @return {void}
  38. */
  39. constructor(ctx) {
  40. super(ctx);
  41. ctx.showProject = true;
  42. ctx.showTitle = true;
  43. ctx.showTender = true;
  44. }
  45. /**
  46. * 检查标段是否只读(审核中,审核完成)
  47. * @param {Object} tenderData
  48. * @return {boolean}
  49. * @private
  50. */
  51. _ledgerReadOnly() {
  52. const tender = this.ctx.tender.data;
  53. return tender.user_id !== this.ctx.session.sessionUser.accountId ||
  54. (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked);
  55. }
  56. _canUpdateDealBills(tender, auditors) {
  57. const isUser = tender.user_id === this.ctx.session.sessionUser.accountId;
  58. const auditorsId = this.ctx.helper._.map(auditors, 'audit_id');
  59. const isAuditor = auditorsId.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
  60. const upPermission = this.ctx.session.sessionUser.permission && this.ctx.session.sessionUser.permission.tender
  61. ? this.ctx.session.sessionUser.permission.tender.indexOf('3') >= 0
  62. : false;
  63. return ((tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) && isUser) ||
  64. (tender.ledger_status === auditConst.status.checking && isAuditor) ||
  65. (tender.ledger_status === auditConst.status.checked && isAuditor && upPermission);
  66. }
  67. /**
  68. * 获取SpreadSetting
  69. * @private
  70. */
  71. _getSpreadSetting() {
  72. const _ = this.app._;
  73. function removeFieldCols(setting, cols) {
  74. _.remove(setting.cols, function(c) {
  75. return cols.indexOf(c.field) > -1;
  76. });
  77. }
  78. function hideFieldCols(setting, cols) {
  79. for (const c of setting.cols) {
  80. if (cols.indexOf(c.field) > -1) {
  81. c.visible = false;
  82. }
  83. }
  84. }
  85. const tender = this.ctx.tender;
  86. const setting = tender.data.measure_type === measureType.tz.value
  87. ? (tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl)
  88. : (tender.info.display.ledger.clQty ? spreadConst.withClGcl : spreadConst.withoutClGcl);
  89. const ledger = JSON.parse(JSON.stringify(setting.ledger));
  90. const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank;
  91. if (this._ledgerReadOnly(tender.data)) {
  92. ledger.readOnly = true;
  93. pos.readOnly = true;
  94. }
  95. if (tender.data.measure_type === measureType.tz.value) {
  96. removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols);
  97. }
  98. if (!tender.info.display.ledger.dgnQty) {
  99. removeFieldCols(ledger, spreadConst.filterCols.dgnCols);
  100. }
  101. return [ledger, pos];
  102. }
  103. /**
  104. * 台账分解页面 (Get)
  105. *
  106. * @param {Object} ctx - egg全局变量
  107. * @return {void}
  108. */
  109. async explode(ctx) {
  110. try {
  111. const tender = ctx.tender;
  112. const [ledgerSpread, posSpread] = await this._getSpreadSetting();
  113. const sjsRela = await this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id);
  114. this.ctx.helper.refreshSpreadShow(sjsRela.ledgerCol, [ledgerSpread, posSpread]);
  115. const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
  116. const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
  117. const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
  118. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
  119. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
  120. const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
  121. const auditHistory = [];
  122. if (times >= 1) {
  123. for (let i = 1; i <= times; i++) {
  124. auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
  125. }
  126. }
  127. const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
  128. ctx.tender.data.valuation, ctx.tender.data.measure_type);
  129. // 获取附件列表
  130. const attData = await ctx.service.ledgerAtt.getDataByTenderId(ctx.tender.id);
  131. for (const index in attData) {
  132. attData[index].in_time = moment(attData[index].in_time * 1000).format('YYYY-MM-DD');
  133. attData[index].orginpath = ctx.app.config.fujianOssPath + attData[index].filepath;
  134. delete attData[index].filepath;
  135. }
  136. const whiteList = this.ctx.app.config.multipart.whitelist;
  137. const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
  138. const renderData = {
  139. tender: tender.data,
  140. tenderInfo: tender.info,
  141. auditConst,
  142. auditors,
  143. curAuditor,
  144. user,
  145. attData,
  146. whiteList,
  147. auditHistory,
  148. ledgerSpreadSetting: JSON.stringify(ledgerSpread),
  149. posSpreadSetting: JSON.stringify(posSpread),
  150. tenderMenu,
  151. preUrl: '/tender/' + tender.id,
  152. measureType,
  153. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
  154. stdBills,
  155. stdChapters,
  156. dealBillsPermission: this._canUpdateDealBills(tender.data, auditors.filter(x => {return x.audit_order > 0})),
  157. shenpiConst,
  158. categoryData,
  159. };
  160. if ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId) {
  161. // renderData.accountGroup = accountGroup;
  162. // 获取所有项目参与者
  163. const accountList = await ctx.service.projectAccount.getAllDataByCondition({
  164. where: { project_id: ctx.session.sessionProject.id, enable: 1 },
  165. columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
  166. });
  167. renderData.accountList = accountList;
  168. renderData.accountGroup = accountGroup.map((item, idx) => {
  169. const groupList = accountList.filter(item => item.account_group === idx);
  170. return { groupName: item, groupList };
  171. });
  172. renderData.auditorList = await ctx.service.ledgerAudit.getAuditors(tender.id, tender.data.ledger_times);
  173. }
  174. await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
  175. } catch (err) {
  176. ctx.helper.log(err);
  177. this.postError(err, '标段数据错误');
  178. // await this.redirect('/dashboard');
  179. ctx.redirect(ctx.request.header.referer);
  180. }
  181. }
  182. /**
  183. * 获取子节点 (Ajax)
  184. * @param ctx
  185. * @return {Promise<void>}
  186. */
  187. async getChildren(ctx) {
  188. const responseData = {
  189. err: 0,
  190. msg: '',
  191. data: [],
  192. };
  193. try {
  194. const data = JSON.parse(ctx.request.body.data);
  195. const id = data.ledger_id;
  196. if (isNaN(id) || id <= 0) {
  197. throw '参数错误';
  198. }
  199. responseData.data = await ctx.service.ledger.getChildrenByParentId(ctx.tender.id, id);
  200. } catch (err) {
  201. responseData.err = 1;
  202. responseData.msg = err;
  203. }
  204. ctx.body = responseData;
  205. }
  206. /**
  207. * 树结构基本操作(增、删、上下移、升降级)
  208. * @param {Object} ctx - egg全局变量
  209. * @return {Promise<void>}
  210. */
  211. async _base(ctx, type, data) {
  212. if (isNaN(data.id) || data.id <= 0) throw '数据错误';
  213. if (type !== 'add') {
  214. if (isNaN(data.count) || data.count <= 0) data.count = 1;
  215. }
  216. switch (type) {
  217. case 'add':
  218. return await ctx.service.ledger.addNodeBatch(ctx.tender.id, data.id, {}, data.count);
  219. case 'delete':
  220. return await ctx.service.ledger.delete(ctx.tender.id, data.id, data.count);
  221. case 'up-move':
  222. return await ctx.service.ledger.upMoveNode(ctx.tender.id, data.id, data.count);
  223. case 'down-move':
  224. return await ctx.service.ledger.downMoveNode(ctx.tender.id, data.id, data.count);
  225. case 'up-level':
  226. return await ctx.service.ledger.upLevelNode(ctx.tender.id, data.id, data.count);
  227. case 'down-level':
  228. return await ctx.service.ledger.downLevelNode(ctx.tender.id, data.id, data.count);
  229. }
  230. }
  231. /**
  232. * 复制粘贴整块
  233. *
  234. * @param ctx
  235. * @return {Promise<void>}
  236. */
  237. async _pasteBlock(ctx, data) {
  238. if ((isNaN(data.id) || data.id <= 0) ||
  239. (!data.tid && data.tid <= 0) ||
  240. (!data.block || data.block.length <= 0)) throw '参数错误';
  241. return await ctx.service.ledger.pasteBlockData(ctx.tender.id, data.id, data.block);
  242. }
  243. /**
  244. * 从标准项目表添加数据
  245. * @param ctx
  246. * @return {Promise<void>}
  247. */
  248. async _addStd(ctx, data) {
  249. if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
  250. // todo 校验项目是否使用该库的权限
  251. let stdLib,
  252. addType;
  253. switch (data.stdType) {
  254. case 'xmj':
  255. stdLib = ctx.service.stdXmj;
  256. addType = stdDataAddType.withParent;
  257. break;
  258. case 'gcl':
  259. stdLib = ctx.service.stdGcl;
  260. const selectNode = await ctx.service.ledger.getDataByNodeId(ctx.tender.id, data.id);
  261. if (selectNode.b_code) {
  262. addType = stdDataAddType.next;
  263. } else {
  264. addType = stdDataAddType.child;
  265. }
  266. break;
  267. default:
  268. throw '未知标准库';
  269. }
  270. const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
  271. switch (addType) {
  272. case stdDataAddType.child:
  273. return await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData);
  274. case stdDataAddType.next:
  275. return await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData);
  276. case stdDataAddType.withParent:
  277. return await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, stdData, stdLib);
  278. default:
  279. throw '未知添加方式';
  280. }
  281. }
  282. /**
  283. * 从签约清单添加节点
  284. * @param ctx
  285. * @return {Promise<void>}
  286. */
  287. async _addDeal(ctx, data) {
  288. if (!data.type || !data.dealBills) throw '数据错误';
  289. data.dealBills.unit_price = this.ctx.helper.round(data.dealBills.unit_price, ctx.tender.info.decimal.up);
  290. if (data.type === 'child') {
  291. return await ctx.service.ledger.addChild(ctx.tender.id, data.id, data.dealBills);
  292. } else if (data.type === 'next') {
  293. return await ctx.service.ledger.addNode(ctx.tender.id, data.id, data.dealBills);
  294. }
  295. throw '数据错误';
  296. }
  297. /**
  298. * 批量插入数据
  299. *
  300. * data = {id, batchData, batchType}
  301. * data.batchType = 'batchInsertChild'/'batchInsertNext'
  302. * data.batchData = [{name, children}] -- 项目节列表
  303. * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表
  304. *
  305. * @param ctx
  306. * @return {Promise<void>}
  307. */
  308. async _batchInsert(ctx, data) {
  309. if ((isNaN(data.id) || data.id <= 0) || !data.batchType) throw '参数错误';
  310. switch (data.batchType) {
  311. case 'child':
  312. return await ctx.service.ledger.batchInsertChild(ctx.tender.id, data.id, data.batchData);
  313. case 'next':
  314. return await ctx.service.ledger.batchInsertNext(ctx.tender.id, data.id, data.batchData);
  315. default:
  316. throw '参数错误';
  317. }
  318. }
  319. /**
  320. * 更新清单相关 (Ajax)
  321. * @param ctx
  322. * @return {Promise<void>}
  323. */
  324. async update(ctx) {
  325. try {
  326. if (!ctx.tender.data) throw '标段数据错误';
  327. if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作';
  328. const data = JSON.parse(ctx.request.body.data);
  329. if (!data.postType || !data.postData) throw '数据错误';
  330. const responseData = {
  331. err: 0,
  332. msg: '',
  333. data: [],
  334. };
  335. switch (data.postType) {
  336. case 'add':
  337. case 'delete':
  338. case 'up-move':
  339. case 'down-move':
  340. case 'up-level':
  341. case 'down-level':
  342. responseData.data = await this._base(ctx, data.postType, data.postData);
  343. break;
  344. case 'update':
  345. ctx.helper.checkDgnQtyPrecision(data.postData);
  346. responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data.postData);
  347. break;
  348. case 'paste-block':
  349. responseData.data = await this._pasteBlock(ctx, data.postData);
  350. break;
  351. case 'add-std':
  352. responseData.data = await this._addStd(ctx, data.postData);
  353. break;
  354. case 'add-deal':
  355. responseData.data = await this._addDeal(ctx, data.postData);
  356. break;
  357. case 'batch-insert':
  358. responseData.data = await this._batchInsert(ctx, data.postData);
  359. break;
  360. default:
  361. throw '未知操作';
  362. }
  363. ctx.body = responseData;
  364. } catch (err) {
  365. this.log(err);
  366. ctx.body = this.ajaxErrorBody(err);
  367. }
  368. }
  369. /**
  370. * 定位
  371. * @param ctx
  372. * @return {Promise<void>}
  373. */
  374. async locate(ctx) {
  375. const responseData = {
  376. err: 0,
  377. msg: '',
  378. data: [],
  379. };
  380. try {
  381. const tenderId = ctx.params.id;
  382. if (!tenderId) {
  383. throw '当前未打开标段';
  384. }
  385. const data = JSON.parse(ctx.request.body.data);
  386. if ((isNaN(data.id) || data.id <= 0)) {
  387. throw '参数错误';
  388. }
  389. responseData.data = await ctx.service.ledger.locateNode(tenderId, data.id);
  390. } catch (err) {
  391. this.log(err);
  392. responseData.err = 1;
  393. responseData.msg = err;
  394. }
  395. ctx.body = responseData;
  396. }
  397. /**
  398. * 获取全部子节点
  399. *
  400. * @param ctx
  401. * @return {Promise<void>}
  402. */
  403. async posterity(ctx) {
  404. const responseData = {
  405. err: 0,
  406. msg: '',
  407. data: [],
  408. };
  409. try {
  410. const tenderId = ctx.params.id;
  411. if (!tenderId) {
  412. throw '当前未打开标段';
  413. }
  414. const data = JSON.parse(ctx.request.body.data);
  415. if ((isNaN(data.id) || data.id <= 0)) {
  416. throw '参数错误';
  417. }
  418. const expandData = await ctx.service.ledger.getPosterityByParentId(tenderId, data.id);
  419. responseData.data = { expand: expandData };
  420. } catch (err) {
  421. this.log(err);
  422. responseData.err = 1;
  423. responseData.msg = err;
  424. }
  425. ctx.body = responseData;
  426. }
  427. /**
  428. * 获取部位明细数据(Ajax)
  429. *
  430. * @param ctx
  431. * @return {Promise<void>}
  432. */
  433. async loadExplodeData(ctx) {
  434. try {
  435. const ledgerData = ctx.tender.ledgerReadOnly && ctx.tender.his
  436. ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file)
  437. : await ctx.service.ledger.getData(ctx.tender.id);
  438. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  439. ? (ctx.tender.ledgerReadOnly && ctx.tender.his
  440. ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file)
  441. : await ctx.service.pos.getPosData({tid: ctx.tender.id}))
  442. : [];
  443. const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id);
  444. ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, tags: ledgerTags } };
  445. } catch (err) {
  446. this.log(err);
  447. ctx.body = { err: 1, msg: err.toString(), data: [] };
  448. }
  449. }
  450. async check(ctx) {
  451. try {
  452. const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
  453. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  454. ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
  455. const checkDataModel = require('../lib/ledger').checkData;
  456. const checkData = new checkDataModel(ctx, measureType);
  457. checkData.loadData(ledgerData, posData);
  458. ctx.tender.info.ledger_check.same_code && checkData.checkSameCode();
  459. ctx.tender.info.ledger_check.sibling && checkData.checkSibling();
  460. if (ctx.tender.data.measure_type === measureType.tz.value) checkData.checkBillsQty(['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']);
  461. checkData.checkBillsTp([
  462. {qty: 'sgfh_qty', tp: 'sgfh_tp'}, {qty: 'qtcl_qty', tp: 'qtcl_tp'},
  463. {qty: 'sjcl_qty', tp: 'sjcl_tp'}, {qty: 'quantity', tp: 'total_price'}
  464. ], this.ctx.tender.info.decimal);
  465. ctx.body = { err: 0, msg: '', data: checkData.checkResult };
  466. } catch (err) {
  467. this.log(err);
  468. ctx.body = this.ajaxErrorBody(err, '检查数据错误');
  469. }
  470. }
  471. /**
  472. * 更新 部位明细数据
  473. *
  474. * @param ctx
  475. * @return {Promise<void>}
  476. */
  477. async posUpdate(ctx) {
  478. try {
  479. await this.checkMeasureType(measureType.tz.value);
  480. const data = JSON.parse(ctx.request.body.data);
  481. const responseData = await ctx.service.pos.savePosData(data, ctx.tender.id);
  482. ctx.body = { err: 0, msg: '', data: responseData };
  483. } catch (err) {
  484. this.log(err);
  485. ctx.body = { err: 1, msg: err.toString(), data: null };
  486. }
  487. }
  488. /**
  489. * 复制粘贴 部位明细
  490. * @param ctx
  491. * @return {Promise<void>}
  492. */
  493. async posPaste(ctx) {
  494. try {
  495. await this.checkMeasureType(measureType.tz.value);
  496. const data = JSON.parse(ctx.request.body.data);
  497. const result = await ctx.service.pos.pastePosData(data, ctx.tender.id);
  498. ctx.body = { err: 0, msg: '', data: result };
  499. } catch (err) {
  500. this.log(err);
  501. ctx.body = { err: 1, msg: err.toString(), data: null };
  502. }
  503. }
  504. /**
  505. * 上传 清单Excel 并导入
  506. * @param ctx
  507. * @return {Promise<void>}
  508. */
  509. async uploadExcel(ctx) {
  510. try {
  511. const ueType = ctx.params.ueType;
  512. const compressData = ctx.request.body.data;
  513. const data = JSON.parse(LzString.decompressFromUTF16(compressData));
  514. const responseData = { err: 0, msg: '', data: {} };
  515. switch (ueType) {
  516. case 'tz':
  517. const templateId = await this.ctx.service.valuation.getValuationTemplate(
  518. this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
  519. responseData.data = await ctx.service.ledger.importExcel(templateId, data.sheet, data.filter);
  520. break;
  521. case 'gcl2xmj':
  522. responseData.data = await ctx.service.ledger.importGclExcel(data.id, data.sheet);
  523. break;
  524. default:
  525. throw '数据错误';
  526. }
  527. ctx.body = responseData;
  528. } catch (err) {
  529. this.log(err);
  530. ctx.body = { err: 1, msg: err.toString(), data: null };
  531. }
  532. }
  533. // async uploadExcel(ctx) {
  534. // let stream;
  535. // try {
  536. // const responseData = { err: 0, msg: '', data: {}, };
  537. // // 保存文件
  538. // stream = await ctx.getFileStream();
  539. // const create_time = Date.parse(new Date());
  540. // const fileInfo = path.parse(stream.filename);
  541. // const fileName = this.app.config.filePath + '/cache/ledger/uploads/' + create_time + fileInfo.ext;
  542. // await ctx.helper.saveStreamFile(stream, fileName);
  543. // // 读取excel
  544. // console.log(ctx.query);
  545. // const name = ctx.query.sheetName;
  546. // if (!name) throw '未选择需要导入的工作簿';
  547. // const wb = xlsx.readFile(fileName);
  548. // const sheetData = {
  549. // rows: xlsx.utils.sheet_to_json(wb.Sheets[name], {header: 1}),
  550. // merge: wb.Sheets[name]["!merges"],
  551. // };
  552. // if (!sheetData.rows) throw '读取工作簿数据错误';
  553. // const ueType = ctx.query.ueType;
  554. // switch (ueType) {
  555. // case 'tz':
  556. // const templateId = await this.ctx.service.valuation.getValuationTemplate(
  557. // this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
  558. // responseData.data = await ctx.service.ledger.importExcel(templateId, sheetData);
  559. // break;
  560. // case 'gcl2xmj':
  561. // responseData.data = await ctx.service.ledger.importGclExcel(ctx.tender.id, sheetData);
  562. // break;
  563. // default:
  564. // throw '数据错误';
  565. // }
  566. // ctx.body = responseData;
  567. // } catch (err) {
  568. // console.log(err);
  569. // this.log(err);
  570. // // 失败需要消耗掉stream 以防卡死
  571. // if (stream) await sendToWormhole(stream);
  572. // ctx.body = {err: 1, msg: err.toString(), data: null};
  573. // }
  574. // }
  575. /**
  576. * 填设计量(Ajax)
  577. *
  578. * @param ctx
  579. * @return {Promise<void>}
  580. */
  581. async deal2sgfh(ctx) {
  582. try {
  583. if (!ctx.tender.data) throw '标段数据错误';
  584. if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作';
  585. if (this.ctx.tender.measure_type === measureType.tz.value) throw '该功能仅工程量清单模式可用';
  586. await this.ctx.service.ledger.deal2sgfh(ctx.tender.id);
  587. const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
  588. ctx.body = { err: 0, msg: '', data: { bills: ledgerData } };
  589. } catch (err) {
  590. this.log(err);
  591. ctx.body = { err: 1, msg: err.toString(), data: null };
  592. }
  593. }
  594. /**
  595. * 下载(清单Excel模板 or 导出项目台账Excel)
  596. * @param ctx
  597. * @return {Promise<void>}
  598. */
  599. async download(ctx) {
  600. const file = ctx.params.file;
  601. if (file) {
  602. try {
  603. let fileName;
  604. if (file === '导入分项清单EXCEL格式.xlsx') {
  605. fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xlsx');
  606. ctx.body = await fs.readFileSync(fileName);
  607. } else if (file === '台账分解.xlsx') {
  608. const create_time = Date.parse(new Date()) / 1000;
  609. fileName = this.app.baseDir + '/app/public/files/ledger' + ctx.tender.id + '-' + create_time + '.xlsx';
  610. const exportor = new exportExcel.exportLedger2Excel(ctx);
  611. await exportor.export2File(fileName);
  612. ctx.body = await fs.readFileSync(fileName);
  613. // 输出文件后删除
  614. fs.unlinkSync(fileName);
  615. }
  616. } catch (err) {
  617. this.log(err);
  618. this.setMessage(err.toString(), this.messageType.ERROR);
  619. }
  620. }
  621. }
  622. /**
  623. * 部位台账 页面 (Get)
  624. * @param ctx
  625. * @return {Promise<void>}
  626. */
  627. async bwtz(ctx) {
  628. try {
  629. const sjsRela = await this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id);
  630. const renderData = {
  631. tender: ctx.tender.data,
  632. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.bwtz),
  633. ex_memo1: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo1'; }),
  634. ex_memo2: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo2'; }),
  635. ex_memo3: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo3'; }),
  636. };
  637. await this.layout('ledger/bwtz.ejs', renderData);
  638. } catch (err) {
  639. this.log(err);
  640. ctx.redirect(ctx.request.header.referer);
  641. }
  642. }
  643. async loadBwtz(ctx) {
  644. try {
  645. const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
  646. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  647. ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
  648. const convert = new billsPosConvert(ctx);
  649. convert.loadData(ledgerData, posData, []);
  650. const result = await convert.convert();
  651. // const wbsCodeHis = await ctx.service.externalData.getExValue(ctx.tender.id, -1,
  652. // externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode) || [];
  653. // const [result, needUpdate] = convert.convertByWbsCode(wbsCodeHis);
  654. // if (needUpdate) {
  655. // await ctx.service.externalData.saveExValue(ctx.tender.id, -1,
  656. // externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode, wbsCodeHis);
  657. // }
  658. ctx.body = { err: 0, msg: '', data: result };
  659. } catch (err) {
  660. this.log(err);
  661. ctx.body = this.ajaxErrorBody(err, '加载合同支付数据错误');
  662. }
  663. }
  664. /**
  665. * 台账对比 页面 (Get)
  666. * @param ctx
  667. * @return {Promise<void>}
  668. */
  669. async gather(ctx) {
  670. try {
  671. const renderData = {
  672. measureType,
  673. tender: ctx.tender.data,
  674. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.gather),
  675. };
  676. await this.layout('ledger/gather.ejs', renderData);
  677. } catch (err) {
  678. this.log(err);
  679. await this.redirect(ctx.request.header.referer);
  680. }
  681. }
  682. /**
  683. * 获取 台账对比 数据 (Ajax)
  684. * @param ctx
  685. * @return {Promise<void>}
  686. */
  687. async loadGatherData(ctx) {
  688. try {
  689. const billsData = await ctx.service.ledger.getData(ctx.tender.id);
  690. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  691. ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
  692. const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
  693. const zlj = JSON.parse(JSON.stringify(stdConst.zlj));
  694. zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
  695. ctx.body = { err: 0, msg: '', data: {
  696. bills: billsData, pos: posData, dealBills, spec: {zlj: zlj, jrg: stdConst.jrg},
  697. }};
  698. } catch (err) {
  699. this.log(err);
  700. ctx.body = { err: 1, msg: err.toString(), data: [] };
  701. }
  702. }
  703. /**
  704. * 计量台账页面 (Get)
  705. *
  706. * @param {object} ctx - egg全局变量
  707. * @return {void}
  708. */
  709. async index(ctx) {
  710. const renderData = {};
  711. await this.layout('ledger/index.ejs', renderData);
  712. }
  713. /**
  714. * 审批流程(Get)
  715. * @param ctx
  716. * @return {Promise<void>}
  717. */
  718. async ledgerAuditors(ctx) {
  719. try {
  720. const responseData = {
  721. err: 0, msg: '', data: {},
  722. };
  723. const tender = ctx.tender;
  724. const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
  725. const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
  726. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
  727. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
  728. const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
  729. const auditHistory = [];
  730. if (times >= 1) {
  731. for (let i = 1; i <= times; i++) {
  732. auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
  733. }
  734. }
  735. responseData.data.auditHistory = auditHistory;
  736. // 获取审批流程中左边列表
  737. responseData.data.auditors = auditors;
  738. responseData.data.user = user;
  739. ctx.body = responseData;
  740. } catch (error) {
  741. this.log(error);
  742. ctx.body = { err: 1, msg: error.toString(), data: null };
  743. }
  744. }
  745. /**
  746. * 上传附件
  747. * @param {Object} ctx - egg全局变量
  748. * @return {void}
  749. */
  750. async uploadFile(ctx) {
  751. const responseData = {
  752. err: 0,
  753. msg: '',
  754. data: [],
  755. };
  756. let stream;
  757. try {
  758. const parts = ctx.multipart({ autoFields: true });
  759. const files = [];
  760. let index = 0;
  761. const extra_upload = ctx.tender.data.ledger_status === auditConst.status.checked;
  762. while ((stream = await parts()) !== undefined) {
  763. // 判断用户是否选择上传文件
  764. if (!stream.filename) {
  765. throw '请选择上传的文件!';
  766. }
  767. const fileInfo = path.parse(stream.filename);
  768. const create_time = Date.parse(new Date()) / 1000;
  769. const filepath = `app/public/upload/${this.ctx.tender.id}/ledger/fujian_${create_time + index.toString() + fileInfo.ext}`;
  770. // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, filepath));
  771. await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
  772. if (stream) {
  773. await sendToWormhole(stream);
  774. }
  775. // 保存数据到att表
  776. const fileData = {
  777. tid: ctx.params.id,
  778. in_time: create_time,
  779. filename: fileInfo.name,
  780. fileext: fileInfo.ext,
  781. filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
  782. filepath,
  783. extra_upload,
  784. };
  785. const result = await ctx.service.ledgerAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
  786. if (!result) {
  787. throw '导入数据库保存失败';
  788. }
  789. const attData = await ctx.service.ledgerAtt.getDataByFid(result.insertId);
  790. attData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
  791. files.length !== 0 ? files.unshift(attData) : files.push(attData);
  792. ++index;
  793. }
  794. responseData.data = files;
  795. } catch (err) {
  796. this.log(err);
  797. // 失败需要消耗掉stream 以防卡死
  798. if (stream) {
  799. await sendToWormhole(stream);
  800. }
  801. this.setMessage(err.toString(), this.messageType.ERROR);
  802. responseData.err = 1;
  803. responseData.msg = err.toString();
  804. }
  805. ctx.body = responseData;
  806. }
  807. /**
  808. * 下载附件
  809. * @param {Object} ctx - egg全局变量
  810. * @return {void}
  811. */
  812. async downloadFile(ctx) {
  813. const id = ctx.params.fid;
  814. if (id) {
  815. try {
  816. const fileInfo = await ctx.service.ledgerAtt.getDataById(id);
  817. if (fileInfo !== undefined && fileInfo !== '') {
  818. // const fileName = path.join(this.app.baseDir, fileInfo.filepath);
  819. // 解决中文无法下载问题
  820. const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
  821. let disposition = '';
  822. if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
  823. disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext);
  824. } else if (userAgent.indexOf('firefox') >= 0) {
  825. disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"';
  826. } else {
  827. /* safari等其他非主流浏览器只能自求多福了 */
  828. disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary');
  829. }
  830. ctx.response.set({
  831. 'Content-Type': 'application/octet-stream',
  832. 'Content-Disposition': disposition,
  833. 'Content-Length': fileInfo.filesize,
  834. });
  835. // ctx.body = await fs.createReadStream(fileName);
  836. ctx.body = await ctx.helper.ossFileGet(fileInfo.filepath);
  837. } else {
  838. throw '不存在该文件';
  839. }
  840. } catch (err) {
  841. this.log(err);
  842. this.setMessage(err.toString(), this.messageType.ERROR);
  843. }
  844. }
  845. }
  846. /**
  847. * 查看附件
  848. * @param {Object} ctx - egg全局变量
  849. * @return {void}
  850. */
  851. async checkFile(ctx) {
  852. const responseData = { err: 0, msg: '' };
  853. const { id = '' } = JSON.parse(ctx.request.body.data);
  854. if (id) {
  855. try {
  856. const fileInfo = await ctx.service.ledgerAtt.getDataById(id);
  857. if (fileInfo && Object.keys(fileInfo).length) {
  858. let filepath = fileInfo.filepath;
  859. if (!ctx.helper.canPreview(fileInfo.fileext)) {
  860. filepath = `/tender/${ctx.tender.id}/ledger/download/file/${fileInfo.id}`;
  861. } else {
  862. // filepath = filepath.replace(/^app|\/app/, '');
  863. filepath = ctx.app.config.fujianOssPath + filepath;
  864. }
  865. fileInfo.filepath && (responseData.data = { filepath });
  866. }
  867. } catch (error) {
  868. this.log(error);
  869. this.setMessage(error.toString(), this.messageType.ERROR);
  870. responseData.err = 1;
  871. responseData.msg = error.toString();
  872. }
  873. }
  874. ctx.body = responseData;
  875. }
  876. /**
  877. * 删除附件
  878. * @param {Object} ctx - egg全局变量
  879. * @return {void}
  880. */
  881. async deleteFile(ctx) {
  882. const responseData = {
  883. err: 0,
  884. msg: '',
  885. data: '',
  886. };
  887. try {
  888. // this._checkStageCanModifyRe(ctx);
  889. const data = JSON.parse(ctx.request.body.data);
  890. const fileInfo = await ctx.service.ledgerAtt.getDataById(data.id);
  891. if (!fileInfo || !Object.keys(fileInfo).length) {
  892. throw '该文件不存在';
  893. }
  894. if (!fileInfo.extra_upload && ctx.tender.status === auditConst.status.checked) {
  895. throw '无权限删除';
  896. }
  897. if (fileInfo !== undefined && fileInfo !== '') {
  898. // 先删除文件
  899. // await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
  900. await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath);
  901. // 再删除数据库
  902. await ctx.service.ledgerAtt.deleteById(data.id);
  903. responseData.data = '';
  904. } else {
  905. throw '不存在该文件';
  906. }
  907. } catch (err) {
  908. responseData.err = 1;
  909. responseData.msg = err;
  910. }
  911. ctx.body = responseData;
  912. }
  913. /**
  914. * 保存附件(或替换)
  915. * @param {Object} ctx - egg全局变量
  916. * @return {void}
  917. */
  918. async saveFile(ctx) {
  919. const responseData = {
  920. err: 0,
  921. msg: '',
  922. data: [],
  923. };
  924. let stream;
  925. try {
  926. // this._checkStageCanModifyRe(ctx);
  927. stream = await ctx.getFileStream({ requireFile: false });
  928. let fileData = {};
  929. if (stream.filename !== undefined) {
  930. const create_time = Date.parse(new Date()) / 1000;
  931. const fileInfo = path.parse(stream.filename);
  932. const dirName = `app/public/upload/${this.ctx.tender.id}/ledger`;
  933. const fileName = 'fujian_' + create_time + fileInfo.ext;
  934. // 判断文件夹是否存在,不存在则直接创建文件夹
  935. // if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
  936. // await fs.mkdirSync(path.join(this.app.baseDir, dirName));
  937. // }
  938. // 保存文件
  939. // await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
  940. await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + path.join(dirName, fileName), stream);
  941. // 保存数据到att表
  942. fileData = {
  943. filesize: stream.fields.size,
  944. filepath: path.join(dirName, fileName),
  945. };
  946. }
  947. const result = await ctx.service.ledgerAtt.updateByID(stream.fields, fileData);
  948. if (!result) {
  949. throw '导入数据库保存失败';
  950. }
  951. const attData = await ctx.service.ledgerAtt.getDataByFid(stream.fields.id);
  952. attData.in_time = moment(attData.in_time * 1000).format('YYYY-MM-DD');
  953. responseData.data = attData;
  954. } catch (err) {
  955. this.log(err);
  956. // 失败需要消耗掉stream 以防卡死
  957. if (stream) {
  958. await sendToWormhole(stream);
  959. }
  960. this.setMessage(err.toString(), this.messageType.ERROR);
  961. responseData.err = 1;
  962. responseData.msg = err.toString();
  963. }
  964. ctx.body = responseData;
  965. }
  966. /**
  967. * 批量下载 - 压缩成zip文件返回
  968. * @param {Object} ctx - 全局上下文
  969. */
  970. async downloadZip(ctx) {
  971. const zipFilename = `${ctx.tender.data.name}-台账分解-附件.zip`;
  972. const time = Date.now();
  973. const zipPath = `app/public/upload/${ctx.tender.id}/ledger/fu_jian_zip${time}.zip`;
  974. const responseData = {
  975. err: 0,
  976. msg: '',
  977. };
  978. try {
  979. const { fileIds = [] } = JSON.parse(ctx.request.body.data);
  980. const size = await ctx.service.ledgerAtt.compressedFile(fileIds, zipPath);
  981. // 解决中文无法下载问题
  982. const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
  983. let disposition = '';
  984. if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
  985. disposition = 'attachment; filename=' + encodeURIComponent(zipFilename);
  986. } else if (userAgent.indexOf('firefox') >= 0) {
  987. disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"';
  988. } else {
  989. /* safari等其他非主流浏览器只能自求多福了 */
  990. disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary');
  991. }
  992. ctx.response.set({
  993. 'Content-Type': 'application/octet-stream',
  994. 'Content-Disposition': disposition,
  995. 'Content-Length': size,
  996. });
  997. const readStream = fs.createReadStream(path.join(this.app.baseDir, zipPath));
  998. ctx.body = readStream;
  999. readStream.on('close', () => {
  1000. if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
  1001. fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
  1002. }
  1003. });
  1004. // fs的错误不能被try catch捕捉
  1005. readStream.on('error', err => {
  1006. this.log(err);
  1007. if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
  1008. fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
  1009. }
  1010. responseData.err = 1;
  1011. responseData.msg = err.toString();
  1012. ctx.body = responseData;
  1013. });
  1014. } catch (err) {
  1015. this.log(err);
  1016. if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
  1017. fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
  1018. }
  1019. this.setMessage(err.toString(), this.messageType.ERROR);
  1020. responseData.err = 1;
  1021. responseData.msg = err.toString();
  1022. ctx.body = responseData;
  1023. }
  1024. }
  1025. }
  1026. return LedgerController;
  1027. };