瀏覽代碼

汇总表相关

MaiXinRong 5 年之前
父節點
當前提交
57fdc744b5

+ 33 - 6
app/controller/report_controller.js

@@ -6,6 +6,7 @@
 
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
+const auditConst = require('../const/audit');
 const accountGroup = require('../const/account_group').group;
 const JpcEx = require('../reports/rpt_component/jpc_ex');
 const JV = require('../reports/rpt_component/jpc_value_define');
@@ -42,6 +43,22 @@ module.exports = app => {
                 const roleList = await ctx.service.signatureRole.getSignatureRolesByTenderId(tender.id);
                 const usedList = await ctx.service.signatureUsed.getSignatureUsedByTenderId(tender.id);
 
+                // 分类列表
+                const categoryData = await this.ctx.service.category.getAllCategory(this.ctx.session.sessionProject.id);
+                // 获取用户权限
+                const accountInfo = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+                const userPermission = accountInfo !== undefined && accountInfo.permission !== '' ? JSON.parse(accountInfo.permission) : null;
+                // 获取用户可查看的标段
+                const tenderList = await this.ctx.service.tender.getList('', userPermission);
+                for (const t of tenderList) {
+                    if (t.ledger_status === auditConst.ledger.status.checked) {
+                        t.lastStage = await this.ctx.service.stage.getLastestStage(t.id, true);
+                        if (t.lastStage) {
+                            await this.ctx.service.stage.checkStageGatherData(t.lastStage);
+                        }
+                    }
+                }
+
                 // const allTpls = await ctx.service.rptTpl.getAllTplByIds(tmpRptIds);
                 // for (const tpl of allTpls) {
                 //     const fName = tpl.id + '_' + tpl.rpt_tpl_name + '.js';
@@ -110,7 +127,10 @@ module.exports = app => {
                     tenderMenu,
                     preUrl: '/tender/' + tender.id,
                     measureType,
-                    // jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.explode),
+                    categoryData,
+                    tenderList,
+                    auditConst,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.report.main),
                 };
                 await this.layout('report/index.ejs', renderData, 'report/rpt_all_popup.ejs');
                 // await this.layout('report/index.ejs', renderData);
@@ -167,7 +187,12 @@ module.exports = app => {
             }
             rptTpl = JSON.parse(rptTpl[0].rpt_content);
             // console.log('get the template!');
-            const customSelect = await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, params.stage_id, params.rpt_tpl_id);
+            const customSelect = rptTpl[JV.NODE_CUSTOM_DEFINE] && rptTpl[JV.NODE_CUSTOM_DEFINE][JV.NODE_CUS_AUDIT_SELECT].enable
+                ? await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, params.stage_id, params.rpt_tpl_id)
+                : await ctx.service.rptCustomDefine.getCustomDefine(params.tender_id, -1, params.rpt_tpl_id);
+            if (!params.gather_select) {
+                delete customSelect.gather_select;
+            }
             const pageRst = await getAllPagesCommon(ctx, rptTpl, params, JV.PAGING_OPTION_NORMAL, JV.OUTPUT_TYPE_NORMAL, this.app.baseDir, customSelect);
             // console.log(pageRst);
             // const roleRel = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_tpl_id)) : [];
