Просмотр исходного кода

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

TonyKang 5 лет назад
Родитель
Сommit
11770cb2e3

+ 30 - 0
app/const/standard.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const nodeType = [
+    {text: '', value: 0},
+    {text: '建安费', value: 1},
+    {text: '设备及工(器)具费', value: 2},
+    {text: '工程建设其他费', value: 3},
+    {text: '土地拆迁补偿', value: 4},
+    {text: '预备费', value: 5},
+    {text: '暂列金额', value: 6},
+    {text: '计日工', value: 7},
+    {text: '价差调整', value: 8},
+    {text: '索赔', value: 9},
+    {text: '新增费用', value: 10},
+    {text: '其他费用', value: 11},
+    {text: '回收金额', value: 12},
+    {text: '建设期贷款利息', value: 13},
+];
+
+module.exports = {
+    nodeType,
+};

+ 6 - 2
app/controller/report_controller.js

@@ -416,6 +416,10 @@ async function getReportData(ctx, params, filters, memFieldKeys) {
                     runnableRst.push(ctx.service.reportMemory.getStagePosData(params.tender_id, params.stage_id, memFieldKeys[filter]));
                     runnableKey.push(filter);
                     break;
+                case 'mem_stage_bills_compare':
+                    runnableRst.push(ctx.service.reportMemory.getStagePosData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                    runnableKey.push(filter);
+                    break;
                 case 'change':
                     runnableRst.push(ctx.service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
                     runnableKey.push('change');
@@ -455,7 +459,7 @@ async function getAllPagesCommon(ctx, rptTpl, params, option, outputType, baseDi
     // console.log(rptTpl);
     const filter = rptDataUtil.getDataRequestFilter();
     // console.log(filter.tables);
-    const rawDataObj = await getReportData(ctx, params, filter.tables, filter.memFieldKeys);
+    const rawDataObj = await ctx.service.report.getReportData(params, filter.tables, filter.memFieldKeys);
     // console.log(rawDataObj);
     try {
         const printCom = JpcEx.createNew();
@@ -543,7 +547,7 @@ async function getMultiRptsCommon(ctx, params, outputType, baseDir) {
         }
     }
 
-    const rawDataObj = await getReportData(ctx, params, filterTables, memFieldKeys);
+    const rawDataObj = await ctx.service.report.getReportData(params, filterTables, memFieldKeys);
     try {
         const rptPageRstArray = [];
         // 1. 这里只用一份数据,根据实际应用情况,先备份

+ 32 - 32
app/lib/analysis_excel.js

@@ -533,41 +533,41 @@ class AnalysisGclExcelTree {
             this.cacheTree = new ImportGclBaseTree(this.ctx, parent, maxId, defaultData);
             this.errorData = [];
             this.loadEnd = false;
-
-            // for (const iRow in sheet.rows) {
-            //     const row = sheet.rows[iRow];
-            //     if (this.colsDef && !this.loadEnd) {
-            //         const result = this.loadRowData(row);
-            //         // 读取失败则写入错误数据 todo 返回前端告知用户?
-            //         if (!result) {
-            //             this.errorData.push({
-            //                 serialNo: iRow,
-            //                 data: row,
-            //             });
-            //         }
-            //     } else {
-            //         this.checkColHeader(row);
-            //     }
-            // }
-            // 固定列导入
-            this.colsDef = {
-                b_code: 0,
-                name: 1,
-                unit: 2,
-                quantity: 3,
-                unit_price: 4
-            };
-            for (let iRow = 1, iLen = sheet.rows.length; iRow < iLen; iRow++) {
+            // 识别表头导入
+            for (const iRow in sheet.rows) {
                 const row = sheet.rows[iRow];
-                const result = this.loadRowData(row);
-                // 读取失败则写入错误数据 todo 返回前端告知用户?
-                if (!result) {
-                    this.errorData.push({
-                        serialNo: iRow,
-                        data: row,
-                    });
+                if (this.colsDef && !this.loadEnd) {
+                    const result = this.loadRowData(row);
+                    // 读取失败则写入错误数据 todo 返回前端告知用户?
+                    if (!result) {
+                        this.errorData.push({
+                            serialNo: iRow,
+                            data: row,
+                        });
+                    }
+                } else {
+                    this.checkColHeader(row);
                 }
             }
+            // 固定列导入
+            // this.colsDef = {
+            //     b_code: 0,
+            //     name: 1,
+            //     unit: 2,
+            //     quantity: 3,
+            //     unit_price: 4
+            // };
+            // for (let iRow = 1, iLen = sheet.rows.length; iRow < iLen; iRow++) {
+            //     const row = sheet.rows[iRow];
+            //     const result = this.loadRowData(row);
+            //     // 读取失败则写入错误数据 todo 返回前端告知用户?
+            //     if (!result) {
+            //         this.errorData.push({
+            //             serialNo: iRow,
+            //             data: row,
+            //         });
+            //     }
+            // }
             return this.cacheTree;
         } catch(err) {
             this.ctx.helper.log(err);

+ 224 - 37
app/lib/rpt_data_analysis.js

@@ -8,6 +8,8 @@
  * @version
  */
 
+const math = require('mathjs');
+const standard = require('../const/standard');
 const changeSort = {
     name: '变更令排序',
     hint: '默认的变更令排序,同时对变更令,变更清单进行排序\n' +
@@ -19,8 +21,9 @@ const changeSort = {
      * @param ctx - context常量
      * @param data - 全部数据源{Array}
      * @param fieldsKey - 计算字段
+     * @param options - 计算设置
      */
-    fun: function(ctx, data, fieldsKey) {
+    fun: function(ctx, data, fieldsKey, options) {
         if (!data.change) return;
         // 变更令排序
         data.change.sort(function (a, b) {
@@ -51,7 +54,7 @@ const gatherGcl = {
         '  树结构-层级(level), 树结构-同层排序(order), 树结构-完整路径(full_path),\n' +
         '  图册号(drawing_code), 备注(memo), 节点类型(node_type), 总额计量(is_tp)\n' +
         '3. 如需汇总"未计入清单章节项",请勾选"章节编号(chapter)"字段\n',
-    fun: function (ctx, data, fieldsKey) {
+    fun: function (ctx, data, fieldsKey, options) {
         const gatherFields = function(gcl, data, fields) {
             for (const f of fields) {
                 if (data[f]) {
@@ -124,7 +127,7 @@ const sortGcl = {
         '特别的,如有"未计入清单章节项": \n' +
         '  1. 默认"未计入清单章节项"排列在最后\n' +
         '  2. 如须"未计入清单章节项"排在100章之后,请在清单编号字段后,依次勾选"章节编号(chapter)", "名称(name)"\n',
-    fun: function (ctx, data, fieldsKey) {
+    fun: function (ctx, data, fieldsKey, options) {
         if (fieldsKey.length !== 1 && fieldsKey.length !== 3) return;
         const code = fieldsKey[0].field;
         const chapter = fieldsKey.length > 1 ? fieldsKey[1].field : '';
@@ -161,46 +164,144 @@ const gatherChapter = {
         'e.g.2 要对mem_stage_bills_compare汇总,须勾选mem_stage_bills_compare下的"清单编号(b_code)", "树结构-是否子项((is_leaf)"字段\n' +
         '结果:\n' +
         '汇总结果可参照 清单汇总--章节合计,但是不过滤1000-1300章数据',
-    fun: function (ctx, data, fieldsKey) {
-        if (!data.tender_info || !data.tender_info.chapter) return;
-        if (!fieldsKey && fieldsKey.length < 0) return;
+    defaultSetting: {
+        count: 9,
+        unChapter: {
+            name: '未计入清单章节合计',
+            order: 1,
+        },
+        gclSum: {
+            name: '清单小计',
+            order: 2,
+        },
+        unGcl: {
+            name: '非清单项费用',
+            order: 3,
+        },
+        sum: {
+            name: '合计',
+            order: 4,
+        }
+    },
+    customSetting1: {
+        count: 7,
+        gclSum: {
+            name: '第100章至700章清单合计',
+            order: 1,
+        },
+        custom: [
+            {name: '已包含在清单合计中的材料、工程设备、专业工程暂估价', order: 2},
+            {name: '清单合计减去材料、工程设备、专业工程暂估价(即8-9=10)', order_calc: 'o1-o2', order: 3},
+            {name: '计日工合计', node_type: '计日工', order: 4},
+            {name: '暂列金额(不含计日工总额)(即10×暂列金额比列)', node_type: '暂列金额', order: 5},
+            {name: '投标报价、台账价(8+11+12)=13', order_calc: 'o1+o4+o5', order: 6},
+        ],
+    },
+    _getCalcChapter: function (chapter, options) {
+        const gclChapter = [], otherChapter = [], customChapter = [];
 
-        const getCalcChapter = function (chapter) {
-            const gclChapter = [], otherChapter = [];
-            let serialNo = 1;
-            for (const c of chapter) {
-                const cc = { code: c.code, name: c.name, cType: 1 };
-                cc.serialNo = serialNo++;
-                cc.filter = '^' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
-                gclChapter.push(cc);
-            }
-            gclChapter.push({ name: '未计入章节清单合计', cType: 21, serialNo: serialNo++, });
-            otherChapter.push({ name: '清单小计(A)', cType: 11, serialNo: serialNo++ });
-            otherChapter.push({ name: '非清单项费用(B)', cType: 31, serialNo: serialNo++ });
-            otherChapter.push({ name: '合计(C=A+B)', cType: 41, serialNo: serialNo });
-            return [gclChapter, otherChapter];
-        };
-        const getGclChapter = function (chapter, data) {
-            for (const c of chapter) {
-                if (c.filter) {
-                    const reg = new RegExp(c.filter);
-                    if (reg.test(data.b_code)) {
-                        return c;
-                    }
+        let serialNo = 1;
+        for (const c of chapter) {
+            const cc = { code: c.code, name: c.name, cType: 1 };
+            cc.serialNo = serialNo++;
+            cc.filter = '^' + c.code.substr(0, c.code.length - 2) + '[0-9]{2}-';
+            gclChapter.push(cc);
+
+            if (serialNo > options.count) break;
+        }
+
+        if (options.unChapter) {
+            gclChapter.push({ name: options.unChapter.name, cType: 21, serialNo: serialNo + options.unChapter.order, order: options.unChapter.order });
+        }
+        if (options.gclSum) {
+            otherChapter.push({ name: options.gclSum.name, cType: 11, serialNo: serialNo + options.gclSum.order, order: options.gclSum.order  });
+        }
+        if (options.unGcl) {
+            otherChapter.push({ name: options.unGcl.name, cType: 31, serialNo: serialNo + options.unGcl.order, order: options.unGcl.order  });
+        }
+        if (options.sum) {
+            otherChapter.push({ name: options.sum.name , cType: 41, serialNo: serialNo + options.sum.order, order: options.sum.order  });
+        }
+
+        if (options.custom && options.custom instanceof Array) {
+            for (const c of options.custom) {
+                const cc = {
+                    name: c.name, serialNo: serialNo + c.order,
+                    order_calc: c.order_calc,
+                    cType: 5, order: c.order,
+                };
+                if (c.node_type && c.node_type !== '') {
+                    const nodeType = standard.nodeType.find(function (x) {return x.text === c.node_type});
+                    cc.node_type = nodeType.value;
                 } else {
+                    cc.node_type = -1;
+                }
+                customChapter.push(cc);
+            }
+        }
+        return [gclChapter, otherChapter, customChapter];
+    },
+    _getGclChapter: function (chapter, data) {
+        for (const c of chapter) {
+            if (c.filter) {
+                const reg = new RegExp(c.filter);
+                if (reg.test(data.b_code)) {
                     return c;
                 }
+            } else {
+                return c;
             }
-        };
-        const gatherData = function(chapter, data) {
+        }
+    },
+    _checkFilter: function (fullPath, filter) {
+        for (const f of filter) {
+            if (fullPath.indexOf(f + '-') === 0 || fullPath === f) return true;
+        }
+        return false;
+    },
+    _orderCalc: function (ctx, chapter, fields) {
+        const orderMatch = new RegExp('o[0-9]+', 'igm');
+        for (const c of chapter) {
+            if (c.order_calc && c.order_calc !== '') {
+                const matchs = c.order_calc.match(orderMatch);
+                const calcMatch = [];
+                for (const m of matchs) {
+                    const order = m.substring(1, m.length);
+                    const orderChapter = chapter.find(function (x) {return x.order == order});
+                    if (orderChapter) {
+                        calcMatch.push({match: m, value: orderChapter})
+                    }
+                }
+                for (const f of fields) {
+                    let expr = c.order_calc;
+                    for (const m of calcMatch) {
+                        expr = expr.replace(m.match, m.value[f] ? m.value[f] : 0);
+                    }
+                    try {
+                        c[f] = ctx.helper.round(math.eval(expr), 6);
+                    } catch(err) {
+                    }
+                }
+            }
+        }
+    },
+    fun: function (ctx, data, fieldsKey, options) {
+        if (!data.tender_info || !data.tender_info.chapter) return;
+        if (!fieldsKey && fieldsKey.length < 0) return;
+
+        const calcFields = [];
+        const gatherData = function (chapter, data) {
+            if (!chapter) return;
+
             for (const f in data) {
                 if (data[f] && (f.indexOf('tp') >= 0 || f === 'total_price')) {
                     chapter[f] = ctx.helper.add(chapter[f], data[f]);
+                    if (calcFields.indexOf(f) === -1) calcFields.push(f);
                 }
             }
         };
 
-        const [gclChapter, otherChapter] = getCalcChapter(data.tender_info.chapter);
+        const [gclChapter, otherChapter, customChapter] = this._getCalcChapter(data.tender_info.chapter, options ? options : this.defaultSetting);
         const fields = ctx.helper._.map(fieldsKey, 'field');
         const needFields = ['b_code', 'is_leaf'];
         for (const nf of needFields) {
@@ -208,32 +309,118 @@ const gatherChapter = {
         }
         const sourceData = data[fieldsKey[0].table];
         if (!sourceData) return;
+        const filter = [];
         for (const d of sourceData) {
-            if (!d.is_leaf) continue;
+            for (const c of customChapter) {
+                if (c.node_type && c.node_type > 0 && c.node_type === d.node_type) {
+                    gatherData(c, d);
+                    filter.push(d.full_path);
+                }
+            }
+
+            if (!d.is_leaf || this._checkFilter(d.full_path, filter)) continue;
 
             for (const c of otherChapter) {
                 if (c.cType === 41) {
                     gatherData(c, d);
                 } else if (c.cType === 31 && (!d.b_code || d.b_code === '')) {
-                    gatherData(c, d, fields);
+                    gatherData(c, d);
                 } else if (c.cType === 11 && (d.b_code)) {
-                    gatherData(c, d, fields);
+                    gatherData(c, d);
                 }
             }
             if (d.b_code) {
-                const c = getGclChapter(gclChapter, d);
-                gatherData(c, d, fields);
+                const c = this._getGclChapter(gclChapter, d);
+                gatherData(c, d);
             }
         }
-        data[fieldsKey[0].table] = gclChapter.concat(otherChapter);
+        const chapter = gclChapter.concat(otherChapter).concat(customChapter);
+        this._orderCalc(ctx, chapter, calcFields);
+        chapter.sort(function (a, b) {return a.serialNo - b.serialNo});
+
+        data[fieldsKey[0].table] = chapter;
     },
 };
+const join = {
+    name: "连接两张数据表",
+    hint: "用于处理类似于关联签约清单的情况,会改变主表的数据",
+    defaultSetting: {
+        main: 'mem_stage_bills',
+        sub: 'deal_bills',
+        keyFields: [
+            {main: 'b_code', sub: 'code', type: 'string'},
+            {main: 'name', sub: 'name',type: 'string'},
+            {main: 'unit', sub: 'unit',type: 'string'},
+            {main: 'unit_price', sub: 'unit_price',type: 'number'},
+        ],
+        importFields: [
+            {main: 'ex_value1', sub: 'quantity', type: 'number'},
+            {main: 'ex_value2', sub: 'total_price', type: 'number'}
+        ],
+        joinType: 'outer', //'outer', 'main', 'sub', 'inner',
+    },
+    fun: function (ctx, data, fields, options) {
+        if (!options || !options.main || !options.sub || !options.keyFields || options.keyFields.length === 0) return;
+
+        const main = data[options.main];
+        const sub = data[options.sub];
+        if (!main || !sub) return;
+
+        const _ = ctx.helper._, result = _.cloneDeep(main);
+        for (const r of result) {
+            r._join_tag = 'main';
+        }
+        for (const s of sub) {
+            let r = result.find(function (x) {
+                for (const k of options.keyFields) {
+                    switch (k.type) {
+                        case 'string':
+                            if (x[k.main] !== s[k.sub] && (!_.isNil(x[k.main]) || !_.isNil(s[k.sub]))) return false;
+                            break;
+                        case 'number':
+                            if (!ctx.helper.checkZero(ctx.helper.sub(x[k.main] - s[k.sub]))) return false;
+                            break;
+                    }
+                }
+                return true;
+            });
+            if (r && r._join_tag === 'main') {
+                r._join_tag = 'both';
+            }
+            if (!r) {
+                r = {_join_tag: 'sub'};
+                for (const k of options.keyFields) {
+                    r[k.main] = s[k.sub];
+                }
+                result.push(r);
+            }
+            for (const i of options.importFields) {
+                r[i.main] = s[i.sub];
+            }
+        }
+        switch (options.joinType) {
+            case 'main':
+                data[options.main] = _.filter(result, function (r) {return ['main', 'both'].indexOf(r._join_tag) >= 0});
+                break;
+            case 'sub':
+                data[options.main] = _.filter(result, function (r) {return ['sub', 'both'].indexOf(r._join_tag) >= 0});
+                break;
+            case 'inner':
+                data[options.main] = _.filter(result, function (r) {return r._join_tag === 'both'});
+                break;
+            case 'outer':
+                data[options.main] = result;
+                break;
+        }
+    }
+};
 
 const analysisObj = {
     changeSort,
     gatherGcl,
     sortGcl,
     gatherChapter,
+    join,
 };
 const analysisDefine = (function (obj) {
     const result = [];

+ 21 - 2
app/service/deal_bills.js

@@ -32,6 +32,7 @@ module.exports = app => {
             const transaction = await this.db.beginTransaction();
             try {
                 const bills = [];
+                // 识别表头导入
                 let iCode = -1, iName = -1, iUnit = -1, iUp = -1, iQty = -1, iTp = -1, bCheckCol = false;
                 for (let iRow = 0; iRow < sheet.data.length; iRow++) {
                     const row = sheet.data[iRow];
@@ -39,8 +40,8 @@ module.exports = app => {
                         for (let iCol = 0; iCol < row.length; iCol++) {
                             const value = row[iCol];
                             if (typeof value !== "string") { continue }
-                            if (value === '子目号' || value === '清单编号') iCode = iCol;
-                            if (value.indexOf('名称') >= 0) iName = iCol;
+                            if (['清单编号', '子目号', '子目编号', '编号', '清单编号'].indexOf(value) >= 0) iCode = iCol;
+                            if (['清单名称', '名称', '子目名称'].indexOf(value) >= 0) iName = iCol;
                             if (value.indexOf('单位') >= 0) iUnit = iCol;
                             if (value.indexOf('单价') >= 0) iUp = iCol;
                             if (value.indexOf('数量') >= 0) iQty = iCol;
@@ -73,6 +74,24 @@ module.exports = app => {
                 if (!bCheckCol) {
                     throw '导入的Excel表头定义有误,请下载示例检查';
                 }
+                // 固定列,从第一行开始导入
+                // let iCode = 0, iName = 1, iUnit = 2, iUp = 4, iQty = 3;
+                // for (let iRow = 1; iRow < sheet.data.length; iRow++) {
+                //     const data = {
+                //         deal_id: bills.length + 1,
+                //         tender_id: tenderId,
+                //         code: row[iCode],
+                //         name: row[iName],
+                //         unit: row[iUnit],
+                //         unit_price: row[iUp],
+                //         quantity: row[iQty],
+                //     };
+                //     if (!data.code || data.code === '' || !data.name || data.name === '') continue;
+                //     if ((data.unit_price && !this._.isNumber(data.unit_price)) || (data.quantity && !this._.isNumber(data.quantity))) {
+                //         throw '导入的Excel的数据类型有误,请检查第' + (iRow + 1) + '行';
+                //     }
+                //     bills.push(data);
+                // }
                 if (bills.length > 0) {
                     await transaction.delete(this.tableName, {tender_id: tenderId});
                     const billsResult = await transaction.insert(this.tableName, bills);

+ 8 - 0
app/service/report.js

@@ -37,6 +37,10 @@ module.exports = app => {
                             runnableRst.push(service.tenderInfo.getTenderInfo(params.tender_id));
                             runnableKey.push(filter);
                             break;
+                        case 'deal_bills' :
+                            runnableRst.push(service.dealBills.getDataByTenderId(params.tender_id));
+                            runnableKey.push('deal_bills');
+                            break;
                         case 'ledger' :
                             runnableRst.push(service.ledger.getData(params.tender_id, 0));
                             runnableKey.push(filter);
@@ -75,6 +79,10 @@ module.exports = app => {
                             runnableRst.push(service.reportMemory.getStagePosData(params.tender_id, params.stage_id, memFieldKeys[filter]));
                             runnableKey.push(filter);
                             break;
+                        case 'mem_stage_bills_compare':
+                            runnableRst.push(service.reportMemory.getStageBillsCompareData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                            runnableKey.push(filter);
+                            break;
                         case 'change':
                             runnableRst.push(service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
                             runnableKey.push(filter);

+ 8 - 2
app/service/report_memory.js

@@ -91,7 +91,9 @@ module.exports = app => {
 
         _checkFieldsExist(source, check) {
             for (const s of source) {
-                if (check.indexOf(s)) return true;
+                if (check.indexOf(s) >= 0) {
+                    return true;
+                }
             }
             return false;
         }
@@ -299,6 +301,8 @@ module.exports = app => {
         }
 
         async _calcBillsBgl() {
+            if (!this.ctx.stage) return;
+
             const helper = this.ctx.helper;
             const tender = this.ctx.tender;
             const stage = this.ctx.stage;
@@ -322,7 +326,9 @@ module.exports = app => {
 
         async getStageBillsData(tid, sid, fields) {
             await this.ctx.service.tender.checkTender(tid);
-            await this.ctx.service.stage.checkStage(sid);
+            if (sid) {
+                await this.ctx.service.stage.checkStage(sid);
+            }
 
             const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
             if (this._checkFieldsExist(fields, stageFields)) {

+ 185 - 3
test/app/lib/rpt_data_analysis.test.js

@@ -136,7 +136,6 @@ describe('test/app/service/report_memory.test.js', () => {
                 'chapter',
             ]
         });
-        const bills = data.mem_stage_bills.find(function (x) {return x.b_code === '103-3-a'});
         reportDataAnalysis.analysisObj.gatherChapter.fun(ctx, data, [
             {field: 'b_code', table: 'mem_stage_bills'},
             {field: 'is_leaf', table: 'mem_stage_bills'},
@@ -149,9 +148,176 @@ describe('test/app/service/report_memory.test.js', () => {
         const chapter400 = ctx.helper._.find(data.mem_stage_bills, {code: '400'});
         assert(chapter400.total_price.toFixed(0) == 1231018);
     });
+    it('test gatherChapter', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const params = {
+            tender_id: stage.tid,
+            stage_id: stage.id,
+        };
+        const filters = ['mem_stage_bills', 'tender_info'];
+        const data = yield ctx.service.report.getReportData(params, filters, {
+            mem_stage_bills: [
+                'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+                'code', 'b_code', 'name', 'unit', 'unit_price',
+                'deal_qty', 'deal_tp',
+                'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+                'dgn_qty1', 'dgn_qty2',
+                'drawing_code', 'memo', 'node_type', 'is_tp',
+                'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil',
+                'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
+                'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
+                'final_tp', 'final_ratio',
+                'qc_bgl_code',
+                'chapter',
+            ]
+        });
+        reportDataAnalysis.analysisObj.gatherChapter.fun(ctx, data, [
+            {field: 'b_code', table: 'mem_stage_bills'},
+            {field: 'is_leaf', table: 'mem_stage_bills'},
+        ]);
+        const chapter100 = ctx.helper._.find(data.mem_stage_bills, {code: '100'});
+        assert(chapter100.total_price === 1045756);
+        const chapter200 = ctx.helper._.find(data.mem_stage_bills, {code: '200'});
+        assert(chapter200.total_price.toFixed(0) == 3813369);
+        assert(chapter200.contract_tp == 700.5);
+        const chapter400 = ctx.helper._.find(data.mem_stage_bills, {code: '400'});
+        assert(chapter400.total_price.toFixed(0) == 1231018);
+    });
+    it('test join', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const params = {
+            tender_id: stage.tid,
+            stage_id: stage.id,
+        };
+        const filters = ['mem_stage_bills', 'deal_bills'];
+        const data = yield ctx.service.report.getReportData(params, filters, {
+            mem_stage_bills: [
+                'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+                'code', 'b_code', 'name', 'unit', 'unit_price',
+                'deal_qty', 'deal_tp',
+                'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+                'dgn_qty1', 'dgn_qty2',
+                'drawing_code', 'memo', 'node_type', 'is_tp',
+                'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp', 'postil',
+                'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
+                'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
+                'final_tp', 'final_ratio',
+                'qc_bgl_code',
+                'chapter',
+            ]
+        });
+        assert(data.mem_stage_bills.length === 216);
+        reportDataAnalysis.analysisObj.gatherGcl.fun(ctx, data, [
+            {field: 'b_code', table: 'mem_stage_bills'},
+            {field: 'name', table: 'mem_stage_bills'},
+            {field: 'unit', table: 'mem_stage_bills'},
+            {field: 'unit_price', table: 'mem_stage_bills'},
+            {field: 'is_leaf', table: 'mem_stage_bills'},
+        ]);
+        assert(data.mem_stage_bills.length === 43);
+        const joinOptions = {
+            main: 'mem_stage_bills',
+            sub: 'deal_bills',
+            keyFields: [
+                {main: 'b_code', sub: 'code', type: 'string'},
+                {main: 'name', sub: 'name',type: 'string'},
+                {main: 'unit', sub: 'unit',type: 'string'},
+                {main: 'unit_price', sub: 'unit_price',type: 'number'},
+            ],
+            importFields: [
+                {main: 'ex_value1', sub: 'quantity', type: 'number'},
+                {main: 'ex_value2', sub: 'total_price', type: 'number'}
+            ],
+            joinType: 'outer', //'outer', 'main', 'sub', 'inner'
+        };
+        yield ctx.helper.saveBufferFile(JSON.stringify(joinOptions, '', '\t'), ctx.app.baseDir + '/analysis_join_options.json');
+        reportDataAnalysis.analysisObj.join.fun(ctx, data, [], joinOptions);
+        assert(data.mem_stage_bills.length === 132);
+        reportDataAnalysis.analysisObj.sortGcl.fun(ctx, data, [
+            {field: 'b_code', table: 'mem_stage_bills'},
+        ]);
+        const codeIndex = ctx.helper._.map(data.mem_stage_bills, 'b_code');
+        const codeIndex100 = ['101-1-b', '102-2', '102-3', '102-4', '102-5', '103-1', '103-2',
+            '103-3',  '103-3-a', '103-3-b', '103-4', '104-1', '123-1'];
+        const codeIndex200 = [
+            '203-1-a', '203-1-a', '203-1-b', '203-1-b', '203-1-d', '203-1-d', '204-1-b', '204-1-b',
+            '204-1-c', '204-1-d',
+            '204-1-g-4', '204-1-g-4', '204-1-j', '204-1-j', '205-1-o-1', '205-1-o-1', '206-2', '206-2',
+            '207-1-b',
+            '207-2-a', '207-2-a',
+            '207-2-b', '207-2-c', '207-3-a',
+            '207-4-a', '207-4-a',
+            '207-4-c', '207-10-a',
+            '208-1-a', '208-1-a', '208-3-a', '208-3-a',
+            '208-3-b',
+            '208-3-c', '208-3-c', '208-4-b-1', '208-4-b-1', '209-1-a', '209-1-a',
+            '209-3', '209-3-b', '215-12', '215-12-a'
+        ];
+        let codeResult = codeIndex100.concat(codeIndex200);
+        codeResult.forEach(function (a, i) {
+            assert(a === codeIndex[i]);
+        });
+    });
+    // it('test gatherChapter custom', function* () {
+    //     const ctx = app.mockContext(mockData);
+    //
+    //     const params = {tender_id: 2072};
+    //     const filters = ['mem_stage_bills', 'tender_info'];
+    //     const data = yield ctx.service.report.getReportData(params, filters, {
+    //         mem_stage_bills: [
+    //             'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+    //             'code', 'b_code', 'name', 'unit', 'unit_price',
+    //             'deal_qty', 'deal_tp',
+    //             'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+    //             'dgn_qty1', 'dgn_qty2',
+    //             'drawing_code', 'memo', 'node_type', 'is_tp',
+    //             'qc_bgl_code',
+    //             'chapter',
+    //         ]
+    //     });
+    //     reportDataAnalysis.analysisObj.gatherChapter.fun(ctx, data, [
+    //         {field: 'b_code', table: 'mem_stage_bills'},
+    //         {field: 'is_leaf', table: 'mem_stage_bills'},
+    //     ], {
+    //         count: 7,
+    //         gclSum: {
+    //             name: '第100章至700章清单合计',
+    //             order: 1,
+    //         },
+    //         custom: [
+    //             {name: '已包含在清单合计中的材料、工程设备、专业工程暂估价', order: 2},
+    //             {name: '清单合计减去材料、工程设备、专业工程暂估价(即8-9=10)', order_calc: 'o1-o2', order: 3},
+    //             {name: '计日工合计', node_type: '计日工', order: 4},
+    //             {name: '暂列金额(不含计日工总额)(即10×暂列金额比列)', node_type: '暂列金额', order: 5},
+    //             {name: '投标报价、台账价(8+11+12)=13', order_calc: 'o1+o4+o5', order: 6},
+    //         ],
+    //     });
+    //     const chapter100 = ctx.helper._.find(data.mem_stage_bills, {code: '100'});
+    //     assert(ctx.helper.checkZero(chapter100.total_price));
+    //     const chapter200 = ctx.helper._.find(data.mem_stage_bills, {code: '200'});
+    //     assert(chapter200.total_price === 33211);
+    //     const chapterGclSum = ctx.helper._.find(data.mem_stage_bills, {cType: 11});
+    //     assert(chapterGclSum.total_price == 33211);
+    //     const chapterJRG = ctx.helper._.find(data.mem_stage_bills, {name: '计日工合计'});
+    //     assert(chapterJRG.total_price === 1076);
+    //     const chapterSumWithoutZGJ = ctx.helper._.find(data.mem_stage_bills, {serialNo: 11});
+    //     assert(chapterSumWithoutZGJ.total_price === 33211);
+    //     const chapterZL = ctx.helper._.find(data.mem_stage_bills, {serialNo: 13});
+    //     assert(chapterZL.total_price === 500000);
+    //     const chapterSum = ctx.helper._.find(data.mem_stage_bills, {serialNo: 14});
+    //     assert(chapterSum.total_price === 534287);
+    // });
     it('test analysisDefine', function() {
+        const ctx = app.mockContext(mockData);
+
         const define = reportDataAnalysis.analysisDefine;
-        assert(define.length === 4);
+        assert(define.length === 5);
         assert(define[0].key === 'changeSort');
         assert(define[0].name === reportDataAnalysis.analysisObj.changeSort.name);
         assert(define[0].hint === reportDataAnalysis.analysisObj.changeSort.hint);
@@ -161,5 +327,21 @@ describe('test/app/service/report_memory.test.js', () => {
         assert(define[2].key === 'sortGcl');
         assert(define[2].name === reportDataAnalysis.analysisObj.sortGcl.name);
         assert(define[2].hint === reportDataAnalysis.analysisObj.sortGcl.hint);
+
+        const x = {
+            count: 7,
+            gclSum: {
+                name: '第100章至700章清单合计',
+                order: 1,
+            },
+            custom: [
+                {name: '已包含在清单合计中的材料、工程设备、专业工程暂估价', order: 2},
+                {name: '清单合计减去材料、工程设备、专业工程暂估价(即8-9=10)', order_calc: 'o1-o2', order: 3},
+                {name: '计日工合计', node_type: '计日工', order: 4},
+                {name: '暂列金额(不含计日工总额)(即10×暂列金额比列)', node_type: '暂列金额', order: 5},
+                {name: '投标报价、台账价(8+11+12)=13', order_calc: 'o1+o4+o5', order: 6},
+            ],
+        };
+        ctx.helper.saveBufferFile(JSON.stringify(x, "", "\t"), ctx.app.baseDir + '/mem_stage_pos.json');
     });
-});
+});

+ 51 - 0
test/app/report_data.test.js

@@ -0,0 +1,51 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const mockData = {};
+const tenderId = 2076;
+const stageOrder = 1;
+const postData = {
+    account: 'liangqibo',
+    project: 'P1116',
+    project_password: '123456',
+};
+
+describe('temp_report_test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        // 模拟登录session
+        ctx.session = {};
+        const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
+        assert(loginResult);
+        mockData.session = ctx.session;
+    });
+    it('test temp', function* () {
+        const ctx = app.mockContext(mockData);
+        // test12 - 第4期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: tenderId, order: stageOrder});
+        const mainData = yield ctx.service.reportMemory.getStageBillsCompareData(tenderId, stage.id, [
+            'id', 'tender_id', 'ledger_id', 'ledger_pid', 'level', 'order', 'full_path', 'is_leaf',
+            'code', 'b_code', 'name', 'unit', 'unit_price',
+            'deal_qty', 'deal_tp',
+            'sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp', 'quantity', 'total_price',
+            'dgn_qty1', 'dgn_qty2',
+            'drawing_code', 'memo', 'node_type', 'is_tp',
+            'r0_contract_qty', 'r0_contract_tp', 'r0_qc_qty', 'r0_qc_tp', 'r0_gather_qty', 'r0_gather_tp',
+            'r1_contract_qty', 'r1_contract_tp', 'r1_qc_qty', 'r1_qc_tp', 'r1_gather_qty', 'r1_gather_tp',
+            'pre_contract_qty', 'pre_contract_tp', 'pre_qc_qty', 'pre_qc_tp', 'pre_gather_qty', 'pre_gather_tp',
+            'chapter',
+        ]);
+        if (mainData instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_bills_compare.json');
+        }
+    });
+});

+ 25 - 8
test/app/service/report_memory.test.js

@@ -19,13 +19,33 @@ const dataType = {
 const addFields = function(table, name, field, type) {
     const data = {};
     data.ID = table.ID * 100 + table.items.length + 1;
-    data.Name = name;
+    data.Name = name + '(' + field + ')';
     data.DataType = type;
     data.TableName = table.key;
     data.descr = '';
     data.mapExpression = "$PROJECT.REPORT.getProperty('" + table.key + "', '" + field + "')";
     table.items.push(data);
 };
+const saveTableDefine = async function (ctx, tableDefine, file) {
+    delete tableDefine.ID;
+    delete tableDefine.key;
+    let defineStr = JSON.stringify(tableDefine, "", "\t");
+    const replaceStr = [
+        {match: '"Name"', str: 'Name'},
+        {match: '"remark"', str: 'remark'},
+        {match: '"items"', str: 'items'},
+        {match: '"ID"', str: 'ID'},
+        {match: '"DataType"', str: 'DataType'},
+        {match: '"TableName"', str: 'TableName'},
+        {match: '"descr"', str: 'descr'},
+        {match: '"mapExpression"', str: 'mapExpression'},
+    ];
+    for (const rs of replaceStr) {
+        const reg = new RegExp(rs.match, 'gm');
+        defineStr = defineStr.replace(reg, rs.str);
+    }
+    await ctx.helper.saveBufferFile(defineStr, file);
+};
 
 describe('test/app/service/report_memory.test.js', () => {
     // 准备测试数据
@@ -175,9 +195,7 @@ describe('test/app/service/report_memory.test.js', () => {
 
             addFields(tableDefine, '章节编号', 'chapter', dataType.str);
 
-            delete tableDefine.ID;
-            delete tableDefine.key;
-            yield ctx.helper.saveBufferFile(JSON.stringify(tableDefine,"","\t"), ctx.app.baseDir + '/mem_stage_bills_define.json');
+            yield saveTableDefine(ctx, tableDefine, ctx.app.baseDir + '/mem_stage_bills_define.json');
         }
     });
     // 期部位明细数据
@@ -189,6 +207,7 @@ describe('test/app/service/report_memory.test.js', () => {
         const mainData = yield ctx.service.reportMemory.getStagePosData(12, stage.id);
         if (mainData instanceof Array) {
             yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_pos.json');
+            //yield saveTableDefine(ctx, tableDefine, ctx.app.baseDir + '/mem_stage_bills_compare_define.json');
         }
     });
     // 期 全审核人 数据
@@ -215,7 +234,7 @@ describe('test/app/service/report_memory.test.js', () => {
             tableDefine.Name = '期-清单-全参与人数据表(mem_stage_bills_compare)';
             tableDefine.remark = '';
             tableDefine.ID = 26;
-            tableDefine.key = 'mem_stage_bills';
+            tableDefine.key = 'mem_stage_bills_compare';
             tableDefine.items = [];
             addFields(tableDefine, '台账ID', 'id', dataType.int);
             addFields(tableDefine, '标段ID', 'tender_id', dataType.int);
@@ -270,9 +289,7 @@ describe('test/app/service/report_memory.test.js', () => {
 
             addFields(tableDefine, '章节编号', 'chapter', dataType.str);
 
-            delete tableDefine.ID;
-            delete tableDefine.key;
-            yield ctx.helper.saveBufferFile(JSON.stringify(tableDefine,"","\t"), ctx.app.baseDir + '/mem_stage_bills_compare_define.json');
+            yield saveTableDefine(ctx, tableDefine, ctx.app.baseDir + '/mem_stage_bills_compare_define.json');
         }
     });
 });