浏览代码

报表数据,中间计量表

MaiXinRong 5 年之前
父节点
当前提交
9e86dffd57
共有 6 个文件被更改,包括 1138 次插入1 次删除
  1. 12 0
      app/controller/report_controller.js
  2. 1 1
      app/extend/helper.js
  3. 314 0
      app/lib/ledger.js
  4. 606 0
      app/lib/stage_im.js
  5. 150 0
      app/service/report_memory.js
  6. 55 0
      test/app/service/report_memory.test.js

+ 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,
+};

+ 606 - 0
app/lib/stage_im.js

@@ -0,0 +1,606 @@
+'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) {
+                result = result + '-' + subPeg1.name;
+                if (subPeg2 && subPeg2.id !== subPeg1.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) : '', drawing_code: this._getDrawingCode(p),
+                };
+                if (this.ctx.stage.im_gather && node.check) {
+                    im.check = true;
+                    im.bw = this._getZlGatherBw(node, peg);
+                    im.xm = '';
+                } else {
+                    im.check = false;
+                    im.bw = this._getZlNormalBw(node, peg);
+                    im.xm = node.name;
+                }
+                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;

+ 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;
+};

+ 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');
+        }
+    });
+});