ledger_controller.js 22 KB

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