@@ -391,16 +416,17 @@ module.exports = app => {
         async setCustomDefine(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
-                console.log(data);
-                const filter = {tid: data.tender_id, sid: data.stage_id, rid: data.rpt_tpl_id};
+                const sid = data.gather_select ? -1 : data.stage_id;
+                const filter = {tid: data.tender_id, sid: sid, rid: data.rpt_tpl_id};
                 const count = await ctx.service.rptCustomDefine.count(filter);
                 const updateData = {};
                 if (data.audit_select) updateData.audit_select = JSON.stringify(data.audit_select);
+                if (data.gather_select) updateData.gather_select = JSON.stringify(data.gather_select);
                 if (count > 0) {
                     await ctx.service.rptCustomDefine.update(updateData, filter);
                 } else {
                     updateData.tid = data.tender_id;
-                    updateData.sid = data.stage_id;
+                    updateData.sid = sid;
                     updateData.rid = data.rpt_tpl_id;
                     await ctx.service.rptCustomDefine.db.insert(ctx.service.rptCustomDefine.tableName, updateData);
                 }
@@ -533,7 +559,8 @@ async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDi
     rptDataUtil.initialize(rptTpl);
     const filter = rptDataUtil.getDataRequestFilter();
     // console.log(filter.tables);
-    const rawDataObj = await ctx.service.report.getReportData(params, filter.tables, filter.memFieldKeys);
+    const rawDataObj = await ctx.service.report.getReportData(params, filter.tables, filter.memFieldKeys,
+        rptTpl[JV.NODE_CUSTOM_DEFINE], customSelect);
     await ctx.helper.saveBufferFile(JSON.stringify(rawDataObj, "", "\t"), ctx.app.baseDir + '/mem.json');
     // console.log(rawDataObj);
     try {

+ 2 - 1
app/extend/helper.js

@@ -764,7 +764,7 @@ module.exports = {
     },
 
     /**
-     * 合并相关数据
+     * 合并 相关数据
      * @param {Array} main - 主数据
      * @param {Array[]}rela - 相关数据 {data, fields, prefix, relaId}
      */
@@ -790,6 +790,7 @@ module.exports = {
             loadFields(r.data, r.fields, r.prefix, r.relaId);
         }
     },
+
     whereSql (where, as) {
         if (!where) {
             return '';

+ 70 - 0
app/lib/ledger.js

@@ -412,6 +412,75 @@ class filterGatherTree extends baseTree {
     }
 }
 
+class gatherTree extends baseTree {
+
+    constructor(ctx, setting) {
+        super(ctx, setting);
+        this._newId = 1;
+    }
+
+    get newId() {
+        return this._newId++;
+    }
+
+    loadGatherNode(node, parent, loadFun) {
+        const siblings = parent ? parent.children : this.children;
+        let cur = siblings.find(function (x) {
+            return node.b_code
+                ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price
+                : x.code === node.code && x.name === node.name;
+        });
+        if (!cur) {
+            const id = this.newId;
+            cur = {
+                id: id,
+                pid: parent ? parent.id : this.setting.rootId,
+                full_path: parent ? parent.full_path + '-' + id : '' + id,
+                level: parent ? parent.level + 1 : 1,
+                order: siblings.length + 1,
+                children: [],
+                code: node.code, b_code: node.b_code, name: node.name,
+                unit: node.unit, unit_price: node.unit_price,
+            };
+            siblings.push(cur);
+            this.datas.push(cur);
+        }
+        loadFun(cur, node);
+        for (const c of node.children) {
+            this.loadGatherNode(c, cur, loadFun);
+        }
+    }
+
+    generateSortNodes() {
+        const self = this;
+        const addSortNode = function (node) {
+            self.nodes.push(node);
+            for (const c of node.children) {
+                addSortNode(c);
+            }
+        };
+        this.nodes = [];
+        for (const n of this.children) {
+            addSortNode(n);
+        }
+    }
+
+    loadGatherTree(sourceTree,  loadFun) {
+        for (const c of sourceTree.children) {
+            this.loadGatherNode(c, null, loadFun);
+        }
+        // todo load Pos Data;
+    }
+
+    calculateSum() {
+        if (this.setting.calcSum) {
+            for (const d of this.datas) {
+                this.setting.calcSum(d, this.count);
+            }
+        }
+    }
+}
+
 class pos {
     /**
      * 构造函数
@@ -485,4 +554,5 @@ module.exports = {
     pos,
     filterTree,
     filterGatherTree,
+    gatherTree,
 };

+ 46 - 4
app/lib/rpt_data_analysis.js

@@ -637,7 +637,7 @@ const filter = {
                 return true;
         }
     },
-    fun: function (ctx, data, fields, options, print = false) {
+    fun: function (ctx, data, fields, options) {
         if (!options || !options.table || !options.condition) return;
 
         const fData = data[options.table], self = this;
@@ -647,17 +647,14 @@ const filter = {
         }
 
         data[options.table] = fData.filter(function (d) {
-            if (print) console.log(d);
             if (options.condition.length > 0) {
                 let con = options.condition[0];
                 let result = self[con.fun](ctx, d[con.field], con);
-                if (print) console.log(con, result);
                 for (let i = 1, iLen = options.condition.length; i < iLen; i++) {
                     con = options.condition[i];
                     result = (con.rela && con.rela === 'or')
                         ? (result || self[con.fun](ctx, d[con.field], con))
                         : (result && self[con.fun](ctx, d[con.field], con));
-                    if (print) console.log(con, self[con.fun](ctx, d[con.field], con), result);
                 }
                 return result;
             } else {
@@ -1007,6 +1004,50 @@ const datetimeFormat = {
         }
     }
 };
+const gatherSelectConverse = {
+    name: '汇总标段选择',
+    hint: '需搭配 用户交互--汇总标段选择 一起使用',
+    defaultSetting: {
+        table: ['mem_gather_stage_bills'],
+    },
+    _stageBills: function (helper, data, count) {
+        const result = [];
+        const reg = new RegExp('^t_[0-9]+_');
+        for (let i = 0; i < count; i++) {
+            const curReg = new RegExp('^t_' + i + '_');
+            for (const d of data) {
+                const nd = {};
+                for (const prop in d) {
+                    if (reg.test(prop)) {
+                        if (curReg.test(prop)) {
+                            nd[prop.replace(curReg, 't_')] = d[prop];
+                        }
+                    } else {
+                        nd[prop] = d[prop];
+                    }
+                }
+                result.push(nd);
+            }
+        }
+        return result;
+    },
+    fun: function (ctx, data, fieldsKey, options, csRela) {
+        if (!csRela.tplDefine) return;
+
+        const gsDefine = csRela.tplDefine.gather_select;
+        if (!gsDefine || !gsDefine.enable || !gsDefine.setting || gsDefine.setting === '') return;
+        const gsCustom = csRela.cDefine ? csRela.cDefine.gather_select : null;
+        const count = gsCustom.tenders.length - gsDefine.setting.special.length;
+
+        for (const t of options.table) {
+            switch (t) {
+                case 'mem_gather_stage_bills':
+                    data[t] = this._stageBills(ctx.helper, data[t], count);
+                    break;
+            }
+        }
+    }
+};
 
 const analysisObj = {
     changeSort,
@@ -1021,6 +1062,7 @@ const analysisObj = {
     addSumChapter,
     auditSelect,
     datetimeFormat,
+    gatherSelectConverse,
 };
 const analysisDefine = (function (obj) {
     const result = [];

+ 278 - 5
app/public/report/js/rpt_custom.js

@@ -9,8 +9,132 @@
  */
 
 const rptCustomObj = (function () {
+    // 审批人选择
     const sAuditSelect = 'audit_select';
     let stageFlow = [];
+    // 汇总表
+    const sGatherSelect = 'gather_select';
+    let gsObj = {
+        setting: null,
+
+        gsSheet: null,
+        grSheet: null,
+
+        tenderSourceTree: null,
+        grArray: [],
+
+        orgSelect: null,
+    };
+    const grSpreadSetting = {
+        baseCols: [
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true},
+        ],
+        extraCols: [
+            {title: '%s', colSpan: '1', rowSpan: '1', field: '%s', hAlign: 1, vAlign: '1', width: 60, cellType: 'checkbox', readOnly: true},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        headColWidth: []
+    };
+    const gatherSelectSpreadObj = {
+        _addTender: function (tender) {
+            const gr = gsObj.grArray.find(function (x) {
+                return x.tid === tender.tid;
+            });
+            const t = {tid: tender.tid, name: tender.name}
+            if (!gr) gsObj.grArray.push(t);
+            return t;
+        },
+        _removeTender: function (tender) {
+            const gri = gsObj.grArray.findIndex(function (x, i, arr) {
+                return x.tid === tender.tid;
+            });
+            if (gri >= 0) gsObj.grArray.splice(gri, 1);
+        },
+        reloadResultData: function () {
+            SpreadJsObj.reLoadSheetData(gsObj.grSheet);
+        },
+        gsButtonClicked: function (e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            if (col.field !== 'selected') return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            node.selected = !node.selected;
+            if (node.children && node.children.length > 0) {
+                const posterity = gsObj.tenderSourceTree.getPosterity(node);
+                for (const p of posterity) {
+                    p.selected = node.selected;
+                    if (!p.children || p.children.length === 0){
+                        if (p.selected) {
+                            gatherSelectSpreadObj._addTender(p);
+                        } else {
+                            gatherSelectSpreadObj._removeTender(p);
+                        }
+                    }
+                }
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, posterity.length + 1);
+            } else {
+                if (node.selected) {
+                    gatherSelectSpreadObj._addTender(node);
+                } else {
+                    gatherSelectSpreadObj._removeTender(node);
+                }
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+            }
+            gatherSelectSpreadObj.reloadResultData();
+        },
+        grButtonClicked: function (e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            if (col.field === 'name') return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            const refreshRows = [info.row];
+            node[col.field] = !node[col.field];
+            for (const rCol of info.sheet.zh_setting.cols) {
+                if (rCol.field !== 'name' && rCol.field !== col.field) {
+                    node[rCol.field] = false;
+                }
+            }
+            if (node[col.field]) {
+                for (const [i, gra] of gsObj.grArray.entries()) {
+                    if (gra[col.field] && gra.tid !== node.tid) {
+                        gra[col.field] = false;
+                        refreshRows.push(i);
+                    }
+                }
+            }
+            SpreadJsObj.reLoadRowsData(info.sheet, refreshRows);
+        },
+        initSelectTenders: function (tenders) {
+            if (!tenders || tenders.length === 0) return;
+
+            const specCol = gsObj.setting.special ? gsObj.setting.special : [];
+            const select = [];
+            for (const node of gsObj.tenderSourceTree.nodes) {
+                node.selected = false;
+            }
+            for (const t of tenders) {
+                const tender = gsObj.tenderSourceTree.nodes.find(function (x) { return x.tid === t.tid});
+                tender.selected = true;
+                select.push(tender);
+                const st = this._addTender(tender);
+                for (const sc of specCol) {
+                    st[sc.key] = t[sc.key];
+                }
+            }
+            SpreadJsObj.reLoadNodesData(gsObj.gsSheet, select);
+            if (select.length > 0) SpreadJsObj.locateTreeNode(gsObj.gsSheet, select.tmt_id);
+            this.reloadResultData();
+        }
+    };
 
     const getStageFlowSelectHtml = function (select, id) {
         const html = [];
@@ -60,14 +184,67 @@ const rptCustomObj = (function () {
         }
     };
 
+    const initGrSpreadSetting = function (gsSetting) {
+        grSpreadSetting.cols = [];
+        for (const bc of grSpreadSetting.baseCols) {
+            grSpreadSetting.cols.push(bc);
+        }
+        for (const s of gsSetting.special) {
+            for (const ec of grSpreadSetting.extraCols) {
+                const c = {};
+                c.title = ec.title.replace('%s', s.title);
+                c.colSpan = ec.colSpan;
+                c.field = ec.field.replace('%s', s.key);
+                c.hAlign = ec.hAlign;
+                c.width = s.width ? s.width : ec.width;
+                c.cellType = ec.cellType;
+                c.readOnly = ec.readOnly;
+                grSpreadSetting.cols.push(c);
+            }
+        }
+    };
+
+    const initGatherSelect = function (gsSetting, gsSelect) {
+        gsObj.setting = JSON.parse(gsSetting);
+        gsObj.setting.type = 'zone';
+        gsObj.orgSelect = gsSelect;
+        $('#audit-select-title').html(gsObj.setting.title);
+        initGrSpreadSetting(gsObj.setting);
+        SpreadJsObj.initSheet(gsObj.grSheet, grSpreadSetting);
+        if (gsObj.setting.type === 'month') {
+            $('#gather-by-month').show();
+            $('#gather-by-zone').hide();
+        } else if (gsObj.setting.type === 'zone') {
+            $('#gather-by-month').hide();
+            $('#gather-by-zone').show();
+        }
+        SpreadJsObj.loadSheetData(gsObj.grSheet, SpreadJsObj.DataType.Data, gsObj.grArray);
+        // 初始化选择结果
+        if (gsSelect) {
+            if (gsSelect.zone) {
+                $('#gather-zone').val(gsSelect.zone ? gsSelect.zone : '');
+            } else {
+                $('#gather-month').val(gsSelect.month ? gsSelect.month: '');
+            }
+            gatherSelectSpreadObj.initSelectTenders(gsSelect.tenders);
+        }
+        // 初始化
+        $("#gather-select").modal('show');
+    };
     const init = function (cDefine, sfData, cSelect) {
         stageFlow = sfData;
         if (cDefine && cDefine[sAuditSelect] && cDefine[sAuditSelect].enable && cDefine[sAuditSelect].setting) {
             $('#pnl_audit_select').show();
-            initAuditSelect(cDefine[sAuditSelect].setting, cSelect ? cSelect.audit_select : []);
+            initAuditSelect(cDefine[sAuditSelect].setting, cSelect ? cSelect[sAuditSelect] : []);
         } else {
             $('#pnl_audit_select').hide();
         }
+        if (cDefine && cDefine[sGatherSelect] && cDefine[sGatherSelect].enable && cDefine[sGatherSelect].setting) {
+            $('#pnl_gather_select').show();
+            initGatherSelect(cDefine[sGatherSelect].setting, cSelect ? cSelect[sGatherSelect] : []);
+        } else {
+            $('#pnl_gather_select').hide();
+        }
     };
 
     const reloadReportData = function (result) {
@@ -128,9 +305,7 @@ const rptCustomObj = (function () {
         }
     };
 
-    const resetAuditSelect = function () {
-        const selObj = $('select', '#audit-select-list');
-        const data = { audit_select: [] };
+    const getCommonParams = function (data) {
         data.pageSize = rptControlObj.getCurrentPageSize();
         data.orientation = rptControlObj.getCurrentOrientation();
         data.rpt_tpl_id = zTreeOprObj.currentNode.refId;
@@ -141,6 +316,12 @@ const rptCustomObj = (function () {
         data.stage_status = getStageStatus();
         data.stage_order = getStageOrder();
         data.stage_times = getStageTimes();
+    };
+
+    const resetAuditSelect = function () {
+        const selObj = $('select', '#audit-select-list');
+        const data = { audit_select: [] };
+        getCommonParams(data);
         for (const s of selObj) {
             const sf = stageFlow[s.selectedIndex];
             if (!sf) {
@@ -156,5 +337,97 @@ const rptCustomObj = (function () {
         });
     };
 
-    return {init, resetAuditSelect};
+    const resetGatherSelect = function () {
+        const data = {}, hintObj = $('#gather-hint');
+        getCommonParams(data);
+        data[sGatherSelect] = {
+            tenders: [],
+            type: gsObj.setting.type,
+        };
+        const specCol = gsObj.setting.special ? gsObj.setting.special : [];
+        for (const gra of gsObj.grArray) {
+            const ra = {tid: gra.tid};
+            for (const sc of specCol) {
+                if (gra[sc.key]) {
+                    ra[sc.key] = true;
+                    sc.sCount += 1;
+                }
+            }
+            data[sGatherSelect].tenders.push(ra);
+        }
+        for (const sc of specCol) {
+            if (sc.sCount === 0) {
+                hintObj.html('请选择 ' + sc.title).show();
+                return;
+            }
+        }
+        if (data[sGatherSelect].tenders.length <= specCol.length) {
+            hintObj.html('请至少选择1个普通汇总项目').show();
+            return;
+        }
+        if (gsObj.setting.type === 'month') {
+            data[sGatherSelect].month = $('#gather-month').val();
+            if (data[sGatherSelect].month === '') {
+                hintObj.html('请选择 汇总年月').show();
+                return;
+            }
+        } else {
+            data[sGatherSelect].zone = $('#gather-zone').val();
+            if (data[sGatherSelect].zone === '') {
+                hintObj.html('请选择 汇总周期').show();
+                return;
+            } else if(data[sGatherSelect].zone.indexOf(' - ') < 0) {
+                hintObj.html('请选择 完整汇总周期').show();
+                return;
+            }
+        }
+        hintObj.hide();
+        postData('/report/cDefine', data, function (result) {
+            reloadReportData(result);
+            $('#gather-select').modal('hide');
+        });
+
+    };
+
+    const initTenderTree = function (tenders, category) {
+        const gsSpread = SpreadJsObj.createNewSpread($('#gather-source-spread')[0]);
+        gsObj.gsSheet = gsSpread.getActiveSheet();
+        const spreadSetting = {
+            cols: [
+                {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox', readOnly: true},
+                {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', readOnly: true, cellType: 'tree'},
+                {title: '期数', field: 'phase', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+                {title: '审批状态', field: 'status', hAlign: 1, width: 60, formatter: '@', readOnly: true}
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [0],
+            selectedBackColor: '#fffacd',
+        };
+        SpreadJsObj.initSheet(gsObj.gsSheet, spreadSetting);
+        gsObj.tenderSourceTree = Tender2Tree.convert(category, tenders);
+        SpreadJsObj.loadSheetData(gsObj.gsSheet, SpreadJsObj.DataType.Tree, gsObj.tenderSourceTree);
+        gsSpread.bind(spreadNS.Events.ButtonClicked, gatherSelectSpreadObj.gsButtonClicked);
+
+        const grSpread = SpreadJsObj.createNewSpread($('#gather-result-spread')[0]);
+        gsObj.grSheet = grSpread.getActiveSheet();
+        grSpread.bind(spreadNS.Events.ButtonClicked, gatherSelectSpreadObj.grButtonClicked);
+
+        $('#gather-hint').hide();
+
+        $('#gather-select').bind('shown.bs.modal', function () {
+            if (gsSpread) gsSpread.refresh();
+            if (grSpread) grSpread.refresh();
+        });
+
+        $('.datepicker-here').datepicker({
+            autoClose: true,
+        });
+    };
+
+    return {init, resetAuditSelect, resetGatherSelect, initTenderTree};
 })();

+ 6 - 2
app/service/report.js

@@ -30,7 +30,7 @@ module.exports = app => {
             }
         }
 
-        async getReportData(params, filters, memFieldKeys) {
+        async getReportData(params, filters, memFieldKeys, customDefine, customSelect) {
             const service = this.ctx.service;
             const rst = {};
             const runnableRst = [];
@@ -117,10 +117,14 @@ module.exports = app => {
                             runnableKey.push(filter);
                             break;
                         case 'mem_stage_other':
-                        case 'mem_stage_other':
                             runnableRst.push(service.reportMemory.getStageOther(params.tender_id, params.stage_id, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_gather_stage_bills':
+                            runnableRst.push(service.rptGatherMemory.getGatherStageBills(memFieldKeys[filter],
+                                customDefine.gather_select.setting, customSelect.gather_select));
+                            runnableKey.push(filter);
+                            break;
                         default:
                             break;
                     }

+ 3 - 0
app/service/rpt_custom_define.js

@@ -26,6 +26,9 @@ module.exports = app => {
             if (data && data.audit_select) {
                 data.audit_select = JSON.parse(data.audit_select);
             }
+            if (data && data.gather_select) {
+                data.gather_select = JSON.parse(data.gather_select);
+            }
             return data;
         }
 

+ 271 - 0
app/service/rpt_gather_memory.js

@@ -0,0 +1,271 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Ledger = require('../lib/ledger');
+const moment = require('moment');
+const indexPre = 'id_';
+
+module.exports = app => {
+    class RptGatherMemory extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.resultTree = new Ledger.gatherTree(ctx, {
+                id: 'id',
+                pid: 'pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1
+            });
+        }
+
+        _checkSpecialTender(tender, special) {
+            for (const spec of special) {
+                if (tender[spec.key] === true) return spec.key;
+            }
+            return '';
+        }
+
+        async _gatherMonthData(sTender, index, month) {
+            const helper = this.ctx.helper;
+            const 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', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
+                calc: function (node) {
+                    if (node.children && node.children.length === 0) {
+                        // node.pre_gather_qty = self.ctx.helper.add(node.pre_contract_qty, node.pre_qc_qty);
+                        node.gather_qty = helper.add(node.contract_qty, node.qc_qty);
+                        // node.end_contract_qty = self.ctx.helper.add(node.pre_contract_qty, node.contract_qty);
+                        // node.end_qc_qty = self.ctx.helper.add(node.pre_qc_qty, node.qc_qty);
+                        // node.end_gather_qty = self.ctx.helper.add(node.pre_gather_qty, node.gather_qty);
+                    }
+                    // node.pre_gather_tp = self.ctx.helper.add(node.pre_contract_tp, node.pre_qc_tp);
+                    node.gather_tp = helper.add(node.contract_tp, node.qc_tp);
+                    // node.end_contract_tp = self.ctx.helper.add(node.pre_contract_tp, node.contract_tp);
+                    // node.end_qc_tp = self.ctx.helper.add(node.pre_qc_tp, node.qc_tp);
+                    // node.end_gather_tp = self.ctx.helper.add(node.pre_gather_tp, node.gather_tp);
+                }
+            });
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const billsData = await this.ctx.service.ledger.getData(tender.id);
+            const stage = await this.ctx.service.stage.getDataByCondition({tid: tender.id, s_time: month});
+            if (stage) {
+                await this.ctx.service.stage.doCheckStage(stage);
+                if (stage.readOnly) {
+                    const curStage = await this.ctx.service.stageBills.getAuditorStageData(tender.id,
+                        stage.id, stage.curTimes, 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(tender.id, stage.id);
+                    this.ctx.helper.assignRelaData(billsData, [
+                        {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                    ]);
+                }
+            }
+            billsTree.loadDatas(billsData);
+            billsTree.calculateAll();
+            this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
+                gatherNode['t_' + index + '_id'] = tender.id;
+                gatherNode['t_' + index + '_name'] = tender.name;
+
+                gatherNode['t_' + index + "_qty"] = helper.add(gatherNode['t_' + index + "_qty"], sourceNode.quantity);
+                gatherNode['t_' + index + "_tp"] = helper.add(gatherNode['t_' + index + "_tp"], sourceNode.total_price);
+                gatherNode['t_' + index + "_contract_qty"] = helper.add(gatherNode['t_' + index + "_contract_qty"], sourceNode.contract_qty);
+                gatherNode['t_' + index + "_contract_tp"] = helper.add(gatherNode['t_' + index + "_contract_tp"], sourceNode.contract_tp);
+                gatherNode['t_' + index + "_qc_qty"] = helper.add(gatherNode['t_' + index + "_qc_qty"], sourceNode.contract_qty);
+                gatherNode['t_' + index + "_qc_tp"] = helper.add(gatherNode['t_' + index + "_qc_tp"], sourceNode.contract_tp);
+                gatherNode['t_' + index + "_gather_qty"] = helper.add(gatherNode['t_' + index + "_gather_qty"], sourceNode.contract_qty);
+                gatherNode['t_' + index + "_gather_tp"] = helper.add(gatherNode['t_' + index + "_gather_tp"], sourceNode.contract_tp);
+
+                gatherNode['s_' + "qty"] = helper.add(gatherNode['t_' + "qty"], sourceNode.quantity);
+                gatherNode['s_' + "tp"] = helper.add(gatherNode['t_' + "tp"], sourceNode.total_price);
+                gatherNode['s_' + "contract_qty"] = helper.add(gatherNode['t_' + "contract_qty"], sourceNode.contract_qty);
+                gatherNode['s_' + "contract_tp"] = helper.add(gatherNode['t_' + "contract_tp"], sourceNode.contract_tp);
+                gatherNode['s_' + "qc_qty"] = helper.add(gatherNode['t_' + "qc_qty"], sourceNode.contract_qty);
+                gatherNode['s_' + "qc_tp"] = helper.add(gatherNode['t_' + "qc_tp"], sourceNode.contract_tp);
+                gatherNode['s_' + "gather_qty"] = helper.add(gatherNode['t_' + "gather_qty"], sourceNode.contract_qty);
+                gatherNode['s_' + "gather_tp"] = helper.add(gatherNode['t_' + "gather_tp"], sourceNode.contract_tp);
+            });
+        }
+
+        async _gatherZoneData(sTender, index, zone) {
+            const helper = this.ctx.helper;
+            /**
+             * 汇总并合并 相关数据
+             * @param {Array} index - 主数据
+             * @param {Array[]}rela - 相关数据 {data, fields, prefix, relaId}
+             */
+            const sumAssignRelaData = function (index, rela) {
+                const loadFields = function (datas, fields, prefix, relaId) {
+                    for (const d of datas) {
+                        const key = indexPre + d[relaId];
+                        const m = index[key];
+                        if (m) {
+                            for (const f of fields) {
+                                if (d[f] !== undefined) {
+                                    m[prefix + f] = helper.add(m[prefix + f], d[f]);
+                                }
+                            }
+                        }
+                    }
+                };
+                for (const r of rela) {
+                    loadFields(r.data, r.fields, r.prefix, r.relaId);
+                }
+            };
+
+            const 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 = helper.add(node.contract_qty, node.qc_qty);
+                    }
+                    node.gather_tp = helper.add(node.contract_tp, node.qc_tp);
+                }
+            });
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const billsData = await this.ctx.service.ledger.getData(tender.id);
+
+            let billsIndexData = {};
+            for (const bd of billsData) {
+                billsIndexData[indexPre + bd.id] = bd;
+            }
+
+            const times = zone.split(' - ');
+            if (times.length !== 2) throw '选择的汇总周期无效';
+            const beginTime = moment(times[0], 'YYYY-MM').date();
+            const endTime = moment(times[1], 'YYYY-MM').date();
+
+
+            const stages = await this.ctx.service.stage.getAllDataByCondition({ where: { tid: tender.id } });
+            for (const stage of stages) {
+                const sTime = moment(stage.s_time, 'YYYY-MM').date();
+                if (sTime >= beginTime && sTime <= endTime) {
+                    await this.ctx.service.stage.doCheckStage(stage);
+                    if (stage.readOnly) {
+                        const curStage = await this.ctx.service.stageBills.getAuditorStageData(tender.id,
+                            stage.id, stage.curTimes, stage.curOrder);
+                        sumAssignRelaData(billsIndexData, [
+                            {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                        ]);
+                    } else {
+                        const curStage = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
+                        sumAssignRelaData(billsIndexData, [
+                            {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                        ]);
+                    }
+                }
+            }
+            billsTree.loadDatas(billsData);
+            billsTree.calculateAll();
+            this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
+                gatherNode['t_' + index + '_id'] = tender.id;
+                gatherNode['t_' + index + '_name'] = tender.name;
+
+                gatherNode['t_' + index + "_qty"] = helper.add(gatherNode['t_' + index + "_qty"], sourceNode.quantity);
+                gatherNode['t_' + index + "_tp"] = helper.add(gatherNode['t_' + index + "_tp"], sourceNode.total_price);
+                gatherNode['t_' + index + "_contract_qty"] = helper.add(gatherNode['t_' + index + "_contract_qty"], sourceNode.contract_qty);
+                gatherNode['t_' + index + "_contract_tp"] = helper.add(gatherNode['t_' + index + "_contract_tp"], sourceNode.contract_tp);
+                gatherNode['t_' + index + "_qc_qty"] = helper.add(gatherNode['t_' + index + "_qc_qty"], sourceNode.contract_qty);
+                gatherNode['t_' + index + "_qc_tp"] = helper.add(gatherNode['t_' + index + "_qc_tp"], sourceNode.contract_tp);
+                gatherNode['t_' + index + "_gather_qty"] = helper.add(gatherNode['t_' + index + "_gather_qty"], sourceNode.contract_qty);
+                gatherNode['t_' + index + "_gather_tp"] = helper.add(gatherNode['t_' + index + "_gather_tp"], sourceNode.contract_tp);
+
+                gatherNode['s_' + "qty"] = helper.add(gatherNode['t_' + "qty"], sourceNode.quantity);
+                gatherNode['s_' + "tp"] = helper.add(gatherNode['t_' + "tp"], sourceNode.total_price);
+                gatherNode['s_' + "contract_qty"] = helper.add(gatherNode['t_' + "contract_qty"], sourceNode.contract_qty);
+                gatherNode['s_' + "contract_tp"] = helper.add(gatherNode['t_' + "contract_tp"], sourceNode.contract_tp);
+                gatherNode['s_' + "qc_qty"] = helper.add(gatherNode['t_' + "qc_qty"], sourceNode.contract_qty);
+                gatherNode['s_' + "qc_tp"] = helper.add(gatherNode['t_' + "qc_tp"], sourceNode.contract_tp);
+                gatherNode['s_' + "gather_qty"] = helper.add(gatherNode['t_' + "gather_qty"], sourceNode.contract_qty);
+                gatherNode['s_' + "gather_tp"] = helper.add(gatherNode['t_' + "gather_tp"], sourceNode.contract_tp);
+            });
+        }
+
+        async _gatherSpecialData(sTender, sKey) {
+            const helper = this.ctx.helper;
+            const 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', 'pre_contract_tp', 'pre_qc_tp', 'pre_gather_tp'],
+            });
+            const tender = await this.ctx.service.tender.getCheckTender(sTender.tid);
+            const billsData = await this.ctx.service.ledger.getData(tender.id);
+            billsTree.loadDatas(billsData);
+            billsTree.calculateAll();
+            this.resultTree.loadGatherTree(billsTree, function (gatherNode, sourceNode) {
+                gatherNode['ts_' + sKey + "_qty"] = helper.add(gatherNode['ts_' + sKey + "_qty"], sourceNode.quantity);
+                gatherNode['ts_' + sKey + "_tp"] = helper.add(gatherNode['ts_' + sKey + "_tp"], sourceNode.total_price);
+            })
+        }
+
+        _calculateSum(tree) {
+            for (const node of tree.nodes) {
+
+            }
+        }
+
+        async getGatherStageBills(memFieldKeys, gsDefine, gsCustom) {
+            if (!gsDefine || !gsDefine.enable) return [];
+            if (!gsCustom || !gsCustom.tenders || gsCustom.tenders.length === 0) return [];
+
+            let commonIndex = 0;
+            for (const tender of gsCustom.tenders) {
+                const specialKey = this._checkSpecialTender(tender, gsDefine.setting.special);
+                if (specialKey === '') {
+                    switch (gsDefine.setting.type) {
+                        case 'month':
+                            await this._gatherMonthData(tender, commonIndex, gsCustom.month);
+                            break;
+                        case 'zone':
+                            await this._gatherZoneData(tender, commonIndex, gsCustom.month);
+                            break;
+                    }
+                    commonIndex++;
+                } else {
+                    await this._gatherSpecialData(tender, specialKey);
+                }
+            }
+
+            this.resultTree.generateSortNodes();
+            this._calculateSum(this.resultTree);
+            return this.resultTree.getDefaultDatas();
+        }
+
+    }
+
+    return RptGatherMemory;
+};

+ 63 - 57
app/service/stage.js

@@ -28,67 +28,73 @@ module.exports = app => {
             this.tableName = 'stage';
         }
 
-        async checkStage(sid) {
-            if (!this.ctx.stage) {
-                const status = auditConst.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);
+        async doCheckStage(stage) {
+            const status = auditConst.status;
+            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 = [];
-                const permission = this.ctx.session.sessionUser.permission;
-                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;
-                    }
-                    stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
-                } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
-                    if (stage.status === status.uncheck) {
-                        throw '您无权查看该数据';
-                    }
-                    stage.readOnly = true;
-                    stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
-                    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 = stage.status === status.checked ? this._.max(this._.map(stage.auditors, 'order')) : stage.curAuditor.order - 1;
-                    }
+            const accountId = this.ctx.session.sessionUser.accountId, auditorIds = this._.map(stage.auditors, 'aid'), shareIds = [];
+            const permission = this.ctx.session.sessionUser.permission;
+            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;
+                }
+                stage.readOnly = (stage.status !== status.checking && stage.status !== status.checkNoPre) || accountId !== stage.curAuditor.aid;
+            } else if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) { // 分享人
+                if (stage.status === status.uncheck) {
+                    throw '您无权查看该数据';
+                }
+                stage.readOnly = true;
+                stage.curTimes = stage.status === status.checkNo ? stage.times - 1 : stage.times;
+                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 = stage.status === status.checked ? this._.max(this._.map(stage.auditors, 'order')) : stage.curAuditor.order - 1;
+                }
+            }
+
+            let time = stage.readOnly ? stage.cache_time_r : stage.cache_time_l;
+            if (!time) {
+                time = stage.in_time ? stage.in_time : new Date();
+            }
+            stage.cacheTime = time.getTime();//this.ctx.stage.readOnly ? (this.ctx.stage.cache_time_r).getTime(): (this.ctx.stage.cache_time_l).getTime();
 
+            return stage;
+        }
+
+        async checkStage(sid) {
+            if (!this.ctx.stage) {
+                const stage = await this.ctx.service.stage.getDataById(sid);
+                await this.doCheckStage(stage);
                 this.ctx.stage = stage;
-                let time = this.ctx.stage.readOnly ? this.ctx.stage.cache_time_r : this.ctx.stage.cache_time_l;
-                if (!time) {
-                    time = this.ctx.stage.in_time ? this.ctx.stage.in_time : new Date();
-                }
-                this.ctx.stage.cacheTime = time.getTime();//this.ctx.stage.readOnly ? (this.ctx.stage.cache_time_r).getTime(): (this.ctx.stage.cache_time_l).getTime();
             }
         }
 

+ 7 - 3
app/service/tender.js

@@ -348,11 +348,15 @@ module.exports = app => {
             return result;
         }
 
-        async checkTender(tid) {
-            if (this.ctx.tender) return;
+        async getCheckTender(tid) {
             const tender = await this.ctx.service.tender.getTender(tid);
             tender.info = await this.ctx.service.tenderInfo.getTenderInfo(tid);
-            this.ctx.tender = tender;
+            return tender;
+        }
+
+        async checkTender(tid) {
+            if (this.ctx.tender) return;
+            this.ctx.tender = this.getCheckTender(tid);
         }
 
         async setTenderType(tender, type) {

+ 18 - 1
app/view/report/index.ejs

@@ -133,6 +133,15 @@
                                     </button>
                                 </div>
                             </div>
+                            <div class="panel" id="pnl_gather_select" style="display: none;">
+                                <div class="panel-body">
+                                    <div class="btn-group" role="group">
+                                        <button class="btn btn-primary btn-sm" type="button" data-toggle="modal" data-target="#gather-select">
+                                            选择标段<br><span class="badge badge-light" id="gather-select-count">5</span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
                         </div>
                     </div>
                     <div class="sjs-height-4">
@@ -174,6 +183,14 @@
         }
     });
 </script>
