ledger_controller.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  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 audit = require('../const/audit');
  15. const moment = require('moment');
  16. const auditConst = audit.ledger;
  17. const tenderMenu = require('../../config/menu').tenderMenu;
  18. const measureType = require('../const/tender').measureType;
  19. const spreadConst = require('../const/spread');
  20. const shenpiConst = require('../const/shenpi');
  21. const externalDataConst = require('../const/external_data.js');
  22. const fs = require('fs');
  23. const LzString = require('lz-string');
  24. const accountGroup = require('../const/account_group').group;
  25. const path = require('path');
  26. const exportExcel = require('../lib/export_excel');
  27. const billsPosConvert = require('../lib/bills_pos_convert');
  28. const xlsx = require('js-xlsx');
  29. const stdConst = require('../const/standard');
  30. const sendToWormhole = require('stream-wormhole');
  31. module.exports = app => {
  32. class LedgerController extends app.BaseController {
  33. /**
  34. * 构造函数
  35. *
  36. * @param {Object} ctx - egg全局变量
  37. * @return {void}
  38. */
  39. constructor(ctx) {
  40. super(ctx);
  41. ctx.showProject = true;
  42. ctx.showTitle = true;
  43. ctx.showTender = true;
  44. }
  45. /**
  46. * 检查标段是否只读(审核中,审核完成)
  47. * @param {Object} tenderData
  48. * @return {boolean}
  49. * @private
  50. */
  51. _ledgerReadOnly() {
  52. const tender = this.ctx.tender.data;
  53. return tender.user_id !== this.ctx.session.sessionUser.accountId ||
  54. (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked);
  55. }
  56. _canUpdateDealBills(tender, auditors) {
  57. const isUser = tender.user_id === this.ctx.session.sessionUser.accountId;
  58. const auditorsId = this.ctx.helper._.map(auditors, 'audit_id');
  59. const isAuditor = auditorsId.indexOf(this.ctx.session.sessionUser.accountId) >= 0;
  60. const upPermission = this.ctx.session.sessionUser.permission && this.ctx.session.sessionUser.permission.tender
  61. ? this.ctx.session.sessionUser.permission.tender.indexOf('3') >= 0
  62. : false;
  63. return ((tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) && isUser) ||
  64. (tender.ledger_status === auditConst.status.checking && isAuditor) ||
  65. (tender.ledger_status === auditConst.status.checked && isAuditor && upPermission);
  66. }
  67. /**
  68. * 获取SpreadSetting
  69. * @private
  70. */
  71. _getSpreadSetting() {
  72. const _ = this.app._;
  73. function removeFieldCols(setting, cols) {
  74. _.remove(setting.cols, function(c) {
  75. return cols.indexOf(c.field) > -1;
  76. });
  77. }
  78. function hideFieldCols(setting, cols) {
  79. for (const c of setting.cols) {
  80. if (cols.indexOf(c.field) > -1) {
  81. c.visible = false;
  82. }
  83. }
  84. }
  85. const tender = this.ctx.tender;
  86. const setting = tender.data.measure_type === measureType.tz.value
  87. ? (tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl)
  88. : (tender.info.display.ledger.clQty ? spreadConst.withClGcl : spreadConst.withoutClGcl);
  89. const ledger = JSON.parse(JSON.stringify(setting.ledger));
  90. const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank;
  91. if (this._ledgerReadOnly(tender.data)) {
  92. ledger.readOnly = true;
  93. pos.readOnly = true;
  94. }
  95. if (tender.data.measure_type === measureType.tz.value) {
  96. removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols);
  97. }
  98. if (!tender.info.display.ledger.dgnQty) {
  99. removeFieldCols(ledger, spreadConst.filterCols.dgnCols);
  100. }
  101. return [ledger, pos];
  102. }
  103. _getLedgerColumn(sjsRela) {
  104. const tender = this.ctx.tender;
  105. // const ledgerColumn = [
  106. // 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
  107. // 'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price',
  108. // 'sgfh_qty', 'sgfh_expr', 'sgfh_tp', 'memo', 'drawing_code', 'node_type'];
  109. // if (tender.data.measure_type === measureType.gcl.value) ledgerColumn.push('deal_qty', 'deal_tp');
  110. const ledgerColumn = [
  111. 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
  112. 'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price', 'deal_qty', 'deal_tp',
  113. 'sgfh_qty', 'sgfh_expr', 'sgfh_tp', 'memo', 'drawing_code', 'node_type'];
  114. if (tender.info.display.ledger.dgnQty) ledgerColumn.push('dgn_qty1', 'dgn_qty2');
  115. if (tender.info.display.ledger.clQty) ledgerColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr', 'sjcl_tp', 'qtcl_tp');
  116. const posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'sgfh_qty', 'sgfh_expr', 'add_stage_order', 'drawing_code', 'quantity'];
  117. if (tender.info.display.ledger.clQty) posColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr');
  118. for (const field of sjsRela.ledgerCol) {
  119. if (field.show) {
  120. ledgerColumn.push(field.field);
  121. posColumn.push(field.field);
  122. }
  123. }
  124. return [ledgerColumn, posColumn];
  125. }
  126. /**
  127. * 台账分解页面 (Get)
  128. *
  129. * @param {Object} ctx - egg全局变量
  130. * @return {void}
  131. */
  132. async explode(ctx) {
  133. try {
  134. const tender = ctx.tender;
  135. const [ledgerSpread, posSpread] = await this._getSpreadSetting();
  136. const sjsRela = await this.ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
  137. this.ctx.helper.refreshSpreadShow(sjsRela.ledgerCol, [ledgerSpread, posSpread]);
  138. const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
  139. const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
  140. const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
  141. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
  142. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
  143. const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
  144. const auditHistory = [];
  145. if (times >= 1) {
  146. for (let i = 1; i <= times; i++) {
  147. auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
  148. }
  149. }
  150. const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList(
  151. ctx.tender.data.valuation, ctx.tender.data.measure_type);
  152. // 获取附件列表
  153. const attData = await ctx.service.ledgerAtt.getDataByTenderId(ctx.tender.id);
  154. for (const index in attData) {
  155. attData[index].in_time = moment(attData[index].in_time * 1000).format('YYYY-MM-DD');
  156. attData[index].orginpath = ctx.app.config.fujianOssPath + attData[index].filepath;
  157. delete attData[index].filepath;
  158. }
  159. const whiteList = this.ctx.app.config.multipart.whitelist;
  160. const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
  161. const renderData = {
  162. tender: tender.data,
  163. tenderInfo: tender.info,
  164. auditConst,
  165. auditors,
  166. curAuditor,
  167. user,
  168. attData,
  169. whiteList,
  170. auditHistory,
  171. ledgerSpreadSetting: JSON.stringify(ledgerSpread),
  172. posSpreadSetting: JSON.stringify(posSpread),
  173. tenderMenu,
  174. preUrl: '/tender/' + tender.id,
  175. measureType,
  176. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
  177. stdBills,
  178. stdChapters,
  179. dealBillsPermission: this._canUpdateDealBills(tender.data, auditors.filter(x => {return x.audit_order > 0})),
  180. shenpiConst,
  181. categoryData,
  182. };
  183. if ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId) {
  184. // renderData.accountGroup = accountGroup;
  185. // 获取所有项目参与者
  186. const accountList = await ctx.service.projectAccount.getAllDataByCondition({
  187. where: { project_id: ctx.session.sessionProject.id, enable: 1 },
  188. columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
  189. });
  190. renderData.accountList = accountList;
  191. renderData.accountGroup = accountGroup.map((item, idx) => {
  192. const groupList = accountList.filter(item => item.account_group === idx);
  193. return { groupName: item, groupList };
  194. });
  195. renderData.auditorList = await ctx.service.ledgerAudit.getAuditors(tender.id, tender.data.ledger_times);
  196. }
  197. await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
  198. } catch (err) {
  199. ctx.helper.log(err);
  200. this.postError(err, '标段数据错误');
  201. // await this.redirect('/dashboard');
  202. ctx.redirect(ctx.request.header.referer);
  203. }
  204. }
  205. /**
  206. * 获取子节点 (Ajax)
  207. * @param ctx
  208. * @return {Promise<void>}
  209. */
  210. async getChildren(ctx) {
  211. const responseData = {
  212. err: 0,
  213. msg: '',
  214. data: [],
  215. };
  216. try {
  217. const data = JSON.parse(ctx.request.body.data);
  218. const id = data.ledger_id;
  219. if (isNaN(id) || id <= 0) {
  220. throw '参数错误';
  221. }
  222. responseData.data = await ctx.service.ledger.getChildrenByParentId(ctx.tender.id, id);
  223. } catch (err) {
  224. responseData.err = 1;
  225. responseData.msg = err;
  226. }
  227. ctx.body = responseData;
  228. }
  229. /**
  230. * 树结构基本操作(增、删、上下移、升降级)
  231. * @param {Object} ctx - egg全局变量
  232. * @return {Promise<void>}
  233. */
  234. async _base(ctx, type, data) {
  235. if (isNaN(data.id) || data.id <= 0) throw '数据错误';
  236. if (type !== 'add') {
  237. if (isNaN(data.count) || data.count <= 0) data.count = 1;
  238. }
  239. switch (type) {
  240. case 'add':
  241. return await ctx.service.ledger.addNodeBatch(ctx.tender.id, data.id, {}, data.count);
  242. case 'delete':
  243. return await ctx.service.ledger.delete(ctx.tender.id, data.id, data.count);
  244. case 'up-move':
  245. return await ctx.service.ledger.upMoveNode(ctx.tender.id, data.id, data.count);
  246. case 'down-move':
  247. return await ctx.service.ledger.downMoveNode(ctx.tender.id, data.id, data.count);
  248. case 'up-level':
  249. return await ctx.service.ledger.upLevelNode(ctx.tender.id, data.id, data.count);
  250. case 'down-level':
  251. return await ctx.service.ledger.downLevelNode(ctx.tender.id, data.id, data.count);
  252. }
  253. }
  254. /**
  255. * 复制粘贴整块
  256. *
  257. * @param ctx
  258. * @return {Promise<void>}
  259. */
  260. async _pasteBlock(ctx, data) {
  261. if ((isNaN(data.id) || data.id <= 0) ||
  262. (!data.tid && data.tid <= 0) ||
  263. (!data.block || data.block.length <= 0)) throw '参数错误';
  264. return await ctx.service.ledger.pasteBlockData(ctx.tender.id, data.id, data.block);
  265. }
  266. /**
  267. * 从标准项目表添加数据
  268. * @param ctx
  269. * @return {Promise<void>}
  270. */
  271. async _addStd(ctx, data) {
  272. if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误';
  273. // todo 校验项目是否使用该库的权限
  274. let stdLib,
  275. addType;
  276. switch (data.stdType) {
  277. case 'xmj':
  278. stdLib = ctx.service.stdXmj;
  279. addType = stdDataAddType.withParent;
  280. break;
  281. case 'gcl':
  282. stdLib = ctx.service.stdGcl;
  283. const selectNode = await ctx.service.ledger.getDataByNodeId(ctx.tender.id, data.id);
  284. if (selectNode.b_code) {
  285. addType = stdDataAddType.next;
  286. } else {
  287. addType = stdDataAddType.child;
  288. }
  289. break;
  290. default:
  291. throw '未知标准库';
  292. }
  293. const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode);
  294. switch (addType) {
  295. case stdDataAddType.child:
  296. return await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData);
  297. case stdDataAddType.next:
  298. return await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData);
  299. case stdDataAddType.withParent:
  300. return await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, stdData, stdLib);
  301. default:
  302. throw '未知添加方式';
  303. }
  304. }
  305. /**
  306. * 从签约清单添加节点
  307. * @param ctx
  308. * @return {Promise<void>}
  309. */
  310. async _addDeal(ctx, data) {
  311. if (!data.type || !data.dealBills) throw '数据错误';
  312. data.dealBills.unit_price = this.ctx.helper.round(data.dealBills.unit_price, ctx.tender.info.decimal.up);
  313. if (data.type === 'child') {
  314. return await ctx.service.ledger.addChild(ctx.tender.id, data.id, data.dealBills);
  315. } else if (data.type === 'next') {
  316. return await ctx.service.ledger.addNode(ctx.tender.id, data.id, data.dealBills);
  317. }
  318. throw '数据错误';
  319. }
  320. /**
  321. * 批量插入数据
  322. *
  323. * data = {id, batchData, batchType}
  324. * data.batchType = 'batchInsertChild'/'batchInsertNext'
  325. * data.batchData = [{name, children}] -- 项目节列表
  326. * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表
  327. *
  328. * @param ctx
  329. * @return {Promise<void>}
  330. */
  331. async _batchInsert(ctx, data) {
  332. if ((isNaN(data.id) || data.id <= 0) || !data.batchType) throw '参数错误';
  333. switch (data.batchType) {
  334. case 'child':
  335. return await ctx.service.ledger.batchInsertChild(ctx.tender.id, data.id, data.batchData);
  336. case 'next':
  337. return await ctx.service.ledger.batchInsertNext(ctx.tender.id, data.id, data.batchData);
  338. default:
  339. throw '参数错误';
  340. }
  341. }
  342. /**
  343. * 更新清单相关 (Ajax)
  344. * @param ctx
  345. * @return {Promise<void>}
  346. */
  347. async update(ctx) {
  348. try {
  349. if (!ctx.tender.data) throw '标段数据错误';
  350. if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作';
  351. const data = JSON.parse(ctx.request.body.data);
  352. if (!data.postType || !data.postData) throw '数据错误';
  353. const responseData = {
  354. err: 0,
  355. msg: '',
  356. data: [],
  357. };
  358. switch (data.postType) {
  359. case 'add':
  360. case 'delete':
  361. case 'up-move':
  362. case 'down-move':
  363. case 'up-level':
  364. case 'down-level':
  365. responseData.data = await this._base(ctx, data.postType, data.postData);
  366. break;
  367. case 'update':
  368. ctx.helper.checkDgnQtyPrecision(data.postData);
  369. responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data.postData);
  370. break;
  371. case 'paste-block':
  372. responseData.data = await this._pasteBlock(ctx, data.postData);
  373. break;
  374. case 'add-std':
  375. responseData.data = await this._addStd(ctx, data.postData);
  376. break;
  377. case 'add-deal':
  378. responseData.data = await this._addDeal(ctx, data.postData);
  379. break;
  380. case 'batch-insert':
  381. responseData.data = await this._batchInsert(ctx, data.postData);
  382. break;
  383. default:
  384. throw '未知操作';
  385. }
  386. ctx.body = responseData;
  387. } catch (err) {
  388. this.log(err);
  389. ctx.body = this.ajaxErrorBody(err);
  390. }
  391. }
  392. /**
  393. * 定位
  394. * @param ctx
  395. * @return {Promise<void>}
  396. */
  397. async locate(ctx) {
  398. const responseData = {
  399. err: 0,
  400. msg: '',
  401. data: [],
  402. };
  403. try {
  404. const tenderId = ctx.params.id;
  405. if (!tenderId) {
  406. throw '当前未打开标段';
  407. }
  408. const data = JSON.parse(ctx.request.body.data);
  409. if ((isNaN(data.id) || data.id <= 0)) {
  410. throw '参数错误';
  411. }
  412. responseData.data = await ctx.service.ledger.locateNode(tenderId, data.id);
  413. } catch (err) {
  414. this.log(err);
  415. responseData.err = 1;
  416. responseData.msg = err;
  417. }
  418. ctx.body = responseData;
  419. }
  420. /**
  421. * 获取全部子节点
  422. *
  423. * @param ctx
  424. * @return {Promise<void>}
  425. */
  426. async posterity(ctx) {
  427. const responseData = {
  428. err: 0,
  429. msg: '',
  430. data: [],
  431. };
  432. try {
  433. const tenderId = ctx.params.id;
  434. if (!tenderId) {
  435. throw '当前未打开标段';
  436. }
  437. const data = JSON.parse(ctx.request.body.data);
  438. if ((isNaN(data.id) || data.id <= 0)) {
  439. throw '参数错误';
  440. }
  441. const expandData = await ctx.service.ledger.getPosterityByParentId(tenderId, data.id);
  442. responseData.data = { expand: expandData };
  443. } catch (err) {
  444. this.log(err);
  445. responseData.err = 1;
  446. responseData.msg = err;
  447. }
  448. ctx.body = responseData;
  449. }
  450. /**
  451. * 获取部位明细数据(Ajax)
  452. *
  453. * @param ctx
  454. * @return {Promise<void>}
  455. */
  456. async loadExplodeData(ctx) {
  457. try {
  458. const sjsRela = await ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
  459. const [ledgerColumn, posColumn] = this._getLedgerColumn(sjsRela);
  460. const ledgerData = ctx.tender.ledgerReadOnly && ctx.tender.his
  461. ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file)
  462. : await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } });
  463. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  464. ? (ctx.tender.ledgerReadOnly && ctx.tender.his
  465. ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file)
  466. : await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn))
  467. : [];
  468. const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id);
  469. //ctx.body = { err: 0, msg: '', data: { bills: this.ctx.helper.hpackArr(ledgerData), pos: this.ctx.helper.hpackArr(posData), tags: ledgerTags }, hpack: ['bills', 'pos'] };
  470. ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, tags: ledgerTags } };
  471. } catch (err) {
  472. this.log(err);
  473. ctx.body = { err: 1, msg: err.toString(), data: [] };
  474. }
  475. }
  476. async check(ctx) {
  477. try {
  478. const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
  479. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  480. ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
  481. const checkDataModel = require('../lib/ledger').checkData;
  482. const checkData = new checkDataModel(ctx, measureType);
  483. checkData.loadData(ledgerData, posData);
  484. ctx.tender.info.ledger_check.same_code && checkData.checkSameCode();
  485. ctx.tender.info.ledger_check.sibling && checkData.checkSibling();
  486. if (ctx.tender.data.measure_type === measureType.tz.value) checkData.checkBillsQty(['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']);
  487. checkData.checkBillsTp([
  488. {qty: 'sgfh_qty', tp: 'sgfh_tp'}, {qty: 'qtcl_qty', tp: 'qtcl_tp'},
  489. {qty: 'sjcl_qty', tp: 'sjcl_tp'}, {qty: 'quantity', tp: 'total_price'}
  490. ], this.ctx.tender.info.decimal);
  491. ctx.body = { err: 0, msg: '', data: checkData.checkResult };
  492. } catch (err) {
  493. this.log(err);
  494. ctx.body = this.ajaxErrorBody(err, '检查数据错误');
  495. }
  496. }
  497. /**
  498. * 更新 部位明细数据
  499. *
  500. * @param ctx
  501. * @return {Promise<void>}
  502. */
  503. async posUpdate(ctx) {
  504. try {
  505. await this.checkMeasureType(measureType.tz.value);
  506. const data = JSON.parse(ctx.request.body.data);
  507. const responseData = await ctx.service.pos.savePosData(data, ctx.tender.id);
  508. ctx.body = { err: 0, msg: '', data: responseData };
  509. } catch (err) {
  510. this.log(err);
  511. ctx.body = { err: 1, msg: err.toString(), data: null };
  512. }
  513. }
  514. /**
  515. * 复制粘贴 部位明细
  516. * @param ctx
  517. * @return {Promise<void>}
  518. */
  519. async posPaste(ctx) {
  520. try {
  521. await this.checkMeasureType(measureType.tz.value);
  522. const data = JSON.parse(ctx.request.body.data);
  523. const result = await ctx.service.pos.pastePosData(data, ctx.tender.id);
  524. ctx.body = { err: 0, msg: '', data: result };
  525. } catch (err) {
  526. this.log(err);
  527. ctx.body = { err: 1, msg: err.toString(), data: null };
  528. }
  529. }
  530. /**
  531. * 上传 清单Excel 并导入
  532. * @param ctx
  533. * @return {Promise<void>}
  534. */
  535. async uploadExcel(ctx) {
  536. try {
  537. const ueType = ctx.params.ueType;
  538. const compressData = ctx.request.body.data;
  539. const data = JSON.parse(LzString.decompressFromUTF16(compressData));
  540. const responseData = { err: 0, msg: '', data: {} };
  541. switch (ueType) {
  542. case 'tz':
  543. const templateId = await this.ctx.service.valuation.getValuationTemplate(
  544. this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
  545. responseData.data = await ctx.service.ledger.importExcel(templateId, data.sheet, data.filter);
  546. break;
  547. case 'gcl2xmj':
  548. responseData.data = await ctx.service.ledger.importGclExcel(data.id, data.sheet);
  549. break;
  550. default:
  551. throw '数据错误';
  552. }
  553. ctx.body = responseData;
  554. } catch (err) {
  555. this.log(err);
  556. ctx.body = { err: 1, msg: err.toString(), data: null };
  557. }
  558. }
  559. // async uploadExcel(ctx) {
  560. // let stream;
  561. // try {
  562. // const responseData = { err: 0, msg: '', data: {}, };
  563. // // 保存文件
  564. // stream = await ctx.getFileStream();
  565. // const create_time = Date.parse(new Date());
  566. // const fileInfo = path.parse(stream.filename);
  567. // const fileName = this.app.config.filePath + '/cache/ledger/uploads/' + create_time + fileInfo.ext;
  568. // await ctx.helper.saveStreamFile(stream, fileName);
  569. // // 读取excel
  570. // console.log(ctx.query);
  571. // const name = ctx.query.sheetName;
  572. // if (!name) throw '未选择需要导入的工作簿';
  573. // const wb = xlsx.readFile(fileName);
  574. // const sheetData = {
  575. // rows: xlsx.utils.sheet_to_json(wb.Sheets[name], {header: 1}),
  576. // merge: wb.Sheets[name]["!merges"],
  577. // };
  578. // if (!sheetData.rows) throw '读取工作簿数据错误';
  579. // const ueType = ctx.query.ueType;
  580. // switch (ueType) {
  581. // case 'tz':
  582. // const templateId = await this.ctx.service.valuation.getValuationTemplate(
  583. // this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
  584. // responseData.data = await ctx.service.ledger.importExcel(templateId, sheetData);
  585. // break;
  586. // case 'gcl2xmj':
  587. // responseData.data = await ctx.service.ledger.importGclExcel(ctx.tender.id, sheetData);
  588. // break;
  589. // default:
  590. // throw '数据错误';
  591. // }
  592. // ctx.body = responseData;
  593. // } catch (err) {
  594. // console.log(err);
  595. // this.log(err);
  596. // // 失败需要消耗掉stream 以防卡死
  597. // if (stream) await sendToWormhole(stream);
  598. // ctx.body = {err: 1, msg: err.toString(), data: null};
  599. // }
  600. // }
  601. /**
  602. * 填设计量(Ajax)
  603. *
  604. * @param ctx
  605. * @return {Promise<void>}
  606. */
  607. async deal2sgfh(ctx) {
  608. try {
  609. if (!ctx.tender.data) throw '标段数据错误';
  610. if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作';
  611. if (this.ctx.tender.measure_type === measureType.tz.value) throw '该功能仅工程量清单模式可用';
  612. await this.ctx.service.ledger.deal2sgfh(ctx.tender.id);
  613. const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
  614. ctx.body = { err: 0, msg: '', data: { bills: ledgerData } };
  615. } catch (err) {
  616. this.log(err);
  617. ctx.body = { err: 1, msg: err.toString(), data: null };
  618. }
  619. }
  620. /**
  621. * 下载(清单Excel模板 or 导出项目台账Excel)
  622. * @param ctx
  623. * @return {Promise<void>}
  624. */
  625. async download(ctx) {
  626. const file = ctx.params.file;
  627. if (file) {
  628. try {
  629. let fileName;
  630. if (file === '导入分项清单EXCEL格式.xlsx') {
  631. fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xlsx');
  632. ctx.body = await fs.readFileSync(fileName);
  633. } else if (file === '台账分解.xlsx') {
  634. const create_time = Date.parse(new Date()) / 1000;
  635. fileName = this.app.baseDir + '/app/public/files/ledger' + ctx.tender.id + '-' + create_time + '.xlsx';
  636. const exportor = new exportExcel.exportLedger2Excel(ctx);
  637. await exportor.export2File(fileName);
  638. ctx.body = await fs.readFileSync(fileName);
  639. // 输出文件后删除
  640. fs.unlinkSync(fileName);
  641. }
  642. } catch (err) {
  643. this.log(err);
  644. this.setMessage(err.toString(), this.messageType.ERROR);
  645. }
  646. }
  647. }
  648. /**
  649. * 部位台账 页面 (Get)
  650. * @param ctx
  651. * @return {Promise<void>}
  652. */
  653. async bwtz(ctx) {
  654. try {
  655. const sjsRela = await ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo);
  656. const renderData = {
  657. tender: ctx.tender.data,
  658. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.bwtz),
  659. ex_memo1: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo1'; }),
  660. ex_memo2: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo2'; }),
  661. ex_memo3: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo3'; }),
  662. };
  663. await this.layout('ledger/bwtz.ejs', renderData);
  664. } catch (err) {
  665. this.log(err);
  666. ctx.redirect(ctx.request.header.referer);
  667. }
  668. }
  669. async loadBwtz(ctx) {
  670. try {
  671. const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
  672. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  673. ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
  674. const convert = new billsPosConvert(ctx);
  675. convert.loadData(ledgerData, posData, []);
  676. const result = await convert.convert();
  677. // const wbsCodeHis = await ctx.service.externalData.getExValue(ctx.tender.id, -1,
  678. // externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode) || [];
  679. // const [result, needUpdate] = convert.convertByWbsCode(wbsCodeHis);
  680. // if (needUpdate) {
  681. // await ctx.service.externalData.saveExValue(ctx.tender.id, -1,
  682. // externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode, wbsCodeHis);
  683. // }
  684. ctx.body = { err: 0, msg: '', data: result };
  685. } catch (err) {
  686. this.log(err);
  687. ctx.body = this.ajaxErrorBody(err, '加载合同支付数据错误');
  688. }
  689. }
  690. /**
  691. * 台账对比 页面 (Get)
  692. * @param ctx
  693. * @return {Promise<void>}
  694. */
  695. async gather(ctx) {
  696. try {
  697. const renderData = {
  698. measureType,
  699. tender: ctx.tender.data,
  700. jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.gather),
  701. };
  702. await this.layout('ledger/gather.ejs', renderData);
  703. } catch (err) {
  704. this.log(err);
  705. await this.redirect(ctx.request.header.referer);
  706. }
  707. }
  708. /**
  709. * 获取 台账对比 数据 (Ajax)
  710. * @param ctx
  711. * @return {Promise<void>}
  712. */
  713. async loadGatherData(ctx) {
  714. try {
  715. const billsData = await ctx.service.ledger.getData(ctx.tender.id);
  716. const posData = this.ctx.tender.data.measure_type === measureType.tz.value
  717. ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : [];
  718. const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } });
  719. const zlj = JSON.parse(JSON.stringify(stdConst.zlj));
  720. zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
  721. ctx.body = { err: 0, msg: '', data: {
  722. bills: billsData, pos: posData, dealBills, spec: {zlj: zlj, jrg: stdConst.jrg},
  723. }};
  724. } catch (err) {
  725. this.log(err);
  726. ctx.body = { err: 1, msg: err.toString(), data: [] };
  727. }
  728. }
  729. /**
  730. * 计量台账页面 (Get)
  731. *
  732. * @param {object} ctx - egg全局变量
  733. * @return {void}
  734. */
  735. async index(ctx) {
  736. const renderData = {};
  737. await this.layout('ledger/index.ejs', renderData);
  738. }
  739. /**
  740. * 审批流程(Get)
  741. * @param ctx
  742. * @return {Promise<void>}
  743. */
  744. async ledgerAuditors(ctx) {
  745. try {
  746. const responseData = {
  747. err: 0, msg: '', data: {},
  748. };
  749. const tender = ctx.tender;
  750. const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
  751. const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ?
  752. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) :
  753. await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times);
  754. const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
  755. const auditHistory = [];
  756. if (times >= 1) {
  757. for (let i = 1; i <= times; i++) {
  758. auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i));
  759. }
  760. }
  761. responseData.data.auditHistory = auditHistory;
  762. // 获取审批流程中左边列表
  763. responseData.data.auditors = auditors;
  764. responseData.data.user = user;
  765. ctx.body = responseData;
  766. } catch (error) {
  767. this.log(error);
  768. ctx.body = { err: 1, msg: error.toString(), data: null };
  769. }
  770. }
  771. /**
  772. * 上传附件
  773. * @param {Object} ctx - egg全局变量
  774. * @return {void}
  775. */
  776. async uploadFile(ctx) {
  777. const responseData = {
  778. err: 0,
  779. msg: '',
  780. data: [],
  781. };
  782. let stream;
  783. try {
  784. const parts = ctx.multipart({ autoFields: true });
  785. const files = [];
  786. let index = 0;
  787. const extra_upload = ctx.tender.data.ledger_status === auditConst.status.checked;
  788. while ((stream = await parts()) !== undefined) {
  789. // 判断用户是否选择上传文件
  790. if (!stream.filename) {
  791. throw '请选择上传的文件!';
  792. }
  793. const fileInfo = path.parse(stream.filename);
  794. const create_time = Date.parse(new Date()) / 1000;
  795. const filepath = `app/public/upload/${this.ctx.tender.id}/ledger/fujian_${create_time + index.toString() + fileInfo.ext}`;
  796. // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, filepath));
  797. await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream);
  798. if (stream) {
  799. await sendToWormhole(stream);
  800. }
  801. // 保存数据到att表
  802. const fileData = {
  803. tid: ctx.params.id,
  804. in_time: create_time,
  805. filename: fileInfo.name,
  806. fileext: fileInfo.ext,
  807. filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
  808. filepath,
  809. extra_upload,
  810. };
  811. const result = await ctx.service.ledgerAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId);
  812. if (!result) {
  813. throw '导入数据库保存失败';
  814. }
  815. const attData = await ctx.service.ledgerAtt.getDataByFid(result.insertId);
  816. attData.in_time = moment(create_time * 1000).format('YYYY-MM-DD');
  817. files.length !== 0 ? files.unshift(attData) : files.push(attData);
  818. ++index;
  819. }
  820. responseData.data = files;
  821. } catch (err) {
  822. this.log(err);
  823. // 失败需要消耗掉stream 以防卡死
  824. if (stream) {
  825. await sendToWormhole(stream);
  826. }
  827. this.setMessage(err.toString(), this.messageType.ERROR);
  828. responseData.err = 1;
  829. responseData.msg = err.toString();
  830. }
  831. ctx.body = responseData;
  832. }
  833. /**
  834. * 下载附件
  835. * @param {Object} ctx - egg全局变量
  836. * @return {void}
  837. */
  838. async downloadFile(ctx) {
  839. const id = ctx.params.fid;
  840. if (id) {
  841. try {
  842. const fileInfo = await ctx.service.ledgerAtt.getDataById(id);
  843. if (fileInfo !== undefined && fileInfo !== '') {
  844. // const fileName = path.join(this.app.baseDir, fileInfo.filepath);
  845. // 解决中文无法下载问题
  846. const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
  847. let disposition = '';
  848. if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
  849. disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext);
  850. } else if (userAgent.indexOf('firefox') >= 0) {
  851. disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"';
  852. } else {
  853. /* safari等其他非主流浏览器只能自求多福了 */
  854. disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary');
  855. }
  856. ctx.response.set({
  857. 'Content-Type': 'application/octet-stream',
  858. 'Content-Disposition': disposition,
  859. 'Content-Length': fileInfo.filesize,
  860. });
  861. // ctx.body = await fs.createReadStream(fileName);
  862. ctx.body = await ctx.helper.ossFileGet(fileInfo.filepath);
  863. } else {
  864. throw '不存在该文件';
  865. }
  866. } catch (err) {
  867. this.log(err);
  868. this.setMessage(err.toString(), this.messageType.ERROR);
  869. }
  870. }
  871. }
  872. /**
  873. * 查看附件
  874. * @param {Object} ctx - egg全局变量
  875. * @return {void}
  876. */
  877. async checkFile(ctx) {
  878. const responseData = { err: 0, msg: '' };
  879. const { id = '' } = JSON.parse(ctx.request.body.data);
  880. if (id) {
  881. try {
  882. const fileInfo = await ctx.service.ledgerAtt.getDataById(id);
  883. if (fileInfo && Object.keys(fileInfo).length) {
  884. let filepath = fileInfo.filepath;
  885. if (!ctx.helper.canPreview(fileInfo.fileext)) {
  886. filepath = `/tender/${ctx.tender.id}/ledger/download/file/${fileInfo.id}`;
  887. } else {
  888. // filepath = filepath.replace(/^app|\/app/, '');
  889. filepath = ctx.app.config.fujianOssPath + filepath;
  890. }
  891. fileInfo.filepath && (responseData.data = { filepath });
  892. }
  893. } catch (error) {
  894. this.log(error);
  895. this.setMessage(error.toString(), this.messageType.ERROR);
  896. responseData.err = 1;
  897. responseData.msg = error.toString();
  898. }
  899. }
  900. ctx.body = responseData;
  901. }
  902. /**
  903. * 删除附件
  904. * @param {Object} ctx - egg全局变量
  905. * @return {void}
  906. */
  907. async deleteFile(ctx) {
  908. const responseData = {
  909. err: 0,
  910. msg: '',
  911. data: '',
  912. };
  913. try {
  914. // this._checkStageCanModifyRe(ctx);
  915. const data = JSON.parse(ctx.request.body.data);
  916. const fileInfo = await ctx.service.ledgerAtt.getDataById(data.id);
  917. if (!fileInfo || !Object.keys(fileInfo).length) {
  918. throw '该文件不存在';
  919. }
  920. if (!fileInfo.extra_upload && ctx.tender.status === auditConst.status.checked) {
  921. throw '无权限删除';
  922. }
  923. if (fileInfo !== undefined && fileInfo !== '') {
  924. // 先删除文件
  925. // await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath));
  926. await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath);
  927. // 再删除数据库
  928. await ctx.service.ledgerAtt.deleteById(data.id);
  929. responseData.data = '';
  930. } else {
  931. throw '不存在该文件';
  932. }
  933. } catch (err) {
  934. responseData.err = 1;
  935. responseData.msg = err;
  936. }
  937. ctx.body = responseData;
  938. }
  939. /**
  940. * 保存附件(或替换)
  941. * @param {Object} ctx - egg全局变量
  942. * @return {void}
  943. */
  944. async saveFile(ctx) {
  945. const responseData = {
  946. err: 0,
  947. msg: '',
  948. data: [],
  949. };
  950. let stream;
  951. try {
  952. // this._checkStageCanModifyRe(ctx);
  953. stream = await ctx.getFileStream({ requireFile: false });
  954. let fileData = {};
  955. if (stream.filename !== undefined) {
  956. const create_time = Date.parse(new Date()) / 1000;
  957. const fileInfo = path.parse(stream.filename);
  958. const dirName = `app/public/upload/${this.ctx.tender.id}/ledger`;
  959. const fileName = 'fujian_' + create_time + fileInfo.ext;
  960. // 判断文件夹是否存在,不存在则直接创建文件夹
  961. // if (!fs.existsSync(path.join(this.app.baseDir, dirName))) {
  962. // await fs.mkdirSync(path.join(this.app.baseDir, dirName));
  963. // }
  964. // 保存文件
  965. // await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
  966. await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + path.join(dirName, fileName), stream);
  967. // 保存数据到att表
  968. fileData = {
  969. filesize: stream.fields.size,
  970. filepath: path.join(dirName, fileName),
  971. };
  972. }
  973. const result = await ctx.service.ledgerAtt.updateByID(stream.fields, fileData);
  974. if (!result) {
  975. throw '导入数据库保存失败';
  976. }
  977. const attData = await ctx.service.ledgerAtt.getDataByFid(stream.fields.id);
  978. attData.in_time = moment(attData.in_time * 1000).format('YYYY-MM-DD');
  979. responseData.data = attData;
  980. } catch (err) {
  981. this.log(err);
  982. // 失败需要消耗掉stream 以防卡死
  983. if (stream) {
  984. await sendToWormhole(stream);
  985. }
  986. this.setMessage(err.toString(), this.messageType.ERROR);
  987. responseData.err = 1;
  988. responseData.msg = err.toString();
  989. }
  990. ctx.body = responseData;
  991. }
  992. /**
  993. * 批量下载 - 压缩成zip文件返回
  994. * @param {Object} ctx - 全局上下文
  995. */
  996. async downloadZip(ctx) {
  997. const zipFilename = `${ctx.tender.data.name}-台账分解-附件.zip`;
  998. const time = Date.now();
  999. const zipPath = `app/public/upload/${ctx.tender.id}/ledger/fu_jian_zip${time}.zip`;
  1000. const responseData = {
  1001. err: 0,
  1002. msg: '',
  1003. };
  1004. try {
  1005. const { fileIds = [] } = JSON.parse(ctx.request.body.data);
  1006. const size = await ctx.service.ledgerAtt.compressedFile(fileIds, zipPath);
  1007. // 解决中文无法下载问题
  1008. const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
  1009. let disposition = '';
  1010. if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
  1011. disposition = 'attachment; filename=' + encodeURIComponent(zipFilename);
  1012. } else if (userAgent.indexOf('firefox') >= 0) {
  1013. disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"';
  1014. } else {
  1015. /* safari等其他非主流浏览器只能自求多福了 */
  1016. disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary');
  1017. }
  1018. ctx.response.set({
  1019. 'Content-Type': 'application/octet-stream',
  1020. 'Content-Disposition': disposition,
  1021. 'Content-Length': size,
  1022. });
  1023. const readStream = fs.createReadStream(path.join(this.app.baseDir, zipPath));
  1024. ctx.body = readStream;
  1025. readStream.on('close', () => {
  1026. if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
  1027. fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
  1028. }
  1029. });
  1030. // fs的错误不能被try catch捕捉
  1031. readStream.on('error', err => {
  1032. this.log(err);
  1033. if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
  1034. fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
  1035. }
  1036. responseData.err = 1;
  1037. responseData.msg = err.toString();
  1038. ctx.body = responseData;
  1039. });
  1040. } catch (err) {
  1041. this.log(err);
  1042. if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
  1043. fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
  1044. }
  1045. this.setMessage(err.toString(), this.messageType.ERROR);
  1046. responseData.err = 1;
  1047. responseData.msg = err.toString();
  1048. ctx.body = responseData;
  1049. }
  1050. }
  1051. }
  1052. return LedgerController;
  1053. };