Browse Source

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

TonyKang 5 năm trước cách đây
mục cha
commit
281dfc9656

+ 5 - 0
app/base/base_bills_service.js

@@ -35,6 +35,7 @@ class BaseBillsSerivce extends TreeService {
         data.deal_tp = null;
     }
 
+
     /**
      * 从标准数据中提取有效数据
      * @param {Object} stdData - 从标准库中查询所得
@@ -384,6 +385,10 @@ class BaseBillsSerivce extends TreeService {
                 if (row.b_code) {
                     row.dgn_qty1 = null;
                     row.dgn_qty2 = null;
+                    row.code = null;
+                }
+                if (row.code) {
+                    row.b_code = null;
                 }
                 if (this._checkCalcField(row)) {
                     let calcData = JSON.parse(JSON.stringify(row));

+ 17 - 0
app/const/spread.js

@@ -106,6 +106,8 @@ const withClGcl = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '项目节数量|数量1',  colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 60, type: 'Number'},
             {title: '|数量2',  colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 60, type: 'Number'},
             {title: '经济指标',  colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
@@ -226,6 +228,8 @@ const stageCl = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -293,6 +297,8 @@ const stageNoCl = {
             {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true},
             {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'},
             {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '签约|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_qty', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
             {title: '台账|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
             {title: '本期合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -497,6 +503,16 @@ measure.compare.pos = {
     font: '12px 微软雅黑',
 };
 
+const blank = {
+    cols: [],
+    emptyRows: 0,
+    headRows: 2,
+    headRowHeight: [32],
+    defaultRowHeight: 21,
+    headerFont: '12px 微软雅黑',
+    font: '12px 微软雅黑',
+};
+
 
 module.exports = {
     withCl,
@@ -510,4 +526,5 @@ module.exports = {
     stageCompare,
     filterCols: { tzWithoutCols, dgnCols, clCols, stageDgnCols},
     measure,
+    blank,
 };

+ 4 - 2
app/controller/ledger_audit_controller.js

@@ -36,9 +36,11 @@ module.exports = app => {
                 });
             }
             const tender = this.ctx.tender;
-            const setting = tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl;
+            const setting = tender.data.measure_type === measureType.tz.value
+                ? (tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl)
+                : (tender.info.display.ledger.clQty ? spreadConst.withClGcl : spreadConst.withoutClGcl);
             const ledger = JSON.parse(JSON.stringify(setting.ledger));
-            const pos = JSON.parse(JSON.stringify(setting.pos));
+            const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank;
             ledger.readOnly = true;
             pos.readOnly = true;
             if (tender.data.measure_type === measureType.tz.value) {

+ 1 - 1
app/controller/ledger_controller.js

@@ -86,7 +86,7 @@ module.exports = app => {
             // setColFormat(ledger.cols, 'dgn_price', upFormatter);
             // setColFormat(ledger.cols, 'total_price', tpFormatter);
             // setColFormat(ledger.cols, 'deal_tp', tpFormatter);
-            const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : {};
+            const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank;
 
             if (this._ledgerReadOnly(tender.data)) {
                 ledger.readOnly = true;

+ 12 - 4
app/controller/report_controller.js

@@ -361,13 +361,21 @@ async function getReportData(ctx, params, filters, memFieldKeys) {
                     break;
                 case 'mem_stage_im_zl':
                     // memFieldKeys[filter]
-                    runnableRst.push(ctx.service.reportMemory.getStageImZlData(params.tender_id, params.stage_id));
+                    runnableRst.push(ctx.service.reportMemory.getStageImZlData(params.tender_id, params.stage_id, memFieldKeys[filter]));
                     runnableKey.push('mem_stage_im_zl');
                     break;
                 case 'mem_month_progress':
-                    runnableRst.push(ctx.service.reportMemory.getMonthProgress(params.tender_id));
+                    runnableRst.push(ctx.service.reportMemory.getMonthProgress(params.tender_id, memFieldKeys[filter]));
                     runnableKey.push('mem_month_progress');
                     break;
+                case 'mem_stage_bills':
+                    runnableRst.push(ctx.service.reportMemory.getStageBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                    runnableKey.push('mem_stage_pos');
+                    break;
+                case 'mem_stage_pos':
+                    runnableRst.push(ctx.service.reportMemory.getStagePosData(params.tender_id, params.stage_id, memFieldKeys[filter]));
+                    runnableKey.push('mem_stage_pos');
+                    break;
                 case 'change':
                     runnableRst.push(ctx.service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
                     runnableKey.push('change');
@@ -388,10 +396,10 @@ async function getReportData(ctx, params, filters, memFieldKeys) {
     for (const filter of filters) {
         switch (filter) {
             case 'mem_stage_im_tz':
-                rst[filter] = await ctx.service.reportMemory.getStageImTzData(params.tender_id, params.stage_id);
+                rst[filter] = await ctx.service.reportMemory.getStageImTzData(params.tender_id, params.stage_id, memFieldKeys[filter]);
                 break;
             case 'mem_stage_im_tz_bills':
-                rst[filter] = await ctx.service.reportMemory.getStageImTzBillsData(params.tender_id, params.stage_id);
+                rst[filter] = await ctx.service.reportMemory.getStageImTzBillsData(params.tender_id, params.stage_id, memFieldKeys[filter]);
                 break;
             default:
                 break;

+ 1 - 1
app/controller/revise_controller.js

@@ -193,7 +193,7 @@ module.exports = app => {
                 ? (tender.info.display.ledger.clQty ? spreadConst.withCl : spreadConst.withoutCl)
                 : (tender.info.display.ledger.clQty ? spreadConst.withClGcl : spreadConst.withoutClGcl);
             const ledger = JSON.parse(JSON.stringify(setting.ledger));
-            const pos = JSON.parse(JSON.stringify(setting.pos));
+            const pos = setting.pos ? JSON.parse(JSON.stringify(setting.pos)) : spreadConst.blank;
 
             if (revise.status === audit.revise.status.checking || revise.status === audit.revise.status.checked) {
                 ledger.readOnly = true;

+ 1 - 0
app/lib/analysis_excel.js

@@ -324,6 +324,7 @@ class AnalysisExcelTree {
         const pos = {};
         pos.name = this.ctx.helper.replaceReturn(row[this.colsDef.name]);
         pos.quantity = aeUtils.toNumber(row[this.colsDef.quantity]);
+        pos.sgfh_qty = pos.quantity;
         pos.drawing_code = this.ctx.helper.replaceReturn(row[this.colsDef.drawing_code]);
         return this.cacheTree.addPos(pos);
     }

+ 16 - 0
app/lib/ledger.js

@@ -244,6 +244,18 @@ class billsTree {
             }
         }
     }
+
+    getDatas (fields) {
+        const datas = [];
+        for (const node of this.nodes) {
+            const data = {};
+            for (const field of fields) {
+                data[field] = node[field];
+            }
+            datas.push(data);
+        }
+        return datas;
+    }
 }
 
 class pos {
@@ -306,6 +318,10 @@ class pos {
             this.setting.calc(pos);
         }
     }
+
+    getDatas () {
+        return this.datas;
+    }
 }
 
 module.exports = {

+ 42 - 0
app/lib/rpt_data_analysis.js

@@ -0,0 +1,42 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const changeSort = {
+    name: '变更令排序',
+    hint: '默认的变更令排序,同时对变更令,变更清单进行排序',
+    /**
+     *
+     * @param ctx - context常量
+     * @param data - 全部数据源{Array}
+     * @param fieldsKey - 计算字段
+     */
+    fun: function (ctx, data, fieldsKey) {
+        if (!data.change || data.change_audit_list) return;
+        // 变更令排序
+        data.change.sort(function (a, b) {
+            return a.code.localeCompare(b.code);
+        };
+        data.change_audit_list.sort(function (a, b) {
+            const aCIndex = data.change.findIndex(function (c) {
+                return c.cid === a.cid;
+            });
+            const bCIndex = data.change.findIndex(function (c) {
+                return c.cid === b.cid;
+            });
+            return aCIndex === bCIndex
+                ? ctx.helper.compareCode(a.code, b.code)
+                : aCIndex - bCindex;
+        })
+    },
+};
+
+module.exports = {
+    changeSort,
+};

+ 56 - 12
app/public/js/change_set.js