+<script>
+    const tenders = JSON.parse('<%- JSON.stringify(tenderList) %>');
+    const category = JSON.parse('<%- JSON.stringify(categoryData) %>');
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    $(document).ready(() => {
+        rptCustomObj.initTenderTree(tenders, category);
+    });
+</script>
 
 <script type="text/javascript">  autoFlashHeight();</script>
 <script type="text/javascript" src="/public/jspdf/jspdf.min.js"></script>
@@ -199,9 +216,9 @@
 <script type="text/javascript" src="/public/report/js/jpc_output.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_print.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_signature.js"></script>
-<script type="text/javascript" src="/public/report/js/rpt_custom.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_jspdf.js"></script>
 
+
 <script type="text/javascript">
     const TOP_TREE_NODES = <%- rpt_tpl_data %>;
     let CUST_CFG = <%- cust_cfg %>;

+ 48 - 0
app/view/report/rpt_all_popup.ejs

@@ -269,6 +269,54 @@
         </div>
     </div>
 </div>
+<!--选择标段-->
+<div class="modal fade" id="gather-select" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="gather-select-title">选择标段</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <h5>可选标段 </h5>
+                        <div class="modal-height-300" id="gather-source-spread" style="height: 330px">
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <h5>已选标段 </h5>
+                        <div class="modal-height-300" id="gather-result-spread">
+                        </div>
+                        <div class="mt-1" id="gather-by-month" style="width: 60%">
+                            <div class="input-group input-group-sm">
+                                <div class="input-group-prepend">
+                                    <span class="input-group-text">汇总年月</span>
+                                </div>
+                                <input id="gather-month" class="datepicker-here form-control form-control-sm" auto-close="true" autocomplete="off" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" autocomplete="off">
+                            </div>
+                        </div>
+                        <div id="gather-by-zone">
+                            <div class="input-group input-group-sm">
+                                <div class="input-group-prepend">
+                                    <span class="input-group-text">汇总周期</span>
+                                </div>
+                                <input id="gather-zone" class="datepicker-here form-control mt-0" placeholder="点击选择周期" data-range="true" data-multiple-dates-separator=" - "  data-min-view="months" data-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" autocomplete="off">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="text-danger text-center ml-3" id="gather-hint">我是提示呀</div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button class="btn btn-sm btn-primary" id="gather-select-ok" onclick="rptCustomObj.resetGatherSelect(this)">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
 
 <script>
     zTreeOprObj.getCustomerCfg();

