ledger_controller.js 22 KB

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