Browse Source

1. 调用变更令,查询可变更数量调整
2. 暂存计量
3. 清单汇总,台账+变更令计算调整

MaiXinRong 3 years ago
parent
commit
13de2cfcc4

+ 2 - 2
app/const/spread.js

@@ -437,8 +437,8 @@ const stageGather = {
             {title: '截止本期完成计量|数量', colSpan: '3|1', rowSpan: '1|1', field: 'end_gather_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_gather_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '|完成率(%)', colSpan: '|1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
-            {title: '台账+变更令|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_final_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_final_tp', hAlign: 2, width: 60, type: 'Number'},
+            {title: '台账+变更令|数量', colSpan: '2|1', rowSpan: '1|1', field: 'final_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 60, type: 'Number'},
         ],
         emptyRows: 0,
         headRows: 2,

+ 70 - 3
app/controller/stage_controller.js

@@ -25,6 +25,7 @@ const sendToWormhole = require('stream-wormhole');
 const billsPosConvert = require('../lib/bills_pos_convert');
 const fs = require('fs');
 const stdConst = require('../const/standard');
+const LzString = require('lz-string');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -346,6 +347,9 @@ module.exports = app => {
                         case 'change':
                             responseData.data.changeData = await this._getStageChangeData(ctx);
                             break;
+                        case 'changeBills':
+                            responseData.data.changeBills = await this.ctx.service.changeAuditList.checkedChangeBills(ctx.stage.tid);
+                            break;
                         case 'import_change':
                             responseData.data.import_change = await this.ctx.service.stageImportChange.getStageImportData(this.ctx.stage);
                             break;
@@ -497,9 +501,7 @@ module.exports = app => {
                 }
                 const bills = data.bills ? data.bills : await ctx.service.ledger.getDataById(data.pos.lid);
                 const pos = data.pos;
-                const changes = ctx.stage.readOnly
-                    ? await ctx.service.change.getAuditValidChanges(ctx.tender.id, bills, null, ctx.stage.curTimes, ctx.stage.curOrder)
-                    : await ctx.service.change.getValidChanges(ctx.tender.id, bills);
+                const changes = await ctx.service.change.getValidChanges(ctx.tender.id, ctx.stage.order, bills);
                 const useChanges = ctx.stage.readOnly
                     ? await ctx.service.stageChange.getAuditorStageData(ctx.tender.id, ctx.stage.id, ctx.stage.curTimes, ctx.stage.curOrder, bills.id, pos ? pos.id : -1)
                     : await ctx.service.stageChange.getLastestStageData(ctx.tender.id, ctx.stage.id, bills.id, pos ? pos.id : -1);
@@ -2040,6 +2042,71 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
+
+        async stashList(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权查看';
+                const data = await ctx.service.stageStash.getValidStash(ctx.tender.id);
+                data.forEach(x => {
+                    x.recover = ctx.stage.status === auditConst.status.uncheck;
+                });
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '加载暂存计量数据失败');
+            }
+        }
+
+        async addStash(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权操作';
+                const result = await ctx.service.stageStash.addStash(ctx.stage, '');
+                const stash = await ctx.service.stageStash.getDataById(result);
+                stash.recover = ctx.stage.status === auditConst.status.uncheck;
+                ctx.body = { err: 0, msg: '', data: stash };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存当前计量数据失败');
+            }
+        }
+
+        async recoverStash(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权操作';
+                if (!ctx.stage.status === auditConst.status.uncheck) throw '仅新增期且未上报时,可从暂存计量中导入数据';
+
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.stageStash.recover(ctx.stage, data.id);
+                ctx.body = { err: 0, msg: '', data: null };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存当前计量数据失败');
+            }
+        }
+
+        async delStash(ctx) {
+            try {
+                if (!ctx.session.sessionUser.is_admin) throw '您无权操作';
+
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.stageStash.delStash(data.id);
+                ctx.body = { err: 0, msg: '', data: null };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除数据失败');
+            }
+        }
+
+        async importStageSheet(ctx) {
+            try {
+                const compressData = ctx.request.body.data;
+                const data = JSON.parse(LzString.decompressFromUTF16(compressData));
+
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '导入计量台账数据错误');
+            }
+        }
     }
 
     return StageController;

