浏览代码

Merge branch 'master' of http://192.168.1.41:3000/maixinrong/Calculation

TonyKang 5 年之前
父节点
当前提交
4a4666410a

+ 1 - 13
app/const/spread.js

@@ -24,8 +24,6 @@ const withCl = {
             {title: '设计数量|数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
             {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
             {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '设计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sgfh_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sgfh_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '设计错漏增减|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sjcl_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -72,8 +70,6 @@ const withoutCl = {
             {title: '设计数量|数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
             {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
             {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
-            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '设计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sgfh_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sgfh_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
@@ -171,8 +167,6 @@ const stageCl = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -239,8 +233,6 @@ const stageNoCl = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
-            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -303,17 +295,14 @@ const stageGather = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
-            // {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number'},
-            // {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '签约清单|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_bills_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
             {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'contract_tp', hAlign: 2, width: 60, type: 'Number'},
-            {title: '本期数量变更|数量', colSpan: '3|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_tp', hAlign: 2, width: 60, type: 'Number'},
-            {title: '|变更令', colSpan: '|1', rowSpan: '|1', field: 'qc_tp_bgl', hAlign: 2, width: 60, formatter: '@'},
             {title: '本期完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_tp', hAlign: 2, width: 60, type: 'Number'},
             {title: '截止本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_contract_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -325,7 +314,6 @@ const stageGather = {
             {title: '|完成率(%)', colSpan: '1', rowSpan: '|1', field: 'end_gather_percent', hAlign: 2, width: 80, readOnly: true, type: 'Number'},
             {title: '台账+变更令|数量', colSpan: '3|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: '|1', rowSpan: '|1', field: 'percent', hAlign: 0, width: 100, type: 'Number'},
         ],
         emptyRows: 0,
         headRows: 2,

+ 37 - 0
app/controller/ledger_controller.js

@@ -510,6 +510,43 @@ module.exports = app => {
         }
 
         /**
+         * 台账对比 页面 (Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        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
+         * @returns {Promise<void>}
+         */
+        async loadGatherData(ctx) {
+            try {
+                const billsData = await ctx.service.ledger.getData(ctx.tender.id);
+                const posData = this.ctx.tender.data.measure_type === measureType.tz.value
+                    ? await ctx.service.pos.getPosData({tid: ctx.tender.id}) : [];
+                const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: {tender_id: this.ctx.tender.id} });
+                ctx.body = { err: 0, msg: '', data: {bills: billsData, pos: posData, dealBills: dealBills} };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
+            }
+        }
+
+        /**
          * 计量台账页面 (Get)
          *
          * @param {object} ctx - egg全局变量

+ 12 - 0
app/controller/report_controller.js

@@ -310,6 +310,18 @@ async function getReportData(ctx, params, filters) {
                 runnableRst.push(ctx.service.stagePay.getAuditorStageData(params.stage_id, params.stage_times, params.stage_order));
                 runnableKey.push('stage_pay');
                 break;
+            case 'mem_stage_im_tz':
+                runnableRst.push(ctx.service.reportMemory.getStageImTzData(params.tender_id, params.stage_id));
+                runnableKey.push('mem_stage_im_tz');
+                break;
+            case 'mem_stage_im_tz_bills':
+                runnableRst.push(ctx.service.reportMemory.getStageImTzBillsData(params.tender_id, params.stage_id));
+                runnableKey.push('mem_stage_im_tz_bills');
+                break;
+            case 'mem_stage_im_zl':
+                runnableRst.push(ctx.service.reportMemory.getStageImZlData(params.tender_id, params.stage_id));
+                runnableKey.push('mem_stage_im_zl');
+                break;
             default:
                 break;
         }

+ 1 - 1
app/extend/helper.js

@@ -352,7 +352,7 @@ module.exports = {
      * @return {boolean}
      */
     checkZero(value) {
-        return !(value && Math.abs(value) > zeroRange);
+        return value === undefined || value === null || (this._.isNumber(value) && Math.abs(value) < zeroRange);
     },
     /**
      * 检查数字是否相等

+ 314 - 0
app/lib/ledger.js

@@ -0,0 +1,314 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const itemsPre = 'id_';
+
+class billsTree {
+    /**
+     * 构造函数
+     */
+    constructor (ctx, setting) {
+        this.ctx = ctx;
+        // 无索引
+        this.datas = [];
+        // 以key为索引
+        this.items = {};
+        // 以排序为索引
+        this.nodes = [];
+        // 根节点
+        this.children = [];
+        // 树设置
+        this.setting = setting;
+    }
+
+    /**
+     * 根据id获取树结构节点数据
+     * @param {Number} id
+     * @returns {Object}
+     */
+    getItems (id) {
+        return this.items[itemsPre + id];
+    };
+    /**
+     * 查找node的parent
+     * @param {Object} node
+     * @returns {Object}
+     */
+    getParent (node) {
+        return this.getItems(node[this.setting.pid]);
+    };
+    /**
+     * 查询node的已下载子节点
+     * @param {Object} node
+     * @returns {Array}
+     */
+    getChildren (node) {
+        const setting = this.setting;
+        const pid = node ? node[setting.id] : setting.rootId;
+        const children = this.datas.filter(function (x) {
+            return x[setting.pid] === pid;
+        });
+        children.sort(function (a, b) {
+            return a.order - b.order;
+        });
+        return children;
+    };
+
+    /**
+     * 树结构根据显示排序
+     */
+    sortTreeNode (isResort) {
+        const self = this;
+        const addSortNodes = function (nodes) {
+            if (!nodes) { return }
+            for (let i = 0; i < nodes.length; i++) {
+                self.nodes.push(nodes[i]);
+                nodes[i].index = self.nodes.length - 1;
+                if (!isResort) {
+                    nodes[i].children = self.getChildren(nodes[i]);
+                } else {
+                    nodes[i].children.sort(function (a, b) {
+                        return a.order - b.order;
+                    })
+                }
+                addSortNodes(nodes[i].children);
+            }
+        };
+        this.nodes = [];
+        if (!isResort) {
+            this.children = this.getChildren();
+        } else {
+            this.children.sort(function (a, b) {
+                return a.order - b.order;
+            })
+        }
+        addSortNodes(this.children);
+    }
+
+    /**
+     * 加载数据(初始化), 并给数据添加部分树结构必须数据
+     * @param datas
+     */
+    loadDatas (datas) {
+        // 清空旧数据
+        this.items = {};
+        this.nodes = [];
+        this.datas = [];
+        this.children = [];
+        // 加载全部数据
+        datas.sort(function (a, b) {
+            return a.level - b.level;
+        });
+        for (const data of datas) {
+            const keyName = itemsPre + data[this.setting.id];
+            if (!this.items[keyName]) {
+                const item = JSON.parse(JSON.stringify(data));
+                item.children = [];
+                item.expanded = true;
+                item.visible = true;
+                this.items[keyName] = item;
+                this.datas.push(item);
+                if (item[this.setting.pid] === this.setting.rootId) {
+                    this.children.push(item);
+                } else {
+                    const parent = this.getParent(item);
+                    if (parent) {
+                        parent.children.push(item);
+                    }
+                }
+            }
+        }
+        this.children.sort(function (a, b) {
+            return a.order - b.order;
+        });
+        this.sortTreeNode(true);
+    }
+
+    /**
+     * 递归方式 查询node的已下载的全部后代 (兼容full_path不存在的情况)
+     * @param node
+     * @returns {*}
+     * @private
+     */
+    _recursiveGetPosterity (node) {
+        let posterity = node.children;
+        for (const c of node.children) {
+            posterity = posterity.concat(this._recursiveGetPosterity(c));
+        }
+        return posterity;
+    };
+    /**
+     * 查询node的已下载的全部后代
+     * @param {Object} node
+     * @returns {Array}
+     */
+    getPosterity (node) {
+        if (node.full_path !== '') {
+            const reg = new RegExp('^' + node.full_path + '-');
+            return this.datas.filter(function (x) {
+                return reg.test(x.full_path);
+            });
+        } else {
+            return this._recursiveGetPosterity(node);
+        }
+    };
+
+    /**
+     * 检查节点是否是最底层项目节
+     * @param node
+     * @returns {boolean}
+     */
+    isLeafXmj(node) {
+        if (!node.code) {
+            return false;
+        }
+        for (const child of node.children) {
+            if (!child.b_code || child.b_code === '') {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 查询最底层项目节(本身或父项)
+     * @param {Object} node - 查询节点
+     * @returns {Object}
+     */
+    getLeafXmjParent(node) {
+        let parent = node;
+        while (parent) {
+            if (this.isLeafXmj(parent)) {
+                return parent;
+            } else {
+                parent = this.getParent(parent);
+            }
+        }
+        return null;
+    }
+
+    _mapTreeNode () {
+        let map = {}, maxLevel = 0;
+        for (const node of this.nodes) {
+            let levelArr = map[node.level];
+            if (!levelArr) {
+                levelArr = [];
+                map[node.level] = levelArr;
+            }
+            if (node.level > maxLevel) {
+                maxLevel = node.level;
+            }
+            levelArr.push(node);
+        }
+        return [maxLevel, map];
+    }
+    _calculateNode (node) {
+        const self = this;
+        if (node.children && node.children.length > 0) {
+            const gather = node.children.reduce(function (rst, x) {
+                const result = {};
+                for (const cf of self.setting.calcFields) {
+                    result[cf] = self.ctx.helper.add(rst[cf], x[cf]);
+                }
+                return result;
+            });
+            // 汇总子项
+            for (const cf of this.setting.calcFields) {
+                if (gather[cf]) {
+                    node[cf] = gather[cf];
+                } else {
+                    node[cf] = null;
+                }
+            }
+        }
+        // 自身运算
+        if (this.setting.calc) {
+            this.setting.calc(node);
+        }
+    }
+    calculateAll() {
+        const [maxLevel, levelMap] = this._mapTreeNode();
+        for (let i = maxLevel; i >= 0; i--) {
+            const levelNodes = levelMap[i];
+            if (levelNodes && levelNodes.length > 0) {
+                for (const node of levelNodes) {
+                    this._calculateNode(node);
+                }
+            }
+        }
+    }
+}
+
+class pos {
+    /**
+     * 构造函数
+     * @param {id|Number, masterId|Number} setting
+     */
+    constructor (setting) {
+        // 无索引
+        this.datas = [];
+        // 以key为索引
+        this.items = {};
+        // 以分类id为索引的有序
+        this.ledgerPos = {};
+        // pos设置
+        this.setting = setting;
+    }
+
+    /**
+     * 加载部位明细数据
+     * @param datas
+     */
+    loadDatas(datas) {
+        this.datas = datas;
+        this.items = {};
+        this.ledgerPos = {};
+        for (const data of this.datas) {
+            const key = itemsPre + data[this.setting.id];
+            this.items[key] = data;
+
+            const masterKey = itemsPre + data[this.setting.ledgerId];
+            if (!this.ledgerPos[masterKey]) {
+                this.ledgerPos[masterKey] = [];
+            }
+            this.ledgerPos[masterKey].push(data);
+        }
+        for (const prop in this.ledgerPos) {
+            this.resortLedgerPos(this.ledgerPos[prop]);
+        }
+    }
+
+    getLedgerPos(mid) {
+        return this.ledgerPos[itemsPre + mid];
+    }
+
+    resortLedgerPos(ledgerPos) {
+        if (ledgerPos instanceof Array) {
+            ledgerPos.sort(function (a, b) {
+                return a.porder - b.porder;
+            })
+        }
+    }
+
+    /**
+     * 计算全部
+     */
+    calculateAll() {
+        if (!this.setting.calc) { return; }
+        for (const pos of this.datas) {
+            this.setting.calc(pos);
+        }
+    }
+}
+
+module.exports = {
+    billsTree,
+    pos,
+};

+ 608 - 0
app/lib/stage_im.js

@@ -0,0 +1,608 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Ledger = require('./ledger');
+const imType = require('../const/tender').imType;
+
+class StageIm {
+    constructor (ctx) {
+        const self = this;
+        this.ctx = ctx;
+        this._ = this.ctx.helper._;
+        // mainData
+        this.billsTree = new Ledger.billsTree(this.ctx, {
+            id: 'ledger_id',
+            pid: 'ledger_pid',
+            order: 'order',
+            level: 'level',
+            rootId: -1,
+            keys: ['id', 'tender_id', 'ledger_id'],
+            stageId: 'id',
+            calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp'],
+            calc: function (node) {
+                if (node.children && node.children.length === 0) {
+                    node.gather_qty = self.ctx.helper.add(node.contract_qty, node.qc_qty);
+                }
+                node.gather_tp = self.ctx.helper.add(node.contract_tp, node.qc_tp);
+            }
+        });
+        this.pos = new Ledger.pos({
+            id: 'id', ledgerId: 'lid',
+            updateFields: ['contract_qty', 'qc_qty', 'postil'],
+            calc: function (p) {
+                p.gather_qty = self.ctx.helper.add(p.contract_qty, p.qc_qty);
+            }
+        });
+        // relaData
+        this.change = null;
+        this.details = null;
+        // result
+        this.ImData = [];
+        this.ImBillsData = [];
+        //
+        this.imFields = ['uuid', 'doc_code', 'peg', 'bw', 'xm', 'drawing_code', 'calc_memo', 'calc_img'];
+        this.splitChar = '-';
+    }
+
+    // 加载数据
+    async _loadMainData () {
+        const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+        if (this.ctx.stage.readOnly) {
+            const curStage = await this.ctx.service.stageBills.getAuditorStageData(this.ctx.tender.id,
+                this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+            this.ctx.helper.assignRelaData(billsData, [
+                {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+            ]);
+        } else {
+            const curStage = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id);
+            this.ctx.helper.assignRelaData(billsData, [
+                {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+            ]);
+        }
+        this.billsTree.loadDatas(billsData);
+        this.billsTree.calculateAll();
+
+        const posData = await this.ctx.service.pos.getAllDataByCondition({ where: {tid: this.ctx.tender.id }});
+        if (this.ctx.stage.readOnly) {
+            const curPosStage = await this.ctx.service.stagePos.getAuditorStageData2(this.ctx.tender.id,
+                this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+            this.ctx.helper.assignRelaData(posData, [
+                {data: curPosStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'}
+            ]);
+        } else {
+            const curPosStage = await this.ctx.service.stagePos.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id);
+            this.ctx.helper.assignRelaData(posData, [
+                {data: curPosStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'}
+            ]);
+        }
+        this.pos.loadDatas(posData);
+        this.pos.calculateAll();
+    }
+    async _loadRelaData () {
+        if (this.ctx.stage.readOnly) {
+            this.details = await this.ctx.service.stageDetail.getAuditorStageData(this.ctx.tender.id, this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+        } else {
+            this.details = await this.ctx.service.stageDetail.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id);
+        }
+        if (this.ctx.stage.readOnly) {
+            this.changes = await this.ctx.service.stageChange.getAuditorAllStageData(this.ctx.tender.id, this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+        } else {
+            this.changes = await this.ctx.service.stageChange.getLastestAllStageData(this.ctx.tender.id, this.ctx.stage.id);
+        }
+    }
+
+    // 检查汇总节点
+    async _checkGather() {
+        const gatherNodes = this.ctx.stage.im_gather_node ? this.ctx.stage.im_gather_node.split(',') : [];
+        for (const node of this.billsTree.datas) {
+            node.check = gatherNodes.indexOf(node.id) !== -1;
+        }
+    }
+
+    // 辅助取值方法
+    _getNumberFormat(num, length) {
+        let s = '0000000000';
+        s = s + num;
+        return s.substr(s.length - length);
+    }
+    _getNodeByLevel(node, level) {
+        let cur = node;
+        while (cur && cur.level > level) {
+            cur = this.billsTree.getParent(cur);
+        }
+        return cur;
+    }
+    _checkPeg(text) {
+        const pegReg = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        return pegReg.test(text);
+    }
+    _getPegStr(text) {
+        const pegReg1 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?[~~—][a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+        const result1 = text.match(pegReg1);
+        if (result1) {
+            return result1[0];
+        } else {
+            const pegReg2 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?-[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+            const result2 = text.match(pegReg2);
+            if (result2) {
+                return result2[0];
+            } else {
+                const pegReg3 = /[a-zA-Z]?[kK][0-9]+[++][0-9]{3}([.][0-9]+)?/;
+                const result3 = text.match(pegReg3);
+                return result3 ? result3[0] : '';
+            }
+        }
+    }
+    _getPegNode (node) {
+        if (node) {
+            if (this._checkPeg(node.name)) {
+                return node;
+            } else {
+                const parent = this.billsTree.getParent(node);
+                return parent ? this._getPegNode(parent) : null;
+            }
+        }
+    }
+    _getDrawingCode(node) {
+        if (!node) {
+            return '';
+        } else if (node.drawing_code) {
+            return node.drawing_code;
+        } else {
+            const parent = this.billsTree.getParent(node);
+            return parent ? this._getDrawingCode(parent) : '';
+        }
+    }
+    _getZlNormalBw(node, peg) {
+        if (peg) {
+            const subPeg1 = this._getNodeByLevel(node, peg.level + 1);
+            const subPeg2 = this._getNodeByLevel(node, peg.level + 2);
+            let result = peg.name;
+            if (subPeg1 && subPeg1.id !== peg.id && subPeg1.id !== node.id) {
+                result = result + '-' + subPeg1.name;
+                if (subPeg2 && subPeg2.id !== subPeg1.id && subPeg2.id !== node.id) {
+                    result = result + '-' + subPeg2.name;
+                }
+            }
+            return result;
+        } else {
+            if (node.level === 2 || node.level === 3) {
+                return node.name;
+            } else if (node.level >= 4) {
+                let parent = this.billsTree.getParent(node), result = parent.name;
+                while (parent.level > 3 && parent) {
+                    parent = this._getNodeByLevel(node, parent.level - 1);
+                    result = parent.name + '-' + result;
+                }
+                return result;
+            } else {
+                return '';
+            }
+        }
+    }
+    _getZlGatherBw(node, peg) {
+        if (peg) {
+            const subPeg1 = this._getNodeByLevel(node, peg.level + 1);
+            let result = peg.name;
+            if (subPeg1 && subPeg1.id !== peg.id) {
+                result = result + '-' + subPeg1.name;
+            }
+            return result;
+        } else {
+            if (node.level < 3) {
+                return node.name;
+            } else {
+                let parent = node, result = parent.name;
+                while (parent.level > 3 && parent) {
+                    parent = this._getNodeByLevel(node, parent.level - 1);
+                    result = parent.name + '-' + result;
+                }
+                return result;
+            }
+        }
+    }
+
+    _checkCustomDetail(im) {
+        const self = this;
+        const cd = this._.find(this.details, function (d) {
+            return im.lid === d.lid &&
+                (!im.code || im.code === d.code) &&
+                (!im.name || im.name === d.name) &&
+                (!im.unit || im.unit === d.unit) &&
+                self.ctx.helper.checkZero(self.ctx.helper.sub(im.unit_price, d.unit_price)) &&
+                (!im.pid || im.pid === d.pid);
+        });
+        if (cd) {
+            this._.assignInWith(im, cd, function (oV, sV, key) {
+                return self.imFields.indexOf(key) > -1 && sV !== undefined && sV !== null ? sV : oV;
+            });
+        }
+    }
+
+    _getCalcMemo(im) {
+        if (im.calc_memo !== undefined && im.calc_memo !== null && im.calc_memo !== '') return;
+
+        if (im.leafXmjs && im.leafXmjs.length > 0) {
+            const memo = ['本期计量:' + im.jl + ' ' + im.unit];
+            for (const lx of im.leafXmjs) {
+                for (const p of lx.pos) {
+                    memo.push(p.name + ':' + p.jl + ' ' + im.unit);
+                }
+            }
+            im.calc_memo = memo.join('\n');
+        } else if (im.check) {
+            const memo = [];
+            for (const [i, b] of im.gclBills.entries()) {
+                if (b.pos && b.pos.length > 0) {
+                    memo.push('清单' + (i+1) + b.b_code + ' ' + b.name);
+                    for (const p of b.pos) {
+                        memo.push(p.name + ':' + p.jl + ' ' + b.unit);
+                    }
+                } else {
+                    memo.push('清单' + (i+1) + b.b_code + ' ' + b.name + ':' + b.jl + ' ' + b.unit);
+                }
+            }
+            im.calc_memo = memo.join('\n');
+        } else {
+            im.calc_memo =  '';
+        }
+    }
+    _getChangeInfo(im) {
+        if (im.changes && im.changes.length > 0) {
+            const code = this._.uniq(this._.map(im.changes, 'c_code'));
+            if (!im.bgl_code || im.bgl_code === '') im.bgl_code = code.join(';');
+            const new_code = this._.uniq(this._.map(im.changes, 'c_new_code'));
+            if (!im.bgl_drawing_code || im.bgl_drawing_code === '') im.bgl_drawing_code = new_code.join(';');
+        }
+    }
+
+    _generateTzPosData(node, gclBills) {
+        if (!gclBills.pos) {
+            gclBills.pos = [];
+        }
+        const posRange = this.pos.getLedgerPos(node.id);
+        if (!posRange) { return }
+        for (const p of posRange) {
+            if (!p.gather_qty || this.ctx.helper.checkZero(p.gather_qty)) { continue; }
+            let lp = this._.find(gclBills.pos, {name: p.name});
+            if (!lp) {
+                lp = {name: p.name};
+                gclBills.pos.push(lp);
+            }
+            lp.jl = this.ctx.helper.add(lp.jl, p.gather_qty);
+            lp.contract_jl = this.ctx.helper.add(lp.contract_jl, p.contract_qty);
+            lp.qc_jl = this.ctx.helper.add(lp.qc_jl, p.qc_qty);
+        }
+    }
+    _generateTzGclBillsData(node, im) {
+        if (!im.gclBills) {
+            im.gclBills = [];
+        }
+        const posterity = this.billsTree.getPosterity(node);
+        for (const p of posterity) {
+            if ((!p.b_code || p.b_code === '') || (p.children && p.children.length > 0)) {
+                continue;
+            }
+            if ((!p.contract_tp || p.contract_tp === 0) && (!p.qc_tp || p.qc_tp === 0)) {
+                continue;
+            }
+            let b = this._.find(im.gclBills, {bid: p.id});
+            if (!b) {
+                b = {imid: im.id, bid: p.id, b_code: p.b_code, name: p.name, unit: p.unit};
+                im.gclBills.push(b);
+                this.ImBillsData.push(b);
+            }
+            b.jl = this.ctx.helper.add(b.jl, p.gather_qty);
+            b.contract_jl = this.ctx.helper.add(b.contract_jl, p.contract_qty);
+            b.qc_jl = this.ctx.helper.add(b.qc_jl, p.qc_qty);
+            this._generateTzPosData(p, b);
+        }
+    }
+    _generateTzChangeData(node, im) {
+        if (!im.changes) {
+            im.changes = [];
+        }
+        const posterity = this.billsTree.getPosterity(node);
+        for (const p of posterity) {
+            if (p.children && p.children.length > 0) {
+                continue;
+            }
+            if ((!p.qc_tp || p.qc_tp === 0)) {
+                continue;
+            }
+            const posRange = this.pos.getLedgerPos(p.id);
+            if (!posRange) {
+                for (const c of this.changes) {
+                    if (c.lid === p.id && c.pid == -1 && c.qty && c.qty !== 0) {
+                        im.changes.push(c);
+                    }
+                }
+            } else {
+                for (const pp of posRange) {
+                    if ((!pp.qc_tp || pp.qc_tp === 0)) {
+                        continue;
+                    }
+                    for (const c of this.changes) {
+                        if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
+                            im.changes.push(c);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    /**
+     * 生成 0号台账 中间计量数据
+     * @param {Object} node - 生成中间计量表的节点
+     */
+    _generateTzImData (node) {
+        if (node.gather_tp) {
+            const peg = this._getPegNode(node);
+            const im = {
+                id: this.ImData.length + 1,
+                lid: node.id, pid: '', code: node.code,
+                jl: node.gather_tp, contract_jl: node.contract_tp, qc_jl: node.qc_tp,
+                peg: peg ? this._getPegStr(peg.name) : '', drawing_code: this._getDrawingCode(node),
+            };
+            if (this.ctx.stage.im_gather && node.check) {
+                im.bw = this._getZlGatherBw(node, peg);
+                im.xm = '';
+            } else {
+                im.bw = this._getZlNormalBw(node, peg);
+                im.xm = node.name;
+            }
+            this._checkCustomDetail(im);
+            im.check = this.ctx.stage.im_gather && node.check;
+            this._generateTzGclBillsData(node, im);
+            this.ImData.push(im);
+            this._generateTzChangeData(node, im);
+        }
+    }
+
+    _generateZlPosData(node, lx) {
+        if (!lx.pos) {
+            lx.pos = [];
+        }
+        const posRange = this.pos.getLedgerPos(node.id);
+        if (!posRange) { return }
+        for (const p of posRange) {
+            if (!p.gather_qty || this.ctx.helper.checkZero(p.gather_qty)) { continue; }
+            let lp = this._.find(lx.pos, {name: p.name});
+            if (!lp) {
+                lp = {name: p.name};
+                lx.pos.push(lp);
+            }
+            lp.jl = this.ctx.helper.add(lp.jl, p.gather_qty);
+            lp.contract_jl = this.ctx.helper.add(lp.contract_jl, p.contract_qty);
+            lp.qc_jl = this.ctx.helper.add(lp.qc_jl, p.qc_qty);
+        }
+    }
+    _generateZlLeafXmjData(node, im) {
+        if (!im.leafXmjs) {
+            im.leafXmjs = [];
+        }
+        const leafXmj = this.billsTree.getLeafXmjParent(node);
+        if (!leafXmj) { return }
+        let lx = this._.find(im.leafXmjs, {lxid: leafXmj.id});
+        if (!lx) {
+            lx = {
+                lxid: leafXmj.id,
+                code: leafXmj.code,
+                name: leafXmj.name
+            };
+            im.leafXmjs.push(lx);
+        }
+        lx.jl = this.ctx.helper.add(lx.jl, node.gather_qty);
+        lx.contract_jl = this.ctx.helper.add(lx.contract_jl, node.contract_qty);
+        lx.qc_jl = this.ctx.helper.add(lx.qc_jl, node.qc_qty);
+        this._generateZlPosData(node, lx);
+    }
+    _generateZlChangeData(node, im) {
+        if (!im.changes) {
+            im.changes = [];
+        }
+        if ((!node.qc_qty || node.qc_qty === 0)) {
+            return;
+        }
+        const posRange = this.pos.getLedgerPos(node.id);
+        if (!posRange) {
+            for (const c of this.changes) {
+                if (c.lid === node.id && c.pid == -1 && c.qty && c.qty !== 0) {
+                    im.changes.push(c);
+                }
+            }
+        } else {
+            for (const p of posRange) {
+                if ((!p.qc_qty || p.qc_qty === 0)) {
+                    continue;
+                }
+                for (const c of this.changes) {
+                    if (c.lid === node.id && c.pid === p.id && c.qty && c.qty !== 0) {
+                        im.changes.push(c);
+                    }
+                }
+            }
+        }
+    }
+    /**
+     * 生成 总量控制 中间计量数据
+     * @param {Object} node - 生成中间计量表的节点
+     */
+    _generateZlImData (node) {
+        const self = this;
+        const nodeImData = [], posterity = this.billsTree.getPosterity(node);
+        for (const p of posterity) {
+            if (p.children && p.children.length > 0 ) { continue; }
+            if (!p.b_code || p.b_code === '') { continue }
+            if (this.ctx.helper.checkZero(p.contract_qty) && this.ctx.helper.checkZero(p.qc_qty) ) { continue; }
+            let im = nodeImData.find(function (d) {
+                return d.lid === node.id &&
+                    d.code === p.b_code && p.name === d.name && p.unit === d.unit &&
+                    this.ctx.helper.checkZero(self.ctx.helper.sub(p.unit_price, d.unit_price));
+            });
+            if (!im) {
+                const peg = this._getPegNode(node);
+                im = {
+                    id: this.ImData.length + 1,
+                    lid: node.id, pid: '', code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price,
+                    jl: 0, contract_jl: 0, qc_jl: 0,
+                    peg: peg ? this._getPegStr(peg.name) : ''
+                };
+                if (this.ctx.stage.im_gather && node.check) {
+                    im.check = true;
+                    im.bw = this._getZlGatherBw(node, peg);
+                    im.xm = '';
+                    im.drawing_code = this._getDrawingCode(node);
+                } else {
+                    im.check = false;
+                    im.bw = this._getZlNormalBw(node, peg);
+                    im.xm = node.name;
+                    im.drawing_code = this._getDrawingCode(p);
+                }
+                nodeImData.push(im);
+                this._checkCustomDetail(im);
+                this.ImData.push(im);
+            }
+            if (!this.ctx.stage.im_gather || !node.check) {
+                this._generateZlLeafXmjData(p, im, 'gather_qty');
+            }
+            this._generateZlChangeData(p, im);
+            im.jl = this.ctx.helper.add(im.jl, p.gather_qty);
+            im.contract_jl = this.ctx.helper.add(im.contract_jl, p.contract_qty);
+            im.qc_jl = this.ctx.helper.add(im.qc_jl, p.qc_qty);
+        }
+    }
+
+    _generateBwImData (node) {
+        const posterity = this.billsTree.getPosterity(node);
+        for (const p of posterity) {
+            if (p.children && p.children.length > 0 ) { continue; }
+            if (!p.b_code || p.b_code === '') { continue }
+            const peg = this._getPegNode(node);
+            const pPos = this.pos.getLedgerPos(p.id);
+            const bw = this._getZlNormalBw(node, peg);
+            if (pPos && pPos.length > 0) {
+                for (const pp of pPos) {
+                    if (this.ctx.helper.checkZero(pp.contract_qty) && this.ctx.helper.checkZero(pp.qc_qty)) { continue }
+                    const im = {
+                        id: this.ImData.length + 1,
+                        lid: node.id, code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price, pid: pp.id,
+                        jl: pp.gather_qty, contract_jl: pp.contract_qty, qc_jl: pp.qc_qty,
+                        bw: bw,
+                        peg: this._checkPeg(pp.name) ? this._getPegStr(pp.name) : (peg ? this._getPegStr(peg.name) : ''),
+                        xm: pp.name,
+                        drawing_code: pp.drawing_code,
+                        changes: [],
+                    };
+                    im.calc_memo = '本期计量:' + im.jl + ' ' + im.unit;
+                    this._checkCustomDetail(im);
+                    this.ImData.push(im);
+                    if (pp.qc_qty && pp.qc_qty !== 0) {
+                        for (const c of this.changes) {
+                            if (c.lid === p.id && c.pid === pp.id && c.qty && c.qty !== 0) {
+                                im.changes.push(c);
+                            }
+                        }
+                    }
+                }
+            } else {
+                if (this.ctx.helper.checkZero(p.contract_qty) && this.ctx.helper.checkZero(p.qc_qty)) { continue }
+
+                const im = {
+                    id: this.ImData.length + 1,
+                    lid: node.id, code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price, pid: '',
+                    jl: p.gather_qty, contract_jl: p.contract_qty, qc_jl: p.qc_qty,
+                    bw: bw,
+                    peg: peg ? this._getPegStr(peg.name) : '',
+                    xm: node.name,
+                    drawing_code: this._getDrawingCode(node),
+                    changes: [],
+                };
+                im.calc_memo = '本期计量:' + im.jl + ' ' + im.unit;
+                this._checkCustomDetail(im);
+                this.ImData.push(im);
+                if (p.qc_qty && p.qc_qty !== 0) {
+                    for (const c of this.changes) {
+                        if (c.lid === p.id && c.pid == -1 && c.qty && c.qty !== 0) {
+                            im.changes.push(c);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 递归 生成中间计量表
+     * @param {Array} nodes
+     */
+    _recursiveBuildImData (nodes) {
+        if (!nodes || nodes.length === 0) { return; }
+        for (const node of nodes) {
+            if (this.billsTree.isLeafXmj(node) || (this.ctx.stage.im_type !== imType.bw.value && this.ctx.stage.im_gather && node.check)) {
+                if (this.ctx.stage.im_type === imType.tz.value) {
+                    this._generateTzImData(node);
+                } else if (this.ctx.stage.im_type === imType.zl.value) {
+                    this._generateZlImData(node);
+                } else if (this.ctx.stage.im_type === imType.bw.value) {
+                    this._generateBwImData(node);
+                }
+            } else {
+                this._recursiveBuildImData(node.children);
+            }
+        }
+    }
+
+    // 生成中间计量数据
+    async buildImData () {
+        const self = this;
+        // 初始化
+        await this._loadMainData();
+        await this._loadRelaData();
+        if (this.ctx.stage.im_gather) {
+            this._checkGather();
+        }
+        // 生成数据
+        this._recursiveBuildImData(this.billsTree.children);
+        // 排序
+        if (this.ctx.stage.im_type !== imType.tz.value) {
+            this.ImData.sort(function (x, y) {
+                return self.ctx.helper.compareCode(x.code, y.code);
+            });
+        }
+        // 生成数据(需要缓存,并清理缓存)
+        const pre = (this.ctx.stage.im_pre && this.ctx.stage.im_pre !== '') ? this.ctx.stage.im_pre + splitChar : '';
+        for (const [i, im] of this.ImData.entries()) {
+            im.im_code = pre + this._getNumberFormat(this.ctx.stage.order, 2) + this.splitChar + this._getNumberFormat(i + 1, 3);
+            if (im.gclBills) {
+                for (const b of im.gclBills) {
+                    b.im_code = im.im_code;
+                }
+            }
+
+            this._getCalcMemo(im);
+            delete im.leafXmjs;
+            delete im.gclBills;
+
+            this._getChangeInfo(im);
+            delete im.changes;
+        }
+    }
+
+    // 生成中间计量汇总数据
+    async buildImGatherData () {
+        this._loadMainData();
+        if (stage.im_gather) {
+            this._checkGather();
+        }
+    }
+}
+
+module.exports = StageIm;

+ 6 - 3
app/public/js/gcl_gather.js

@@ -13,8 +13,10 @@ const gclGatherModel = (function () {
     // 需要汇总计算的字段
     const ledgerGatherFields = ['quantity', 'total_price', 'deal_qty', 'deal_tp',
         'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp',
-        'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp'];
-    const posGatherFields = ['quantity', 'contract_qty', 'qc_qty', 'gather_qty', 'pre_contract_qty', 'pre_qc_qty'];
+        'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp',
+        'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp'];
+    const posGatherFields = ['quantity', 'contract_qty', 'qc_qty', 'gather_qty',
+        'pre_contract_qty', 'pre_qc_qty', 'pre_gather_qty', 'end_contract_qty', 'end_qc_qty', 'end_gather_qty'];
     // 初始化 清单树
     const gsTreeSetting = {
         id: 'ledger_id',
@@ -57,7 +59,6 @@ const gclGatherModel = (function () {
         if (preStage) {
             gsTree.loadPreStageData(preStage);
         }
-        // todo 加载变更令数据
     }
 
     function loadPosData(pos, curPos, prePos) {
@@ -332,6 +333,8 @@ const gclGatherModel = (function () {
             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);
+                xmj.end_contract_qty = ZhCalc.add(xmj.pre_contract_qty, xmj.contract_qty);
+                xmj.end_qc_qty = ZhCalc.add(xmj.pre_qc_qty, xmj.qc_qty);
                 xmj.end_gather_qty = ZhCalc.add(xmj.pre_gather_qty, xmj.gather_qty);
                 xmj.end_final_qty = ZhCalc.add(xmj.end_qc_qty, xmj.quantity);
                 xmj.end_gather_percent = ZhCalc.mul(ZhCalc.div(xmj.end_gather_qty, xmj.end_final_qty), 100, 2);

+ 7 - 1
app/public/js/ledger.js

@@ -576,7 +576,7 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = pasteData[iRow][iCol];
+                    const value = trimInvalidChar(pasteData[iRow][iCol]);
                     const lPos = pos.getLedgerPos(node.id);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
                         if (!bParentHint) bParentHint = true;
@@ -1369,6 +1369,8 @@ $(document).ready(function() {
             const expr = $(this);
             const posSheet = posSpread.getActiveSheet();
             const select = SpreadJsObj.getSelectObject(posSheet);
+            if (select) return;
+
             const field = expr.attr('field'), orgValue = expr.attr('org'), newValue = expr.val(), row = trimInvalidChar(expr.attr('row'));
             if (orgValue === newValue || (!orgValue && newValue == '')) { return; }
 
@@ -1666,6 +1668,8 @@ $(document).ready(function() {
                         }
                     }
                     treeOperationObj.refreshOperationValid(mainSheet);
+                    ledgerSpread.focus();
+                    posOperationObj.loadCurPosData();
                 });
             });
             this.pathTree = createNewPathTree('base', this.treeSetting);
@@ -1731,6 +1735,8 @@ $(document).ready(function() {
                     const sel = mainSheet.getSelections()[0];
                     mainSheet.setSelection(refreshData.create[0].index, sel.col, sel.rowCount, sel.colCount);
                     treeOperationObj.refreshOperationValid(mainSheet);
+                    ledgerSpread.focus();
+                    posOperationObj.loadCurPosData();
                 });
             });
             }

+ 110 - 0
app/public/js/ledger_gather.js

@@ -0,0 +1,110 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const gclSpread = SpreadJsObj.createNewSpread($('#gcl-spread')[0]);
+    SpreadJsObj.initSheet(gclSpread.getActiveSheet(), {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '签约清单|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_bills_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_bills_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    });
+    const gclSheet = gclSpread.getActiveSheet();
+    const leafXmjSpread = SpreadJsObj.createNewSpread($('#leaf-xmj-spread')[0]);
+    SpreadJsObj.initSheet(leafXmjSpread.getActiveSheet(), {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 120, formatter: '@'},
+            {title: '台账数量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '单位工程', colSpan: '1', rowSpan: '1', field: 'dwgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '分部工程', colSpan: '1', rowSpan: '1', field: 'fbgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '分项工程', colSpan: '1', rowSpan: '1', field: 'fxgc', hAlign: 0, width: 100, formatter: '@'},
+            {title: '细目', colSpan: '1', rowSpan: '1', field: 'jldy', hAlign: 0, width: 100, formatter: '@'},
+            {title: '计量单元', colSpan: '1', rowSpan: '1', field: 'bwmx', hAlign: 0, width: 100, formatter: '@'},
+            {title: '图册号', colSpan: '1', rowSpan: '1', field: 'drawing_code', hAlign: 0, width: 120, formatter: '@'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    });
+    const leafXmjSheet = leafXmjSpread.getActiveSheet();
+
+    let gclGatherData;
+    // 获取项目节数据
+    function loadLeafXmjData(iGclRow) {
+        const gcl = gclGatherData[iGclRow];
+        if (gcl) {
+            SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, gcl.leafXmjs);
+        } else {
+            SpreadJsObj.loadSheetData(leafXmjSheet, SpreadJsObj.DataType.Data, []);
+        }
+    }
+    // 切换清单行,读取所属项目节数据
+    gclSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        const iNewRow = info.newSelections[0].row;
+        if (!info.oldSelections || iNewRow !== info.oldSelections[0].row) {
+            loadLeafXmjData(iNewRow);
+        }
+    });
+
+    postData(window.location.pathname + '/load', {}, function (data) {
+        gclGatherModel.loadLedgerData(data.bills);
+        gclGatherModel.loadPosData(data.pos);
+        gclGatherModel.loadDealBillsData(data.dealBills);
+        gclGatherData = gclGatherModel.gatherGclData();
+        SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
+        loadLeafXmjData(0);
+    }, null, true);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            gclSpread.refresh();
+            leafXmjSpread.refresh();
+        }
+    });
+    $.divResizer({
+        select: '#main-resize',
+        callback: function () {
+            gclSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-40);
+            leafXmjSpread.refresh();
+        }
+    });
+});

+ 3 - 0
app/public/js/ledger_search.js

@@ -55,6 +55,7 @@
                     const pos = resultArr[0];
                     if (pos.index !== curRow) {
                         sheet.setSelection(pos.index, sel ? sel.col : 0, 1, 1);
+                        sheet.getParent().focus();
                         sheet.showRow(pos.index, spreadNS.VerticalPosition.center);
                     }
                 }
@@ -69,6 +70,7 @@
                     if (!next) next = resultArr[0];
                     if (next.index !== curRow) {
                         sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
+                        sheet.getParent().focus();
                         sheet.showRow(next.index, spreadNS.VerticalPosition.center);
                     }
                 }
@@ -83,6 +85,7 @@
                     if (!next) next = resultArr[resultArr.length - 1];
                     if (next.index !== curRow) {
                         sheet.setSelection(next.index, sel ? sel.col : 0, 1, 1);
+                        sheet.getParent().focus();
                         sheet.showRow(next.index, spreadNS.VerticalPosition.center);
                     }
                 }

+ 59 - 1
app/public/js/revise.js

@@ -611,6 +611,59 @@ $(document).ready(() => {
                 return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
             },
             items: {
+                'create': {
+                    name: '新增',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        billsTreeSpreadObj.baseOpr(billsSheet, 'add');
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = billsSheet;
+                        const selection = sheet.getSelections();
+                        const sel = selection ? selection[0] : sheet.getSelections()[0];
+                        const row = sel ? sel.row : -1;
+                        const tree = sheet.zh_tree;
+                        if (!tree) return true;
+                        const first = sheet.zh_tree.nodes[row];
+                        const valid = !sheet.zh_setting.readOnly;
+                        return !(valid && first && first.level > 1);
+                    }
+                },
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        billsTreeSpreadObj.baseOpr(billsSheet, 'delete');
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = billsSheet;
+                        const selection = sheet.getSelections();
+                        const sel = selection ? selection[0] : sheet.getSelections()[0];
+                        const row = sel ? sel.row : -1;
+                        const tree = sheet.zh_tree;
+                        if (!tree) return true;
+                        const first = sheet.zh_tree.nodes[row];
+                        let last = first, sameParent = true, nodeUsed = first.used;
+                        if (sel.rowCount > 1 && first) {
+                            for (let r = 1; r < sel.rowCount; r++) {
+                                const rNode = tree.nodes[sel.row + r];
+                                if (!rNode) {
+                                    sameParent = false;
+                                    break;
+                                }
+                                nodeUsed = nodeUsed || rNode.used;
+                                if (rNode.level > first.level) continue;
+                                if ((rNode.level < first.level) || (rNode.level === first.level && rNode.pid !== first.pid)) {
+                                    sameParent = false;
+                                    break;
+                                }
+                                last = rNode;
+                            }
+                        }
+                        const valid = !sheet.zh_setting.readOnly;
+                        return !(valid && first && sameParent && !(first.level === 1 && first.node_type) && !nodeUsed);
+                    }
+                },
                 'batchInsertBillsPos': {
                     name: '批量插入节点-部位',
                     icon: 'fa-sign-in',
@@ -1065,6 +1118,8 @@ $(document).ready(() => {
                             }
                         }
                         billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                        billsSpread.focus();
+                        posSpreadObj.loadCurPosData();
                     });
                 });
             }
@@ -1134,6 +1189,8 @@ $(document).ready(() => {
                             mainSheet.setSelection(mainTree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
                         }
                         billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                        billsSpread.focus();
+                        posSpreadObj.loadCurPosData();
                     });
                 });
             }
@@ -1232,11 +1289,12 @@ $(document).ready(() => {
                     qdSheet.setRowCount(count);
                     qdSheet.getCell(sel.row + 1, 0, GC.Spread.Sheets.SheetArea.rowHeader).text('节点' + count);
 
-                    const colCount = posSheet.getColumnCount() + 1
+                    const colCount = posSheet.getColumnCount() + 1;
                     posSheet.setColumnCount(colCount);
                     posSheet.getCell(0, colCount - 1, GC.Spread.Sheets.SheetArea.colHeader).text('数量' + count);
                 }
                 qdSheet.setSelection(sel.row + 1, sel.col, 1, 1);
+                qdSheet.getParent().focus();
             });
 
             this.obj.bind('shown.bs.modal', function () {

+ 1 - 0
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -713,6 +713,7 @@ const SpreadJsObj = {
         const index = tree.nodes.indexOf(node);
         const sels = sheet.getSelections();
         sheet.setSelection(index, sels[0].col, 1, 1);
+        sheet.getParent().focus();
         sheet.showRow(index, spreadNS.VerticalPosition.center);
     },
     saveTopAndSelect: function (sheet, cacheKey) {

+ 6 - 5
app/public/js/stage.js

@@ -569,7 +569,7 @@ $(document).ready(() => {
                         id: node.id
                     };
                     updateData.dgn[col.field] = newValue;
-                } else {
+                } else if (col.field !== 'is_tp') {
                     updateData.stage = {
                         lid: node.id
                     };
@@ -844,8 +844,7 @@ $(document).ready(() => {
                     info.cancel = !node.is_tp;
                     break;
                 case 'is_tp':
-                    const posRange = stagePos.getLedgerPos(node.id);
-                    info.cancel = node.pre_used || node.unit !== '总额' || (posRange && posRange.length > 0);
+                    info.cancel = false;
                     break;
             }
         },
@@ -2366,13 +2365,15 @@ $(document).ready(() => {
                     updateData.img = canvas.toDataURL('image/jpeg');
                     updateData.imgInfo = itemInfo;
                     postData(window.location.pathname + '/detail/merge-img', updateData, function (result) {
-                        _.assign(data, result);
+                        data.calc_img = result.calc_img;
+                        data.calc_img_org = result.calc_img_org;
                         self.reLoadDetailData();
                         $('#edit-img').modal('hide');
                     });
                 } else if (data.calc_img) {
                     postData(window.location.pathname + '/detail/merge-img', {updateType: 'clear', lid: data.lid, pid: data.pid, uuid: data.uuid}, function (result) {
-                        _.assign(data, result);
+                        data.calc_img = result.calc_img;
+                        data.calc_img_org = result.calc_img_org;
                         self.reLoadDetailData();
                         $('#edit-img').modal('hide');
                     });

+ 32 - 17
app/public/js/stage_im.js

@@ -198,18 +198,18 @@ const stageIm = (function () {
             const subPeg1 = getNodeByLevel(node, peg.level + 1);
             const subPeg2 = getNodeByLevel(node, peg.level + 2);
             let result = peg.name;
-            if (subPeg1 && subPeg1.id !== peg.id) {
+            if (subPeg1 && subPeg1.id !== peg.id && subPeg1.id !== node.id) {
                 result = result + '-' + subPeg1.name;
-                if (subPeg2 && subPeg2.id !== subPeg1.id) {
+                if (subPeg2 && subPeg2.id !== subPeg1.id && subPeg2.id !== node.id) {
                     result = result + '-' + subPeg2.name;
                 }
             }
             return result;
         } else {
-            if (node.level === 2) {
+            if (node.level === 2 || node.level === 3) {
                 return node.name;
-            } else if (node.level >= 3) {
-                let parent = node, result = parent.name;
+            } else if (node.level >= 4) {
+                let parent = gsTree.getParent(node), result = parent.name;
                 while (parent.level > 3 && parent) {
                     parent = getNodeByLevel(node, parent.level - 1);
                     result = parent.name + '-' + result;
@@ -254,7 +254,7 @@ const stageIm = (function () {
         });
         if (cd) {
             _.assignInWith(im, cd, function (oV, sV, key) {
-                return imFields.indexOf(key) > -1 && sV !== undefined && sV !== null ? sV : oV;
+                return (imFields.indexOf(key) > -1 && sV !== undefined && sV !== null) ? sV : oV;
             });
         }
     }
@@ -405,7 +405,7 @@ const stageIm = (function () {
             const im = {
                 lid: node.id, pid: '', code: node.code,
                 jl: node.gather_tp, contract_jl: node.contract_tp, qc_jl: node.qc_tp,
-                im_code: getNewImCode(),
+                //im_code: getNewImCode(),
                 peg: peg ? getPegStr(peg.name) : '', drawing_code: getDrawingCode(node),
             };
             if (stage.im_gather && node.check) {
@@ -508,15 +508,16 @@ const stageIm = (function () {
                 im = {
                     lid: node.id, pid: '', code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price,
                     jl: 0, contract_jl: 0, qc_jl: 0,
-                    im_code: getNewImCode(),
-                    peg: peg ? getPegStr(peg.name) : '', drawing_code: getDrawingCode(node),
+                    peg: peg ? getPegStr(peg.name) : ''
                 };
                 if (stage.im_gather && node.check) {
                     im.bw = getZlGatherBw(node, peg);
                     im.xm = '';
+                    im.drawing_code = getDrawingCode(node);
                 } else {
                     im.bw = getZlNormalBw(node, peg);
                     im.xm = node.name;
+                    im.drawing_code = getDrawingCode(p);
                 }
                 nodeImData.push(im);
                 checkCustomDetail(im);
@@ -546,7 +547,7 @@ const stageIm = (function () {
                     const im = {
                         lid: node.id, code: p.b_code, name: p.name, unit: p.unit, unit_price: p.unit_price, pid: pp.id,
                         jl: pp.gather_qty, contract_jl: pp.contract_qty, qc_jl: pp.qc_qty,
-                        im_code: getNewImCode(),
+                        //im_code: getNewImCode(),
                         bw: bw,
                         peg: CheckPeg(pp.name) ? getPegStr(pp.name) : (peg ? getPegStr(peg.name) : ''),
                         xm: pp.name,
@@ -625,15 +626,16 @@ const stageIm = (function () {
         }
         // 生成数据
         recursiveBuildImData(gsTree.children);
-        for (const im of ImData) {
-            getCalcMemo(im);
-            getChangeInfo(im);
-        }
         if (stage.im_type !== imType.tz.value) {
             ImData.sort(function (x, y) {
                 return compareCode(x.code, y.code);
             });
         }
+        for (const [i, im] of ImData.entries()) {
+            getCalcMemo(im);
+            getChangeInfo(im);
+            im.im_code = pre + getNumberFormat(stage.order, 2) + splitChar + getNumberFormat(i + 1, 3)
+        }
         return ImData;
     }
 
@@ -648,7 +650,14 @@ const stageIm = (function () {
             }
             let imData = _.find(ImData, {lid: d.lid, uuid: d.uuid});
             if (!imData) {
-                imData = _.find(ImData, {lid: d.lid, code: d.code, name: d.name, unit: d.unit, unit_price: d.unit_price});
+                imData = ImData.find(function (im) {
+                    return im.lid === d.lid &&
+                        (!im.code || im.code === d.code) &&
+                        (!im.name || im.name === d.name) &&
+                        (!im.unit || im.unit === d.unit) &&
+                        checkZero(ZhCalc.sub(im.unit_price, d.unit_price)) &&
+                        (!im.pid || im.pid === d.pid);
+                });
             }
             if (imData) {
                 _.assignInWith(imData, d, function (oV, sV, key) {
@@ -670,8 +679,14 @@ const stageIm = (function () {
     }
     function loadUpdatePosData(data) {
         if (data.pos) {
-            gsPos.updateDatas(data.pos.pos);
-            gsPos.loadCurStageData(data.pos.curStageData);
+            if (data.pos.pos && data.pos.pos.length > 0 && typeof data.pos.pos[0] === 'string') {
+                gsPos.removeDatas(data.pos.pos);
+            } else {
+                gsPos.updateDatas(data.pos.pos);
+            }
+            if (data.pos.curStageData) {
+                gsPos.loadCurStageData(data.pos.curStageData);
+            }
         }
         gsTree.loadPostStageData(data.ledger);
         return buildImData();

+ 20 - 11
app/public/js/tender_list.js

@@ -190,7 +190,7 @@ function initTenderTree () {
             }
         }
     }
-    function getCategoryNode(category, value, parent) {
+    function getCategoryNode(category, value, parent, i = null) {
         const array = parent ?  parent.children : tenderTree;
         let cate = findCategoryNode(category.id, value, array);
         if (!cate) {
@@ -201,7 +201,7 @@ function initTenderTree () {
                 vid: value,
                 name: cateValue.value,
                 children: [],
-                level: category.level,
+                level: i ? i : category.level,
                 sort_id: ++parentId,
             };
             array.push(cate);
@@ -210,11 +210,17 @@ function initTenderTree () {
     }
     function loadTenderCategory (tender) {
         let tenderCategory = null;
-        for (const lc of levelCategory) {
+        for (const [index,lc] of levelCategory.entries()) {
             const tenderCate = findNode('cid', lc.id, tender.category);
             if (tenderCate) {
                 tenderCategory = getCategoryNode(lc, tenderCate.value, tenderCategory);
             } else {
+                if (index === 0 && tender.category) {
+                    for (const [i,c] of tender.category.entries()) {
+                        const cate = findNode('id', c.cid, category);
+                        tenderCategory = getCategoryNode(cate, c.value, tenderCategory, i+1);
+                    }
+                }
                 return tenderCategory;
             }
         }
@@ -235,6 +241,7 @@ function initTenderTree () {
     for (const t of tenders) {
         calculateTender(t);
         t.valid = true;
+        delete t.level;
         if (t.category && levelCategory.length > 0) {
             const parent = loadTenderCategory(t);
             if (parent) {
@@ -247,6 +254,7 @@ function initTenderTree () {
             tenderTree.push(t);
         }
     }
+    console.log(tenderTree);
 }
 function recursiveGetTenderNodeHtml (node, arr, pid) {
     const html = [];
@@ -254,7 +262,7 @@ function recursiveGetTenderNodeHtml (node, arr, pid) {
     // 名称
     html.push('<td class="in-' + node.level + '">');
     if (node.cid) {
-        html.push('<span class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span></i> <i class="fa fa-folder-o"></i> ', node.name);
+        html.push('<span class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
     } else {
         html.push('<span class="text-muted mr-2">');
         html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
@@ -337,7 +345,6 @@ $(document).ready(() => {
     $('.modal-body', '#add-bd').append(getCategoryHtml());
     // 初始化标段树结构
     initTenderTree();
-    console.log(tenderTree);
     $('.c-body').html(getTenderTreeHtml());
     bindTenderUrl();
     // 分类
@@ -380,13 +387,15 @@ $(document).ready(() => {
             return;
         }
         for (const c of category) {
-            const cate = {cid: c.id};
-            // if (c.type === categoryType.key.dropDown) {
+            if (parseInt($('select', '[cate-id=' + c.id + ']').val()) !== 0) {
+                const cate = {cid: c.id};
+                // if (c.type === categoryType.key.dropDown) {
                 cate.value = parseInt($('select', '[cate-id=' + c.id + ']').val());
-            // } else if (c.type === categoryType.key.radio) {
-            //     cate.value = parseInt($('input:checked', '[cate-id=' + c.id + ']').val());
-            // }
-            data.category.push(cate);
+                // } else if (c.type === categoryType.key.radio) {
+                //     cate.value = parseInt($('input:checked', '[cate-id=' + c.id + ']').val());
+                // }
+                data.category.push(cate);
+            }
         }
         $('#hide-all').show();
         postData('/list/add', data, function (result) {

+ 90 - 22
app/public/js/tender_list_manage.js

@@ -45,6 +45,7 @@ const levelTreeSetting = {
 };
 const levelNodes =[];
 const tenderTree = [];
+let parentId = 0;
 function createTree() {
     const zTree = $.fn.zTree.getZTreeObj('treeLevel');
     if (zTree) {
@@ -188,7 +189,7 @@ function initTenderTree () {
             }
         }
     }
-    function getCategoryNode(category, value, parent) {
+    function getCategoryNode(category, value, parent, i = null) {
         const array = parent ?  parent.children : tenderTree;
         let cate = findCategoryNode(category.id, value, array);
         if (!cate) {
@@ -199,19 +200,27 @@ function initTenderTree () {
                 vid: value,
                 name: cateValue.value,
                 children: [],
-                level: category.level,
+                level: i ? i : category.level,
+                sort_id: ++parentId,
             };
             array.push(cate);
         }
         return cate;
     }
+
     function loadTenderCategory (tender) {
         let tenderCategory = null;
-        for (const lc of levelCategory) {
+        for (const [index,lc] of levelCategory.entries()) {
             const tenderCate = findNode('cid', lc.id, tender.category);
             if (tenderCate) {
                 tenderCategory = getCategoryNode(lc, tenderCate.value, tenderCategory);
             } else {
+                if (index === 0 && tender.category) {
+                    for (const [i,c] of tender.category.entries()) {
+                        const cate = findNode('id', c.cid, category);
+                        tenderCategory = getCategoryNode(cate, c.value, tenderCategory, i+1);
+                    }
+                }
                 return tenderCategory;
             }
         }
@@ -220,6 +229,7 @@ function initTenderTree () {
     tenderTree.splice(0, tenderTree.length);
     for (const t of tenders) {
         t.valid = true;
+        delete t.level;
         if (t.category && levelCategory.length > 0) {
             const parent = loadTenderCategory(t);
             if (parent) {
@@ -233,13 +243,13 @@ function initTenderTree () {
         }
     }
 }
-function recursiveGetTenderNodeHtml (node, arr) {
+function recursiveGetTenderNodeHtml (node, arr, pid) {
     const html = [];
-    html.push('<tr>');
+    html.push('<tr pid="' + pid + '">');
     // 名称
     html.push('<td class="in-' + node.level + '">');
     if (node.cid) {
-        html.push('<i class="fa fa-folder-o"></i> ', node.name);
+        html.push('<span class="fold-switch mr-1" title="收起" cid="'+ node.sort_id +'"><i class="fa fa-minus-square-o"></i></span> <i class="fa fa-folder-o"></i> ', node.name);
     } else {
         html.push('<span class="text-muted mr-2">');
         html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
@@ -260,7 +270,7 @@ function recursiveGetTenderNodeHtml (node, arr) {
     // 管理
     html.push('<td tid="' + node.id + '">');
     if (!node.cid) {
-        html.push('<a href="#javascript: void(0)" name="edit" class="btn btn-outline-primary btn-sm">编辑</a>');
+        html.push('<a href="javascript: void(0)" name="edit" class="btn btn-outline-primary btn-sm">编辑</a>');
         if (node.lastStage === null || node.lastStage === undefined) {
             html.push('<a href="javascript: void(0)" name="del" class="btn btn-outline-danger btn-sm ml-1">删除</a>');
         } else {
@@ -271,7 +281,7 @@ function recursiveGetTenderNodeHtml (node, arr) {
     html.push('</tr>');
     if (node.children) {
         for (const c of node.children) {
-            html.push(recursiveGetTenderNodeHtml(c, node.children));
+            html.push(recursiveGetTenderNodeHtml(c, node.children, node.sort_id));
         }
     }
     return html.join('');
@@ -294,7 +304,7 @@ function getTenderTreeHtml () {
         html.push('<table class="table table-hover table-bordered">');
         html.push(getTenderTreeHeaderHtml());
         for (const t of tenderTree) {
-            html.push(recursiveGetTenderNodeHtml(t, tenderTree));
+            html.push(recursiveGetTenderNodeHtml(t, tenderTree, ''));
         }
         html.push('</table>');
         return html.join('');
@@ -304,7 +314,7 @@ function getTenderTreeHtml () {
 }
 function bindTenderUrl() {
     // 打开标段
-    $('a[name=name]', '.c-body').bind('click', function () {
+    $('body').on('click', '.c-body a[name=name]', function () {
         const tenderId = parseInt($(this).attr('id'));
         const tender = _.find(tenders, function (t) {
             return t.id === tenderId;
@@ -319,7 +329,7 @@ function bindTenderUrl() {
         }
     });
     // 编辑
-    $('a[name=edit]', '.c-body').on('click', function () {
+    $('body').on('click', '.c-body a[name=edit]', function () {
         const tid = parseInt($(this).parent().attr('tid'));
         const tender = _.find(tenders, {id: tid});
         $('[name=name]', '#edit-bd').val(tender.name);
@@ -332,7 +342,7 @@ function bindTenderUrl() {
         $('#edit-bd').modal('show');
     });
     // 删除
-    $('a[name=del]', '.c-body').bind('click', function () {
+    $('body').on('click', '.c-body a[name=del]', function () {
         $('#del-bd-ok').attr('tid', $(this).parent().attr('tid'));
         $('#del-bd').modal('show');
     });
@@ -349,6 +359,8 @@ $(document).ready(() => {
     initTenderTree();
     $('.c-body').html(getTenderTreeHtml());
     bindTenderUrl();
+    console.log(tenderTree);
+    console.log(category);
     // 分类
     $('#cate-set').on('show.bs.modal', function () {
         createTree();
@@ -394,14 +406,18 @@ $(document).ready(() => {
             return;
         }
         for (const c of category) {
-            const cate = {cid: c.id};
             const cateObj = $('[cate-id=' + c.id + ']', '#add-bd');
-            if (c.type === categoryType.key.dropDown) {
+            if (parseInt($('select', cateObj).val()) !== 0) {
+                const cate = {cid: c.id};
                 cate.value = parseInt($('select', cateObj).val());
-            } else if (c.type === categoryType.key.radio) {
-                cate.value = parseInt($('input:checked', cateObj).val());
+                data.category.push(cate);
             }
-            data.category.push(cate);
+            // if (c.type === categoryType.key.dropDown) {
+            //     cate.value = parseInt($('select', cateObj).val());
+            // } else if (c.type === categoryType.key.radio) {
+            //     cate.value = parseInt($('input:checked', cateObj).val());
+            // }
+
         }
         postData('/list/add', data, function (result) {
             tenders.push(result);
@@ -424,14 +440,18 @@ $(document).ready(() => {
             return;
         }
         for (const c of category) {
-            const cate = {cid: c.id};
             const cateObj = $('[cate-id=' + c.id + ']', '#edit-bd');
-            if (c.type === categoryType.key.dropDown) {
+            if (parseInt($('select', cateObj).val()) !== 0) {
+                const cate = {cid: c.id};
                 cate.value = parseInt($('select', cateObj).val());
-            } else if (c.type === categoryType.key.radio) {
-                cate.value = parseInt($('input:checked', cateObj).val());
+                data.category.push(cate);
             }
-            data.category.push(cate);
+            // if (c.type === categoryType.key.dropDown) {
+            //     cate.value = parseInt($('select', cateObj).val());
+            // } else if (c.type === categoryType.key.radio) {
+            //     cate.value = parseInt($('input:checked', cateObj).val());
+            // }
+            // data.category.push(cate);
         }
         postData('/list/update', data, function (result) {
             const tender = _.find(tenders, {id: result.id});
@@ -484,4 +504,52 @@ $(document).ready(() => {
             $('#del-bd').modal('hide');
         }
     });
+
+    // 展开和收起
+    $('body').on('click', '.fold-switch', function () {
+        if ($(this).children('i').hasClass('fa-minus-square-o')) {
+            $(this).children('i').removeClass('fa-minus-square-o').addClass('fa-plus-square-o');
+            $(this).attr('title', '展开');
+            const cid = $(this).attr('cid');
+            const node = findTenderTreeNode(parseInt(cid), tenderTree);
+            doTrStatus(returnItem, 'hide');
+        } else {
+            $(this).children('i').removeClass('fa-plus-square-o').addClass('fa-minus-square-o');
+            $(this).attr('title', '收起');
+            const cid = $(this).attr('cid');
+            const node = findTenderTreeNode(parseInt(cid), tenderTree);
+            doTrStatus(returnItem, 'show');
+        }
+    })
 });
+
+function doTrStatus(node, status) {
+    if (status === 'show') {
+        $('.c-body').find('tr[pid="'+ node.sort_id +'"]').show();
+        $('.c-body').find('tr[pid="'+ node.sort_id +'"] .fold-switch').attr('title', '收起');
+        $('.c-body').find('tr[pid="'+ node.sort_id +'"] .fold-switch i').removeClass('fa-plus-square-o').removeClass('fa-minus-square-o').addClass('fa-minus-square-o');
+    } else {
+        $('.c-body').find('tr[pid="'+ node.sort_id +'"]').hide();
+        $('.c-body').find('tr[pid="'+ node.sort_id +'"] .fold-switch').attr('title', '展开');
+        $('.c-body').find('tr[pid="'+ node.sort_id +'"] .fold-switch i').removeClass('fa-minus-square-o').removeClass('fa-plus-square-o').addClass('fa-plus-square-o');
+
+    }
+    // 判断是否还有一层
+    if (node.children) {
+        for (const c of node.children) {
+            doTrStatus(c, status);
+        }
+    }
+}
+let returnItem;
+const findTenderTreeNode = function(sortId, tree) {
+    tree.forEach((item) => {
+        if (item.sort_id !== undefined && item.sort_id === sortId) {
+            returnItem = item;
+            return item;
+        } else if (item.children && item.children.length > 0) {
+            findTenderTreeNode(sortId, item.children);
+        }
+    });
+}
+

+ 3 - 0
app/router.js

@@ -103,6 +103,9 @@ module.exports = app => {
     app.post('/tender/:id/ledger/audit/delete', sessionAuth, tenderCheck, 'ledgerAuditController.remove');
     app.post('/tender/:id/ledger/audit/start', sessionAuth, tenderCheck, 'ledgerAuditController.start');
     app.post('/tender/:id/ledger/audit/check', sessionAuth, tenderCheck, 'ledgerAuditController.check');
+    // 台账对比
+    app.get('/tender/:id/ledger/gather', sessionAuth, tenderCheck, 'ledgerController.gather');
+    app.post('/tender/:id/ledger/gather/load', sessionAuth, tenderCheck, 'ledgerController.loadGatherData');
     // 台账修订
     app.get('/tender/:id/revise', sessionAuth, tenderCheck, 'reviseController.index');
     app.post('/tender/:id/revise/add', sessionAuth, tenderCheck, 'reviseController.add');

+ 96 - 21
app/service/pos.js

@@ -98,16 +98,75 @@ module.exports = app => {
             data.add_user = this.ctx.session.sessionUser.accountId;
         }
 
+        async _getUpdateBills(data) {
+            const datas = data instanceof Array ? data : [data];
+            const bills = await this.ctx.service.ledger.getDataById(datas[0].lid);
+            const billsPos = await this.getAllDataByCondition({where: {lid: bills.id} });
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const updateBills = {id: bills.id};
+            for (const bp of billsPos) {
+                updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, bp.sgfh_qty);
+                updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, bp.sjcl_qty);
+                updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, bp.qtcl_qty);
+                updateBills.quantity = this.ctx.helper.add(updateBills.quantity, bp.quantity);
+            }
+            for (const d of datas) {
+                if (d.sgfh_qty) {
+                    d.sgfh_qty = this.ctx.helper.round(d.sgfh_qty, precision.value);
+                    updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, d.sgfh_qty);
+                }
+                if (d.sjcl_qty) {
+                    d.sjcl_qty = this.ctx.helper.round(d.sjcl_qty, precision.value);
+                    updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, d.sjcl_qty);
+                }
+                if (d.qtcl_qty) {
+                    d.qtcl_qty = this.ctx.helper.round(d.qtcl_qty, precision.value);
+                    updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, d.qtcl_qty);
+                }
+                d.quantity = this.ctx.helper.sum([d.sgfh_qty, d.qtcl_qty, d.sjcl_qty]);
+                updateBills.quantity = this.ctx.helper.add(updateBills.quantity, d.quantity);
+            }
+            const info = this.ctx.tender.info;
+            updateBills.sgfh_tp = this.ctx.helper.mul(updateBills.sgfh_qty, bills.unit_price, info.decimal.tp);
+            updateBills.sjcl_tp = this.ctx.helper.mul(updateBills.sjcl_qty, bills.unit_price, info.decimal.tp);
+            updateBills.qtcl_tp = this.ctx.helper.mul(updateBills.qtcl_qty, bills.unit_price, info.decimal.tp);
+            updateBills.total_price = this.ctx.helper.mul(updateBills.quantity, bills.unit_price, info.decimal.tp);
+            return updateBills;
+        }
+
         async _addPosData(tid, data) {
-            if (data.updateData instanceof Array) {
-                for (const d of data.updateData) {
+            let updateBills = null;
+            if (data instanceof Array) {
+                for (const d of data) {
                     this._completeInsertPosData(tid, d);
                 }
+                if (data[0].sgfh_qty !== undefined || data[0].sjcl_qty !== undefined || data[0].qtcl_qty !== undefined) {
+                    updateBills = await this._getUpdateBills(data);
+                }
             } else {
                 this._completeInsertPosData(tid, data);
+                if (data.sgfh_qty !== undefined || data.sjcl_qty !== undefined || data.qtcl_qty !== undefined) {
+                    updateBills = await this._getUpdateBills(data);
+                }
+            }
+            if (updateBills) {
+                const transaction = await this.db.beginTransaction();
+                try {
+                    await transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                    updateBills.ledger_id = bills.ledger_id;
+                    await transaction.commit();
+                } catch (err) {
+                    await transaction.rollback();
+                    throw err;
+                }
+                return {
+                    ledger: { update: [updateBills] },
+                    pos: data,
+                }
+            } else {
+                await this.db.insert(this.tableName, data);
+                return { pos: data }
             }
-            await this.db.insert(this.tableName, data);
-            return { pos: data }
         }
 
         async _updatePosData(tid, data) {
@@ -150,9 +209,7 @@ module.exports = app => {
 
                 const transaction = await this.db.beginTransaction();
                 try {
-                    console.log(data);
                     transaction.update(this.tableName, data);
-                    console.log(updateBills);
                     transaction.update(this.ctx.service.ledger.tableName, updateBills);
                     await transaction.commit();
                     updateBills.ledger_id = bills.ledger_id;
@@ -216,7 +273,7 @@ module.exports = app => {
                 for (const d of data) {
                     transaction.update(this.tableName, d);
                 }
-                transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                await transaction.update(this.ctx.service.ledger.tableName, updateBills);
                 await transaction.commit();
                 updateBills.ledger_id = bills.ledger_id;
                 return {
@@ -297,53 +354,71 @@ module.exports = app => {
             }
 
             const transaction = await this.db.beginTransaction();
-            const result = { ledger: {}, pos: null }, updateBills = [];
+            const result = { ledger: {}, pos: null };
             const orgPos = await this.getPosData({tid: tid, id: this._.map(data, 'id')});
-            let bills = null, precision = null;
+
+            const bills = await this.ctx.service.ledger.getDataById(data[0].lid);
+            const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+            const updateBills = {id: bills.id};
+            const billsPos = await this.getAllDataByCondition({where: {lid: bills.id} });
+            for (const bp of billsPos) {
+                const d = data.find(function (x) {
+                    return bp.id ? x.id === bp.id : false;
+                });
+                if (d) continue;
+                updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, bp.sgfh_qty);
+                updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, bp.sjcl_qty);
+                updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, bp.qtcl_qty);
+                updateBills.quantity = this.ctx.helper.add(updateBills.quantity, bp.quantity);
+            }
+
             try {
                 for (const d of data) {
                     const op = d.id ? this._.find(orgPos, {id: d.id}) : null;
                     if (d.sgfh_qty || d.sjcl_qty || d.qtcl_qty) {
-                        if (!bills || bills.id !== d.lid) {
-                            bills = await this.ctx.service.ledger.getDataById(d.lid);
-                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                            updateBills.push(bills);
-                        }
                         if (d.sgfh_qty !== undefined) {
                             d.sgfh_qty = this.round(d.sgfh_qty, precision.value);
                         } else if (op) {
                             d.sgfh_qty = op.sgfh_qty;
                         }
+                        updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, d.sgfh_qty);
                         if (d.sjcl_qty !== undefined) {
                             d.sjcl_qty = this.round(d.sjcl_qty, precision.value);
                         } else if (op) {
                             d.sjcl_qty = op.sjcl_qty;
                         }
+                        updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, d.sjcl_qty);
                         if (d.qtcl_qty) {
                             d.qtcl_qty = this.round(d.qtcl_qty, precision.value);
                         } else if (op) {
                             d.qtcl_qty = op.qtcl_qty;
                         }
+                        updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, d.qtcl_qty);
                         d.quantity = this.ctx.helper.sum([d.sgfh_qty, d.qtcl_qty, d.sjcl_qty]);
+                        updateBills.quantity = this.ctx.helper.add(updateBills.quantity, d.quantity);
                     }
                     if (d.id) {
                         await transaction.update(this.tableName, d);
                     } else {
-                        this._insertPosData(transaction, d, tid);
+                        this._completeInsertPosData(tid, d);
+                        transaction.insert(this.tableName, d);
                     }
                 }
-                for (const ub of updateBills) {
-                    await this.ctx.service.ledger.calcNode(ub, transaction);
-                }
+
+                const info = this.ctx.tender.info;
+                updateBills.sgfh_tp = this.ctx.helper.mul(updateBills.sgfh_qty, bills.unit_price, info.decimal.tp);
+                updateBills.sjcl_tp = this.ctx.helper.mul(updateBills.sjcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.qtcl_tp = this.ctx.helper.mul(updateBills.qtcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.total_price = this.ctx.helper.mul(updateBills.quantity, bills.unit_price, info.decimal.tp);
+                await transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                updateBills.ledger_id = bills.ledger_id;
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
                 throw err;
             }
             result.pos = data;
-            if (updateBills.length > 0) {
-                result.ledger.update = await this.ctx.service.ledger.getDataByIds(this._.map(updateBills, 'id'));
-            }
+            result.ledger.update = [updateBills];
             return result;
         }
 

+ 150 - 0
app/service/report_memory.js

@@ -0,0 +1,150 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Service = require('egg').Service;
+const StageIm = require('../lib/stage_im');
+const imType = require('../const/tender').imType;
+const audit = require('../const/audit');
+
+module.exports = app => {
+    class ReportMemory extends Service {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.db = this.app.mysql;
+            this.cache = this.app.redis;
+            this._ = this.app._;
+            // 需要缓存的数据
+            this.stageImData = null;
+        }
+
+        async _checkTender(tid) {
+            const tender = await this.ctx.service.tender.getTender(tid);
+            tender.info = await this.ctx.service.tenderInfo.getTenderInfo(tid);
+            this.ctx.tender = tender;
+        }
+
+        async _checkStage(sid) {
+            const status = audit.stage.status;
+            const stage = await this.ctx.service.stage.getDataById(sid);
+            stage.auditors = await this.ctx.service.stageAudit.getAuditors(stage.id, stage.times);
+            stage.curAuditor = await this.ctx.service.stageAudit.getCurAuditor(stage.id, stage.times);
+
+            const accountId = this.ctx.session.sessionUser.accountId, auditorIds = this._.map(stage.auditors, 'aid'), shareIds = [];
+            if (accountId === stage.user_id) { // 原报
+                if (stage.curAuditor) {
+                    stage.readOnly = stage.curAuditor.aid !== accountId;
+                } else {
+                    stage.readOnly = stage.status !== status.uncheck && stage.status !== status.checkNo;
+                }
+                stage.curTimes = stage.times;
+                if (stage.status === status.uncheck || stage.status === status.checkNo) {
+                    stage.curOrder = 0;
+                } else if (stage.status === status.checked) {
+                    stage.curOrder = this._.max(this._.map(stage.auditors, 'order'));
+                } else {
+                    stage.curOrder = stage.curAuditor.aid === accountId ? stage.curAuditor.order : stage.curAuditor.order - 1;
+                }
+            } else if (auditorIds.indexOf(accountId) !== -1) { // 审批人
+                if (stage.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
+                if (stage.status === status.checked) {
+                    stage.curOrder = this._.max(this._.map(stage.auditors, 'order'));
+                } else if (stage.status === status.checkNo) {
+                    const audit = await this.service.stageAudit.getDataByCondition({
+                        sid: stage.id, times: stage.times - 1, status: status.checkNo
+                    });
+                    stage.curOrder = audit.order;
+                } else {
+                    stage.curOrder = accountId === stage.curAuditor.aid ? stage.curAuditor.order : stage.curAuditor.order - 1;
+                }
+            } else if (shareIds.indexOf(accountId) !== -1) { // 分享人
+                if (stage.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
+                stage.curOrder = stage.status === status.checked ? this._.max(this._.map(stage.auditors, 'order')) : stage.curAuditor.order - 1;
+            }
+
+            this.ctx.stage = stage;
+        }
+
+        async _generateStageIm(tid, sid, isTz = true) {
+            await this._checkTender(tid);
+            await this._checkStage(sid);
+            if (isTz && this.ctx.stage.im_type !== imType.tz.value) {
+                throw '您查看的报表跟设置不符,请查看“总量控制”的报表';
+            } else if (!isTz && this.ctx.stage.im_type === imType.tz.value) {
+                throw '您查看的报表跟设置不符,请查看“0号台账”的报表';
+            }
+            const stageIm = new StageIm(this.ctx);
+            await stageIm.buildImData();
+            this.stageImData.main = stageIm.ImData;
+            if (isTz) {
+                this.stageImData.bills = stageIm.ImBillsData;
+            }
+        }
+
+        async getStageImTzData(tid, sid) {
+            if (!this.stageImData) {
+                this.stageImData = {};
+                try {
+                    await this._generateStageIm(tid, sid);
+                } catch(err) {
+                    if (err.statck) {
+                        this.ctx.logger.error(error);
+                    }
+                    this.stageImData.main = err.statck ? '数据错误' : err;
+                    this.stageImData.bills = this.stageImData.main;
+                }
+            }
+            return this.stageImData.main;
+        }
+
+        async getStageImTzBillsData(tid, sid) {
+            if (!this.stageImData) {
+                this.stageImData = {};
+                try {
+                    await this._generateStageIm(tid, sid);
+                } catch(err) {
+                    if (err.statck) {
+                        this.ctx.logger.error(error);
+                    }
+                    this.stageImData.main = err.statck ? '数据错误' : err;
+                    this.stageImData.bills = this.stageImTz.main;
+                }
+            }
+            return this.stageImData.bills;
+        }
+
+        async getStageImZlData(tid, sid) {
+            this.stageImData = {};
+            try {
+                await this._generateStageIm(tid, sid, false);
+            } catch(err) {
+                if (err.statck) {
+                    this.ctx.logger.error(error);
+                }
+                this.stageImData.main = err.statck ? '数据错误' : err;
+            }
+            return this.stageImData.main;
+        }
+    }
+
+    return ReportMemory;
+};

+ 3 - 3
app/service/stage_pos.js

@@ -180,7 +180,7 @@ module.exports = app => {
                     // 如果存在本期计算数据,更新计算清单本期计量数据
                     if (d.contract_qty || d.qc_qty || d.postil) {
                         const ps = {
-                            pid: d.id,
+                            pid: p.id,
                             lid: d.lid,
                             tid: this.ctx.tender.id,
                             sid: this.ctx.stage.id,
@@ -191,7 +191,7 @@ module.exports = app => {
                         if (d.contract_qty) ps.contract_qty = this.round(d.contract_qty, precision.value);
                         if (d.qc_qty) ps.qc_qty = this.round(d.qc_qty, precision.value);
                         if (d.postil) ps.postil = d.postil;
-                        await transaction.insert(ps);
+                        await transaction.insert(this.tableName, ps);
                         if (d.contract_qty || d.qc_qty) {
                             calcStageBills.push(ps.lid);
                         }
@@ -202,7 +202,7 @@ module.exports = app => {
                     await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
                 }
                 for (const lid of calcStageBills) {
-                    await this.ctx.service.stageBills.calc(ctx.tender.id, ctx.stage.id, lid, transaction);
+                    await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
                 }
                 await transaction.commit();
                 return result;

+ 38 - 0
app/view/ledger/gather.ejs

@@ -0,0 +1,38 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    台帐对比
+                </div>
+            </div>
+            <div class="ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="c-body">
+            <div class="sjs-height-1" id="gcl-spread">
+            </div>
+            <div class="bcontent-wrap">
+                <div id="main-resize" class="resize-y" r-Type="height" div1="#gcl-spread" div2="#main-bottom" store-id="ledger-gather" store-version="1.0.0" min="100"></div>
+                <div class="bc-bar mb-1">
+                    <ul class="nav nav-tabs">
+                        <li class="nav-item">
+                            <a class="nav-link active" data-toggle="tab" href="#xmujie" role="tab">所属项目节</a>
+                        </li>
+                    </ul>
+                </div>
+                <div class="tab-content">
+                    <div class="tab-pane active" id="xmujie">
+                        <div class="sp-wrap" id="leaf-xmj-spread">
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 1
app/view/tender/manage_modal.ejs

@@ -80,7 +80,7 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm">确定添加</button>
+                <button type="button" class="btn btn-primary btn-sm" id="set-cate-ok">确定添加</button>
             </div>
         </div>
     </div>

+ 1 - 0
app/view/tender/tender_sub_menu.ejs

@@ -17,6 +17,7 @@
                 <% if (ctx.tender.data.ledger_status !== ctx.tender.auditLedgerConst.status.uncheck) { %>
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/audit') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/audit"><span>台帐审批</span></a></li>
                 <% } %>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/gather') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/gather"><span>台帐对比</span></a></li>
                 <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/revise') >= 0) { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/revise"><span>台帐修订</span></a></li>
             </ul>
         </div>

+ 3 - 0
app/view/tender/tender_sub_mini_menu.ejs

@@ -14,7 +14,10 @@
             <h3><i class="fa fa-list-alt"></i> 0号台帐</h3>
             <ul class="nav-list list-unstyled sub-list">
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger"><span>台帐分解</span></a></li>
+                <% if (ctx.tender.data.ledger_status !== ctx.tender.auditLedgerConst.status.uncheck) { %>
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/audit') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/audit"><span>台帐审批</span></a></li>
+                <% } %>
+                <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/ledger/gather') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/ledger/gather"><span>台帐对比</span></a></li>
                 <li <% if (ctx.url === '/tender/' + ctx.tender.id + '/revise') { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/revise"><span>台帐修订</span></a></li>
             </ul>
         </div>

+ 16 - 0
config/web.js

@@ -153,6 +153,22 @@ const JsFiles = {
                 ],
                 mergeFile: 'ledger_audit',
             },
+            gather: {
+                files: [
+                    "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
+                    "/public/js/decimal.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/sub_menu.js",
+                    "/public/js/div_resizer.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    "/public/js/gcl_gather.js",
+                    "/public/js/ledger_gather.js",
+                ],
+                mergeFile: 'ledger_gather',
+            },
             revise: {
                 files: [
                     "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",

+ 55 - 0
test/app/service/report_memory.test.js

@@ -0,0 +1,55 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const mockData = {};
+
+describe('test/app/service/report_memory.test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        // 模拟登录session
+        const postData = {
+            account: '734406061@qq.com',
+            project: 'T201711273363',
+            project_password: 'mai654321',
+        };
+        ctx.session = {};
+        const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
+        assert(loginResult);
+        mockData.session = ctx.session;
+    });
+    // 生成中间计量表数据 - 台账
+    it('test getStageImTzData & getStageImTzBillsData', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const mainData = yield ctx.service.reportMemory.getStageImTzData(12, stage.id);
+        const billsData = yield ctx.service.reportMemory.getStageImTzBillsData(12, stage.id);
+        if (mainData instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_im_tz.json');
+        }
+        if (billsData instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(billsData,"","\t"), ctx.app.baseDir + '/mem_stage_im_tz_bills.json');
+        }
+    });
+    // 生成中间计量表数据 - 总量
+    it('test getStageImZlData', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const mainData = yield ctx.service.reportMemory.getStageImZlData(12, stage.id);
+        if (mainData instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_im_zl.json');
+        }
+    });
+});