ledger_controller.js 45 KB

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