'use strict'; /** * 台账相关控制器 * * @author CaiAoLin * @date 2017/11/30 * @version */ const stdDataAddType = { withParent: 1, child: 2, next: 3, }; 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 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'); const sendToWormhole = require('stream-wormhole'); 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 && 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] = await this._getSpreadSetting(); const sjsRela = await this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id); this.ctx.helper.refreshSpreadShow(sjsRela.ledgerCol, [ledgerSpread, posSpread]); 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 && !ctx.tender.isTourist ? 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 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 renderData = { tender: tender.data, tenderInfo: tender.info, auditConst, auditors, curAuditor, user, attData, whiteList, 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, categoryData, }; 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'); 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.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': 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 ledgerData = ctx.tender.ledgerReadOnly && ctx.tender.his ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.bills_file) : await ctx.service.ledger.getData(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})) : []; const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id); ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, 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(); 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, '检查数据错误'); } } /** * 更新 部位明细数据 * * @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格式.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 this.ctx.service.project.getSjsRela(ctx.session.sessionProject.id); 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 = { 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); } } /** * 获取 台账对比 数据 (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 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, dealBills, 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 responseData = { err: 0, msg: '', data: {}, }; const tender = ctx.tender; const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times; const auditors = tender.data.ledger_status === auditConst.status.checkNo && tender.data.user_id !== ctx.session.sessionUser.accountId && !ctx.tender.isTourist ? 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)); } } responseData.data.auditHistory = auditHistory; // 获取审批流程中左边列表 responseData.data.auditors = auditors; responseData.data.user = user; ctx.body = responseData; } 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; };