@@ -46,7 +46,7 @@ $(document).ready(() => {
     for (const ggd in gclGatherData) {
         gclGatherData[ggd].code = gclGatherData[ggd].b_code;
     }
-    console.log(gclGatherData);
+    // console.log(gclGatherData);
     // 数组去重
     for (const db of gclGatherData) {
         const exist_index = dealBillList.findIndex(function (item) {
@@ -323,9 +323,7 @@ $(document).ready(() => {
 
     // 打开签约清单modal并删除之前的操作
     $('#open-list-modal').click(function () {
-       $('#table-list-select tr').removeClass('table-success');
-       $('#table-list-select tr').attr('data-bwmx', '');
-       $('#code-list').html('');
+        tableDataRemake(changeListData);
     });
 
     // 清单选中和移除
@@ -335,7 +333,7 @@ $(document).ready(() => {
         const isCheck = $(this).hasClass('table-success') ? true : false;
         const data_bwmx = $(this).attr('data-bwmx').split('$#$');
         const isDeal = $(this).data('gcl') !== undefined ? true : false;
-        let codeHtml = '<tr quantity="0"><td colspan="4" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
+        let codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'"><td colspan="4" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox"></td></tr>';
         if (isDeal) {
             const gcl = gclGatherData[$(this).data('gcl')];
             codeHtml = '';
@@ -351,7 +349,7 @@ $(document).ready(() => {
                     '></td></tr>';
             }
         } else if (!isDeal && isCheck) {
-            codeHtml = '<tr quantity="0"><td colspan="4" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
+            codeHtml = '<tr quantity="'+ $(this).children('td').eq(5).text() +'"><td colspan="4" class="colspan_1">&nbsp;</td><td class="colspan_2"><input type="checkbox" checked></td></tr>';
         }
         $('#code-list').attr('data-index', $(this).children('td').eq(0).text());
         $('#code-list').html(codeHtml);
@@ -443,6 +441,8 @@ $(document).ready(() => {
             }
         }
 
+        tableDataRemake(changeListData);
+
         // 重新绘制table
         maketablelist();
     });
@@ -531,6 +531,42 @@ $(document).ready(() => {
 
 });
 
+function tableDataRemake(changeListData) {
+    $('#table-list-select tr').removeClass('table-warning');
+    $('#table-list-select tr').removeClass('table-success');
+    $('#table-list-select tr').attr('data-bwmx', '');
+    $('#code-list').html('');
+    // 根据已添加的清单显示
+    const changeList = $('#change-list').val().split('^_^');
+    console.log(changeList);
+    if (changeList.length > 0 && changeList[0]) {
+        for (const cl of changeList) {
+            const clinfo = cl.split(';');
+            const listinfo = changeListData[clinfo[8] - 1];
+            console.log(listinfo);
+            $('#table-list-select tr[data-index="'+ clinfo[8] +'"]').addClass('table-success');
+            let pushbwmx = '0;0';
+            if (listinfo.leafXmjs !== undefined) {
+                const leafInfo = listinfo.leafXmjs.find(function (item) {
+                    return (item.bwmx === undefined || item.bwmx === clinfo[2]) && item.quantity === parseFloat(clinfo[5]);
+                });
+                console.log(leafInfo);
+                pushbwmx = leafInfo.code + '_' + (leafInfo.bwmx !== undefined ? leafInfo.bwmx : '') + ';' + leafInfo.quantity;
+            } else {
+                pushbwmx = '0;' + (listinfo.quantity !== null ? listinfo.quantity : 0);
+            }
+            const bwmx = $('#table-list-select tr[data-index="'+ clinfo[8] +'"]').attr('data-bwmx');
+            if (bwmx) {
+                const bwmxArray = bwmx.split('$#$');
+                bwmxArray.push(pushbwmx);
+                $('#table-list-select tr[data-index="'+ clinfo[8] +'"]').attr('data-bwmx', bwmxArray.join('$#$'));
+            } else {
+                $('#table-list-select tr[data-index="'+ clinfo[8] +'"]').attr('data-bwmx', pushbwmx);
+            }
+        }
+    }
+}
+
 //判断元素是否在数组中,相当于php的in_array();
 function in_array(arr, obj) {
     let i = arr.length;
@@ -567,7 +603,8 @@ function maketablelist(status){
 
     // 原有清单(不含空白清单)
     let radionList = $('#change-list').val() !== '' ? $('#change-list').val().split('^_^') : [];
-    // 获取选中的签约清单并插入到原有清单中
+    const newTableList = [];
+    // 获取选中的签约清单判断并插入到原有清单中
     $('#table-list-select .table-success').each(function(){
         let code = $(this).children('td').eq(1).text();
         let name = $(this).children('td').eq(2).text();
@@ -587,15 +624,22 @@ function maketablelist(status){
             const oamount = b.split(';')[1];
             let bwmx = b.split(';')[0] != 0 ? b.split(';')[0].split('_')[1] : '';
             let trlist = [code, name, bwmx, unit, price, oamount, scnum, '', lid];
-            radionList.push(trlist.join(';'));
+            const radionInfo = radionList.find(function (item) {
+                const info = item.split(';');
+                return info[0] === code && parseInt(info[8]) === parseInt(lid) && info[2] === bwmx;
+            });
+            if (radionInfo) {
+                trlist[6] = radionInfo.split(';')[6];
+            }
+            newTableList.push(trlist.join(';'));
         }
     });
     // 排序
-    radionList.sort();
+    // radionList.sort();
     let index = 0;
     let whiteIndex = 0;
     let deteletr = '<td><a class="text-danger">移除</a></td>';
-    for (const radion of radionList) {
+    for (const radion of newTableList) {
         const radionArray = radion.split(';');
         let code = radionArray[0];
         let name = radionArray[1];
@@ -625,7 +669,7 @@ function maketablelist(status){
             deteletr +'</tr>';
         index ++;
     }
-    $('#change-list').val(radionList.join('^_^'));
+    $('#change-list').val(newTableList.join('^_^'));
 
 
     let radionWhiteList = $('#change-whitelist').val() !== '' ? $('#change-whitelist').val().split('^_^') : [];
@@ -696,7 +740,7 @@ function maketablelist(status){
         column.visible(!column.visible());
     }
     totalamount(decimal);
-    $('#table-list-select tr').removeClass('table-success');
+    // $('#table-list-select tr').removeClass('table-success');
 }
 
 //统计合计金额

+ 34 - 21
app/public/js/ledger.js

@@ -21,6 +21,7 @@ const invalidFields = {
     gcl: ['dgn_qty1', 'dgn_qty2'],
     posCode: ['b_code'],
     posCalc: ['sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp'],
+    posXmj: ['code'],
 };
 function transExpr(expr) {
     return expr.replace('=', '').replace('%', '/100');
@@ -567,7 +568,14 @@ $(document).ready(function() {
                 : (info.pasteData.text === ''
                     ? SpreadJsObj.Clipboard.getAnalysisPasteText()
                     : SpreadJsObj.analysisPasteText(info.pasteData.text));
-            let bParentHint = false, bGclHint = false, bCodeHint = false, bQtyHint = false, bNumHint = false;
+            const hint = {
+                invalidExpr: {type: 'warning', msg: '粘贴的表达式非法'},
+                posCode: {type: 'warning', msg: '清单含有计量单元,不可粘贴清单编号为空'},
+                posQty: {type: 'warning', msg: '清单含有计量单元,数量金额根据计量单元汇总计算所得,不可粘贴'},
+                parent: {type: 'warning', msg: '含有子项的清单,不可粘贴数量、单价、金额'},
+                gcl: {type: 'warning', msg: '工程量清单,不可粘贴项目节数量'},
+                posXmj: {type: 'warning', msg: '清单含有计量单元,不可粘贴项目节编号'},
+            };
             const datas = [], filterNodes = [];
             for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
                 const curRow = info.cellRange.row + iRow;
@@ -582,20 +590,24 @@ $(document).ready(function() {
                     const value = trimInvalidChar(pasteData[iRow][iCol]);
                     const lPos = pos.getLedgerPos(node.id);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
-                        if (!bParentHint) bParentHint = true;
+                        toastMessageUniq(hint.parent);
                         continue;
                     }
                     if (!_.isEmpty(node.b_code) && invalidFields.gcl.indexOf(colSetting.field) >= 0) {
-                        if (!bGclHint) bGclHint = true;
+                        toastMessageUniq(hint.gcl);
                         continue;
                     }
                     if (lPos && lPos.length > 0) {
                         if (value === '' && invalidFields.posCode.indexOf(colSetting.field) >= 0) {
-                            if (!bCodeHint) bCodeHint = true;
+                            toastMessageUniq(hint.posCode);
                             continue;
                         }
                         if (invalidFields.posCalc.indexOf(colSetting.field) >= 0) {
-                            if (!bQtyHint) bQtyHint = true;
+                            toastMessageUniq(hint.posQty);
+                            continue;
+                        }
+                        if (value !== '' && invalidFields.posXmj.indexOf(colSetting.field) >= 0) {
+                            toastMessageUniq(hint.posXmj);
                             continue;
                         }
                     }
@@ -608,7 +620,7 @@ $(document).ready(function() {
                             try {
                                 data[colSetting.field] = math.evaluate(transExpr(value));
                             } catch(err) {
-                                bNumHint = true;
+                                toastMessageUniq(hint.invalidExpr);
                             }
                         }
                     } else {
@@ -622,15 +634,6 @@ $(document).ready(function() {
                     filterNodes.push(node);
                 }
             }
-            const hint = [];
-            if (bParentHint) hint.push('含有子项的清单,不可粘贴数量、单价、金额');
-            if (bGclHint) hint.push('工程量清单,不可粘贴项目节数量');
-            if (bQtyHint) hint.push('含有计量单元的清单,不可粘贴数量');
-            if (bCodeHint) hint.push('含有计量单元的清单,清单编号不可粘贴空值');
-            if (bNumHint) hint.push('单价、数量等应输入数字或可计算的表达式');
-            // if (hint.length > 0) hint.unshift('复制粘贴的数据中,存在非法数据,已过滤:');
-            // if (hint.length > 0) toastr.warning(hint.join('</br>'));
-            if (hint.length > 0) toastr.warning('复制粘贴的数据中,存在非法数据,已过滤');
             if (datas.length > 0) {
                 postData(window.location.pathname + '/update', {postType: 'update', postData: datas}, function (result) {
                     const refreshNode = tree.loadPostData(result);
@@ -794,7 +797,15 @@ $(document).ready(function() {
             if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
             const col = info.sheet.zh_setting.cols[info.col];
             const node = info.sheet.zh_tree.nodes[info.row];
+            if (!node) {
+                info.cancel = true;
+                return;
+            }
             switch (col.field) {
+                case 'code':
+                    const posRange = pos.getLedgerPos(node.id);
+                    info.cancel = posRange && posRange.length > 0;
+                    break;
                 case 'unit_price':
                 case 'sgfh_qty':
                 case 'sgfh_tp':
@@ -823,12 +834,14 @@ $(document).ready(function() {
                 if (!children || children.length === 0) return;
 
                 for (const [i, child] of children.entries()) {
-                    const code = parentCode + '-' + (i + 1);
-                    const cData = tree.getNodeKeyData(child);
-                    cData.code = code;
-                    data.push(cData);
-                    if (!tree.isLeafXmj(child)) {
-                        recursiveSortCode(data, code, child.children);
+                    if (!child.b_code || child.b_code === '') {
+                        const code = parentCode + '-' + (i + 1);
+                        const cData = tree.getNodeKeyData(child);
+                        cData.code = code;
+                        data.push(cData);
+                        if (!tree.isLeafXmj(child)) {
+                            recursiveSortCode(data, code, child.children);
+                        }
                     }
                 }
             };

+ 23 - 20
app/public/js/revise.js

@@ -14,6 +14,7 @@ const invalidFields = {
     gcl: ['dgn_qty1', 'dgn_qty2'],
     posCode: ['b_code'],
     posCalc: ['sgfh_qty', 'sgfh_tp', 'sjcl_qty', 'sjcl_tp', 'qtcl_qty', 'qtcl_tp'],
+    posXmj: ['code'],
 };
 function transExpr(expr) {
     return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
@@ -416,10 +417,11 @@ $(document).ready(() => {
                 usedUp: {type: 'warning', msg: '节点已计量,不可修改单价'},
                 usedCode: {type: 'warning', msg: '节点已计量,编号不可修改为空值'},
                 invalidExpr: {type: 'warning', msg: '粘贴的表达式非法'},
-                posCode: {type: 'warning', msg: '清单含有计量单元,请先删除计量单元,再修改清单编号为空'},
-                posQty: {type: 'warning', msg: '清单含有计量单元,数量金额根据计量单元汇总计算所得,不可修改'},
+                posCode: {type: 'warning', msg: '清单含有计量单元,不可粘贴清单编号为空'},
+                posQty: {type: 'warning', msg: '清单含有计量单元,数量金额根据计量单元汇总计算所得,不可粘贴'},
                 parent: {type: 'warning', msg: '含有子项的清单,不可粘贴数量、单价、金额'},
                 gcl: {type: 'warning', msg: '工程量清单,不可粘贴项目节数量'},
+                posXmj: {type: 'warning', msg: '清单含有计量单元,不可粘贴项目节编号'},
             };
             const datas = [], filterNodes = [];
 
@@ -454,6 +456,10 @@ $(document).ready(() => {
                             toastMessageUniq(hint.posQty);
                             continue;
                         }
+                        if (value !== '' && invalidFields.posXmj.indexOf(colSetting.field) >= 0) {
+                            toastMessageUniq(hint.posXmj);
+                            continue;
+                        }
                     }
 
                     const value = trimInvalidChar(pasteData[iRow][iCol]);
@@ -642,7 +648,14 @@ $(document).ready(() => {
             if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
             const col = info.sheet.zh_setting.cols[info.col];
             const node = info.sheet.zh_tree.nodes[info.row];
+            if (!node) {
+                info.cancel = true;
+                return;
+            }
             switch (col.field) {
+                case 'code':
+                    const posRange = pos.getLedgerPos(node.id);
+                    return posRange && posRange.length > 0;
                 case 'unit_price':
                     info.cancel = (node.children && node.children.length > 0) || node.used;
                     break;
@@ -716,12 +729,14 @@ $(document).ready(() => {
                 if (!children || children.length === 0) return;
 
                 for (const [i, child] of children.entries()) {
-                    const code = parentCode + '-' + (i + 1);
-                    const cData = tree.getNodeKeyData(child);
-                    cData.code = code;
-                    data.push(cData);
-                    if (!tree.isLeafXmj(child)) {
-                        recursiveSortCode(data, code, child.children);
+                    if (!child.b_code || child.b_code === '') {
+                        const code = parentCode + '-' + (i + 1);
+                        const cData = tree.getNodeKeyData(child);
+                        cData.code = code;
+                        data.push(cData);
+                        if (!tree.isLeafXmj(child)) {
+                            recursiveSortCode(data, code, child.children);
+                        }
                     }
                 }
             };
@@ -942,18 +957,6 @@ $(document).ready(() => {
                     }
                 },
                 'sprBatch': '-----------',
-                'sortChildren': {
-                    name: '顺序重排子项编号',
-                    icon: 'fa-sort-numeric-asc',
-                    disabled: function (key, opt) {
-                        const node = SpreadJsObj.getSelectObject(billsSheet);
-                        return !node || !node.code || !node.children || node.children === 0;
-                    },
-                    callback: function (key, opt) {
-                        billsTreeSpreadObj.sortCode(billsSheet);
-                    }
-                },
-                'sprSort': '-----------',
                 'importGclBills2Xmj': {
                     name: '导入工程量清单至项目节',
                     icon: 'fa-file-excel-o',

+ 33 - 33
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -292,7 +292,7 @@ const SpreadJsObj = {
      * @param {GC.Spread.Sheets.Worksheet} sheet
      */
     _initSheetHeader: function (sheet) {
-        if (!sheet.zh_setting) { return; }
+        if (!sheet.zh_setting || !sheet.zh_setting.cols) { return; }
 
         sheet.setColumnCount(sheet.zh_setting.cols.length);
         sheet.setRowCount(sheet.zh_setting.headRows, spreadNS.SheetArea.colHeader);
@@ -385,6 +385,33 @@ const SpreadJsObj = {
         });
 
     },
+    _addActivePaintEvents: function (sheet, cellType) {
+        if (!sheet.ActiveType) {
+            sheet.ActiveType = [];
+        }
+        sheet.ActiveType.push(cellType);
+
+        if (!sheet.AcitveRefresh) {
+            sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
+                const cellType = info.sheet.getCell(info.row, info.col).cellType();
+                if (sheet.ActiveType.indexOf(cellType) >= 0) {
+                    info.sheet.leaveCell = {row: info.row, col: info.col};
+                } else {
+                    delete info.sheet.leaveCell;
+                }
+            });
+            sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
+                const cellType = info.sheet.getCell(info.row, info.col).cellType();
+                if (sheet.ActiveType.indexOf(cellType) >= 0) {
+                    info.sheet.repaint(info.sheet.getCellRect(info.row, info.col));
+                }
+                if (info.sheet.leaveCell) {
+                    info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
+                }
+            });
+            sheet.AcitveRefresh = true;
+        }
+    },
     _defineColCellType: function (sheet, col, colSetting) {
         sheet.AcitveComboRefresh = false;
         if(colSetting.cellType === 'ellipsis') {
@@ -450,22 +477,7 @@ const SpreadJsObj = {
         if (colSetting.cellType === 'unit') {
             if (!sheet.extendCellType.unit) {
                 sheet.extendCellType.unit = this.CellType.getUnitCellType();
-                if (!sheet.AcitveComboRefresh) {
-                    sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
-                        const cellType = info.sheet.getCell(info.row, info.col).cellType();
-                        if (cellType === sheet.extendCellType.unit) {
-                            info.sheet.leaveCell = {row: info.row, col: info.col};
-                        } else {
-                            delete info.sheet.leaveCell;
-                        }
-                    });
-                    sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
-                        if (info.sheet.leaveCell) {
-                            info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
-                        }
-                    });
-                    sheet.AcitveComboRefresh = true;
-                }
+                SpreadJsObj._addActivePaintEvents(sheet, sheet.extendCellType.unit);
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType.unit);
         }
@@ -473,22 +485,7 @@ const SpreadJsObj = {
             const cellKey = colSetting.cellTypeKey ? 'customizeCombo-' + colSetting.cellTypeKey : 'customizeCombo';
             if (!sheet.extendCellType[cellKey]) {
                 sheet.extendCellType[cellKey] = this.CellType.getCustomizeComboCellType(colSetting.comboItems);
-                if (!sheet.AcitveComboRefresh) {
-                    sheet.bind(spreadNS.Events.LeaveCell, function (e, info) {
-                        const cellType = info.sheet.getCell(info.row, info.col).cellType();
-                        if (cellType === sheet.extendCellType[cellKey]) {
-                            info.sheet.leaveCell = {row: info.row, col: info.col};
-                        } else {
-                            delete info.sheet.leaveCell;
-                        }
-                    });
-                    sheet.bind(spreadNS.Events.EnterCell, function (e, info) {
-                        if (info.sheet.leaveCell) {
-                            info.sheet.repaint(info.sheet.getCellRect(info.sheet.leaveCell.row, info.sheet.leaveCell.col));
-                        }
-                    });
-                    sheet.AcitveComboRefresh = true;
-                }
+                SpreadJsObj._addActivePaintEvents(sheet, sheet.extendCellType[cellKey]);
             }
             sheet.getRange(-1, col, -1, 1).cellType(sheet.extendCellType[cellKey]);
         }
@@ -1816,6 +1813,9 @@ const SpreadJsObj = {
                     spreadNS.CellTypes.Base.prototype.paintValue.apply(this, arguments);
                 }
             };
+            proto.processMouseEnter = function (hitinfo) {
+                hitinfo.sheet.repaint(hitinfo.cellRect);
+            };
             proto.getHitInfo = function (x, y, cellStyle, cellRect, options) {
                 const sheet = options.sheet;
                 if (options.row === sheet.getActiveRowIndex() && options.col === sheet.getActiveColumnIndex()

+ 13 - 4
app/public/js/stage.js

@@ -202,9 +202,11 @@ $(document).ready(() => {
                 getColor: function (sheet, data, col, defaultColor) {
                     if (col.field === 'uamount') {
                         if (!data.vamount) {
-                            return (data.uamount && data.uamount > 0) ? '#ff6f5c' : defaultColor;
+                            return (data.uamount && math.abs(data.uamount) > 0) ? '#ff6f5c' : defaultColor;
                         } else if (data.uamount) {
-                            return data.uamount > data.vamount ? '#ff6f5c' : defaultColor;
+                            return data.vamount > 0
+                                ? data.uamount > data.vamount ? '#ff6f5c' : defaultColor
+                                : data.uamount < data.vamount ? '#ff6f5c' : defaultColor;
                         } else {
                             return defaultColor;
                         }
@@ -298,7 +300,14 @@ $(document).ready(() => {
                 for (const c of self.displayChanges) {
                     if (c.uamount) {
                         const vamount = (!c.vamount || checkZero(c.vamount)) ? 0 : c.vamount;
-                        if (c.uamount > vamount) {
+                        if (vamount > 0 && c.uamount < 0) {
+                            toastr.error('变更令:' + c.code + ' 下,请勿进行负变更');
+                            return;
+                        } else if (vamount < 0 && c.uamount > 0) {
+                            toastr.error('变更令:' + c.code + ' 下,请勿进行正变更');
+                            return;
+                        }
+                        if ((vamount > 0 && c.uamount > vamount) || (vamount < 0 && c.uamount < vamount)) {
                             toastr.error('变更令:' + c.code + ' 超计,请修改本期计量后,再提交');
                             return;
                         }
@@ -1109,7 +1118,7 @@ $(document).ready(() => {
                 const range = info.cellRange;
                 const validField = ['contract_qty', 'qc_qty', 'postil'];
                 if (!checkTzMeasureType()) {
-                    validField.push('name', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty');
+                    validField.push('name', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty', 'position');
                 }
                 for (let iCol = range.col; iCol < range.col + range.colCount; iCol++) {
                     const col = info.sheet.zh_setting.cols[iCol];

+ 1 - 1
app/service/change_audit_list.js

@@ -24,7 +24,7 @@ module.exports = app => {
         }
 
         async gatherBgBills (tid) {
-            const sql = 'SELECT cb.code, cb.name, cb.unit, cb.unit_price, Sum(cb.samount + 0) as quantity' +
+            const sql = 'SELECT cb.code, cb.name, cb.unit, cb.unit_price, Round(Sum(cb.samount + 0), 6) as quantity' +
                 '  FROM ' + this.tableName + ' cb' +
                 '  LEFT JOIN ' + this.ctx.service.change.tableName + ' c ON cb.cid = c.cid' +
                 '  WHERE cb.tid = ? and c.status = ?' +

+ 113 - 4
app/service/report_memory.js

@@ -19,6 +19,8 @@ const stageImTzBills = 'mem_stage_im_tz_bills';
 const stageImZl = 'mem_stage_im_zl';
 const stageImVersion = '1.0';
 
+const Ledger = require('../lib/ledger');
+
 module.exports = app => {
     class ReportMemory extends app.BaseService {
 
@@ -30,7 +32,48 @@ module.exports = app => {
          */
         constructor(ctx) {
             super(ctx);
+            const self = this;
             this.tableName = 'report_memory';
+            // 基础数据类
+            // mainData
+            this.billsTree = new Ledger.billsTree(this.ctx, {
+                id: 'ledger_id',
+                pid: 'ledger_pid',
+                order: 'order',
+                level: 'level',
+                rootId: -1,
+                keys: ['id', 'tender_id', 'ledger_id'],
+                stageId: 'id',
+                calcFields: ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp'],
+                calc: function (node) {
+                    if (node.children && node.children.length === 0) {
+                        node.pre_gather_qty = self.ctx.helper.add(node.pre_contract_qty, node.pre_qc_qty);
+                        node.gather_qty = self.ctx.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 = self.ctx.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);
+
+                    node.final_tp = self.ctx.helper.add(node.total_price, node.end_qc_tp);
+                    node.final_ratio = self.ctx.helper.mul(self.ctx.helper.div(node.end_gather_tp, node.final_tp, 4), 100);
+                }
+            });
+            this.pos = new Ledger.pos({
+                id: 'id', ledgerId: 'lid',
+                updateFields: ['contract_qty', 'qc_qty', 'postil'],
+                calc: function (p) {
+                    p.pre_gather_qty = ctx.helper.add(p.pre_contract_qty, p.pre_qc_qty);
+                    p.gather_qty = ctx.helper.add(p.contract_qty, p.qc_qty);
+                    p.end_contract_qty = self.ctx.helper.add(p.pre_contract_qty, p.contract_qty);
+                    p.end_qc_qty = self.ctx.helper.add(p.pre_qc_qty, p.qc_qty);
+                    p.end_gather_qty = self.ctx.helper.add(p.pre_gather_qty, p.gather_qty);
+                }
+            });
             // 需要缓存的数据
             this.stageImData = null;
         }
@@ -125,7 +168,7 @@ module.exports = app => {
             return rst;
         }
 
-        async getStageImTzData(tid, sid) {
+        async getStageImTzData(tid, sid, fields) {
             await this.ctx.service.tender.checkTender(tid);
             await this.ctx.service.stage.checkStage(sid);
             const cache = await this._getReportMemoryCache('mem_stage_im_tz', tid, sid, this.ctx.stage.cacheTime, stageImVersion);
@@ -150,7 +193,7 @@ module.exports = app => {
             return this.stageImData.main;
         }
 
-        async getStageImTzBillsData(tid, sid) {
+        async getStageImTzBillsData(tid, sid, fields) {
             await this.ctx.service.tender.checkTender(tid);
             await this.ctx.service.stage.checkStage(sid);
             const cache = await this._getReportMemoryCache('mem_stage_im_tz_bills', tid, sid, this.ctx.stage.cacheTime, stageImVersion);
@@ -171,7 +214,7 @@ module.exports = app => {
             return this.stageImData.bills;
         }
 
-        async getStageImZlData(tid, sid) {
+        async getStageImZlData(tid, sid, fields) {
             await this.ctx.service.tender.checkTender(tid);
             await this.ctx.service.stage.checkStage(sid);
             const cache = await this._getReportMemoryCache('mem_stage_im_zl', tid, sid, this.ctx.stage.cacheTime, stageImVersion);
@@ -189,7 +232,7 @@ module.exports = app => {
             return this.stageImData.main;
         }
 
-        async getMonthProgress(tid) {
+        async getMonthProgress(tid, fields) {
             const helper = this.ctx.helper;
             await this.ctx.service.tender.checkTender(tid);
             const tender = this.ctx.tender;
@@ -236,6 +279,72 @@ module.exports = app => {
             }
             return monthProgress;
         }
+
+        async getStageBillsData(tid, sid, fields) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+            const billsData = await this.ctx.service.ledger.getData(this.ctx.tender.id);
+            if (this.ctx.stage.readOnly) {
+                const curStage = await this.ctx.service.stageBills.getAuditorStageData(this.ctx.tender.id,
+                    this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+                this.ctx.helper.assignRelaData(billsData, [
+                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                ]);
+            } else {
+                const curStage = await this.ctx.service.stageBills.getLastestStageData(this.ctx.tender.id, this.ctx.stage.id);
+                this.ctx.helper.assignRelaData(billsData, [
+                    {data: curStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: '', relaId: 'lid'}
+                ]);
+            }
+            const preStage = this.ctx.stage.order > 1 ? await this.ctx.service.stageBillsFinal.getFinalData(this.ctx.tender, this.ctx.stage.order - 1) : [];
+
+            this.ctx.helper.assignRelaData(billsData, [
+                {data: preStage, fields: ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'], prefix: 'pre_', relaId: 'lid'}
+            ]);
+            this.billsTree.loadDatas(billsData);
+            this.billsTree.calculateAll();
+
+            return this.billsTree.getDatas([
+                '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',
+            ]);
+        }
+
+        async getStagePosData(tid, sid, fields) {
+            await this.ctx.service.tender.checkTender(tid);
+            await this.ctx.service.stage.checkStage(sid);
+
+            const posData = await this.ctx.service.pos.getAllDataByCondition({ where: {tid: this.ctx.tender.id }});
+            if (this.ctx.stage.readOnly) {
+                const curPosStage = await this.ctx.service.stagePos.getAuditorStageData2(this.ctx.tender.id,
+                    this.ctx.stage.id, this.ctx.stage.curTimes, this.ctx.stage.curOrder);
+                this.ctx.helper.assignRelaData(posData, [
+                    {data: curPosStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'}
+                ]);
+            } else {
+                const curPosStage = await this.ctx.service.stagePos.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id);
+                this.ctx.helper.assignRelaData(posData, [
+                    {data: curPosStage, fields: ['contract_qty', 'qc_qty'], prefix: '', relaId: 'pid'}
+                ]);
+            }
+            const prePosStage = this.ctx.stage.order > 1 ? await this.ctx.service.stagePosFinal.getFinalData(this.ctx.tender, this.ctx.stage.order - 1) : [];
+            this.ctx.helper.assignRelaData(posData, [
+                {data: prePosStage, fields: ['contract_qty', 'qc_qty'], prefix: 'pre_', relaId: 'pid'}
+            ]);
+            this.pos.loadDatas(posData);
+            this.pos.calculateAll();
+
+            return this.pos.getDatas();
+        }
     }
 
     return ReportMemory;

+ 0 - 1
app/service/stage_bills.js

@@ -316,7 +316,6 @@ module.exports = app => {
                 if (stageBills.contract_qty === posGather.contract_qty && stageBills.qc_qty === posGather.qc_qty) {
                     return;
                 } else {
-                    const curOrder = this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor.order : 0;
                     if (stageBills.times === this.ctx.stage.curTimes && stageBills.order === this.ctx.stage.curOrder) {
                         posGather.id = stageBills.id;
                         await transaction.update(this.tableName, posGather);

+ 189 - 130
app/service/stage_pos.js

@@ -146,61 +146,86 @@ module.exports = app => {
          * @private
          */
         async _addStagePosData(data) {
-            let bills , precision;
+            let bills , precision, updateBills = null;
             const  result = {pos: [], ledger: []};
-            const datas = data instanceof Array ? data : [data], calcBills = [], calcStageBills = [];
+            const datas = data instanceof Array ? data : [data], calcStageBills = [];
+            if (datas[0].sgfh_qty !== undefined || datas[0].sjcl_qty !== undefined || datas[0].qtcl_qty !== undefined
+                || datas[0].contract_qty !== undefined || datas[0].qc_qty !== undefined) {
+                bills = await this.ctx.service.ledger.getDataById(datas[0].lid);
+                precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                result.ledger.push(bills.id);
+            }
+            if (datas[0].contract_qty !== undefined || datas[0].qc_qty !== undefined || datas[0].postil !== undefined) {
+                result.stageUpdate = true;
+            }
 
-            const transaction = await this.db.beginTransaction();
-            try {
-                for (const d of datas) {
-                    if (d.sgfh_qty !== undefined || d.sjcl_qty !== undefined || d.qtcl_qty !== undefined) {
-                        if (!bills || bills.id !== data.lid) {
-                            bills = await this.ctx.service.ledger.getDataById(d.lid);
-                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                        }
-                    }
-                    // 在主表pos中新增数据
-                    const p = {
-                        id: this.uuid.v4(), tid: this.ctx.tender.id, lid: d.lid, name: d.name, porder: d.porder, position: d.position,
-                        add_stage: this.ctx.stage.id,
-                        add_times: this.ctx.stage.curTimes,
-                        add_user: this.ctx.session.sessionUser.accountId,
-                    };
-                    if (d.sgfh_qty) p.sgfh_qty = this.round(d.sgfh_qty, precision.value);
-                    if (d.sjcl_qty) p.sjcl_qty = this.round(d.sjcl_qty, precision.value);
-                    if (d.qtcl_qty) p.qtcl_qty = this.round(d.qtcl_qty, precision.value);
+            const insertPos = [], insertPosStage = [];
+            for (const d of datas) {
+                const p = {
+                    id: this.uuid.v4(), tid: this.ctx.tender.id, lid: d.lid, name: d.name, porder: d.porder, position: d.position,
+                    add_stage: this.ctx.stage.id,
+                    add_times: this.ctx.stage.curTimes,
+                    add_user: this.ctx.session.sessionUser.accountId,
+                };
+                if (d.sgfh_qty !== undefined || d.sjcl_qty !== undefined || d.qtcl_qty !== undefined) {
+                    if (d.sgfh_qty!== undefined) p.sgfh_qty = this.round(d.sgfh_qty, precision.value);
+                    if (d.sjcl_qty!== undefined) p.sjcl_qty = this.round(d.sjcl_qty, precision.value);
+                    if (d.qtcl_qty!== undefined) p.qtcl_qty = this.round(d.qtcl_qty, precision.value);
                     p.quantity = this.ctx.helper.sum([p.sgfh_qty, p.sjcl_qty, p.qtcl_qty]);
-                    const addRst = await transaction.insert(this.ctx.service.pos.tableName, p);
-                    result.pos.push(p.id);
-                    // 如果存在复核数据,更新计算主表清单
-                    if (p.sgfh_qty || p.sjcl_qty || p.qtcl_qty) {
-                        calcBills.push(p.lid);
-                        result.ledger.push(p.lid);
-                    }
-                    // 如果存在本期计算数据,更新计算清单本期计量数据
-                    if (d.contract_qty || d.qc_qty || d.postil) {
-                        const ps = {
-                            pid: p.id,
-                            lid: d.lid,
-                            tid: this.ctx.tender.id,
-                            sid: this.ctx.stage.id,
-                            said: this.ctx.session.sessionUser.accountId,
-                            times: this.ctx.stage.curTimes,
-                            order: this.ctx.stage.curOrder,
-                        };
-                        if (d.contract_qty) ps.contract_qty = this.round(d.contract_qty, precision.value);
-                        if (d.qc_qty) ps.qc_qty = this.round(d.qc_qty, precision.value);
-                        if (d.postil) ps.postil = d.postil;
-                        await transaction.insert(this.tableName, ps);
-                        if ((d.contract_qty || d.qc_qty) && calcStageBills.indexOf(ps.lid) === -1) {
-                            calcStageBills.push(ps.lid);
-                        }
-                        result.stageUpdate = true;
+                    if (!updateBills) updateBills = {id: bills.id, sgfh_qty: bills.sgfh_qty, sjcl_qty: bills.sjcl_qty, qtcl_qty: bills.qtcl_qty};
+                }
+                insertPos.push(p);
+                result.pos.push(p.id);
+
+                if (d.contract_qty!== undefined || d.qc_qty!== undefined || d.postil!== undefined) {
+                    const ps = {
+                        pid: p.id,
+                        lid: d.lid,
+                        tid: this.ctx.tender.id,
+                        sid: this.ctx.stage.id,
+                        said: this.ctx.session.sessionUser.accountId,
+                        times: this.ctx.stage.curTimes,
+                        order: this.ctx.stage.curOrder,
+                    };
+                    if (d.contract_qty !== undefined) ps.contract_qty = this.round(d.contract_qty, precision.value);
+                    if (d.qc_qty!== undefined) ps.qc_qty = this.round(d.qc_qty, precision.value);
+                    if (d.postil!== undefined) ps.postil = d.postil;
+                    insertPosStage.push(ps);
+                    if ((d.contract_qty || d.qc_qty) && calcStageBills.indexOf(ps.lid) === -1) {
+                        calcStageBills.push(ps.lid);
                     }
                 }
-                for (const lid of calcBills) {
-                    await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
+            }
+            if (updateBills) {
+                for (const d of insertPos) {
+                    if (d.sgfh_qty) {
+                        d.sgfh_qty = this.ctx.helper.round(d.sgfh_qty, precision.value);
+                        updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, d.sgfh_qty);
+                    }
+                    if (d.sjcl_qty) {
+                        d.sjcl_qty = this.ctx.helper.round(d.sjcl_qty, precision.value);
+                        updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, d.sjcl_qty);
+                    }
+                    if (d.qtcl_qty) {
+                        d.qtcl_qty = this.ctx.helper.round(d.qtcl_qty, precision.value);
+                        updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, d.qtcl_qty);
+                    }
+                    d.quantity = this.ctx.helper.sum([d.sgfh_qty, d.qtcl_qty, d.sjcl_qty]);
                 }
+                updateBills.quantity = this.ctx.helper.sum([updateBills.sgfh_qty, updateBills.qtcl_qty, updateBills.sjcl_qty]);
+                const info = this.ctx.tender.info;
+                updateBills.sgfh_tp = this.ctx.helper.mul(updateBills.sgfh_qty, bills.unit_price, info.decimal.tp);
+                updateBills.sjcl_tp = this.ctx.helper.mul(updateBills.sjcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.qtcl_tp = this.ctx.helper.mul(updateBills.qtcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.total_price = this.ctx.helper.mul(updateBills.quantity, bills.unit_price, info.decimal.tp);
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (insertPos.length > 0) await transaction.insert(this.ctx.service.pos.tableName, insertPos);
+                if (updateBills) await transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                if (insertPosStage.length > 0) await transaction.insert(this.tableName, insertPosStage);
+
                 for (const lid of calcStageBills) {
                     await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
                 }
@@ -221,89 +246,110 @@ module.exports = app => {
          * @private
          */
         async _updateStagePosData(data) {
-            let bills, precision;
-            const result = {ledger: [], pos: [], stageUpdate: true}, ledgerCalc = [];
+            let bills, precision, updateBills = null;
             const datas = data instanceof Array ? data : [data];
             const orgPos = await this.ctx.service.pos.getPosDataByIds(this._.map(datas, 'pid'));
+            const result = {ledger: [], pos: [], stageUpdate: true};
             const orgStagePos = await this.getLastestStageData2(this.ctx.tender.id, this.ctx.stage.id,
                 {pid: this._.map(datas, 'pid')});
 
-            const transaction = await this.db.beginTransaction();
-            try {
-                for (const d of datas) {
-                    if (d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined
-                        || d.contract_qty !== undefined || d.qc_qty !== undefined) {
-                        if (!bills || bills.id !== data.lid) {
-                            bills = await this.ctx.service.ledger.getDataById(d.lid);
-                            precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
-                        }
-                    }
-                    if (d.name !== undefined || d.drawing_code !== undefined || d.position !== undefined
-                        || d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined) {
-                        const op = this._.find(orgPos, {id: d.pid});
-                        if (op.add_stage !== this.ctx.stage.id) throw '不可修改数据';
-                        const p = {id: d.pid};
-                        if (d.name !== undefined) p.name = d.name;
-                        if (d.position !== undefined) p.position = d.position;
-                        if (d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined) {
-                            p.sgfh_qty = d.sgfh_qty !== undefined ? d.sgfh_qty : op.sgfh_qty;
-                            p.sjcl_qty = d.sjcl_qty !== undefined ? d.sjcl_qty : op.sjcl_qty;
-                            p.qtcl_qty = d.qtcl_qty !== undefined ? d.qtcl_qty : op.qtcl_qty;
-                            p.quantity = this.ctx.helper.sum([p.sgfh_qty, p.sjcl_qty, p.qtcl_qty]);
-                            if (ledgerCalc.indexOf(op.lid) === -1) {
-                                ledgerCalc.push(op.lid);
-                            }
-                        }
-                        if (d.drawing_code !== undefined) p.drawing_code = d.drawing_code;
-                        await transaction.update(this.ctx.service.pos.tableName, p);
+            if (datas[0].sgfh_qty !== undefined || datas[0].qtcl_qty !== undefined || datas[0].sjcl_qty !== undefined
+                || datas[0].contract_qty !== undefined || datas[0].qc_qty !== undefined) {
+                bills = await this.ctx.service.ledger.getDataById(datas[0].lid);
+                precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, bills.unit);
+                result.ledger.push(bills.id);
+            }
+            const updatePos = [], updatePosStage = [], insertPosStage = [];
+            for (const d of datas) {
+                if (d.name !== undefined || d.drawing_code !== undefined || d.position !== undefined
+                    || d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined) {
+
+                    const op = this._.find(orgPos, {id: d.pid});
+                    if (op.add_stage !== this.ctx.stage.id) throw '不可修改数据';
+
+                    const p = {id: op.id};
+                    if (d.name !== undefined) p.name = d.name;
+                    if (d.position !== undefined) p.position = d.position;
+                    if (d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined) {
+                        p.sgfh_qty = d.sgfh_qty !== undefined ? d.sgfh_qty : op.sgfh_qty;
+                        p.sjcl_qty = d.sjcl_qty !== undefined ? d.sjcl_qty : op.sjcl_qty;
+                        p.qtcl_qty = d.qtcl_qty !== undefined ? d.qtcl_qty : op.qtcl_qty;
+                        p.quantity = this.ctx.helper.sum([p.sgfh_qty, p.sjcl_qty, p.qtcl_qty]);
+                        if (!updateBills) updateBills = {id: bills.id};
                     }
+                    if (d.drawing_code !== undefined) p.drawing_code = d.drawing_code;
+                    updatePos.push(p);
+                }
 
-                    if (d.contract_qty !== undefined || d.qc_qty !== undefined || d.postil !== undefined) {
-                        const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
-                        if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
-                            const sp = {};
-                            if (d.contract_qty !== undefined) {
-                                sp.contract_qty = this.ctx.helper.round(d.contract_qty, precision.value);
-                            }
-                            if (d.qc_qty !== undefined) {
-                                sp.qc_qty = this.ctx.helper.round(d.qc_qty, precision.value);
-                            }
-                            if (d.postil !== undefined) {
-                                sp.postil = d.postil;
-                            }
-                            await transaction.update(this.tableName, sp, {where: {id: osp.id}});
-                        } else {
-                            const sp = {
-                                pid: d.pid, lid: d.lid,
-                                tid: this.ctx.tender.id, sid: this.ctx.stage.id,
-                                said: this.ctx.session.sessionUser.accountId,
-                                times: this.ctx.stage.curTimes, order: this.ctx.stage.curOrder
-                            };
-                            if (d.contract_qty !== undefined || osp) {
-                                sp.contract_qty = d.contract_qty === undefined && osp
-                                    ? osp.contract_qty
-                                    : this.ctx.helper.round(d.contract_qty, precision.value);
-                            }
-                            if (d.qc_qty || osp) {
-                                sp.qc_qty = d.qc_qty === undefined && osp
-                                    ? osp.qc_qty
-                                    : this.ctx.helper.round(d.qc_qty, precision.value);
-                            }
-                            if (d.postil || osp) {
-                                sp.postil = d.postil === undefined && osp ? osp.postil : d.postil;
-                            }
-                            await transaction.insert(this.tableName, sp);
+                if (d.contract_qty !== undefined || d.qc_qty !== undefined || d.postil !== undefined) {
+                    const osp = this._.find(orgStagePos, function (p) { return p.pid === d.pid; });
+                    if (osp && osp.times === this.ctx.stage.curTimes && osp.order === this.ctx.stage.curOrder) {
+                        const sp = {id: osp.id};
+                        if (d.contract_qty !== undefined) {
+                            sp.contract_qty = this.ctx.helper.round(d.contract_qty, precision.value);
                         }
-                    }
-                    result.pos.push(d.pid);
-                    if ((d.sgfh_qty !== undefined || d.qtcl_qty !== undefined || d.sjcl_qty !== undefined ||
-                            d.contract_qty === undefined || d.qc_qty === undefined)
-                         && (result.ledger.indexOf(d.lid) === -1)) {
-                        result.ledger.push(d.lid);
+                        if (d.qc_qty !== undefined) {
+                            sp.qc_qty = this.ctx.helper.round(d.qc_qty, precision.value);
+                        }
+                        if (d.postil !== undefined) {
+                            sp.postil = d.postil;
+                        }
+                        updatePosStage.push(sp);
+                    } else {
+                        const sp = {
+                            pid: d.pid, lid: d.lid,
+                            tid: this.ctx.tender.id, sid: this.ctx.stage.id,
+                            said: this.ctx.session.sessionUser.accountId,
+                            times: this.ctx.stage.curTimes, order: this.ctx.stage.curOrder
+                        };
+                        if (d.contract_qty !== undefined || osp) {
+                            sp.contract_qty = d.contract_qty === undefined && osp
+                                ? osp.contract_qty
+                                : this.ctx.helper.round(d.contract_qty, precision.value);
+                        }
+                        if (d.qc_qty || osp) {
+                            sp.qc_qty = d.qc_qty === undefined && osp
+                                ? osp.qc_qty
+                                : this.ctx.helper.round(d.qc_qty, precision.value);
+                        }
+                        if (d.postil || osp) {
+                            sp.postil = d.postil === undefined && osp ? osp.postil : d.postil;
+                        }
+                        insertPosStage.push(sp);
                     }
                 }
-                for (const lid of ledgerCalc) {
-                    await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
+                result.pos.push(d.pid);
+            }
+            if (updateBills) {
+                const billsPos = await this.ctx.service.pos.getAllDataByCondition({where: {tid: bills.tender_id, lid: bills.id} });
+                for (const bp of billsPos) {
+                    const newPos = updatePos.find(function (x) { return x.id === bp.id });
+                    const calcData = newPos ? newPos : bp;
+                    updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, calcData.sgfh_qty);
+                    updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, calcData.sjcl_qty);
+                    updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, calcData.qtcl_qty);
+                    updateBills.quantity = this.ctx.helper.add(updateBills.quantity, calcData.quantity);
+                }
+                const info = this.ctx.tender.info;
+                updateBills.sgfh_tp = this.ctx.helper.mul(updateBills.sgfh_qty, bills.unit_price, info.decimal.tp);
+                updateBills.sjcl_tp = this.ctx.helper.mul(updateBills.sjcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.qtcl_tp = this.ctx.helper.mul(updateBills.qtcl_qty, bills.unit_price, info.decimal.tp);
+                updateBills.total_price = this.ctx.helper.mul(updateBills.quantity, bills.unit_price, info.decimal.tp);
+            }
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (updatePos.length > 0) {
+                    await transaction.updateRows(this.ctx.service.pos.tableName, updatePos);
+                }
+                if (updateBills) {
+                    await transaction.update(this.ctx.service.ledger.tableName, updateBills);
+                }
+                if (updatePosStage.length > 0) {
+                    await transaction.updateRows(this.tableName, updatePosStage);
+                }
+                if (insertPosStage.length > 0) {
+                    await transaction.insert(this.tableName, insertPosStage);
                 }
                 for (const lid of result.ledger) {
                     await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
@@ -332,27 +378,40 @@ module.exports = app => {
                     if (p.add_stage !== this.ctx.stage.id /*|| p.add_times !== this.ctx.stage.curTimes || p.add_user !== this.ctx.session.sessionUser.accountId*/) {
                         throw '不可删除该数据';
                     }
+                    if (p.lid !== pos[0].lid) {
+                        throw '提交数据错误';
+                    }
                 }
             } else if (pos.add_stage !== this.ctx.stage.id /*|| pos.add_times !== this.ctx.stage.curTimes || pos.add_user !== this.ctx.session.sessionUser.accountId*/) {
                 throw '不可删除该数据';
             }
-            const ledgerIds = this._.map(pos, 'lid');
+            const bills = await this.ctx.service.ledger.getDataById(pos[0].lid);
+            const billsPos = await this.ctx.service.pos.getAllDataByCondition({ where: {tid: bills.tender_id, lid: bills.id} });
+            const updateBills = {id: bills.id, sgfh_qty: null, sjcl_qty: null, qtcl_qty: null, quantity: null};
+            for (const bp of billsPos) {
+                if (data.indexOf(bp.id) >= 0) continue;
+                updateBills.sgfh_qty = this.ctx.helper.add(updateBills.sgfh_qty, bp.sgfh_qty);
+                updateBills.sjcl_qty = this.ctx.helper.add(updateBills.sjcl_qty, bp.sjcl_qty);
+                updateBills.qtcl_qty = this.ctx.helper.add(updateBills.qtcl_qty, bp.qtcl_qty);
+                updateBills.quantity = this.ctx.helper.add(updateBills.quantity, bp.quantity);
+            }
+            const info = this.ctx.tender.info;
+            updateBills.sgfh_tp = this.ctx.helper.mul(updateBills.sgfh_qty, bills.unit_price, info.decimal.tp);
+            updateBills.sjcl_tp = this.ctx.helper.mul(updateBills.sjcl_qty, bills.unit_price, info.decimal.tp);
+            updateBills.qtcl_tp = this.ctx.helper.mul(updateBills.qtcl_qty, bills.unit_price, info.decimal.tp);
+            updateBills.total_price = this.ctx.helper.mul(updateBills.quantity, bills.unit_price, info.decimal.tp);
 
             const transaction = await this.db.beginTransaction();
             try {
                 // 删除部位明细
                 await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id, id: data});
-                for (const lid of ledgerIds) {
-                    await this.ctx.service.ledger.calc(this.ctx.tender.id, lid, transaction);
-                }
+                await transaction.update(this.ctx.service.ledger.tableName, updateBills);
                 // 删除部位明细计量数据
                 await transaction.delete(this.tableName, {tid: this.ctx.tender.id, lid: data});
-                for (const lid of ledgerIds) {
-                    await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, lid, transaction);
-                }
+                await this.ctx.service.stageBills.calc(this.ctx.tender.id, this.ctx.stage.id, bills.id, transaction);
                 await transaction.commit();
                 // 获取需要更新的数据
-                result.ledger = ledgerIds;
+                result.ledger = [bills.id];
                 result.stageUpdate = true;
                 return result;
             } catch (err) {

+ 2 - 2
app/view/change/index.ejs

@@ -30,9 +30,9 @@
                 <table class="table table-bordered">
                     <thead>
                     <tr>
-                        <th>申请编号/变更令号</th><th>工程名称</th>
+                        <th width="20%">申请编号/变更令号</th><th width="30%">工程名称</th>
                         <th width="10%">变更类别</th><th width="10%">变更金额</th>
-                        <th width="10%">审批状态</th><th width="10%">审批进度</th><th width="10%"></th>
+                        <th width="10%">审批状态</th><th width="15%">审批进度</th><th></th>
                     </tr>
                     </thead>
                     <tbody id="changeList">

+ 146 - 0
test/app/controller/report_controller.test.js

@@ -0,0 +1,146 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const mockData = {};
+const path = require('path');
+
+async function getReportData(ctx, params, filters, memFieldKeys) {
+    const rst = {};
+    const runnableRst = [];
+    const runnableKey = []; // 这个配合runnableRst用,未来考虑并行查询优化
+    // console.log('params');
+    // console.log(params);
+    // console.log('memFieldKeys');
+    // console.log(memFieldKeys);
+    for (const filter of filters) {
+        if (runnableKey.indexOf(filter) < 0) {
+            switch (filter) {
+                case 'project' :
+                    runnableRst.push(ctx.service.project.getProjectById(params.project_id));
+                    runnableKey.push('project');
+                    break;
+                case 'tender_info' :
+                    runnableRst.push(ctx.service.tenderInfo.getTenderInfo(params.tender_id));
+                    runnableKey.push('tender_info');
+                    break;
+                case 'ledger' :
+                    runnableRst.push(ctx.service.ledger.getData(params.tender_id, 0));
+                    runnableKey.push('ledger');
+                    break;
+                case 'stage_bills':
+                    runnableRst.push(ctx.service.stageBills.getLastestStageData(params.tender_id, params.stage_id));
+                    runnableKey.push('stage_bills');
+                    break;
+                case 'stage_bills_final':
+                    await checkStg(ctx, params);
+                    runnableRst.push(ctx.service.stageBillsFinal.getFinalDataEx(params.tender_id, params.stage_order));
+                    runnableKey.push('stage_bills_final');
+                    break;
+                case 'stage':
+                    runnableRst.push(ctx.service.stage.getStageById(params.stage_id));
+                    runnableKey.push('stage');
+                    break;
+                case 'stage_pay':
+                    await checkStg(ctx, params);
+                    runnableRst.push(ctx.service.stagePay.getAuditorStageData(params.stage_id, params.stage_times, params.stage_order));
+                    runnableKey.push('stage_pay');
+                    break;
+                case 'mem_stage_im_zl':
+                    // memFieldKeys[filter]
+                    runnableRst.push(ctx.service.reportMemory.getStageImZlData(params.tender_id, params.stage_id));
+                    runnableKey.push('mem_stage_im_zl');
+                    break;
+                case 'mem_month_progress':
+                    runnableRst.push(ctx.service.reportMemory.getMonthProgress(params.tender_id));
+                    runnableKey.push('mem_month_progress');
+                    break;
+                case 'mem_stage_bills':
+                    runnableRst.push(ctx.service.reportMemory.getStageBillsData(params.tender_id, params.stage_id));
+                    runnableKey.push('mem_stage_pos');
+                    break;
+                case 'mem_stage_pos':
+                    runnableRst.push(ctx.service.reportMemory.getStagePosData(params.tender_id, params.stage_id));
+                    runnableKey.push('mem_stage_pos');
+                    break;
+                case 'change':
+                    runnableRst.push(ctx.service.change.getListByStatus(params.tender_id, 3)); // 获取所有审核通过的变更主信息
+                    runnableKey.push('change');
+                    break;
+                case 'change_audit_list':
+                    runnableRst.push(ctx.service.changeAuditList.getChangeAuditBills(params.tender_id)); // 获取所有审核通过的变更清单
+                    runnableKey.push('change_audit_list');
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    const queryRst = await Promise.all(runnableRst);
+    for (let idx = 0; idx < runnableKey.length; idx++) {
+        rst[runnableKey[idx]] = queryRst[idx];
+        if (['change', 'change_audit_list'].indexOf(runnableKey[idx]) >= 0) {
+            await ctx.helper.saveBufferFile(JSON.stringify(queryRst[idx],"","\t"), ctx.app.baseDir + '/' + runnableKey[idx] +'.json');
+        }
+    }
+    for (const filter of filters) {
+        switch (filter) {
+            case 'mem_stage_im_tz':
+                rst[filter] = await ctx.service.reportMemory.getStageImTzData(params.tender_id, params.stage_id);
+                break;
+            case 'mem_stage_im_tz_bills':
+                rst[filter] = await ctx.service.reportMemory.getStageImTzBillsData(params.tender_id, params.stage_id);
+                break;
+            default:
+                break;
+        }
+    }
+    return rst;
+}
+
+describe('test/app/service/report_memory.test.js', () => {
+    // 准备测试数据
+    before(function* () {
+        const ctx = app.mockContext();
+        // 模拟登录session
+        const postData = {
+            account: 'fuqingqing',
+            project: 'P0505',
+            project_password: '123456',
+        };
+        // const postData = {
+        //     account: 'chente',
+        //     project: 'T201711273363',
+        //     project_password: '123456',
+        // };
+        ctx.session = {};
+        const loginResult = yield ctx.service.projectAccount.accountLogin(postData, 2);
+        assert(loginResult);
+        mockData.session = ctx.session;
+    });
+    // 数据
+    it('test getReportData', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const params = {
+            tid: stage.tid,
+            sid: stage.sid,
+        };
+        const filters = ['change', 'change_audit_list'];
+        const result = yield getReportData(ctx, params, filters);
+        const savePath = path.join(ctx.app.baseDir, 'report_temp');
+        yield ctx.helper.recursiveMkdirSync(savePath);
+        for (const table in result) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(result[table],"","\t"), path.join(savePath, table + '.json'));
+        }
+    });
+});

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

@@ -10,6 +10,22 @@
 
 const { app, assert } = require('egg-mock/bootstrap');
 const mockData = {};
+const dataType = {
+    int: 'int',
+    str: 'string',
+    double: 'double',
+    currency: 'currency',
+};
+const addFields = function(table, name, field, type) {
+    const data = {};
+    data.ID = table.ID * 100 + table.items.length + 1;
+    data.Name = name;
+    data.DataType = type;
+    data.TableName = table.key;
+    data.descr = '';
+    data.mapExpression = "$PROJECT.REPORT.getProperty('" + table.key + "', '" + field + "')";
+    table.items.push(data);
+};
 
 describe('test/app/service/report_memory.test.js', () => {
     // 准备测试数据
@@ -69,4 +85,95 @@ describe('test/app/service/report_memory.test.js', () => {
             yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_month_progress.json');
         }
     });
+    // 期清单数据
+    it('test getStageBills', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const mainData = yield ctx.service.reportMemory.getStageBillsData(12, stage.id);
+        if (mainData instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_bills.json');
+            const tableDefine = {};
+            tableDefine.Name = '期 - 清单数据表(mem_stage_bills)';
+            tableDefine.remark = '';
+            tableDefine.ID = 25;
+            tableDefine.key = 'mem_stage_bills';
+            tableDefine.items = [];
+            addFields(tableDefine, '台账ID', 'id', dataType.int);
+            addFields(tableDefine, '标段ID', 'tender_id', dataType.int);
+            addFields(tableDefine, '树结构-ID', 'ledger_id', dataType.int);
+            addFields(tableDefine, '树结构-父项ID', 'ledger_pid', dataType.int);
+            addFields(tableDefine, '树结构-层级', 'level', dataType.int);
+            addFields(tableDefine, '树结构-同层排序', 'order', dataType.int);
+            addFields(tableDefine, '树结构-完整路径', 'full_path', dataType.int);
+            addFields(tableDefine, '树结构-是否子项', 'is_leaf', dataType.int);
+
+            addFields(tableDefine, '项目节编号', 'code', dataType.str);
+            addFields(tableDefine, '清单编号', 'b_code', dataType.str);
+            addFields(tableDefine, '名称', 'name', dataType.str);
+            addFields(tableDefine, '单位', 'unit', dataType.str);
+            addFields(tableDefine, '单价', 'unit_price', dataType.currency);
+
+            addFields(tableDefine, '签约-数量', 'deal_qty', dataType.currency);
+            addFields(tableDefine, '签约-金额', 'deal_tp', dataType.currency);
+
+            addFields(tableDefine, '施工复核-数量', 'sgfh_qty', dataType.currency);
+            addFields(tableDefine, '施工复核-金额', 'sgfh_tp', dataType.currency);
+            addFields(tableDefine, '设计错漏-数量', 'sjcl_qty', dataType.currency);
+            addFields(tableDefine, '设计错漏-金额', 'sjcl_tp', dataType.currency);
+            addFields(tableDefine, '其他错漏-数量', 'qtcl_qty', dataType.currency);
+            addFields(tableDefine, '其他错漏-金额', 'qtcl_tp', dataType.currency);
+            addFields(tableDefine, '台账-数量', 'quantity', dataType.currency);
+            addFields(tableDefine, '台账-金额', 'total_price', dataType.currency);
+
+            addFields(tableDefine, '项目节-数量1', 'dgn_qty1', dataType.currency);
+            addFields(tableDefine, '项目节-金额2', 'dgn_qty2', dataType.currency);
+
+            addFields(tableDefine, '图册号', 'drawing_code', dataType.str);
+            addFields(tableDefine, '备注', 'memo', dataType.str);
+            addFields(tableDefine, '节点类型', 'node_type', dataType.int);
+            addFields(tableDefine, '总额计量', 'is_tp', dataType.int);
+
+            addFields(tableDefine, '本期-合同-数量', 'contract_qty', dataType.currency);
+            addFields(tableDefine, '本期-合同-金额', 'contract_tp', dataType.currency);
+            addFields(tableDefine, '本期-数量变更-数量', 'qc_qty', dataType.currency);
+            addFields(tableDefine, '本期-数量变更-金额', 'qc_tp', dataType.currency);
+            addFields(tableDefine, '本期-完成-数量', 'gather_qty', dataType.currency);
+            addFields(tableDefine, '本期-完成-金额', 'gather_tp', dataType.currency);
+            addFields(tableDefine, '本期批注', 'postil', dataType.str);
+
+            addFields(tableDefine, '截止上期-合同-数量', 'pre_contract_qty', dataType.currency);
+            addFields(tableDefine, '截止上期-合同-金额', 'pre_contract_tp', dataType.currency);
+            addFields(tableDefine, '截止上期-数量变更-数量', 'pre_qc_qty', dataType.currency);
+            addFields(tableDefine, '截止上期-数量变更-金额', 'pre_qc_tp', dataType.currency);
+            addFields(tableDefine, '截止上期-完成-数量', 'pre_gather_qty', dataType.currency);
+            addFields(tableDefine, '截止上期-完成-金额', 'pre_gather_tp', dataType.currency);
+
+            addFields(tableDefine, '截止本期-合同-数量', 'end_contract_qty', dataType.currency);
+            addFields(tableDefine, '截止本期-合同-金额', 'end_contract_tp', dataType.currency);
+            addFields(tableDefine, '截止本期-数量变更-数量', 'end_qc_qty', dataType.currency);
+            addFields(tableDefine, '截止本期-数量变更-金额', 'end_qc_tp', dataType.currency);
+            addFields(tableDefine, '截止本期-完成-数量', 'end_gather_qty', dataType.currency);
+            addFields(tableDefine, '截止本期-完成-金额', 'end_gather_tp', dataType.currency);
+
+            addFields(tableDefine, '(台账 + 截止本期变更)-金额', 'final_tp', dataType.currency);
+            addFields(tableDefine, '截止本期完成率(%)', 'final_ratio', dataType.double);
+
+            delete tableDefine.ID;
+            delete tableDefine.key;
+            yield ctx.helper.saveBufferFile(JSON.stringify(tableDefine,"","\t"), ctx.app.baseDir + '/mem_stage_bills_define.json');
+        }
+    });
+    // 期部位明细数据
+    it('test getStagePos', function* () {
+        const ctx = app.mockContext(mockData);
+
+        // test12 - 第6期
+        const stage = yield ctx.service.stage.getDataByCondition({tid: 12, order: 6});
+        const mainData = yield ctx.service.reportMemory.getStagePosData(12, stage.id);
+        if (mainData instanceof Array) {
+            yield ctx.helper.saveBufferFile(JSON.stringify(mainData,"","\t"), ctx.app.baseDir + '/mem_stage_pos.json');
+        }
+    });
 });