ledger_controller.js 23 KB

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