ledger_controller.js 23 KB

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