ledger_controller.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  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 auditConst = require('../const/audit').ledger;
  15. const tenderMenu = require('../../config/menu').tenderMenu;
  16. const measureType = require('../const/tender').measureType;
  17. const spreadConst = require('../const/spread');
  18. const fs = require('fs');
  19. const LzString = require('lz-string');
  20. module.exports = app => {
  21. class LedgerController extends app.BaseController {
  22. /**
  23. * 构造函数
  24. *
  25. * @param {Object} ctx - egg全局变量
  26. * @return {void}
  27. */
  28. constructor(ctx) {
  29. super(ctx);
  30. ctx.showProject = true;
  31. ctx.showTitle = true;
  32. ctx.showTender = true;
  33. }
  34. /**
  35. * 检查标段是否只读(审核中,审核完成)
  36. * @param {Object} tenderData
  37. * @return {boolean}
  38. * @private
  39. */
  40. _ledgerReadOnly() {
  41. const tender = this.ctx.tender.data;
  42. return tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked;
  43. }
  44. /**
  45. * 获取SpreadSetting
  46. * @private
  47. */
  48. _getSpreadSetting() {
  49. const _ = this.app._;
  50. function removeFieldCols(setting, cols) {
  51. _.remove(setting.cols, function(c) {
  52. return cols.indexOf(c.field) > -1;
  53. });
  54. }
  55. function setColFormat(cols, field, formatter) {
  56. const col = _.find(cols, function (c) {
  57. return c.field === field;
  58. });
  59. col.formatter = formatter;
  60. }
  61. const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp);
  62. const upFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.up);
  63. const ledger = JSON.parse(JSON.stringify(spreadConst.ledgerSpread));
  64. setColFormat(ledger.cols, 'unit_price', upFormatter);
  65. setColFormat(ledger.cols, 'dgn_price', upFormatter);
  66. setColFormat(ledger.cols, 'total_price', tpFormatter);
  67. setColFormat(ledger.cols, 'deal_tp', tpFormatter);
  68. const pos = JSON.parse(JSON.stringify(spreadConst.ledgerPosSpread));
  69. const tender = this.ctx.tender;
  70. if (this._ledgerReadOnly(tender.data)) {
  71. ledger.readOnly = true;
  72. pos.readOnly = true;
  73. }
  74. if (tender.data.measure_type === measureType.tz.value) {
  75. removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols);
  76. }
  77. if (!tender.info.display.ledger.dgnQty) {
  78. removeFieldCols(ledger, spreadConst.filterCols.dgnCols);
  79. }
  80. return [ledger, pos];
  81. }
  82. /**
  83. * 台账分解页面 (Get)
  84. *
  85. * @param {Object} ctx - egg全局变量
  86. * @return {void}
  87. */
  88. async explode(ctx) {
  89. try {
  90. const tender = ctx.tender;
  91. const [ledgerSpread, posSpread] = this._getSpreadSetting();
  92. const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
  93. const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
  94. const auditors = await ctx.service.ledgerAudit.getAuditors(tender.id, times);
  95. const content = auditors.length > 0 ? await ctx.service.ledgerAuditContent.getAllDataByCondition({
  96. where: { tender_id: tender.id, times, audit_id: auditors[0].audit_id },
  97. }) : null;
  98. const ledgerData = await ctx.service.ledger.getDataByTenderId(tender.id, -1);
  99. const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
  100. const auditHistory = [];
  101. if (ctx.tender.data.ledger_times > 1) {
  102. for (let i = 1; i < ctx.tender.data.ledger_times; i++) {
  103. auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
  104. }
  105. }
  106. const renderData = {
  107. tender: tender.data,
  108. tenderInfo: tender.info,
  109. auditConst,
  110. auditors,
  111. curAuditor,
  112. user,
  113. auditHistory,
  114. content,
  115. ledger: JSON.stringify(ledgerData),
  116. ledgerSpreadSetting: JSON.stringify(ledgerSpread),
  117. posSpreadSetting: JSON.stringify(posSpread),
  118. tenderMenu,
  119. preUrl: '/tender/' + tender.id,
  120. measureType,
  121. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
  122. };
  123. await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
  124. } catch (err) {
  125. this.log(err);
  126. await this.layout('/dashboard');
  127. }
  128. }
  129. /**
  130. * 获取子节点 (Ajax)
  131. * @param ctx
  132. * @return {Promise<void>}
  133. */
  134. async getChildren(ctx) {
  135. const responseData = {
  136. err: 0,
  137. msg: '',
  138. data: [],
  139. };
  140. try {
  141. const data = JSON.parse(ctx.request.body.data);
  142. const id = data.ledger_id;
  143. if (isNaN(id) || id <= 0) {
  144. throw '参数错误';
  145. }
  146. responseData.data = await ctx.service.ledger.getChildrenByParentId(ctx.tender.id, id);
  147. } catch (err) {
  148. responseData.err = 1;
  149. responseData.msg = err;
  150. }
  151. ctx.body = responseData;
  152. }
  153. /**
  154. * 树结构基本操作(增、删、上下移、升降级)(Ajax)
  155. * @param {Object} ctx - egg全局变量
  156. * @return {Promise<void>}
  157. */
  158. async baseOperation(ctx) {
  159. const responseData = {
  160. err: 0,
  161. msg: '',
  162. data: [],
  163. };
  164. try {
  165. if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
  166. throw '标段数据错误';
  167. }
  168. const data = JSON.parse(ctx.request.body.data);
  169. switch (data.postType) {
  170. case 'add':
  171. responseData.data = await ctx.service.ledger.addNode(ctx.tender.id, data.id);
  172. break;
  173. case 'delete':
  174. responseData.data = await ctx.service.ledger.deleteNode(ctx.tender.id, data.id);
  175. break;
  176. case 'up-move':
  177. responseData.data = await ctx.service.ledger.upMoveNode(ctx.tender.id, data.id);
  178. break;
  179. case 'down-move':
  180. responseData.data = await ctx.service.ledger.downMoveNode(ctx.tender.id, data.id);
  181. break;
  182. case 'up-level':
  183. responseData.data = await ctx.service.ledger.upLevelNode(ctx.tender.id, data.id);
  184. break;
  185. case 'down-level':
  186. responseData.data = await ctx.service.ledger.downLevelNode(ctx.tender.id, data.id);
  187. break;
  188. default:
  189. throw '未知操作';
  190. }
  191. } catch (err) {
  192. responseData.err = 1;
  193. responseData.msg = err;
  194. this.log(err);
  195. }
  196. ctx.body = responseData;
  197. }
  198. /**
  199. * 提交更新数据 (Ajax)
  200. * @param ctx
  201. * @return {Promise<void>}
  202. */
  203. async updateInfo(ctx) {
  204. const responseData = {
  205. err: 0,
  206. msg: '',
  207. data: [],
  208. };
  209. try {
  210. if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
  211. throw '标段数据错误';
  212. }
  213. const data = JSON.parse(ctx.request.body.data);
  214. if (data instanceof Array) {
  215. responseData.data = await ctx.service.ledger.updateInfos(ctx.tender.id, data);
  216. } else {
  217. responseData.data = await ctx.service.ledger.updateInfo(ctx.tender.id, data);
  218. }
  219. } catch (err) {
  220. responseData.err = 1;
  221. responseData.msg = err;
  222. }
  223. ctx.body = responseData;
  224. }
  225. async update(ctx) {
  226. const responseData = {
  227. err: 0,
  228. msg: '',
  229. data: [],
  230. };
  231. try {
  232. const tenderId = parseInt(ctx.params.id);
  233. if (!tenderId) {
  234. throw '当前未打开标段';
  235. }
  236. const tenderData = await ctx.service.tender.getDataById(tenderId);
  237. if (!tenderData || tenderData.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly(tenderData)) {
  238. throw '标段数据错误';
  239. }
  240. const data = JSON.parse(ctx.request.body.data);
  241. responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data);
  242. } catch (err) {
  243. this.log(err);
  244. responseData.err = 1;
  245. responseData.msg = err;
  246. }
  247. ctx.body = responseData;
  248. }
  249. /**
  250. * 复制粘贴整块 (Ajax)
  251. *
  252. * @param ctx
  253. * @return {Promise<void>}
  254. */
  255. async pasteBlock(ctx) {
  256. const responseData = {
  257. err: 0,
  258. msg: '',
  259. data: [],
  260. };
  261. try {
  262. if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
  263. throw '标段数据错误';
  264. }
  265. const data = JSON.parse(ctx.request.body.data);
  266. if ((isNaN(data.id) || data.id <= 0) || (!data.block || data.block.length <= 0)) {
  267. throw '参数错误';
  268. }
  269. responseData.data = await ctx.service.ledger.pasteBlock(ctx.tender.id, data.id, data.block);
  270. } catch (err) {
  271. responseData.err = 1;
  272. responseData.msg = err;
  273. }
  274. ctx.body = responseData;
  275. }
  276. /**
  277. * 从标准项目表添加数据 (Ajax)
  278. * @param ctx
  279. * @return {Promise<void>}
  280. */
  281. async addFromStandardLib(ctx) {
  282. const responseData = {
  283. err: 0,
  284. msg: '',
  285. data: [],
  286. };
  287. try {
  288. if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
  289. throw '标段数据错误';
  290. }
  291. const data = JSON.parse(ctx.request.body.data);
  292. if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) {
  293. throw '参数错误';
  294. }
  295. // todo 校验项目是否使用该库的权限
  296. let stdLib, addType;
  297. switch (data.stdType) {
  298. case 'chapter':
  299. stdLib = ctx.service.stdChapter;
  300. addType = stdDataAddType.withParent;
  301. break;
  302. case 'bills':
  303. stdLib = ctx.service.stdBills;
  304. const selectNode = await ctx.service.ledger.getDataByNodeId(ctx.tender.id, data.id);
  305. if (selectNode.b_code) {
  306. addType = stdDataAddType.next;
  307. } else {
  308. addType = stdDataAddType.child;
  309. }
  310. break;
  311. default:
  312. throw '未知标准库';
  313. }
  314. const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
  315. switch (addType) {
  316. case stdDataAddType.child:
  317. responseData.data = await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData);
  318. break;
  319. case stdDataAddType.next:
  320. responseData.data = await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData);
  321. break;
  322. case stdDataAddType.withParent:
  323. responseData.data = await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, stdData, stdLib);
  324. break;
  325. default:
  326. throw '未知添加方式';
  327. }
  328. } catch (err) {
  329. responseData.err = 1;
  330. responseData.msg = err;
  331. }
  332. ctx.body = responseData;
  333. }
  334. /**
  335. * 批量插入数据 (Ajax)
  336. *
  337. * data = {id, batchData, batchType}
  338. * data.batchType = 'batchInsertChild'/'batchInsertNext'
  339. * data.batchData = [{name, children}] -- 项目节列表
  340. * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表
  341. *
  342. * @param ctx
  343. * @return {Promise<void>}
  344. */
  345. async batchInsert(ctx) {
  346. const responseData = {
  347. err: 0,
  348. msg: '',
  349. data: [],
  350. };
  351. try {
  352. if (!ctx.tender.data || ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) {
  353. throw '标段数据错误';
  354. }
  355. const data = JSON.parse(ctx.request.body.data);
  356. if ((isNaN(data.id) || data.id <= 0) || !data.batchType) {
  357. throw '参数错误';
  358. }
  359. switch (data.batchType) {
  360. case 'child':
  361. responseData.data = await ctx.service.ledger.batchInsertChild(ctx.tender.id, data.id, data.batchData);
  362. break;
  363. case 'next':
  364. responseData.data = await ctx.service.ledger.batchInsertNext(ctx.tender.id, data.id, data.batchData);
  365. break;
  366. default:
  367. throw '参数错误';
  368. }
  369. } catch (err) {
  370. this.log(err);
  371. responseData.err = 1;
  372. responseData.msg = err;
  373. }
  374. ctx.body = responseData;
  375. }
  376. /**
  377. * 查询
  378. *
  379. * @param ctx
  380. * @return {Promise<void>}
  381. */
  382. async search(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 (!data.keyword || data.keyword === '') {
  395. throw '参数错误';
  396. }
  397. responseData.data = await ctx.service.ledger.search(tenderId, {
  398. value: ctx.app.mysql.escape('%' + data.keyword + '%'),
  399. operate: 'Like',
  400. fields: ['code', 'b_code', 'name'],
  401. });
  402. } catch (err) {
  403. this.log(err);
  404. responseData.err = 1;
  405. responseData.msg = err;
  406. }
  407. ctx.body = responseData;
  408. }
  409. /**
  410. * 定位
  411. * @param ctx
  412. * @return {Promise<void>}
  413. */
  414. async locate(ctx) {
  415. const responseData = {
  416. err: 0,
  417. msg: '',
  418. data: [],
  419. };
  420. try {
  421. const tenderId = ctx.params.id;
  422. if (!tenderId) {
  423. throw '当前未打开标段';
  424. }
  425. const data = JSON.parse(ctx.request.body.data);
  426. if ((isNaN(data.id) || data.id <= 0)) {
  427. throw '参数错误';
  428. }
  429. responseData.data = await ctx.service.ledger.locateNode(tenderId, data.id);
  430. } catch (err) {
  431. this.log(err);
  432. responseData.err = 1;
  433. responseData.msg = err;
  434. }
  435. ctx.body = responseData;
  436. }
  437. /**
  438. * 获取全部子节点
  439. *
  440. * @param ctx
  441. * @return {Promise<void>}
  442. */
  443. async posterity(ctx) {
  444. const responseData = {
  445. err: 0,
  446. msg: '',
  447. data: [],
  448. };
  449. try {
  450. const tenderId = ctx.params.id;
  451. if (!tenderId) {
  452. throw '当前未打开标段';
  453. }
  454. const data = JSON.parse(ctx.request.body.data);
  455. if ((isNaN(data.id) || data.id <= 0)) {
  456. throw '参数错误';
  457. }
  458. const expandData = await ctx.service.ledger.getPosterityByParentId(tenderId, data.id);
  459. responseData.data = { expand: expandData };
  460. } catch (err) {
  461. this.log(err);
  462. responseData.err = 1;
  463. responseData.msg = err;
  464. }
  465. ctx.body = responseData;
  466. }
  467. /**
  468. * 获取部位明细数据(Ajax)
  469. *
  470. * @param ctx
  471. * @return {Promise<void>}
  472. */
  473. async pos(ctx) {
  474. try {
  475. await this.checkMeasureType(measureType.tz.value);
  476. const condition = JSON.parse(ctx.request.body.data) || {};
  477. condition.tid = ctx.tender.id;
  478. const posData = await ctx.service.pos.getPosData(condition);
  479. ctx.body = { err: 0, msg: '', data: posData };
  480. } catch (err) {
  481. this.log(err);
  482. ctx.body = { err: 1, msg: err.toString(), data: [] };
  483. }
  484. }
  485. /**
  486. * 更新 部位明细数据
  487. *
  488. * @param ctx
  489. * @return {Promise<void>}
  490. */
  491. async posUpdate(ctx) {
  492. try {
  493. await this.checkMeasureType(measureType.tz.value);
  494. const data = JSON.parse(ctx.request.body.data);
  495. const responseData = await ctx.service.pos.savePosData(data, ctx.tender.id);
  496. ctx.body = { err: 0, msg: '', data: responseData };
  497. } catch (err) {
  498. this.log(err);
  499. ctx.body = { err: 1, msg: err.toString(), data: null };
  500. }
  501. }
  502. /**
  503. * 复制粘贴 部位明细
  504. * @param ctx
  505. * @returns {Promise<void>}
  506. */
  507. async posPaste(ctx) {
  508. try {
  509. await this.checkMeasureType(measureType.tz.value);
  510. const data = JSON.parse(ctx.request.body.data);
  511. const result = await ctx.service.pos.pastePosData(data, ctx.tender.id);
  512. ctx.body = { err: 0, msg: '', data: result };
  513. } catch(err) {
  514. this.log(err);
  515. ctx.body = { err: 1, msg: err.toString(), data: null };
  516. }
  517. }
  518. /**
  519. * 上传 清单Excel 并导入
  520. * @param ctx
  521. * @return {Promise<void>}
  522. */
  523. async uploadExcel(ctx) {
  524. try {
  525. const compressData = ctx.request.body.data;
  526. const data = JSON.parse(LzString.decompressFromUTF16(compressData));
  527. const responseData = { err: 0, msg: '', data: {}, };
  528. await ctx.service.ledger.importExcel(data);
  529. responseData.data.bills = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
  530. responseData.data.pos = await ctx.service.pos.getPosData(null);
  531. ctx.body = responseData;
  532. } catch (err) {
  533. this.log(err);
  534. ctx.body = { err: 1, msg: err.toString(), data: null };
  535. }
  536. }
  537. /**
  538. * 下载(清单Excel模板 or 导出项目台账Excel)
  539. * @param ctx
  540. * @return {Promise<void>}
  541. */
  542. async download(ctx) {
  543. const file = ctx.params.file;
  544. if (file) {
  545. try {
  546. let fileName;
  547. if (file === 'template') {
  548. fileName = this.app.baseDir + '/app/public/files/template/ledger/导入分项清单Excel格式.xls';
  549. } else if (file === 'ledger') {
  550. const create_time = Date.parse(new Date()) / 1000;
  551. fileName = this.app.baseDir + '/app/public/files/downloads/ledger/' + ctx.tender.id + '-' + create_time + '.xlsx';
  552. // todo 导出台账清单Excel
  553. }
  554. ctx.body = await fs.readFileSync(fileName);
  555. } catch (err) {
  556. this.log(err);
  557. this.setMessage(err.toString(), this.messageType.ERROR);
  558. }
  559. }
  560. }
  561. /**
  562. * 台账变更页面 (Get)
  563. *
  564. * @param {object} ctx - egg全局变量
  565. * @return {void}
  566. */
  567. async change(ctx) {
  568. try {
  569. const renderData = {
  570. tender: ctx.tender.data,
  571. tenderMenu: this.menu.tenderMenu,
  572. preUrl: '/tender/' + ctx.tender.id,
  573. };
  574. await this.layout('ledger/change.ejs', renderData, 'ledger/change_modal.ejs');
  575. } catch (err) {
  576. this.log(err);
  577. ctx.redirect(ctx.request.header.referer);
  578. }
  579. }
  580. /**
  581. * 计量台账页面 (Get)
  582. *
  583. * @param {object} ctx - egg全局变量
  584. * @return {void}
  585. */
  586. async index(ctx) {
  587. const renderData = {};
  588. await this.layout('ledger/index.ejs', renderData);
  589. }
  590. }
  591. return LedgerController;
  592. };