+ 76 - 0
builder_report_index_define.js

@@ -424,6 +424,81 @@ const stage_im_tz_bills = {
     ],
 };
 
+const gather_stage_bills = {
+    name: '汇总-清单数据表(mem_gather_stage_bills)',
+    remark: '',
+    id: 33,
+    key: 'mem_gather_stage_bills',
+    prefix: '汇总-清单数据',
+    cols: [
+        { name: '项目节编号', field: 'code', type: dataType.str },
+        { name: '清单编号', field: 'b_code', type: dataType.str },
+        { name: '名称', field: 'name', type: dataType.str },
+        { name: '单位', field: 'unit', type: dataType.str },
+        { name: '单价', field: 'unit_price', type: dataType.currency },
+
+        { name: '标段id', field: 't_id', type: dataType.int },
+        { name: '标段-名称', field: 't_name', type: dataType.int },
+
+        { name: '(标段)台账-数量', field: 't_qty', type: dataType.currency },
+        { name: '(标段)台账-金额', field: 't_tp', type: dataType.currency },
+
+        { name: '(标段)本期-合同-数量', field: 't_contract_qty', type: dataType.currency },
+        { name: '(标段)本期-合同-金额', field: 't_contract_tp', type: dataType.currency },
+        { name: '(标段)本期-变更-数量', field: 't_qc_qty', type: dataType.currency },
+        { name: '(标段)本期-变更-金额', field: 't_qc_tp', type: dataType.currency },
+        { name: '(标段)本期-完成-数量', field: 't_gather_qty', type: dataType.currency },
+        { name: '(标段)本期-完成-金额', field: 't_gather_tp', type: dataType.currency },
+
+        // { name: '(标段)截止上期-合同-数量', field: 't_pre_contract_qty', type: dataType.currency },
+        // { name: '(标段)截止上期-合同-金额', field: 't_pre_contract_tp', type: dataType.currency },
+        // { name: '(标段)截止上期-变更-数量', field: 't_pre_qc_qty', type: dataType.currency },
+        // { name: '(标段)截止上期-变更-金额', field: 't_pre_qc_tp', type: dataType.currency },
+        // { name: '(标段)截止上期-完成-数量', field: 't_pre_gather_qty', type: dataType.currency },
+        // { name: '(标段)截止上期-完成-金额', field: 't_pre_gather_tp', type: dataType.currency },
+        //
+        // { name: '(标段)截止本期-合同-数量', field: 't_end_contract_qty', type: dataType.currency },
+        // { name: '(标段)截止本期-合同-金额', field: 't_end_contract_tp', type: dataType.currency },
+        // { name: '(标段)截止本期-变更-数量', field: 't_end_qc_qty', type: dataType.currency },
+        // { name: '(标段)截止本期-变更-金额', field: 't_end_qc_tp', type: dataType.currency },
+        // { name: '(标段)截止本期-完成-数量', field: 't_end_gather_qty', type: dataType.currency },
+        // { name: '(标段)截止本期-完成-金额', field: 't_end_gather_tp', type: dataType.currency },
+
+        { name: '(合计)台账-数量', field: 's_ty', type: dataType.currency },
+        { name: '(合计)台账-金额', field: 's_tp', type: dataType.currency },
+
+        { name: '(合计)本期-合同-数量', field: 's_contract_qty', type: dataType.currency },
+        { name: '(合计)本期-合同-金额', field: 's_contract_tp', type: dataType.currency },
+        { name: '(合计)本期-变更-数量', field: 's_qc_qty', type: dataType.currency },
+        { name: '(合计)本期-变更-金额', field: 's_qc_tp', type: dataType.currency },
+        { name: '(合计)本期-完成-数量', field: 's_gather_qty', type: dataType.currency },
+        { name: '(合计)本期-完成-金额', field: 's_gather_tp', type: dataType.currency },
+
+        // { name: '(合计)截止上期-合同-数量', field: 's_pre_contract_qty', type: dataType.currency },
+        // { name: '(合计)截止上期-合同-金额', field: 's_pre_contract_tp', type: dataType.currency },
+        // { name: '(合计)截止上期-变更-数量', field: 's_pre_qc_qty', type: dataType.currency },
+        // { name: '(合计)截止上期-变更-金额', field: 's_pre_qc_tp', type: dataType.currency },
+        // { name: '(合计)截止上期-完成-数量', field: 's_pre_gather_qty', type: dataType.currency },
+        // { name: '(合计)截止上期-完成-金额', field: 's_pre_gather_tp', type: dataType.currency },
+        //
+        // { name: '(合计)截止本期-合同-数量', field: 's_end_contract_qty', type: dataType.currency },
+        // { name: '(合计)截止本期-合同-金额', field: 's_end_contract_tp', type: dataType.currency },
+        // { name: '(合计)截止本期-变更-数量', field: 's_end_qc_qty', type: dataType.currency },
+        // { name: '(合计)截止本期-变更-金额', field: 's_end_qc_tp', type: dataType.currency },
+        // { name: '(合计)截止本期-完成-数量', field: 's_end_gather_qty', type: dataType.currency },
+        // { name: '(合计)截止本期-完成-金额', field: 's_end_gather_tp', type: dataType.currency },
+
+        { name: '(特殊1-需替换key1)台账-数量', field: 'ts_key1_qty', type: dataType.currency },
+        { name: '(特殊1-需替换key1)台账-金额', field: 'ts_key1_tp', type: dataType.currency },
+
+        { name: '(特殊2-需替换key2)台账-数量', field: 'ts_key2_qty', type: dataType.currency },
+        { name: '(特殊2-需替换key2)台账-金额', field: 'ts_key2_tp', type: dataType.currency },
+
+        { name: '(特殊3-需替换key3)台账-数量', field: 'ts_key3_qty', type: dataType.currency },
+        { name: '(特殊3-需替换key3)台账-金额', field: 'ts_key3_tp', type: dataType.currency },
+    ],
+};
+
 const recursiveMkdirSync = async function(pathName) {
     if (!fs.existsSync(pathName)) {
         const upperPath = path.dirname(pathName);
@@ -519,6 +594,7 @@ const defines = [stage_jgcl, stage_bonus, stage_other,
     stage_pos, stage_pos_compare,
     stage_pay,
     stage_im_zl, stage_im_tz, stage_im_tz_bills,
+    gather_stage_bills,
 ];
 for (const d of defines) {
     exportTableDefine(d);

+ 17 - 0
config/web.js

@@ -549,6 +549,23 @@ const JsFiles = {
                 ],
                 mergeFile: 'gather_stage',
             },
+        },
+        report: {
+            main: {
+                files: [
+                    "/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js",
+                    "/public/js/decimal.min.js",
+                    "/public/js/moment/moment.min.js",
+                ],
+                mergeFiles: [
+                    "/public/js/zh_calc.js",
+                    "/public/js/path_tree.js",
+                    "/public/js/spreadjs_rela/spreadjs_zh.js",
+                    "/public/js/shares/tenders2tree.js",
+                    "/public/report/js/rpt_custom.js",
+                ],
+                mergeFile: 'report_main',
+            }
         }
     }
 

