Browse Source

导入计量台账Excel

MaiXinRong 2 years ago
parent
commit
737bf3195d

+ 4 - 4
app/controller/stage_controller.js

@@ -247,9 +247,6 @@ module.exports = app => {
 
         async _getStageLedgerData(ctx, ledgerColumn) {
             // const ledgerData = ctx.stage.ledgerHis
-            //     ? await ctx.helper.loadLedgerDataFromOss(ctx.stage.ledgerHis.bills_file)
-            //     : await ctx.service.ledger.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
-            // const ledgerData = ctx.stage.ledgerHis
             //     ? await ctx.service.ledger.loadDataFromOss(ctx.tender.id, ctx.stage.ledgerHis.bills_file)
             //     : await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } });
             const ledgerData = await ctx.service.ledger.getAllDataByCondition({ columns: ledgerColumn, where: { tender_id: ctx.tender.id } });
@@ -2103,11 +2100,14 @@ module.exports = app => {
 
         async importStageSheet(ctx) {
             try {
+                if (!ctx.stage.status === auditConst.status.uncheck) throw '仅新增期且未上报时,可从暂存计量中导入数据';
+
                 const compressData = ctx.request.body.data;
                 const data = JSON.parse(LzString.decompressFromUTF16(compressData));
-
+                await ctx.service.stageStash.loadExcelSheet(ctx.stage, data.sheet);
                 await ctx.service.stage.updateCheckCalcFlag(ctx.stage, true);
                 await ctx.service.stage.updateCacheTime(ctx.stage.id);
+                ctx.body = { err: 0, msg: '', data: null };
             } catch (err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '导入计量台账数据错误');

+ 79 - 30
app/lib/analysis_excel.js

@@ -296,8 +296,12 @@ class ImportBaseTree {
      * @param {object} pos - 部位明细
      * @returns {*}
      */
-    addPos (pos){
+    addPos (pos, strict = false){
         if (this.finalNode && this.finalNode.pos) {
+            if (strict) {
+                const exist = this.finalNode.pos.find(x => { return x.name === pos.name; });
+                if (exist) return;
+            }
             pos.id = this.ctx.app.uuid.v4();
             pos.lid = this.finalNode.id;
             pos.tid = this.ctx.tender.id;
@@ -876,17 +880,12 @@ class AnalysisGclExcelTree {
     }
 }
 
-class ImportStageBaseTree {
-
-}
-
-class AnalysisStageTree {
+class AnalysisStageExcelTree extends AnalysisExcelTree {
     /**
      * 构造函数
      */
     constructor(ctx, setting) {
-        this.ctx = ctx;
-        this.setting = setting;
+        super(ctx, setting);
         this.mid = ctx.tender.id;
         this.decimal = ctx.tender.info.decimal;
         this.precision = ctx.tender.info.precision;
@@ -897,23 +896,11 @@ class AnalysisStageTree {
             pos: {value: ['计量单元'], type: colDefineType.match},
             name: {value: ['名称'], type: colDefineType.match},
             unit: {value: ['单位'], type: colDefineType.match},
+            unit_price: {value: ['单价'], type: colDefineType.match},
             contract_qty: {value: ['本期合同计量|数量'], type: colDefineType.match},
             contract_tp: {value: ['本期合同计量|金额'], type: colDefineType.match},
         };
-        this.needCols = ['code', 'b_code', 'pos', 'name', 'unit', 'contract_qty', 'contract_tp'];
-    }
-
-    /**
-     * 读取表头并检查
-     * @param {Number} row - Excel数据行
-     */
-    checkColHeader(row) {
-        const colsDef = aeUtils.checkColHeader(row, this.colHeaderMatch);
-        let check = true;
-        for (const col of this.needCols) {
-            if (!colsDef[col]) check = false;
-        }
-        if (check) this.colsDef = colsDef;
+        this.needCols = ['code', 'b_code', 'pos', 'name', 'unit', 'unit_price', 'contract_qty', 'contract_tp'];
     }
 
     mergeHeaderRow(iRow, row, subRow, merge) {
@@ -934,8 +921,69 @@ class AnalysisStageTree {
         return result;
     }
 
-    loadRowData(row, iRow) {
-
+    /**
+     * 读取项目节节点
+     * @param {Array} row - excel行数据
+     * @returns {*}
+     * @private
+     */
+    _loadXmjNode(row) {
+        try {
+            const node = {};
+            node.code = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.code]));
+            node.name = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.name]));
+            node.unit = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.unit]));
+            return this.cacheTree.addXmjNode(node);
+        } catch (error) {
+            if (error.stack) {
+                this.ctx.logger.error(error);
+            } else {
+                this.ctx.getLogger('fail').info(JSON.stringify({
+                    error,
+                    project: this.ctx.session.sessionProject,
+                    user: this.ctx.session.sessionUser,
+                    body: row,
+                }));
+            }
+            return null;
+        }
+    }
+    /**
+     * 读取工程量清单数据
+     * @param {Array} row - excel行数据
+     * @returns {*}
+     * @private
+     */
+    _loadGclNode(row) {
+        if (this.filter.filterGcl) return true;
+        const node = {};
+        node.b_code = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.b_code]));
+        node.name = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.name]));
+        node.unit = this.ctx.helper.replaceReturn(this.ctx.helper._.trimEnd(row[this.colsDef.unit]));
+        node.unit_price = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.unit_price]), this.decimal.up);
+        const precision = this.ctx.helper.findPrecision(this.precision, node.unit);
+        node.contract_qty = this.ctx.helper.round(aeUtils.toNumber(row[this.colsDef.contract_qty]), precision.value);
+        if (node.quantity && node.unit_price) {
+            node.contract_tp = this.ctx.helper.mul(node.quantity, node.unit_price, this.decimal.tp);
+        } else {
+            node.contract_tp = null;
+        }
+        if (this.filter.filterZeroGcl && !node.quantity && !node.total_price) return true;
+        return this.cacheTree.addGclNode(node);
+    }
+    /**
+     * 读取部位明细数据
+     * @param {Array} row - excel行数据
+     * @returns {*}
+     * @private
+     */
+    _loadPos(row) {
+        if (this.filter.filterPos) return true;
+        const pos = {};
+        pos.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
+        pos.quantity = aeUtils.toNumber(row[this.colsDef.quantity]);
+        pos.contrac_qty = pos.contract_qty;
+        return this.cacheTree.addPos(pos, true);
     }
 
     /**
@@ -944,20 +992,21 @@ class AnalysisStageTree {
      * @param {Array} tempData - 新建项目使用的清单模板
      * @returns {ImportBaseTree}
      */