+ 23 - 2
app/public/js/gcl_gather.js

@@ -35,7 +35,7 @@ const gclGatherModel = (function () {
         updateFields: ['contract_qty', 'qc_qty'],
     };
     const gsPos = new StagePosData(posSetting);
-    let deal = [];
+    let deal = [], change;
 
     const gclList = [], leafXmjs = [];
     const mergeChar = ';';
@@ -70,6 +70,10 @@ const gclGatherModel = (function () {
         deal = dealBills;
     }
 
+    function loadChangeBillsData(data) {
+        change = data;
+    }
+
     function gatherfields(obj, src, fields) {
         if (obj && src) {
             for (const f of fields) {
@@ -322,6 +326,19 @@ const gclGatherModel = (function () {
         }
     }
 
+    function gatherChangeBillsData() {
+        if (change && change.length > 0) {
+            for (const node of change) {
+                node.b_code = node.code;
+                node.quantity = parseFloat(node.samount);
+                node.total_price = ZhCalc.mul(node.quantity, node.unit_price, node.tp_decimal);
+                const gcl = getGclNode(node);
+                gcl.change_bills_qty = ZhCalc.add(gcl.change_bills_qty, node.quantity);
+                gcl.change_bills_tp = ZhCalc.add(gcl.change_bills_tp, node.total_price);
+            }
+        }
+    }
+
     function calculateGatherData() {
         for (const gcl of gclList) {
             gcl.pre_gather_qty = ZhCalc.add(gcl.pre_contract_qty, gcl.pre_qc_qty);
@@ -341,6 +358,8 @@ const gclGatherModel = (function () {
             gcl.end_gather_percent = gcl.end_final_qty && gcl.end_gather_qty
                 ? ZhCalc.mul(ZhCalc.div(gcl.end_gather_qty, gcl.end_final_qty), 100, 2)
                 : ZhCalc.mul(ZhCalc.div(gcl.end_gather_tp, gcl.end_final_tp), 100, 2);
+            gcl.final_qty = ZhCalc.add(gcl.quantity, gcl.change_bills_qty);
+            gcl.final_tp = ZhCalc.add(gcl.total_price, gcl.change_bills_tp);
             for (const xmj of gcl.leafXmjs) {
                 xmj.pre_gather_qty = ZhCalc.add(xmj.pre_contract_qty, xmj.pre_qc_qty);
                 xmj.gather_qty = ZhCalc.add(xmj.contract_qty, xmj.qc_qty);
@@ -395,8 +414,9 @@ const gclGatherModel = (function () {
             gclList.length = 0; //splice(0, gclList.length);
         }
         recursiveGatherGclData(gsTree.children, null);
-        calculateGatherData();
         gatherDealBillsData();
+        gatherChangeBillsData();
+        calculateGatherData();
         gclList.sort(function (a, b) {
             return compareCode(a.b_code, b.b_code) || ZhCalc.sub(a.unit_price, b.unit_price);
         });
@@ -530,6 +550,7 @@ const gclGatherModel = (function () {
         loadLedgerData,
         loadPosData,
         loadDealBillsData,
+        loadChangeBillsData,
         gatherGclData,
         checkDiffer,
         gatherChapterData,

+ 15 - 1
app/public/js/stage.js

@@ -530,7 +530,6 @@ $(document).ready(() => {
                 if (uc) {
                     c.org_uamount = uc.qty;
                     c.uamount = uc.qty;
-                    c.vamount = ZhCalc.add(c.vamount, c.uamount);
                 }
                 c.pre_amount = ZhCalc.sub(c.used_amount, c.uamount);
             }
@@ -4714,6 +4713,21 @@ $(document).ready(() => {
         sessionStorage.rpt_name = rpt_name;
         window.open('/individualReport/A4');
     });
+
+    $('#importExcel').click(() => {
+        importExcel.doImport({
+            template: {
+                hint: '计量台账',
+                url: 'https://jl-assets.oss-cn-shenzhen.aliyuncs.com/template/导入计量台账Excel格式.xlsx',
+            },
+            callback: function (sheet) {
+                postDataCompress(window.location.pathname + '/importStageSheet', {sheet}, function (result) {
+                    // todo 刷新界面
+                    console.log(result);
+                }, null);
+            }
+        });
+    })
 });
 function makeOneShouFang(sf) {
     const lData = _.find(ledgerData, { id: sf.lid });

+ 2 - 1
app/public/js/stage_gather.js

@@ -124,11 +124,12 @@ $(document).ready(function () {
         SpreadJsObj.reLoadSheetData(gclSpread.getActiveSheet());
     });
 
-    postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec' }, function (result) {
+    postData(preUrl + '/load', { filter: 'ledger;pos;dealBills;spec;changeBills' }, function (result) {
         // 解析清单汇总数据
         gclGatherModel.loadLedgerData(result.ledgerData);
         gclGatherModel.loadPosData(result.posData);
         gclGatherModel.loadDealBillsData(result.dealBills);
+        gclGatherModel.loadChangeBillsData(result.changeBills);
         gclGatherData = gclGatherModel.gatherGclData();
         gclGatherModel.checkDiffer(gclGatherData);
         checkOverRange(gclGatherData);

+ 6 - 0
app/router.js

@@ -286,6 +286,12 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/im-file/del', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.deleteImFile');
     app.get('/tender/:id/measure/stage/:order/im-file/download', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadImFile');
 
+    // 暂存计量
+    app.post('/tender/:id/measure/stage/:order/stash/list', sessionAuth, tenderCheck, stageCheck, 'stageController.stashList');
+    app.post('/tender/:id/measure/stage/:order/stash/add', sessionAuth, tenderCheck, stageCheck, 'stageController.addStash');
+    app.post('/tender/:id/measure/stage/:order/stash/del', sessionAuth, tenderCheck, stageCheck, 'stageController.delStash');
+    app.post('/tender/:id/measure/stage/:order/stash/recover', sessionAuth, tenderCheck, stageCheck, 'stageController.recoverStash');
+
     // 计量附件
     app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadFile');
     app.get('/tender/:id/measure/stage/:order/download/file/:fid', sessionAuth, 'stageController.downloadFile');

+ 6 - 60
app/service/change.js

@@ -1148,14 +1148,13 @@ module.exports = app => {
          * @param pos - 查询的部位
          * @return {Promise<*>} - 可用的变更令列表
          */
-        async getValidChanges(tid, bills, pos) {
+        async getValidChanges(tid, sorder, bills, pos) {
             const self = this;
             const getFilterPart = function(field, value) {
                 return value
                     ? field + ' = ' + self.db.escape(value)
                     : self.db.format("(?? = null or ?? = '')", [field, field]);
             };
-            const timesLen = 100;
             const filter = getFilterPart('cb.code', bills.b_code) +
                 ' And ' + getFilterPart('cb.name', bills.name) +
                 ' And ' + getFilterPart('cb.unit', bills.unit) +
@@ -1169,67 +1168,14 @@ module.exports = app => {
                 '  FROM ' + this.tableName + ' As c ' +
                 '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
                 '  Left Join (' +
-                '    SELECT SUM(sc.qty) As used_amount, sc.cbid' +
-                '      FROM ' + this.ctx.service.stageChange.tableName + ' As sc' +
-                '      INNER JOIN (SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `flow`, cbid, sid ' +
-                '        FROM ' + this.ctx.service.stageChange.tableName +
-                '        WHERE tid = ?' +
-                '        GROUP BY cbid, sid' +
-                '      ) As MF' +
-                '      ON (sc.stimes * ' + timesLen + ' + sc.sorder) = MF.flow And sc.cbid = MF.cbid And sc.sid = MF.sid' +
-                '    GROUP BY sc.cbid' +
+                '    SELECT SUM(qty) As used_amount, cbid' +
+                '      FROM ' + this.ctx.service.stageChangeFinal.tableName +
+                '        WHERE tid = ? and sorder < ?' +
+                '        GROUP BY cbid' +
                 '  ) As scb ON cb.id = scb.cbid' +
                 '  WHERE c.tid = ? And c.status = ? And c.valid And ' + filter +
                 '  ORDER BY c.in_time';
-            const sqlParam = [tid, tid, audit.flow.status.checked];
-            const changes = await this.db.query(sql, sqlParam);
-            for (const c of changes) {
-                const aSql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +
-                    '  FROM ?? As ca ' +
-                    '  Left Join ?? As pa ' +
-                    '  On ca.uid = pa.id ' +
-                    '  Where ca.cid = ?';
-                const aSqlParam = [this.ctx.service.changeAtt.tableName, this.ctx.service.projectAccount.tableName, c.cid];
-                c.attachments = await this.db.query(aSql, aSqlParam);
-            }
-            return changes;
-        }
-
-        /**
-         * 查询审批人可用的变更令
-         * @param bills - 查询的清单
-         * @param pos - 查询的部位
-         * @return {Promise<*>} - 可用的变更令列表
-         */
-        async getAuditValidChanges(tid, bills, pos, times, order) {
-            const timesLen = 100;
-            const filter =
-                'cb.`code` = ' + this.db.escape(bills.b_code) +
-                ' And cb.`name` = ' + this.db.escape(bills.name) +
-                ' And cb.`unit` = ' + this.db.escape(bills.unit) +
-                ' And cb.`unit_price` = ' + this.db.escape(bills.unit_price) +
-                (pos ? ' And cb.`bwmx` = ' + this.db.escape(pos.name) : '');
-            const sql =
-                'SELECT c.cid, c.code, c.name, c.w_code, c.p_code, c.peg, c.org_name, c.org_code, c.new_name, c.new_code,' +
-                '    c.content, c.basis, c.memo, c.type, c.class, c.quality, c.company, c.charge, ' +
-                '    cb.id As cbid, cb.code As b_code, cb.name As b_name, cb.unit As b_unit, cb.samount As b_amount, cb.detail As b_detail, cb.bwmx As b_bwmx, cb.gcl_id, ' +
-                '    scb.used_amount' +
-                '  FROM ' + this.tableName + ' As c ' +
-                '  Left Join ' + this.ctx.service.changeAuditList.tableName + ' As cb On c.cid = cb.cid ' +
-                '  Left Join (' +
-                '    SELECT SUM(sc.qty) As used_amount, sc.cbid' +
-                '      FROM ' + this.ctx.service.stageChange.tableName + ' As sc' +
-                '      INNER JOIN (SELECT MAX(`stimes` * ' + timesLen + ' + `sorder`) As `flow`, cbid, sid ' +
-                '        FROM ' + this.ctx.service.stageChange.tableName +
-                '        WHERE tid = ? And (`stimes` < ? OR (`stimes` = ? AND `sorder` <= ?)) ' +
-                '        GROUP BY cbid, sid' +
-                '      ) As MF' +
-                '      ON (sc.stimes * ' + timesLen + ' + sc.sorder) = MF.flow And sc.cbid = MF.cbid And sc.sid = MF.sid' +
-                '    GROUP BY sc.cbid' +
-                '  ) As scb ON cb.id = scb.cbid' +
-                '  WHERE c.tid = ? And c.status = ? And c.valid And ' + filter +
-                '  ORDER BY c.in_time';
-            const sqlParam = [tid, times, times, order, tid, audit.flow.status.checked];
+            const sqlParam = [tid, sorder, tid, audit.flow.status.checked];
             const changes = await this.db.query(sql, sqlParam);
             for (const c of changes) {
                 const aSql = 'SELECT ca.*, pa.name As u_name, pa.role As u_role ' +

+ 8 - 0
app/service/change_audit_list.js

@@ -890,6 +890,14 @@ module.exports = app => {
                 await transaction.query(pSql, []);
             }
         }
+
+        async checkedChangeBills(tid) {
+            const DefaultDecimal = this.ctx.tender.info.decimal.tp;
+            const sql = 'SELECT cal.*, c.tp_decimal FROM ' + this.ctx.service.changeAuditList.tableName + ' cal LEFT JOIN ' + this.ctx.service.change.tableName + ' c on cal.cid = c.cid where c.tid = ? and c.valid and c.status = ?';
+            const changeBills = await this.db.query(sql, [tid, audit.flow.status.checked]);
+            changeBills.forEach(x => { x.tp_decimal = x.tp_decimal !== 0 ? x.tp_decimal : DefaultDecimal });
+            return changeBills;
+        }
     }
 
     return ChangeAuditList;

+ 1 - 1
app/service/pay.js

@@ -29,7 +29,7 @@ module.exports = app => {
          * @returns {Promise<boolean>}
          */
         async addDefaultPayData(tid, transaction) {
-            const existPays = this.getAllDataByCondition({tid: tid});
+            const existPays = this.getAllDataByCondition({ where: { tid } });
             if (existPays.length >= 0) { return false; }
 
             try {

+ 11 - 0
app/service/stage_change_final.js

@@ -9,6 +9,7 @@
  */
 
 const timesLen = require('../const/audit').stage.timesLen;
+const auditConst = require('../const/audit').flow;
 
 module.exports = app => {
 
@@ -90,6 +91,16 @@ module.exports = app => {
                 '  Where cf.sid = ?';
             return await this.db.query(sql, [sid]);
         }
+
+        async getChangeBillsValidQty(cbid) {
+            const sql = 'SELECT cal.* FROM ' + this.ctx.service.changeAuditList.tableName + ' cal LEFT JOIN ' + this.ctx.service.change.tableName + ' c on cal.cid = c.cid where cal.id = ? and c.valid and c.status = ?';
+            const changeBills = await this.db.queryOne(sql, [cbid, auditConst.status.checked]);
+            if (!changeBills) return undefined;
+
+            const qty = parseFloat(changeBills.samount);
+            const usedQty = await this.db.queryOne('Select SUM(qty) as qty FROM ' + this.tableName + ' WHERE cbid = ?', [cbid]);
+            return usedQty ? this.ctx.helper.sub(qty, usedQty.qty) : qty;
+        }
     }
 
     return StageChangeFinal;

+ 173 - 0
app/service/stage_stash.js

@@ -0,0 +1,173 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class StageStash extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_stash';
+        }
+
+        async getValidStash(tid) {
+            const sql = `SELECT id, sorder, create_time, uid, uname, remark FROM ${this.tableName} WHERE tid = ? and valid and TO_DAYS(NOW()) - TO_DAYS(create_time) <= 30 ORDER BY create_time DESC`;
+            return await this.db.query(sql, [tid]);
+        }
+
+        async addStash(stage, remark = '') {
+            const bills = await this.ctx.service.stageBills.getLastestStageData2(stage.tid, stage.id);
+            const pos = await this.ctx.service.stagePos.getLastestStageData2(stage.tid, stage.id);
+            const change = await this.ctx.service.stageChange.getFinalStageData(stage.tid, stage.id);
+
+            const timestamp = (new Date()).getTime();
+            const filepath = `${this.ctx.session.sessionProject.id}-${stage.tid}-${timestamp}.json`;
+            await this.ctx.hisOss.put(this.ctx.stashOssPath + filepath, Buffer.from(JSON.stringify({bills, pos, change}), 'utf8'));
+
+            const result = await this.db.insert(this.tableName, {
+                pid: this.ctx.session.sessionProject.id, tid: stage.tid, sid: stage.id, sorder: stage.order,
+                uid: this.ctx.session.sessionUser.accountId, uname: this.ctx.session.sessionUser.name,
+                filepath, info: {status: stage.status, flow: stage.curAuditor ? stage.curAuditor.user_id : stage.uid}, remark
+            });
+            return result.insertId;
+        }
+
+        async delStash(id) {
+            const stash = await this.getDataById(id);
+            if (!stash) throw '暂存计量不存在';
+            if (stash.tid !== this.ctx.tender.id) throw '非当前标段暂存计量,不可操作';
+
+            await this.deleteById(id);
+            await this.ctx.hisOss.delete(this.ctx.stashOssPath + stash.filepath);
+        }
+
+        async loadLedgerDataFromOss(filepath) {
+            const File = await this.ctx.hisOss.get(this.ctx.stashOssPath + filepath);
+            if (File.res.status !== 200) return '获取暂存计量有误';
+            const result = JSON.parse(File.content);
+            return result;
+        }
+
+        async loadAndAnalysis(filepath) {
+            const data = await this.loadLedgerDataFromOss(filepath);
+            const billsIndex = {};
+            for (const b of data.bills) {
+                billsIndex[b.lid] = b;
+            }
+            for (const p of data.pos) {
+                if (!billsIndex[p.lid]) continue;
+                if (!billsIndex[p.lid].pos) billsIndex[p.lid].pos = [];
+                billsIndex[p.lid].pos.push(p);
+            }
+            for (const c of data.change) {
+                if (!billsIndex[c.lid]) continue;
+                if (billsIndex[c.lid].pos) {
+                    const p = billsIndex[c.lid].pos.find(x => { return x.pid === c.pid });
+                    if (!p) continue;
+                    if (!p.change) p.change = [];
+                    p.change.push(c);
+                } else {
+                    if (!billsIndex[c.lid].change) billsIndex[c.lid].change = [];
+                    billsIndex[c.lid].change.push(c);
+                }
+            }
+            return data.bills;
+        }
+
+        async reCalcStashData(stage, data) {
+            const decimal = this.ctx.tender.info.decimal;
+            const insertBillsData = [], insertPosData = [], insertChangeData = [];
+
+            const bills = await this.ctx.service.ledger.getAllDataByCondition({ where: { tender_id: stage.tid, is_leaf: true } });
+            const pos = await this.ctx.service.pos.getAllDataByCondition({ where: { tid: stage.tid } });
+
+            const said = this.ctx.session.sessionUser.accountId;
+            for (const d of data) {
+                const b = bills.find(x => { return x.id === d.lid });
+                if (!b) continue;
+
+                const nbs = { tid: stage.tid, sid: stage.id, said, lid: b.id, times: 1, order: 0 };
+                if (d.pos) {
+                    for (const bp of d.pos) {
+                        const p = pos.find(x => { return x.id === bp.pid});
+                        if (!p) continue;
+
+                        const nps = { tid: stage.tid, sid: stage.id, said, lid: b.id, pid: p.id, times: 1, order: 0 };
+                        nps.contract_qty = this.ctx.helper.round(bp.contract_qty, decimal.qty);
+                        nps.qc_qty = 0;
+                        insertPosData.push(nps);
+                        if (bp.change) {
+                            for (const c of bp.change) {
+                                if (!c.qty) continue;
+                                const ncs = { tid: stage.tid, sid: stage.id, lid: b.id, pid: p.id, stimes: 1, sorder: 0, cid: c.cid, cbid: c.cbid };
+                                const validQty = await this.ctx.service.stageChangeFinal.getChangeBillsValidQty(c.cbid);
+                                ncs.qty = validQty >= c.qty ? c.qty : validQty;
+                                insertChangeData.push(ncs);
+                                nps.qc_qty = await this.ctx.helper.add(nps.qc_qty, ncs.qty);
+                            }
+                        }
+                        nbs.contract_qty = this.ctx.helper.add(nbs.contract_qty, nps.contract_qty);
+                        nbs.qc_qty = this.ctx.helper.add(nbs.qc_qty, nps.qc_qty);
+
+                    }
+                    nbs.contract_tp = this.ctx.helper.mul(nbs.contract_qty, b.unit_price, decimal.tp);
+                    nbs.qc_tp = this.ctx.helper.mul(nbs.qc_qty, b.unit_price, decimal.tp);
+                } else {
+                    nbs.contract_qty = this.ctx.helper.round(d.contract_qty, decimal.qty);
+                    nbs.contract_tp = this.ctx.helper.mul(nbs.contract_qty, b.unit_price, decimal.tp);
+                    nbs.qc_qty = 0;
+                    if (d.change) {
+                        for (const c of d.change) {
+                            if (!c.qty) continue;
+                            const ncs = { tid: stage.tid, sid: stage.id, lid: d.id, pid: -1, stimes: 1, sorder: 0, cid: c.cid, cbid: c.cbid };
+                            const validQty = await this.ctx.service.stageChangeFinal.getChangeBillsValidQty(c.cbid);
+                            ncs.qty = validQty >= c.qty ? c.qty : validQty;
+                            insertChangeData.push(ncs);
+                            nbs.qc_qty = await this.ctx.helper.add(nbs.qc_qty, ncs.qty);
+                        }
+                    }
+                    nbs.qc_tp = this.ctx.helper.mul(nbs.qc_qty, b.unit_price, decimal.tp);
+                }
+                insertBillsData.push(nbs);
+            }
+            return [insertBillsData, insertPosData, insertChangeData]
+        }
+
+        async recover(stage, id) {
+            const stash = await this.getDataById(id);
+            if (!stash) throw '暂存计量不存在';
+            if (stash.tid !== stage.tid) throw '暂存计量不可导入';
+
+            const stashData = await this.loadAndAnalysis(stash.filepath);
+            const [insertBills, insertPos, insertChange] = await this.reCalcStashData(stage, stashData);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.delete(this.ctx.service.stageBills.tableName, { sid: stage.id });
+                await conn.delete(this.ctx.service.stageChange.tableName, { sid: stage.id });
+                await conn.delete(this.ctx.service.stagePos.tableName, { sid: stage.id });
+
+                if (insertBills.length > 0) conn.insert(this.ctx.service.stageBills.tableName, insertBills);
+                if (insertPos.length > 0) conn.insert(this.ctx.service.stagePos.tableName, insertPos);
+                if (insertChange.length > 0) conn.insert(this.ctx.service.stageChange.tableName, insertChange);
+                await conn.commit();
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        };
+    }
+
+    return StageStash;
+};

+ 56 - 0
app/view/shares/stage_stash_modal.ejs

@@ -0,0 +1,56 @@
+<div class="modal fade" id="stage-stash" data-backdrop="static">
+    <div class="modal-dialog modal-lx" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">暂存计量</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <h5>可用数据 </h5>
+                <table class="table table-sm table-bordered">
+                    <thead><th>期</th><th>创建时间</th><th>创建人</th><th>操作</th></thead>
+                    <tbody id="stage-stash-list">
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button class="btn btn-sm btn-primary" id="add-stage-stash">新增</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const getStashHtml = function (stash) {
+        const recover = stash.recover ? `<button class="btn btn-sm btn-warning" name="stage-stash-recover" sid="${stash.id}">导入</button>` : '';
+        return `<tr><td>第${stash.sorder}期</td><td>${moment(stash.create_time).format('YYYY-MM-DD HH:mm:ss')}</td><td>${stash.uname}</td><td>${recover}<button class="btn btn-sm btn-danger ml-1" name="stage-stash-del" sid="${stash.id}">删除</button></td></tr>`
+    }
+    $('#add-stage-stash').click(() => {
+        postData(`${window.location.pathname}/stash/add`, {}, function (result) {
+            $('#stage-stash-list').append(getStashHtml(result));
+            toastr.success('新增成功');
+        });
+    });
+    $('#stage-stash').on('show.bs.modal', function () {
+        postData(`${window.location.pathname}/stash/list`, {}, function (result) {
+            const html = [];
+            for (const r of result) {
+                html.push(getStashHtml(r));
+            }
+            $('#stage-stash-list').html(html.join(''));
+        })
+    });
+    $('body').on('click', '[name=stage-stash-del]', function() {
+        const obj = $(this);
+        postData(`${window.location.pathname}/stash/del`, {id: this.getAttribute('sid')}, function (result) {
+            obj.parent().parent().remove();
+        })
+    });
+    $('body').on('click', '[name=stage-stash-recover]', function() {
+        postData(`${window.location.pathname}/stash/recover`, {id: this.getAttribute('sid')}, function (result) {
+            window.location.reload();
+        })
+    });
+</script>

+ 6 - 0
app/view/stage/index.ejs

@@ -34,6 +34,9 @@
                 </div>
                 <div class="d-inline-block ml-3">
                     <a id="exportExcel" class="btn btn-primary btn-sm" href="javascript: void(0)">导出计量台账Excel</a>
+                    <% if (!ctx.stage.readOnly && !ctx.stage.revising && ctx.stage.status === auditConst.status.uncheck) { %>
+                    <a id="importExcel" class="btn btn-primary btn-sm" href="javascript: void(0)">导入计量台账Excel</a>
+                    <% } %>
                     <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="ledger-check2">数据检查</a>
                 </div>
                 <div class="d-inline-block">
@@ -41,6 +44,9 @@
                 </div>
             </div>
             <div class="ml-auto">
+                <% if (ctx.session.sessionUser.is_admin) { %>
+                <a class="btn btn-primary btn-sm" href="#stage-stash" data-toggle="modal" data-target="#stage-stash" href="javascript: void(0)">暂存计量</a>
+                <% } %>
             </div>
         </div>
     </div>

+ 2 - 0
app/view/stage/modal.ejs

@@ -626,3 +626,5 @@
 <% include ../shares/check_modal2.ejs %>
 <% include ../shares/new_tag_modal.ejs %>
 <% include ../shares/tender_select_modal.ejs %>
+<% include ../shares/import_excel_modal.ejs%>
+<% include ../shares/stage_stash_modal.ejs%>

+ 1 - 0
config/config.default.js

@@ -209,6 +209,7 @@ module.exports = appInfo => {
     config.proxy = true;
 
     config.hisOssPath = 'prod/';
+    config.stashOssPath = 'stash/prod/';
     config.oss = {
         clients: {
             signPdf: {

+ 1 - 0
config/config.local.js

@@ -92,6 +92,7 @@ module.exports = appInfo => {
     };
 
     config.hisOssPath = 'qa/';
+    config.stashOssPath = 'stash/qa/';
     config.oss = {
         clients: {
             signPdf: {

+ 1 - 0
config/config.qa.js

@@ -54,6 +54,7 @@ module.exports = appInfo => {
     };
 
     config.hisOssPath = 'qa/';
+    config.stashOssPath = 'stash/qa/';
     config.oss = {
         clients: {
             signPdf: {

+ 1 - 0
config/config.uat.js

@@ -60,6 +60,7 @@ module.exports = appInfo => {
     };
 
     config.hisOssPath = 'uat/';
+    config.stashOssPath = 'stash/uat/';
     config.oss = {
         clients: {
             signPdf: {

+ 3 - 0
config/web.js

@@ -304,6 +304,8 @@ const JsFiles = {
             // 本期计量台账
             index: {
                 files: [
+                    '/public/js/js-xlsx/xlsx.full.min.js',
+                    '/public/js/js-xlsx/xlsx.utils.js',
                     '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
                     '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
                     '/public/js/decimal.min.js',
@@ -327,6 +329,7 @@ const JsFiles = {
                     '/public/js/stage_im.js',
                     '/public/js/shares/tenders2tree.js',
                     '/public/js/shares/tender_select.js',
+                    '/public/js/shares/stage_excel.js',
                     '/public/js/ledger_check.js',
                     '/public/js/stage.js',
                     '/public/js/stage_audit.js',