+ 64 - 0
test/app/service/rpt_gather_memory.test.js

@@ -0,0 +1,64 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2020/4/17
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const path = require('path');
+let savePath;
+const mockData = {};
+
+describe('test/app/service/rpt_gather_memory.test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        savePath = path.join(ctx.app.baseDir,'report_temp');
+        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 getGatherStageBills - month', function* () {
+        const ctx = app.mockContext(mockData);
+
+        const select = {
+            tenders: [{tid: 2256}, {tid: 2257}, {tid: 2258, gs: true}],
+            type: 'month',
+            month: '2020-01',
+        };
+        const define = {
+            enable: true,
+            setting: {
+                title: '请选择汇总的标段',
+                type: 'month',
+                special: [
+                    {"title": "批复概算", "key": "gs"}
+                ]
+            }
+        };
+
+        const mem_gather_stage_bills = yield ctx.service.rptGatherMemory.getGatherStageBills([], define, select);
+        yield ctx.helper.saveBufferFile(JSON.stringify(mem_gather_stage_bills, "", "\t"), path.join(savePath, 'mem_gather_stage_bills.json'));
+
+        const reportDataAnalysis = require('../../../app/lib/rpt_data_analysis');
+        // 配合部位明细
+        const reportData = {mem_gather_stage_bills: mem_gather_stage_bills};
+        reportDataAnalysis.analysisObj.gatherSelectConverse.fun(ctx, reportData, [], {table: ["mem_gather_stage_bills"]}, {
+            cDefine: { gather_select: select },
+            tplDefine: {gather_select: define },
+        });
+        yield ctx.helper.saveBufferFile(JSON.stringify(reportData, "", "\t"), path.join(savePath, 'mem_gather_stage_bills_coverse.json'));
+        assert(reportData.mem_gather_stage_bills.length === mem_gather_stage_bills.length * 2);
+    });
+});