'use strict'; /** * 台账相关控制器 * * @author CaiAoLin * @date 2017/11/30 * @version */ const stdDataAddType = { withParent: 1, child: 2, next: 3, }; const audit = require('../const/audit'); const auditConst = audit.ledger; const tenderMenu = require('../../config/menu').tenderMenu; const measureType = require('../const/tender').measureType; const spreadConst = require('../const/spread'); const shenpiConst = require('../const/shenpi'); const externalDataConst = require('../const/external_data.js'); const fs = require('fs'); const LzString = require('lz-string'); const accountGroup = require('../const/account_group').group; const path = require('path'); const exportExcel = require('../lib/export_excel'); const billsPosConvert = require('../lib/bills_pos_convert'); const xlsx = require('js-xlsx'); const stdConst = require('../const/standard'); module.exports = app => { class LedgerController extends app.BaseController { /** * 构造函数 * * @param {Object} ctx - egg全局变量 * @return {void} */ constructor(ctx) { super(ctx); ctx.showProject = true; ctx.showTitle = true; ctx.showTender = true; } /** * 检查标段是否只读(审核中,审核完成) * @param {Object} tenderData * @return {boolean} * @private */ _ledgerReadOnly() { const tender = this.ctx.tender.data; return tender.user_id !== this.ctx.session.sessionUser.accountId || (tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked); } _canUpdateDealBills(tender, auditors) { const isUser = tender.user_id === this.ctx.session.sessionUser.accountId; const auditorsId = this.ctx.helper._.map(auditors, 'audit_id'); const isAuditor = auditorsId.indexOf(this.ctx.session.sessionUser.accountId) >= 0; const upPermission = this.ctx.session.sessionUser.permission ? this.ctx.session.sessionUser.permission.tender.indexOf('3') >= 0 : false; return ((tender.ledger_status === auditConst.status.uncheck || tender.ledger_status === auditConst.status.checkNo) && isUser) || (tender.ledger_status === auditConst.status.checking && isAuditor) || (tender.ledger_status === auditConst.status.checked && isAuditor && upPermission); } /** * 获取SpreadSetting * @private */ _getSpreadSetting() { const _ = this.app._; function removeFieldCols(setting, cols) { _.remove(setting.cols, function(c) { return cols.indexOf(c.field) > -1; }); } function hideFieldCols(setting, cols) { for (const c of setting.cols) { if (cols.indexOf(c.field) > -1) { c.visible = false; } } } function setColFormat(cols, field, formatter) { const col = _.find(cols, function(c) { return c.field === field; }); col.formatter = formatter; } // const tpFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.tp); // const upFormatter = this.ctx.helper.getNumberFormatter(this.ctx.tender.info.decimal.up); const tender = this.ctx.tender; const setting = tender.data.measure_type === measureType.tz.value ? (tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl) : (tender.info.display.ledger.clQty ? spreadConst.withClGcl : spreadConst.withoutClGcl); const ledger = JSON.parse(JSON.stringify(setting.ledger)); // setColFormat(ledger.cols, 'unit_price', upFormatter); // setColFormat(ledger.cols, 'dgn_price', upFormatter); // setColFormat(ledger.cols, 'total_price', tpFormatter); // setColFormat(ledger.cols, 'deal_tp', tpFormatter); const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank; if (this._ledgerReadOnly(tender.data)) { ledger.readOnly = true; pos.readOnly = true; } if (tender.data.measure_type === measureType.tz.value) { removeFieldCols(ledger, spreadConst.filterCols.tzWithoutCols); } if (!tender.info.display.ledger.dgnQty) { removeFieldCols(ledger, spreadConst.filterCols.dgnCols); } return [ledger, pos]; } /** * 台账分解页面 (Get) * * @param {Object} ctx - egg全局变量 * @return {void} */ async explode(ctx) { try { const tender = ctx.tender; const [ledgerSpread, posSpread] = this._getSpreadSetting(); const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times; const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times); const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId ? await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, times) : await ctx.service.ledgerAudit.getAuditorsWithOwner(tender.id, tender.data.ledger_times); const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id); const auditHistory = []; if (times >= 1) { for (let i = 1; i <= times; i++) { auditHistory.push(await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, i)); } } const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList( ctx.tender.data.valuation, ctx.tender.data.measure_type); const renderData = { tender: tender.data, tenderInfo: tender.info, auditConst, auditors, curAuditor, user, auditHistory, ledgerSpreadSetting: JSON.stringify(ledgerSpread), posSpreadSetting: JSON.stringify(posSpread), tenderMenu, preUrl: '/tender/' + tender.id, measureType, jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode), stdBills, stdChapters, dealBillsPermission: this._canUpdateDealBills(tender.data, auditors.filter(x => {return x.audit_order > 0})), shenpiConst, }; if ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId) { // renderData.accountGroup = accountGroup; // 获取所有项目参与者 const accountList = await ctx.service.projectAccount.getAllDataByCondition({ where: { project_id: ctx.session.sessionProject.id, enable: 1 }, columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'], }); renderData.accountList = accountList; renderData.accountGroup = accountGroup.map((item, idx) => { const groupList = accountList.filter(item => item.account_group === idx); return { groupName: item, groupList }; }); renderData.auditorList = await ctx.service.ledgerAudit.getAuditors(tender.id, tender.data.ledger_times); } await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs'); } catch (err) { ctx.helper.log(err); this.postError(err, '标段数据错误'); await this.redirect('/dashboard'); } } /** * 获取子节点 (Ajax) * @param ctx * @return {Promise} */ async getChildren(ctx) { const responseData = { err: 0, msg: '', data: [], }; try { const data = JSON.parse(ctx.request.body.data); const id = data.ledger_id; if (isNaN(id) || id <= 0) { throw '参数错误'; } responseData.data = await ctx.service.ledger.getChildrenByParentId(ctx.tender.id, id); } catch (err) { responseData.err = 1; responseData.msg = err; } ctx.body = responseData; } /** * 树结构基本操作(增、删、上下移、升降级) * @param {Object} ctx - egg全局变量 * @return {Promise} */ async _base(ctx, type, data) { if (isNaN(data.id) || data.id <= 0) throw '数据错误'; if (type !== 'add') { if (isNaN(data.count) || data.count <= 0) data.count = 1; } switch (type) { case 'add': return await ctx.service.ledger.addNodeBatch(ctx.tender.id, data.id, {}, data.count); case 'delete': return await ctx.service.ledger.delete(ctx.tender.id, data.id, data.count); case 'up-move': return await ctx.service.ledger.upMoveNode(ctx.tender.id, data.id, data.count); case 'down-move': return await ctx.service.ledger.downMoveNode(ctx.tender.id, data.id, data.count); case 'up-level': return await ctx.service.ledger.upLevelNode(ctx.tender.id, data.id, data.count); case 'down-level': return await ctx.service.ledger.downLevelNode(ctx.tender.id, data.id, data.count); } } /** * 复制粘贴整块 * * @param ctx * @return {Promise} */ async _pasteBlock(ctx, data) { if ((isNaN(data.id) || data.id <= 0) || (!data.tid && data.tid <= 0) || (!data.block || data.block.length <= 0)) throw '参数错误'; return await ctx.service.ledger.pasteBlockData(ctx.tender.id, data.id, data.block); } /** * 从标准项目表添加数据 * @param ctx * @return {Promise} */ async _addStd(ctx, data) { if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) throw '参数错误'; // todo 校验项目是否使用该库的权限 let stdLib, addType; switch (data.stdType) { case 'xmj': stdLib = ctx.service.stdXmj; addType = stdDataAddType.withParent; break; case 'gcl': stdLib = ctx.service.stdGcl; const selectNode = await ctx.service.ledger.getDataByNodeId(ctx.tender.id, data.id); if (selectNode.b_code) { addType = stdDataAddType.next; } else { addType = stdDataAddType.child; } break; default: throw '未知标准库'; } const stdData = await stdLib.getDataByDataId(data.stdLibId, data.stdNode); switch (addType) { case stdDataAddType.child: return await ctx.service.ledger.addStdNodeAsChild(ctx.tender.id, data.id, stdData); case stdDataAddType.next: return await ctx.service.ledger.addStdNode(ctx.tender.id, data.id, stdData); case stdDataAddType.withParent: return await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, stdData, stdLib); default: throw '未知添加方式'; } } /** * 从签约清单添加节点 * @param ctx * @return {Promise} */ async _addDeal(ctx, data) { if (!data.type || !data.dealBills) throw '数据错误'; data.dealBills.unit_price = this.ctx.helper.round(data.dealBills.unit_price, ctx.tender.info.decimal.up); if (data.type === 'child') { return await ctx.service.ledger.addChild(ctx.tender.id, data.id, data.dealBills); } else if (data.type === 'next') { return await ctx.service.ledger.addNode(ctx.tender.id, data.id, data.dealBills); } throw '数据错误'; } /** * 批量插入数据 * * data = {id, batchData, batchType} * data.batchType = 'batchInsertChild'/'batchInsertNext' * data.batchData = [{name, children}] -- 项目节列表 * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表 * * @param ctx * @return {Promise} */ async _batchInsert(ctx, data) { if ((isNaN(data.id) || data.id <= 0) || !data.batchType) throw '参数错误'; switch (data.batchType) { case 'child': return await ctx.service.ledger.batchInsertChild(ctx.tender.id, data.id, data.batchData); case 'next': return await ctx.service.ledger.batchInsertNext(ctx.tender.id, data.id, data.batchData); default: throw '参数错误'; } } /** * 更新清单相关 (Ajax) * @param ctx * @return {Promise} */ async update(ctx) { try { if (!ctx.tender.data) throw '标段数据错误'; if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作'; const data = JSON.parse(ctx.request.body.data); if (!data.postType || !data.postData) throw '数据错误'; const responseData = { err: 0, msg: '', data: [], }; switch (data.postType) { case 'add': case 'delete': case 'up-move': case 'down-move': case 'up-level': case 'down-level': responseData.data = await this._base(ctx, data.postType, data.postData); break; case 'update': responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data.postData); break; case 'paste-block': responseData.data = await this._pasteBlock(ctx, data.postData); break; case 'add-std': responseData.data = await this._addStd(ctx, data.postData); break; case 'add-deal': responseData.data = await this._addDeal(ctx, data.postData); break; case 'batch-insert': responseData.data = await this._batchInsert(ctx, data.postData); break; default: throw '未知操作'; } ctx.body = responseData; } catch (err) { this.log(err); ctx.body = this.ajaxErrorBody(err); } } /** * 定位 * @param ctx * @return {Promise} */ async locate(ctx) { const responseData = { err: 0, msg: '', data: [], }; try { const tenderId = ctx.params.id; if (!tenderId) { throw '当前未打开标段'; } const data = JSON.parse(ctx.request.body.data); if ((isNaN(data.id) || data.id <= 0)) { throw '参数错误'; } responseData.data = await ctx.service.ledger.locateNode(tenderId, data.id); } catch (err) { this.log(err); responseData.err = 1; responseData.msg = err; } ctx.body = responseData; } /** * 获取全部子节点 * * @param ctx * @return {Promise} */ async posterity(ctx) { const responseData = { err: 0, msg: '', data: [], }; try { const tenderId = ctx.params.id; if (!tenderId) { throw '当前未打开标段'; } const data = JSON.parse(ctx.request.body.data); if ((isNaN(data.id) || data.id <= 0)) { throw '参数错误'; } const expandData = await ctx.service.ledger.getPosterityByParentId(tenderId, data.id); responseData.data = { expand: expandData }; } catch (err) { this.log(err); responseData.err = 1; responseData.msg = err; } ctx.body = responseData; } /** * 获取部位明细数据(Ajax) * * @param ctx * @return {Promise} */ async loadExplodeData(ctx) { try { const ledgerData = await ctx.service.ledger.getData(ctx.tender.id); const posData = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : []; ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData } }; } catch (err) { this.log(err); ctx.body = { err: 1, msg: err.toString(), data: [] }; } } async check(ctx) { try { const ledgerData = await ctx.service.ledger.getData(ctx.tender.id); const posData = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : []; const qtyData = ctx.helper.checkBillsWithPos(ledgerData, posData, ['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']); qtyData.error.forEach(x => { x.errorType = 'qty'; }); const tpData = ctx.helper.checkBillsTp(ledgerData, [ {qty: 'sgfh_qty', tp: 'sgfh_tp'}, {qty: 'qtcl_qty', tp: 'qtcl_tp'}, {qty: 'sjcl_qty', tp: 'sjcl_tp'}, {qty: 'quantity', tp: 'total_price'} ], this.ctx.tender.info.decimal); tpData.error.forEach(x => { x.errorType = 'tp'; }); ctx.body = { err: 0, msg: '', data: { error: [...qtyData.error, ...tpData.error], source: { bills: [...qtyData.source.bills, ...tpData.source.bills], pos: [...qtyData.source.pos, ...tpData.source.pos], }, }}; } catch (err) { this.log(err); ctx.body = this.ajaxErrorBody(err, '检查数据错误'); } } /** * 更新 部位明细数据 * * @param ctx * @return {Promise} */ async posUpdate(ctx) { try { await this.checkMeasureType(measureType.tz.value); const data = JSON.parse(ctx.request.body.data); const responseData = await ctx.service.pos.savePosData(data, ctx.tender.id); ctx.body = { err: 0, msg: '', data: responseData }; } catch (err) { this.log(err); ctx.body = { err: 1, msg: err.toString(), data: null }; } } /** * 复制粘贴 部位明细 * @param ctx * @return {Promise} */ async posPaste(ctx) { try { await this.checkMeasureType(measureType.tz.value); const data = JSON.parse(ctx.request.body.data); const result = await ctx.service.pos.pastePosData(data, ctx.tender.id); ctx.body = { err: 0, msg: '', data: result }; } catch (err) { this.log(err); ctx.body = { err: 1, msg: err.toString(), data: null }; } } /** * 上传 清单Excel 并导入 * @param ctx * @return {Promise} */ async uploadExcel(ctx) { try { const ueType = ctx.params.ueType; const compressData = ctx.request.body.data; const data = JSON.parse(LzString.decompressFromUTF16(compressData)); const responseData = { err: 0, msg: '', data: {} }; switch (ueType) { case 'tz': const templateId = await this.ctx.service.valuation.getValuationTemplate( this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type); responseData.data = await ctx.service.ledger.importExcel(templateId, data.sheet, data.filter); break; case 'gcl2xmj': responseData.data = await ctx.service.ledger.importGclExcel(data.id, data.sheet); break; default: throw '数据错误'; } ctx.body = responseData; } catch (err) { this.log(err); ctx.body = { err: 1, msg: err.toString(), data: null }; } } // async uploadExcel(ctx) { // let stream; // try { // const responseData = { err: 0, msg: '', data: {}, }; // // 保存文件 // stream = await ctx.getFileStream(); // const create_time = Date.parse(new Date()); // const fileInfo = path.parse(stream.filename); // const fileName = this.app.config.filePath + '/cache/ledger/uploads/' + create_time + fileInfo.ext; // await ctx.helper.saveStreamFile(stream, fileName); // // 读取excel // console.log(ctx.query); // const name = ctx.query.sheetName; // if (!name) throw '未选择需要导入的工作簿'; // const wb = xlsx.readFile(fileName); // const sheetData = { // rows: xlsx.utils.sheet_to_json(wb.Sheets[name], {header: 1}), // merge: wb.Sheets[name]["!merges"], // }; // if (!sheetData.rows) throw '读取工作簿数据错误'; // const ueType = ctx.query.ueType; // switch (ueType) { // case 'tz': // const templateId = await this.ctx.service.valuation.getValuationTemplate( // this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type); // responseData.data = await ctx.service.ledger.importExcel(templateId, sheetData); // break; // case 'gcl2xmj': // responseData.data = await ctx.service.ledger.importGclExcel(ctx.tender.id, sheetData); // break; // default: // throw '数据错误'; // } // ctx.body = responseData; // } catch (err) { // console.log(err); // this.log(err); // // 失败需要消耗掉stream 以防卡死 // if (stream) await sendToWormhole(stream); // ctx.body = {err: 1, msg: err.toString(), data: null}; // } // } /** * 填设计量(Ajax) * * @param ctx * @return {Promise} */ async deal2sgfh(ctx) { try { if (!ctx.tender.data) throw '标段数据错误'; if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作'; if (this.ctx.tender.measure_type === measureType.tz.value) throw '该功能仅工程量清单模式可用'; await this.ctx.service.ledger.deal2sgfh(ctx.tender.id); const ledgerData = await ctx.service.ledger.getData(ctx.tender.id); ctx.body = { err: 0, msg: '', data: { bills: ledgerData } }; } catch (err) { this.log(err); ctx.body = { err: 1, msg: err.toString(), data: null }; } } /** * 下载(清单Excel模板 or 导出项目台账Excel) * @param ctx * @return {Promise} */ async download(ctx) { const file = ctx.params.file; if (file) { try { let fileName; if (file === '导入分项清单EXCEL格式.xls') { fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xls'); ctx.body = await fs.readFileSync(fileName); } else if (file === '台账分解.xlsx') { const create_time = Date.parse(new Date()) / 1000; fileName = this.app.baseDir + '/app/public/files/ledger' + ctx.tender.id + '-' + create_time + '.xlsx'; const exportor = new exportExcel.exportLedger2Excel(ctx); await exportor.export2File(fileName); ctx.body = await fs.readFileSync(fileName); // 输出文件后删除 fs.unlinkSync(fileName); } } catch (err) { this.log(err); this.setMessage(err.toString(), this.messageType.ERROR); } } } /** * 部位台账 页面 (Get) * @param ctx * @return {Promise} */ async bwtz(ctx) { try { const renderData = { tender: ctx.tender.data, jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.bwtz), }; await this.layout('ledger/bwtz.ejs', renderData); } catch (err) { this.log(err); ctx.redirect(ctx.request.header.referer); } } async loadBwtz(ctx) { try { const ledgerData = await ctx.service.ledger.getData(ctx.tender.id); const posData = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : []; const convert = new billsPosConvert(ctx); convert.loadData(ledgerData, posData, []); const result = await convert.convert(); // const wbsCodeHis = await ctx.service.externalData.getExValue(ctx.tender.id, -1, // externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode) || []; // const [result, needUpdate] = convert.convertByWbsCode(wbsCodeHis); // if (needUpdate) { // await ctx.service.externalData.saveExValue(ctx.tender.id, -1, // externalDataConst.FuLong.exType, externalDataConst.FuLong.exFields.wbsCode, wbsCodeHis); // } ctx.body = { err: 0, msg: '', data: result }; } catch (err) { this.log(err); ctx.body = this.ajaxErrorBody(err, '加载合同支付数据错误'); } } /** * 台账对比 页面 (Get) * @param ctx * @return {Promise} */ async gather(ctx) { try { const renderData = { tender: ctx.tender.data, jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.gather), chapterFilter: stdConst.chapterFilter, }; await this.layout('ledger/gather.ejs', renderData); } catch (err) { this.log(err); await this.redirect(ctx.request.header.referer); } } /** * 获取 台账对比 数据 (Ajax) * @param ctx * @return {Promise} */ async loadGatherData(ctx) { try { const billsData = await ctx.service.ledger.getData(ctx.tender.id); const posData = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.pos.getPosData({ tid: ctx.tender.id }) : []; const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: this.ctx.tender.id } }); ctx.body = { err: 0, msg: '', data: { bills: billsData, pos: posData, dealBills } }; } catch (err) { this.log(err); ctx.body = { err: 1, msg: err.toString(), data: [] }; } } /** * 计量台账页面 (Get) * * @param {object} ctx - egg全局变量 * @return {void} */ async index(ctx) { const renderData = {}; await this.layout('ledger/index.ejs', renderData); } } return LedgerController; };