'use strict'; /** * 台账相关控制器 * * @author CaiAoLin * @date 2017/11/30 * @version */ const audit = require('../const/audit'); const moment = require('moment'); const auditConst = audit.ledger; const tenderMenu = require('../../config/menu').tenderMenu; const measureType = require('../const/tender').measureType; const shenpiConst = require('../const/shenpi'); 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'); const sendToWormhole = require('stream-wormhole'); const spreadSetting = require('../lib/spread_setting'); const PermissionCheck = require('../const/account_permission').PermissionCheck; const streamToArray = require('stream-to-array'); 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 ? 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 && upPermission); } async _getLedgerColumn() { const tender = this.ctx.tender; const ledgerColumn = [ 'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price', 'sgfh_qty', 'sgfh_expr', 'sgfh_tp', 'memo', 'features', 'drawing_code', 'node_type']; if (tender.info.display.ledger.deal) ledgerColumn.push('deal_qty', 'deal_tp'); if (tender.info.display.ledger.dgnQty) ledgerColumn.push('dgn_qty1', 'dgn_qty2'); if (tender.info.display.ledger.clQty) ledgerColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr', 'sjcl_tp', 'qtcl_tp'); const posColumn = ['id', 'tid', 'lid', 'name', 'position', 'porder', 'sgfh_qty', 'sgfh_expr', 'add_stage_order', 'drawing_code', 'quantity']; if (tender.info.display.ledger.clQty) posColumn.push('sjcl_qty', 'qtcl_qty', 'sjcl_expr', 'qtcl_expr'); const extraFields = await spreadSetting.getExtraFields(this.ctx, tender.id); if (extraFields.length > 0) { ledgerColumn.push(...extraFields); posColumn.push(...extraFields); } return [ledgerColumn, posColumn]; } /** * 台账分解页面 (Get) * * @param {Object} ctx - egg全局变量 * @return {void} */ async explode(ctx) { try { const tender = ctx.tender; const [ledgerSpread, posSpread] = await spreadSetting.getLedgerSpreadSetting(ctx, tender.id, this._ledgerReadOnly(tender.data)); await ctx.service.ledgerAudit.loadLedgerUser(tender.data); await ctx.service.ledgerAudit.loadLedgerAuditViewData(tender.data); const [stdBills, stdChapters] = await this.ctx.service.valuation.getValuationStdList( ctx.tender.data.valuation, ctx.tender.data.measure_type); // 获取附件列表 const attData = await ctx.service.ledgerAtt.getDataByTenderId(ctx.tender.id); for (const index in attData) { attData[index].in_time = moment(attData[index].in_time * 1000).format('YYYY-MM-DD'); attData[index].orginpath = ctx.app.config.fujianOssPath + attData[index].filepath; delete attData[index].filepath; } const whiteList = this.ctx.app.config.multipart.whitelist; const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id); const syncLedger = await this.ctx.service.specPull.syncLedger(this.ctx.session.sessionProject.id); // 是否已验证手机短信 const pa = await ctx.service.projectAccount.getDataById(ctx.session.sessionUser.accountId); const revise = await this.ctx.service.ledgerRevise.getLastestRevise(tender.id); tender.data.hasRevise = !!revise; const stage = await this.ctx.service.stage.getDataByCondition({ tid: tender.id }); tender.data.hasStage = !!stage; const renderData = { tender: tender.data, tenderInfo: tender.info, auditConst, auditType: audit.auditType, auditors: tender.data.auditors, curAuditors: tender.data.curAuditors, auditHistory: tender.data.auditHistory, user: tender.data.user, attData, whiteList, 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, tender.data.auditors), shenpiConst, categoryData, syncLedgerUrl: syncLedger ? `${ctx.app.config.url3f}/${syncLedger.pull_class}/sync-tz/${tender.id}` : '', nodeType: stdConst.nodeType, authMobile: pa.auth_mobile, deleteFilePermission: PermissionCheck.delFile(this.ctx.session.sessionUser.permission), }; if ((tender.data.ledger_status !== auditConst.status.checked && ctx.session.sessionUser.is_admin) || ((tender.data.ledger_status === auditConst.status.uncheck || tender.data.ledger_status === auditConst.status.checkNo) && tender.data.user_id === ctx.session.sessionUser.accountId)) { // 获取所有项目参与者 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; const unitList = await ctx.service.constructionUnit.getAllDataByCondition({ where: { pid: ctx.session.sessionProject.id } }); renderData.accountGroup = unitList.map(item => { const groupList = accountList.filter(item1 => item1.company === item.name); return { groupName: item.name, 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'); ctx.redirect(ctx.request.header.referer); } } /** * 获取子节点 (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.stdData) throw '参数错误'; // todo 校验项目是否使用该库的权限 switch (data.stdType) { case 'xmj': return await ctx.service.ledger.addStdNodeWithParent(ctx.tender.id, data.stdData); case 'gcl': return data.withParent ? await ctx.service.ledger.addGclStdNodeWithParent(ctx.tender.id, data.id, data.stdData) : await ctx.service.ledger.addGclStdNode(ctx.tender.id, data.id, data.stdData[data.stdData.length - 1]); 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': ctx.helper.checkDgnQtyPrecision(data.postData); 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 [ledgerColumn, posColumn] = await this._getLedgerColumn(); // const ledgerData = ctx.tender.ledgerReadOnly && ctx.tender.his // ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file) // : await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } }); // const posData = this.ctx.tender.data.measure_type === measureType.tz.value // ? (ctx.tender.ledgerReadOnly && ctx.tender.his // ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file) // : await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn)) // : []; const ledgerData = await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } }); const posData = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn) : []; const ancillaryGclData = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.ancillaryGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } }) : []; const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id); ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, ancGcl: ancillaryGclData, tags: ledgerTags } }; } 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 checkDataModel = require('../lib/ledger').checkData; const checkData = new checkDataModel(ctx, measureType); checkData.loadData(ledgerData, posData); ctx.tender.info.ledger_check.same_code && checkData.checkSameCode(); ctx.tender.info.ledger_check.sibling && checkData.checkSibling(); if (ctx.tender.data.measure_type === measureType.tz.value) checkData.checkBillsQty(['sgfh_qty', 'qtcl_qty', 'sjcl_qty', 'quantity']); checkData.checkBillsTp([ {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); ctx.body = { err: 0, msg: '', data: checkData.checkResult }; } catch (err) { this.log(err); ctx.body = this.ajaxErrorBody(err, '检查数据错误'); } } async ancGclUpdate(ctx) { try { await this.checkMeasureType(measureType.tz.value); const data = JSON.parse(ctx.request.body.data); const responseData = await ctx.service.ancillaryGcl.updateDatas(data); 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 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 uploadYbp(ctx) { const stream = await ctx.getFileStream(); try { // 读取字节流 const parts = await streamToArray(stream); // 转化为buffer const buffer = Buffer.concat(parts); const bills = await ctx.service.ledger.importYbp(ctx.tender, buffer.toString()); ctx.body = { err: 0, msg: '', data: { bills, pos: [] }}; } catch (err) { await sendToWormhole(stream); ctx.log(err); } } // 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格式.xlsx') { fileName = path.join(this.app.baseDir, 'app', 'public', 'files', 'template', 'ledger', '导入分项清单EXCEL格式.xlsx'); 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 sjsRela = await ctx.service.project.getTenderSjsRela(ctx.session.sessionProject.id, ctx.tender.info.display.exMemo); const renderData = { tender: ctx.tender.data, jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.bwtz), ex_memo1: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo1'; }), ex_memo2: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo2'; }), ex_memo3: sjsRela.ledgerCol.find(x => { return x.field === 'ex_memo3'; }), }; 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 = { measureType, tender: ctx.tender.data, jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.gather), }; await this.layout('ledger/gather.ejs', renderData); } catch (err) { this.log(err); await this.redirect(ctx.request.header.referer); } } async _getChangeData(ctx) { const change = await ctx.service.change.getAllDataByCondition({ columns: [ 'cid', 'code', 'name', 'w_code' ], where: { tid: ctx.tender.id, valid: 1, status: audit.flow.status.checked }, orders: [['code', 'asc']], }); const changeBills = await ctx.service.changeAuditList.getAllDataByCondition({ columns: [ 'cid', 'code', 'name', 'unit', 'unit_price', 'oamount2', 'checked_amount', 'checked_price', 'is_valuation' ], where: { tid: ctx.tender.id } }); const changeIndex = {}; change.forEach(x => { changeIndex[x.cid] = x; x.bills = [] }); changeBills.forEach(cb => { const c = changeIndex[cb.cid]; if (!c) return; c.bills.push(cb); }); return change; } /** * 获取 台账对比 数据 (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 } }); const ancGcl = this.ctx.tender.data.measure_type === measureType.tz.value ? await ctx.service.ancillaryGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } }) : []; const change = await this._getChangeData(ctx); const zlj = JSON.parse(JSON.stringify(stdConst.zlj)); zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice; ctx.body = { err: 0, msg: '', data: { bills: billsData, pos: posData, ancGcl, dealBills, change, spec: {zlj: zlj, jrg: stdConst.jrg}, }}; } 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); } /** * 审批流程(Get) * @param ctx * @return {Promise} */ async ledgerAuditors(ctx) { try { const tender = ctx.tender.data; await ctx.service.ledgerAudit.loadLedgerUser(tender); await ctx.service.ledgerAudit.loadLedgerAuditViewData(tender); ctx.body = {err: 0, msg: '', data: tender}; } catch (error) { this.log(error); ctx.body = { err: 1, msg: error.toString(), data: null }; } } /** * 上传附件 * @param {Object} ctx - egg全局变量 * @return {void} */ async uploadFile(ctx) { const responseData = { err: 0, msg: '', data: [], }; let stream; try { const parts = ctx.multipart({ autoFields: true }); const files = []; let index = 0; const extra_upload = ctx.tender.data.ledger_status === auditConst.status.checked; while ((stream = await parts()) !== undefined) { // 判断用户是否选择上传文件 if (!stream.filename) { throw '请选择上传的文件!'; } const fileInfo = path.parse(stream.filename); const create_time = Date.parse(new Date()) / 1000; const filepath = `app/public/upload/${this.ctx.tender.id}/ledger/fujian_${create_time + index.toString() + fileInfo.ext}`; // await ctx.helper.saveStreamFile(stream, path.resolve(this.app.baseDir, filepath)); await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + filepath, stream); if (stream) { await sendToWormhole(stream); } // 保存数据到att表 const fileData = { tid: ctx.params.id, in_time: create_time, filename: fileInfo.name, fileext: fileInfo.ext, filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size, filepath, extra_upload, }; const result = await ctx.service.ledgerAtt.save(parts.field, fileData, ctx.session.sessionUser.accountId); if (!result) { throw '导入数据库保存失败'; } const attData = await ctx.service.ledgerAtt.getDataByFid(result.insertId); attData.in_time = moment(create_time * 1000).format('YYYY-MM-DD'); files.length !== 0 ? files.unshift(attData) : files.push(attData); ++index; } responseData.data = files; } catch (err) { this.log(err); // 失败需要消耗掉stream 以防卡死 if (stream) { await sendToWormhole(stream); } this.setMessage(err.toString(), this.messageType.ERROR); responseData.err = 1; responseData.msg = err.toString(); } ctx.body = responseData; } /** * 下载附件 * @param {Object} ctx - egg全局变量 * @return {void} */ async downloadFile(ctx) { const id = ctx.params.fid; if (id) { try { const fileInfo = await ctx.service.ledgerAtt.getDataById(id); if (fileInfo !== undefined && fileInfo !== '') { // const fileName = path.join(this.app.baseDir, fileInfo.filepath); // 解决中文无法下载问题 const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase(); let disposition = ''; if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) { disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext); } else if (userAgent.indexOf('firefox') >= 0) { disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"'; } else { /* safari等其他非主流浏览器只能自求多福了 */ disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary'); } ctx.response.set({ 'Content-Type': 'application/octet-stream', 'Content-Disposition': disposition, 'Content-Length': fileInfo.filesize, }); // ctx.body = await fs.createReadStream(fileName); ctx.body = await ctx.helper.ossFileGet(fileInfo.filepath); } else { throw '不存在该文件'; } } catch (err) { this.log(err); this.setMessage(err.toString(), this.messageType.ERROR); } } } /** * 查看附件 * @param {Object} ctx - egg全局变量 * @return {void} */ async checkFile(ctx) { const responseData = { err: 0, msg: '' }; const { id = '' } = JSON.parse(ctx.request.body.data); if (id) { try { const fileInfo = await ctx.service.ledgerAtt.getDataById(id); if (fileInfo && Object.keys(fileInfo).length) { let filepath = fileInfo.filepath; if (!ctx.helper.canPreview(fileInfo.fileext)) { filepath = `/tender/${ctx.tender.id}/ledger/download/file/${fileInfo.id}`; } else { // filepath = filepath.replace(/^app|\/app/, ''); filepath = ctx.app.config.fujianOssPath + filepath; } fileInfo.filepath && (responseData.data = { filepath }); } } catch (error) { this.log(error); this.setMessage(error.toString(), this.messageType.ERROR); responseData.err = 1; responseData.msg = error.toString(); } } ctx.body = responseData; } /** * 删除附件 * @param {Object} ctx - egg全局变量 * @return {void} */ async deleteFile(ctx) { const responseData = { err: 0, msg: '', data: '', }; try { // this._checkStageCanModifyRe(ctx); const data = JSON.parse(ctx.request.body.data); const fileInfo = await ctx.service.ledgerAtt.getDataById(data.id); if (!fileInfo || !Object.keys(fileInfo).length) { throw '该文件不存在'; } if (!fileInfo.extra_upload && ctx.tender.status === auditConst.status.checked) { throw '无权限删除'; } if (fileInfo !== undefined && fileInfo !== '') { // 先删除文件 // await fs.unlinkSync(path.join(this.app.baseDir, fileInfo.filepath)); await ctx.app.fujianOss.delete(ctx.app.config.fujianOssFolder + fileInfo.filepath); // 再删除数据库 await ctx.service.ledgerAtt.deleteById(data.id); responseData.data = ''; } else { throw '不存在该文件'; } } catch (err) { responseData.err = 1; responseData.msg = err; } ctx.body = responseData; } /** * 保存附件(或替换) * @param {Object} ctx - egg全局变量 * @return {void} */ async saveFile(ctx) { const responseData = { err: 0, msg: '', data: [], }; let stream; try { // this._checkStageCanModifyRe(ctx); stream = await ctx.getFileStream({ requireFile: false }); let fileData = {}; if (stream.filename !== undefined) { const create_time = Date.parse(new Date()) / 1000; const fileInfo = path.parse(stream.filename); const dirName = `app/public/upload/${this.ctx.tender.id}/ledger`; const fileName = 'fujian_' + create_time + fileInfo.ext; // 判断文件夹是否存在,不存在则直接创建文件夹 // if (!fs.existsSync(path.join(this.app.baseDir, dirName))) { // await fs.mkdirSync(path.join(this.app.baseDir, dirName)); // } // 保存文件 // await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName)); await ctx.app.fujianOss.put(ctx.app.config.fujianOssFolder + path.join(dirName, fileName), stream); // 保存数据到att表 fileData = { filesize: stream.fields.size, filepath: path.join(dirName, fileName), }; } const result = await ctx.service.ledgerAtt.updateByID(stream.fields, fileData); if (!result) { throw '导入数据库保存失败'; } const attData = await ctx.service.ledgerAtt.getDataByFid(stream.fields.id); attData.in_time = moment(attData.in_time * 1000).format('YYYY-MM-DD'); responseData.data = attData; } catch (err) { this.log(err); // 失败需要消耗掉stream 以防卡死 if (stream) { await sendToWormhole(stream); } this.setMessage(err.toString(), this.messageType.ERROR); responseData.err = 1; responseData.msg = err.toString(); } ctx.body = responseData; } /** * 批量下载 - 压缩成zip文件返回 * @param {Object} ctx - 全局上下文 */ async downloadZip(ctx) { const zipFilename = `${ctx.tender.data.name}-台账分解-附件.zip`; const time = Date.now(); const zipPath = `app/public/upload/${ctx.tender.id}/ledger/fu_jian_zip${time}.zip`; const responseData = { err: 0, msg: '', }; try { const { fileIds = [] } = JSON.parse(ctx.request.body.data); const size = await ctx.service.ledgerAtt.compressedFile(fileIds, zipPath); // 解决中文无法下载问题 const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase(); let disposition = ''; if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) { disposition = 'attachment; filename=' + encodeURIComponent(zipFilename); } else if (userAgent.indexOf('firefox') >= 0) { disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"'; } else { /* safari等其他非主流浏览器只能自求多福了 */ disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary'); } ctx.response.set({ 'Content-Type': 'application/octet-stream', 'Content-Disposition': disposition, 'Content-Length': size, }); const readStream = fs.createReadStream(path.join(this.app.baseDir, zipPath)); ctx.body = readStream; readStream.on('close', () => { if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) { fs.unlinkSync(path.resolve(this.app.baseDir, zipPath)); } }); // fs的错误不能被try catch捕捉 readStream.on('error', err => { this.log(err); if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) { fs.unlinkSync(path.resolve(this.app.baseDir, zipPath)); } responseData.err = 1; responseData.msg = err.toString(); ctx.body = responseData; }); } catch (err) { this.log(err); if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) { fs.unlinkSync(path.resolve(this.app.baseDir, zipPath)); } this.setMessage(err.toString(), this.messageType.ERROR); responseData.err = 1; responseData.msg = err.toString(); ctx.body = responseData; } } } return LedgerController; };