-    analysisData(ledger, pos, sheet) {
+    analysisData(sheet, tempData, filter) {
         this.filter = filter ? filter : {};
         this.colsDef = null;
-        this.cacheTree = this._getNewLedger();
+        this.cacheTree = this._getNewCacheTree(tempData);
         this.errorData = [];
         this.loadEnd = false;
         this.loadBegin = sheet.rows.length;
 
-        for (const iRow in sheet.rows) {
+        for (const [iRow, row] of sheet.rows.entries()) {
             if (this.colsDef && !this.loadEnd) {
                 if (iRow < this.loadBegin) continue;
-                this.loadRowData(sheet.rows[iRow], iRow);
+                this.loadRowData(row, iRow);
             } else {
-                const mergeRow = this.mergeHeaderRow(iRow, sheet.rows[iRow], sheet.rows[iRow + 1], sheet.merge);
+                if (iRow === sheet.rows.length - 1) continue;
+                const mergeRow = this.mergeHeaderRow(iRow, row, sheet.rows[iRow + 1], sheet.merge);
                 this.checkColHeader(mergeRow);
                 if (this.colsDef) this.loadBegin = iRow + 2;
             }
@@ -967,4 +1016,4 @@ class AnalysisStageTree {
     }
 }
 
-module.exports = { AnalysisExcelTree, AnalysisGclExcelTree, AnalysisStageTree };
+module.exports = { AnalysisExcelTree, AnalysisGclExcelTree, AnalysisStageExcelTree };

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

@@ -4726,8 +4726,7 @@ $(document).ready(() => {
             },
             callback: function (sheet) {
                 postDataCompress(window.location.pathname + '/importStageSheet', {sheet}, function (result) {
-                    // todo 刷新界面
-                    console.log(result);
+                    window.location.reload();
                 }, null);
             }
         });

+ 1 - 0
app/router.js

@@ -292,6 +292,7 @@ module.exports = app => {
     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/importStageSheet', sessionAuth, tenderCheck, stageCheck, 'stageController.importStageSheet');
 
     // 计量附件
     app.post('/tender/:id/measure/stage/:order/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.uploadFile');

+ 1 - 0
app/service/stage.js

@@ -725,6 +725,7 @@ module.exports = app => {
             const lastStage = await this.ctx.service.stage.getLastestStage(tid, true);
             return lastStage ? lastStage.id === sid : false;
         }
+
     }
 
     return Stage;

+ 163 - 0
app/service/stage_stash.js

@@ -8,6 +8,120 @@
  * @version
  */
 
+class loadStageExcelTree {
+    constructor(ctx) {
+        this.ctx = ctx;
+
+        this.decimal = ctx.tender.info.decimal;
+
+        this.insertBills = [];
+        this.insertPos = [];
+        this.updateBills = [];
+        this.updatePos = [];
+    }
+    init(source) {
+        this.default = source.default;
+
+        const LedgerModel = require('../lib/ledger');
+        this.ledgerTree = new LedgerModel.billsTree(this.ctx, {
+            id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level', rootId: -1, calcFields: [],
+        });
+        this.pos = new LedgerModel.pos({ id: 'id', ledgerId: 'lid' });
+        this.ledgerTree.loadDatas(source.ledgerData);
+        this.pos.loadDatas(source.posData);
+
+        this.stageBills = source.stageBills;
+        this.stagePos = source.stagePos;
+    }
+    findNode(node, parent) {
+        const _ = this.ctx.helper._;
+        const sibling = parent ? parent.children : this.ledgerTree.children;
+        const self = this;
+        return sibling.find(x => {
+            if (node.is_leaf !== x.is_leaf) return false;
+
+            if (node.b_code) {
+                if (x.has_pos === undefined) {
+                    const relaPos = self.pos.getLedgerPos(x.id);
+                    x.has_pos = !!relaPos && relaPos.length > 0;
+                }
+                return node.b_code === _.trimEnd(x.b_code) && node.name === _.trimEnd(x.name) && node.unit === _.trimEnd(x.unit)
+                    && node.unit_price === x.unit_price && node.has_pos === x.has_pos ;
+            } else {
+                return node.code === _.trimEnd(x.code) && node.name === _.trimEnd(x.name);
+            }
+        });
+    }
+    loadLeaf(node, source) {
+        const curStageBills = this.stageBills.find(csb => { return csb.lid === source.id; });
+        if (node.has_pos) {
+            const sourcePos = this.pos.getLedgerPos(source.id);
+            const sourceStagePos = this.stagePos.filter(sp => { return sp.lid === source.id; });
+
+            let contract_qty = 0;
+            for (const p of node.pos) {
+                if (!p.contract_qty) continue;
+                const sp = sourcePos.find(x => { return x.name === p.name; });
+                if (!sp) continue;
+
+                contract_qty = this.ctx.helper.add(contract_qty, p.contract_qty);
+                let ssp = sourceStagePos.find(x => { return x.lid === sp.id; });
+                sourceStagePos.splice(sourceStagePos.indexOf(ssp), 1);
+                if (ssp) {
+                    this.updatePos.push({ id: ssp.id, contract_qty: p.contract_qty });
+                } else {
+                    this.insertPos.push({ tid: this.default.tid, sid: this.default.sid, said: this.default.said, times: 1, order: 0, lid: source.id, contract_qty: p.contract_qty });
+                }
+            }
+            for (const ssp of sourceStagePos) {
+                contract_qty = this.ctx.helper.add(contract_qty, ssp.contract_qty);
+            }
+            const contract_tp = this.ctx.helper.mul(contract_qty, source.unit_price, this.decimal.tp);
+            if (curStageBills) {
+                this.updateBills.push({ id: curStageBills.id, contract_qty, contract_tp });
+            } else {
+                this.insertBills.push({ tid: this.default.tid, sid: this.default.sid, said: this.default.said, times: 1, order: 0, lid: source.id, contract_qty, contract_tp });
+            }
+        } else {
+            if (!node.contract_qty && !node.contract_tp) return;
+            const contract_qty = node.contract_qty;
+            const contract_tp = contract_qty
+                ? this.ctx.helper.mul(contract_qty, source.unit_price, this.decimal.tp)
+                : this.ctx.helper.round(contract_tp, this.decimal.tp);
+
+            if (curStageBills) {
+                this.updateBills.push({ id: curStageBills.id, contract_qty: contract_qty, contract_tp: contract_tp });
+            } else {
+                this.insertBills.push({ tid: this.default.tid, sid: this.default.sid, said: this.default.said, times: 1, order: 0, lid: source.id, contract_qty: contract_qty, contract_tp: contract_tp });
+            }
+        }
+    }
+    loadNode(node, parent) {
+        node.is_leaf = !node.children || node.children.length === 0 ? 1 : 0;
+        node.has_pos = node.pos && node.pos.length > 0;
+        const cur = this.findNode(node, parent);
+        if (!cur) return;
+
+        if (cur) {
+            if (node.is_leaf) {
+                this.loadLeaf(node, cur);
+            } else {
+                for (const c of node.children) {
+                    this.loadNode(c, cur);
+                }
+            }
+        }
+    }
+    load(excelTree, source) {
+        this.init(source);
+
+        for (const node of excelTree.roots) {
+            this.loadNode(node, null);
+        }
+    }
+}
+
+
 module.exports = app => {
     class StageStash extends app.BaseService {
         /**
@@ -174,6 +288,55 @@ module.exports = app => {
                 throw err;
             }
         };
+
+        /**
+         * 导入Excel期计量(仅导入合同计量)
+         * 该方法本应该独立或者在stage下,但是跟stage_stash业务非常类似,暂归类于此
+         * @param stage
+         * @param sheet
+         * @returns {Promise<void>}
+         */
+        async loadExcelSheet(stage, excelData) {
+            const AnalysisExcel = require('../lib/analysis_excel').AnalysisStageExcelTree;
+            const analysisExcel = new AnalysisExcel(this.ctx, this.ctx.service.ledger.setting);
+
+            try {
+                const templateId = await this.ctx.service.valuation.getValuationTemplate(
+                    this.ctx.tender.data.valuation, this.ctx.tender.data.measure_type);
+                const tempData = await this.ctx.service.tenderNodeTemplate.getData(templateId, true);
+                const cacheTree = analysisExcel.analysisData(excelData, tempData, { filterZeroGcl: false });
+
+                const ledgerData = await this.ctx.service.ledger.getAllDataByCondition({
+                    columns: ['id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf', 'code', 'b_code', 'name', 'unit', 'unit_price', 'is_tp'],
+                    where: { tender_id: stage.tid},
+                });
+                const posData = await this.ctx.service.pos.getAllDataByCondition({
+                    columns: ['id', 'lid', 'name', 'porder'],
+                    where: { tid: stage.tid },
+                });
+                const stageBills = await this.ctx.service.stageBills.getAllDataByCondition({ where: { tid: stage.tid }});
+                const stagePos = await this.ctx.service.stagePos.getAllDataByCondition({ where: { tid: stage.tid }});
+
+                const loadModal = new loadStageExcelTree(this.ctx);
+                loadModal.load(cacheTree, {ledgerData, posData, stageBills, stagePos, default: { tid: stage.tid, sid: stage.id, said: this.ctx.session.sessionUser.accountId } });
+
+                const conn = await this.db.beginTransaction();
+                try {
+                    if (loadModal.insertBills.length > 0) conn.insert(this.ctx.service.stageBills.tableName, loadModal.insertBills);
+                    if (loadModal.updateBills.length > 0) conn.updateRows(this.ctx.service.stageBills.tableName, loadModal.updateBills);
+                    if (loadModal.insertPos.length > 0) conn.insert(this.ctx.service.stagePos.tableName, loadModal.insertPos);
+                    if (loadModal.updatePos.length > 0) conn.updateRows(this.ctx.service.stagePos.tableName, loadModal.updatePos);
+                    await conn.commit();
+                } catch (err) {
+                    await conn.rollback();
+                    this.ctx.log(err);
+                    throw '保存导入数据失败';
+                }
+            } catch (err) {
+                this.ctx.log(err);
+                throw '解析Excel错误';
+            }
+        }
     }
 
     return StageStash;