Browse Source

汇总对比,台账汇总、计量汇总相关

MaiXinRong 3 months ago
parent
commit
bc5742cb4a

+ 52 - 28
app/controller/spss_controller.js

@@ -10,6 +10,7 @@
 
 const measureType = require('../const/tender').measureType;
 const status = require('../const/audit').stage.status;
+const moment = require('moment');
 
 module.exports = app => {
 
@@ -33,7 +34,7 @@ module.exports = app => {
                     categoryData,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.info)
                 };
-                await this.layout('spss/info.ejs', renderData, 'spss/gather_select_modal.ejs');
+                await this.layout('spss/info.ejs', renderData, 'spss/spss_select_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -46,7 +47,7 @@ module.exports = app => {
                     categoryData,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherLedger)
                 };
-                await this.layout('spss/gather_ledger.ejs', renderData, 'spss/gather_select_modal.ejs');
+                await this.layout('spss/gather_ledger.ejs', renderData, 'spss/spss_select_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -59,7 +60,7 @@ module.exports = app => {
                     categoryData,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherStage)
                 };
-                await this.layout('spss/gather_stage.ejs', renderData, 'spss/gather_select_modal.ejs');
+                await this.layout('spss/gather_stage.ejs', renderData, 'spss/spss_select_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -72,7 +73,7 @@ module.exports = app => {
                     categoryData,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.gatherStageExtra)
                 };
-                await this.layout('spss/gather_stage_extra.ejs', renderData, 'spss/gather_select_modal.ejs');
+                await this.layout('spss/gather_stage_extra.ejs', renderData, 'spss/spss_select_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -85,7 +86,7 @@ module.exports = app => {
                     categoryData,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.compareLedger)
                 };
-                await this.layout('spss/compare_ledger.ejs', renderData, 'spss/compare_select_modal.ejs');
+                await this.layout('spss/compare_ledger.ejs', renderData, 'spss/spss_select_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -98,7 +99,7 @@ module.exports = app => {
                     categoryData,
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.spss.compareStage),
                 };
-                await this.layout('spss/compare_stage.ejs', renderData, 'spss/compare_select_modal.ejs');
+                await this.layout('spss/compare_stage.ejs', renderData, 'spss/spss_select_modal.ejs');
             } catch (err) {
                 ctx.helper.log(err);
             }
@@ -128,20 +129,20 @@ module.exports = app => {
         }
         async _filterOrder(tender, order) {
             const stages = await this._getValidStages(tender.id);
-            return { type: 'stage', stage: this.ctx.helper._.find(stages, { order }) };
+            return { type: 'stage', stage: this.ctx.helper._.find(stages, { order }), filter: `第${order}期` };
         }
         async _filterMonth(tender, month) {
             const stages = await this._getValidStages(tender.id);
-            return { type: 'stage', stage: this.ctx.helper._.find(stages, { s_time: month }) };
+            return { type: 'stage', stage: this.ctx.helper._.find(stages, { s_time: month }), filter: month };
         }
         async _filterFinal(tender) {
             const stages = await this._getValidStages(tender.id);
-            return { type: 'stage', stage: stages[0] };
+            return { type: 'stage', stage: stages[0], filter: `第${stages[0].order}期` };
         }
         async _filterCheckedFinal(tender) {
             const stages = await this._getValidStages(tender.id);
             const checkedStages = stages.filter(x => { return x.status === status.checked; });
-            return { type: 'stage', stage: checkedStages[0] };
+            return { type: 'stage', stage: checkedStages[0], filter: `第${checkedStages[0].order}期` };
         }
         async _filterOrderZone(tender, zone) {
             let [iBegin, iEnd] = zone.split(':');
@@ -158,7 +159,7 @@ module.exports = app => {
                     validStages.push(stage);
                 }
             }
-            return { type: 'stages', stages: validStages, preStage, endStage };
+            return { type: 'stages', stages: validStages, preStage, endStage, filter: `第${iBegin}期 ~ 第${iEnd}期` };
         }
         async _filterTimeZone(tender, zone) {
             const times = zone.split(' - ');
@@ -178,7 +179,7 @@ module.exports = app => {
                     if (!endStage || moment(endStage.s_time, 'YYYY-MM').isAfter(sTime)) endStage = stage;
                 }
             }
-            return { type: 'stages', stages: validStages, preStage, endStage };
+            return { type: 'stages', stages: validStages, preStage, endStage, filter: zone };
         }
         async _filterStages(tender, stageInfo) {
             switch (stageInfo.type) {
@@ -199,11 +200,17 @@ module.exports = app => {
             }
         }
         async _loadStageData(tender, stage) {
-            await this.ctx.service.stage.doCheckStage(stage);
             const bills = await this.ctx.service.ledger.getAllDataByCondition({
                 columns: ['id', 'ledger_id', 'ledger_pid', 'level', 'full_path', 'is_leaf', 'order', 'code', 'b_code', 'name', 'unit', 'unit_price', 'quantity', 'total_price'],
                 where: { tender_id: tender.id },
             });
+            const pos = await this.ctx.service.pos.getAllDataByCondition({
+                columns: ['id', 'lid', 'name', 'quantity'],
+                where: { tid: tender.id },
+            });
+            if (!stage) return [bills, pos];
+
+            await this.ctx.service.stage.doCheckStage(stage);
             const curStage = stage.readOnly
                 ? await this.ctx.service.stageBills.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
                 : await this.ctx.service.stageBills.getLastestStageData2(tender.id, stage.id);
@@ -215,10 +222,6 @@ module.exports = app => {
                 { data: bpcStage, fields: ['contract_pc_tp', 'qc_pc_tp', 'pc_tp'], prefix: '', relaId: 'lid' },
             ]);
 
-            const pos = await this.ctx.service.pos.getAllDataByCondition({
-                columns: ['id', 'lid', 'name', 'quantity'],
-                where: { tid: tender.id },
-            });
             const curStagePos = stage.readOnly
                 ? await this.ctx.service.stagePos.getAuditorStageData2(tender.id, stage.id, stage.curTimes, stage.curOrder)
                 : await this.ctx.service.stagePos.getLastestStageData2(tender.id, stage.id);
@@ -230,7 +233,7 @@ module.exports = app => {
             return [bills, pos];
         }
         async _loadStagesData(tender, stages, preStage, endStage) {
-            const indexPre = 'id_';
+            const indexPre = 'id_', helper = this.ctx.helper;
             const sumAssignRelaData = function (index, rela) {
                 const loadFields = function (datas, fields, prefix, relaId) {
                     for (const d of datas) {
@@ -259,11 +262,11 @@ module.exports = app => {
             });
 
             let billsIndexData = {};
-            for (const bd of billsData) {
+            for (const bd of bills) {
                 billsIndexData[indexPre + bd.id] = bd;
             }
             let posIndexData = {};
-            for (const p of posData) {
+            for (const p of pos) {
                 posIndexData[indexPre + p.id] = p;
             }
 
@@ -335,31 +338,52 @@ module.exports = app => {
                         if (filter.indexOf('stage') < 0) [result.bills, result.pos] = await this._loadLedgerData(tender);
                         break;
                     case 'stage':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
-                        [result.bills, result.pos, result.stages] = await this._loadStageLedgerData(tender, stageFilter);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
+                        [result.bills, result.pos] = await this._loadStageLedgerData(tender, stageFilter);
                         break;
                     case 'jgcl':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
                         result.jgcl = await this._loadJgclData(tender, stageFilter);
                         break;
                     case 'yjcl':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
                         result.jgcl = await this._loadYjclData(tender, stageFilter);
                         break;
                     case 'bonus':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
                         result.jgcl = await this._loadBonusData(tender, stageFilter);
                         break;
                     case 'safeProd':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
                         result.jgcl = await this._loadSafeProdData(tender, stageFilter);
                         break;
                     case 'tempLand':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
                         result.jgcl = await this._loadTempLandData(tender, stageFilter);
                         break;
                     case 'other':
-                        if (!stageFilter) stageFilter = await this._filterStages(tender, stageInfo);
+                        if (!stageFilter) {
+                            stageFilter = await this._filterStages(tender, stageInfo);
+                            result.filter = stageFilter.filter;
+                        }
                         result.jgcl = await this._loadOtherData(tender, stageFilter);
                         break;
                     default: throw '查询的数据类型未定义';

+ 46 - 47
app/public/js/path_tree.js

@@ -322,7 +322,7 @@ const createNewPathTree = function (type, setting) {
             //     });
             // }
         }
-        clearData() {
+        clearDatas() {
             this.items = {};
             this.nodes = [];
             this.datas = [];
@@ -379,7 +379,7 @@ const createNewPathTree = function (type, setting) {
         loadDatas(datas) {
             const self = this;
             // 清空旧数据
-            this.clearData();
+            this.clearDatas();
             // 加载全部数据
             this.sortByLevel(datas);
             const setting = this.setting;
@@ -1701,12 +1701,6 @@ const createNewPathTree = function (type, setting) {
     }
 
     class FilterTree extends BaseTree {
-        clearDatas() {
-            this.items = {};
-            this.nodes = [];
-            this.datas = [];
-            this.children = [];
-        }
         addData(data, fields) {
             const item = {};
             for (const prop in data) {
@@ -1739,13 +1733,6 @@ const createNewPathTree = function (type, setting) {
     }
 
     class GatherTree extends BaseTree {
-        clearDatas() {
-            this.items = {};
-            this.nodes = [];
-            this.datas = [];
-            this.children = [];
-        }
-
         get newId() {
             if (!this._maxId) {
                 this._maxId = 0;
@@ -1898,7 +1885,7 @@ const createNewPathTree = function (type, setting) {
         }
 
         loadCompareData(data1, data2) {
-            this.clearData();
+            this.clearDatas();
             this.loadCompareTree(data1, this.setting.loadInfo1);
             this.loadCompareTree(data2, this.setting.loadInfo2);
             for (const d of this.datas) {
@@ -1914,7 +1901,6 @@ const createNewPathTree = function (type, setting) {
     }
 
     class TreeGatherTree extends FxTree {
-
         constructor(setting) {
             super(setting);
             this._newId = 1;
@@ -1924,31 +1910,47 @@ const createNewPathTree = function (type, setting) {
             return this._newId++;
         }
 
-        loadGatherNode(node, parent, index, loadFun) {
-            const siblings = parent ? parent.children : this.children;
-            let cur = siblings.find(function (x) {
-                return node.b_code
-                    ? x.b_code === node.b_code && x.name === node.name && x.unit === node.unit && x.unit_price === node.unit_price
-                    : x.code === node.code && x.name === node.name;
-            });
+        findGatherNode(node, parent, source) {
+            if (this.setting.findNode) {
+                return this.setting.findNode(this, node, parent, source);
+            } else {
+                const siblings = parent ? parent.children : this.children;
+                const checkData = { code: node.code || '', b_code: node.b_code || '', name: node.name || '', unit: node.unit || '', unit_price: node.unit_price || 0 };
+                return siblings.find(function (x) {
+                    return checkData.b_code
+                        ? x.b_code === checkData.b_code && x.name === checkData.name && x.unit === checkData.unit && x.unit_price === checkData.unit_price
+                        : x.code === checkData.code && x.name === checkData.name;
+                });
+            }
+        }
+
+        loadGatherNode(source, node, parent, index, loadFun) {
+            let cur = this.findGatherNode(node, parent, source);
             if (!cur) {
+                const siblings = parent ? parent.children : this.children;
                 const id = this.newId;
                 cur = {
-                    id: id,
-                    pid: parent ? parent.id : this.setting.rootId,
-                    full_path: parent ? parent.full_path + '-' + id : '' + id,
-                    level: parent ? parent.level + 1 : 1,
-                    order: siblings.length + 1,
-                    children: [],
-                    code: node.code, b_code: node.b_code, name: node.name,
-                    unit: node.unit, unit_price: node.unit_price,
+                    children: [], pos: [],
+                    code: node.code || '', b_code: node.b_code || '', name: node.name || '',
+                    unit: node.unit || '', unit_price: node.unit_price || 0,
                 };
+                cur[this.setting.id] = id;
+                cur[this.setting.pid] = parent ? parent[this.setting.id] : this.setting.rootId;
+                cur[this.setting.fullPath] = parent ? parent[this.setting.fullPath] + '-' + id : '' + id;
+                cur[this.setting.level] = parent ? parent[this.setting.level] + 1 : 1;
+                cur[this.setting.order] = siblings.length + 1;
                 siblings.push(cur);
                 this.datas.push(cur);
             }
-            loadFun(cur, node, index);
+            loadFun(cur, node, index, source);
             for (const c of node.children) {
-                this.loadGatherNode(c, cur, index, loadFun);
+                this.loadGatherNode(source, c, cur, index, loadFun);
+            }
+        }
+
+        loadGatherTree(data, index, loadFun) {
+            for (const c of data.billsTree.children) {
+                this.loadGatherNode(data, c, null, index, loadFun);
             }
         }
 
@@ -1965,14 +1967,6 @@ const createNewPathTree = function (type, setting) {
                 addSortNode(n);
             }
         }
-
-        loadGatherTree(data, index, loadFun) {
-            for (const c of data.billsTree.children) {
-                this.loadGatherNode(c, null, index, loadFun);
-            }
-            // todo load Pos Data;
-        }
-
         calculateSum() {
             if (this.setting.calcSum) {
                 for (const d of this.datas) {
@@ -1980,7 +1974,6 @@ const createNewPathTree = function (type, setting) {
                 }
             }
         }
-
         loadGatherData(datas) {
             this.count = datas.length;
             for (const [i, data] of datas.entries()) {
@@ -2313,13 +2306,19 @@ const treeCalc = {
     },
     calculateNode: function (tree, node) {
         if (node.children && node.children.length > 0) {
-            const gather = node.children.reduce(function (rst, x) {
-                const result = {};
+            const gather = {};
+            node.children.forEach(c => {
                 for (const cf of tree.setting.calcFields) {
-                    result[cf] = ZhCalc.add(rst[cf], x[cf]);
+                    gather[cf] = ZhCalc.add(gather[cf], c[cf]);
                 }
-                return result;
             });
+            // const gather = node.children.reduce(function (rst, x) {
+            //     const result = {};
+            //     for (const cf of tree.setting.calcFields) {
+            //         result[cf] = ZhCalc.add(rst[cf], x[cf]);
+            //     }
+            //     return result;
+            // });
             // 汇总子项
             for (const cf of tree.setting.calcFields) {
                 if (gather[cf]) {

+ 1 - 1
app/public/js/shares/tender_select_multi.js

@@ -1,6 +1,6 @@
 const TenderSelectMulti = function (setting) {
     $('#tsm-title').html(setting.title);
-    if (setting.type === 'gather' && setting.type === 'stage') {
+    if (setting.type === 'gather' && setting.dataType === 'stage') {
         $('#tsm-stage-info').show();
     }
     $('[name=tsm-source]').click(function() {

+ 4 - 3
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -309,10 +309,11 @@ const SpreadJsObj = {
      * 根据sheet.zh_setting初始化sheet表头
      * @param {GC.Spread.Sheets.Worksheet} sheet
      */
-    _initSheetHeader: function (sheet) {
+    _initSheetHeader: function (sheet, force) {
         const setting = sheet.zh_setting;
         if (!setting || !setting.cols) { return; }
 
+        if (force) sheet.setColumnCount(0);
         sheet.setColumnCount(setting.cols.length);
         sheet.setRowCount(setting.headRows, spreadNS.SheetArea.colHeader);
         for (let iRow = 0; iRow < setting.headRowHeight.length; iRow ++) {
@@ -417,11 +418,11 @@ const SpreadJsObj = {
         this.endMassOperation(sheet);
         if (!setting.invalidCopyFilterHiddenRow) this.copyFilterHiddenRow(sheet);
     },
-    reLoadSheetHeader: function (sheet) {
+    reLoadSheetHeader: function (sheet, force = false) {
         if (sheet.zh_setting) {
             this.beginMassOperation(sheet);
             this._loadCacheSetting(sheet, sheet.zh_setting);
-            this._initSheetHeader(sheet);
+            this._initSheetHeader(sheet, force);
             this.endMassOperation(sheet);
         }
     },

+ 299 - 0
app/public/js/spss_gather_ledger.js

@@ -0,0 +1,299 @@
+const billsCompareField = ['quantity', 'total_price', ];
+const posCompareField = ['quantity'];
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const billsSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 100, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '合计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sum_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sum_total_price', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        baseCols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 100, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+        ],
+        extraCols: [
+            {title: '标段1|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity_1', hAlign: 2, width: 80, type: 'Number', formatTitle: '%s|数量', formatField: 'quantity_%d'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price_1', hAlign: 2, width: 80, type: 'Number', formatField: 'total_price_%d'},
+        ],
+        endCols: [
+            {title: '合计|数量', colSpan: '2|1', rowSpan: '1|1', field: 'sum_quantity', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'sum_total_price', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [32, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    const posSpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '差值', colSpan: '1', rowSpan: '1', field: 'su,_quantity', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        baseCols: [
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+        ],
+        extraCols: [
+            {title: '标段1', colSpan: '1', rowSpan: '1', field: 'quantity_1', hAlign: 2, width: 100, type: 'Number', formatTitle: '%s', formatField: 'quantity_%d'},
+        ],
+        endCols: [
+            {title: '合计', colSpan: '1', rowSpan: '1', field: 'sum_quantity', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
+
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        full_path: 'full_path',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        calcFields: ['sum_tp'],
+        // todo 判断同一清单时,是否应区分 清单是否含计量单元?
+        // findNode: function(tree, node, parent, source) {
+        //     const siblings = parent ? parent.children : tree.children;
+        //     const checkData = { code: node.code || '', b_code: node.b_code || '', name: node.name || '', unit: node.unit || '', unit_price: node.unit_price || 0 };
+        //     return siblings.find(function (x) {
+        //         if (!checkData.b_code) return x.code === checkData.code && x.name === checkData.name;
+        //         const posRange = source.pos.getLedgerPos(node.id);
+        //         const hasPos = posRange && posRange.length > 0;
+        //         const xHasPos = x.pos && x.pos.length > 0;
+        //         return hasPos === xHasPos && x.b_code === checkData.b_code && x.name === checkData.name && x.unit === checkData.unit && x.unit_price === checkData.unit_price;
+        //     });
+        // },
+        loadInfo: function (node, sourceNode, index, source) {
+            for (const f of billsCompareField) {
+                node[f + '_' + index] = sourceNode[f];
+            }
+            const posRange = source.pos.getLedgerPos(sourceNode.id);
+            if (posRange && posRange.length > 0) {
+                if (!node.pos) node.pos = [];
+                for (const p of posRange) {
+                    let nP = _.find(node.pos, {name: p.name || ''});
+                    if (!nP) {
+                        nP = {name: p.name || ''};
+                        node.pos.push(nP);
+                    }
+                    for (const f of posCompareField) {
+                        nP[f + '_' + index] = p[f];
+                    }
+                }
+            }
+        },
+        calcSum: function(data, count) {
+            for (const f of billsCompareField) {
+                data['sum_' + f] = 0;
+                for (let i = 1; i <= count; i++) {
+                    data['sum_' + f] = ZhCalc.add(data['sum_' + f], data[f + '_' + i]);
+                }
+            }
+            for (const p of data.pos) {
+                for (const f of posCompareField) {
+                    p['sum_' + f] = 0;
+                    for (let i = 1; i <= count; i++) {
+                        p['sum_' + f] = ZhCalc.add(p['sum_' + f], p[f + '_' + i]);
+                    }
+                }
+            }
+        },
+    };
+    const billsTree = createNewPathTree('tree-gather', treeSetting);
+
+    const billsTreeSpreadObj = {
+        selectionChanged: function (e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    posSpreadObj.loadCurPosData();
+                }
+            }
+        },
+        rebuildSpreadSetting(tenders) {
+            billsSpreadSetting.cols = [...billsSpreadSetting.baseCols];
+            posSpreadSetting.cols = [...posSpreadSetting.baseCols];
+            for (const [i, tender] of tenders.entries()) {
+                for (const col of billsSpreadSetting.extraCols) {
+                    const newCol = JSON.parse(JSON.stringify(col));
+                    if (newCol.formatTitle) newCol.title = newCol.formatTitle.replace('%s', tender.name);
+                    if (newCol.formatField) newCol.field = newCol.formatField.replace('%d', i + 1);
+                    billsSpreadSetting.cols.push(newCol);
+                }
+                for (const col of posSpreadSetting.extraCols) {
+                    const newCol = JSON.parse(JSON.stringify(col));
+                    if (newCol.formatTitle) newCol.title = newCol.formatTitle.replace('%s', tender.name);
+                    if (newCol.formatField) newCol.field = newCol.formatField.replace('%d', i + 1);
+                    posSpreadSetting.cols.push(newCol)
+                }
+            }
+            billsSpreadSetting.cols.push(...billsSpreadSetting.endCols);
+            posSpreadSetting.cols.push(...posSpreadSetting.endCols);
+        }
+    };
+    billsSpread.bind(spreadNS.Events.SelectionChanged, billsTreeSpreadObj.selectionChanged);
+    const posSpreadObj = {
+        loadCurPosData: function () {
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            console.log(node);
+            if (node) {
+                SpreadJsObj.loadSheetData(posSheet, 'data', node.pos || []);
+            } else {
+                SpreadJsObj.loadSheetData(posSheet, 'data', []);
+            }
+        },
+    };
+
+    const tenderSelect = TenderSelectMulti({
+        title: '汇总标段',
+        type: 'gather',
+        dataType: 'ledger',
+        afterSelect: function(select) {
+            const data = { filter: 'ledger', tender: select };
+            postData(`/sp/${spid}/spss/load`, data, function(result) {
+                billsTreeSpreadObj.rebuildSpreadSetting(result);
+                SpreadJsObj.reLoadSheetHeader(billsSheet);
+                SpreadJsObj.reLoadSheetHeader(posSheet);
+
+                treeSetting.calcFields = ['sum_total_price'];
+                for (let i = 1; i <= result.length; i++) {
+                    treeSetting.calcFields.push('total_price_' + i);
+                }
+
+                const tenderTreeSetting = {
+                    id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level',
+                    rootId: -1,
+                    keys: ['id', 'tender_id', 'ledger_id'],
+                    calcFields: ['total_price'],
+                };
+                const posSetting = { id: 'id', ledgerId: 'lid' };
+                const tenders = [];
+                for (const t of result) {
+                    const tender = {
+                        billsTree: createNewPathTree('ledger', tenderTreeSetting),
+                        pos: new PosData(posSetting),
+                    };
+                    tender.billsTree.loadDatas(t.bills);
+                    tender.pos.loadDatas(t.pos);
+                    tenders.push(tender);
+                }
+
+                billsTree.clearDatas();
+                billsTree.loadGatherData(tenders);
+                treeCalc.calculateAll(billsTree);
+                SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree, true);
+                posSpreadObj.loadCurPosData();
+            });
+        },
+    });
+    $('#gather-select').click(tenderSelect.showSelect);
+
+    $('#export-excel').click(function() {
+        const excelData = [];
+        for (const node of billsTree.nodes) {
+            const data = {};
+            for (const c of billsSpreadSetting.cols) {
+                data[c.field] = node[c.field];
+            }
+            excelData.push(data);
+            for (const p of node.pos) {
+                const pData = {};
+                for (const c of posSpreadSetting.cols) {
+                    pData[c.field] = p[c.field];
+                }
+                excelData.push(pData);
+            }
+        }
+        SpreadExcelObj.exportSimpleXlsxSheet(billsSpreadSetting, excelData, "台账汇总.xlsx");
+    });
+    $.divResizer({
+        select: '#pos-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            posSpread.refresh();
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "differ":
+                    tree.expandByCustom(function (x) {
+                        const posterity = tree.getPosterity(x);
+                        if (posterity.length === 0) return x.differ_qty || x.differ_tp;
+                        for (const p of posterity) {
+                            if (x.differ_qty || x.differ_tp) return true;
+                        }
+                        return false;
+                    });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+                $('.c-body table thead').css('left', '56px');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+                $('.c-body table thead').css('left', '176px');
+            }
+            autoFlashHeight();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+});

+ 374 - 0
app/public/js/spss_gather_stage.js

@@ -0,0 +1,374 @@
+const billsCompareField = [
+    'quantity', 'total_price',
+    'contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp',
+    'end_contract_qty', 'end_contract_tp', 'end_qc_qty', 'end_qc_tp', 'end_gather_qty', 'end_gather_tp',
+];
+const posCompareField = [
+    'quantity',
+    'contract_qty', 'qc_qty', 'gather_qty',
+    'end_contract_qty', 'end_qc_qty', 'end_gather_qty',
+];
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const billsSpreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '3', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '3', field: 'b_code', hAlign: 0, width: 100, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '3', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '3', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '3', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '合计||数量', colSpan: '2||1', rowSpan: '2||1', field: 'sum_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '||金额', colSpan: '|1', rowSpan: '|1', field: 'sum_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        baseCols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '3', field: 'code', hAlign: 0, width: 185, formatter: '@', cellType: 'tree'},
+            {title: '清单编号', colSpan: '1', rowSpan: '3', field: 'b_code', hAlign: 0, width: 100, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '3', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '3', field: 'unit', hAlign: 1, width: 60, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '3', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+        ],
+        extraCols: [
+            {title: '标段1|第1期|数量', colSpan: '2|2|1', rowSpan: '1|1|1', field: 'quantity_1', hAlign: 2, width: 80, type: 'Number', formatTitle: '%s|%f|数量', formatField: 'gather_qty_%d'},
+            {title: '||金额', colSpan: '|1', rowSpan: '|1', field: 'total_price_1', hAlign: 2, width: 80, type: 'Number', formatField: 'gather_tp_%d'},
+        ],
+        endCols: [
+            {title: '合计||数量', colSpan: '2||1', rowSpan: '2||1', field: 'sum_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '||金额', colSpan: '|1', rowSpan: '|1', field: 'sum_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 3,
+        headRowHeight: [32, 25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    const posSpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+            {title: '差值', colSpan: '1', rowSpan: '2', field: 'sum_qty', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        baseCols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 235, formatter: '@'},
+        ],
+        extraCols: [
+            {title: '标段1|第1期', colSpan: '1|1', rowSpan: '1|1', field: 'quantity_1', hAlign: 2, width: 100, type: 'Number', formatTitle: '%s|%f', formatField: 'gather_qty_%d'},
+        ],
+        endCols: [
+            {title: '合计', colSpan: '1', rowSpan: '2', field: 'sum_qty', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [32, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+
+    const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
+    const billsSheet = billsSpread.getActiveSheet();
+    sjsSettingObj.setFxTreeStyle(billsSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
+
+    const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
+    const posSheet = posSpread.getActiveSheet();
+    SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        full_path: 'full_path',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        calcFields: ['sum_tp'],
+        // todo 判断同一清单时,是否应区分 清单是否含计量单元?
+        // findNode: function(tree, node, parent, source) {
+        //     const siblings = parent ? parent.children : tree.children;
+        //     const checkData = { code: node.code || '', b_code: node.b_code || '', name: node.name || '', unit: node.unit || '', unit_price: node.unit_price || 0 };
+        //     return siblings.find(function (x) {
+        //         if (!checkData.b_code) return x.code === checkData.code && x.name === checkData.name;
+        //         const posRange = source.pos.getLedgerPos(node.id);
+        //         const hasPos = posRange && posRange.length > 0;
+        //         const xHasPos = x.pos && x.pos.length > 0;
+        //         return hasPos === xHasPos && x.b_code === checkData.b_code && x.name === checkData.name && x.unit === checkData.unit && x.unit_price === checkData.unit_price;
+        //     });
+        // },
+        loadInfo: function (node, sourceNode, index, source) {
+            for (const f of billsCompareField) {
+                node[f + '_' + index] = sourceNode[f];
+            }
+            const posRange = source.pos.getLedgerPos(sourceNode.id);
+            if (posRange && posRange.length > 0) {
+                if (!node.pos) node.pos = [];
+                for (const p of posRange) {
+                    let nP = _.find(node.pos, {name: p.name || ''});
+                    if (!nP) {
+                        nP = {name: p.name || ''};
+                        node.pos.push(nP);
+                    }
+                    for (const f of posCompareField) {
+                        nP[f + '_' + index] = p[f];
+                    }
+                }
+            }
+        },
+        calcSum: function(data, count) {
+            for (const f of billsCompareField) {
+                data['sum_' + f] = 0;
+                for (let i = 1; i <= count; i++) {
+                    data['sum_' + f] = ZhCalc.add(data['sum_' + f], data[f + '_' + i]);
+                }
+            }
+            for (const p of data.pos) {
+                for (const f of posCompareField) {
+                    p['sum_' + f] = 0;
+                    for (let i = 1; i <= count; i++) {
+                        p['sum_' + f] = ZhCalc.add(p['sum_' + f], p[f + '_' + i]);
+                    }
+                }
+            }
+        },
+    };
+    const billsTree = createNewPathTree('tree-gather', treeSetting);
+
+    const billsTreeSpreadObj = {
+        selectionChanged: function (e, info) {
+            if (info.newSelections) {
+                if (!info.oldSelections || info.newSelections[0].row !== info.oldSelections[0].row) {
+                    posSpreadObj.loadCurPosData();
+                }
+            }
+        },
+        loadShowData: function() {
+            const field = $('[name=compare-data]:checked').val();
+            for (const node of billsTree.nodes) {
+                for (let i = 1; i < billsTree.count; i++) {
+                    node['gather_qty_' + i] = node[field + '_qty_' + i];
+                    node['gather_tp_' + i] = node[field + '_tp_' + i];
+                }
+                node.sum_qty = node['sum_' + field + '_qty'];
+                node.sum_tp = node['sum_' + field + '_tp'];
+                for (const p of node.pos) {
+                    for (let i = 1; i < billsTree.count; i++) {
+                        p['gather_qty_' + i] = p[field + '_qty_' + i];
+                    }
+                    p.sum_qty = p['sum_' + field + '_qty'];
+                }
+            }
+            this.compareData = field;
+        },
+        rebuildSpreadSetting(tenders) {
+            if(tenders.length > 0) {
+                if (tenders[0].filter.indexOf('~') > 0) {
+                    $('[datatype=end]').hide();
+                } else {
+                    $('[datatype=end]').show();
+                }
+            }
+            billsSpreadSetting.cols = [...billsSpreadSetting.baseCols];
+            posSpreadSetting.cols = [...posSpreadSetting.baseCols];
+            for (const [i, tender] of tenders.entries()) {
+                for (const col of billsSpreadSetting.extraCols) {
+                    const newCol = JSON.parse(JSON.stringify(col));
+                    if (newCol.formatTitle) newCol.title = newCol.formatTitle.replace('%s', tender.name).replace('%f', tender.filter);
+                    if (newCol.formatField) newCol.field = newCol.formatField.replace('%d', i + 1);
+                    billsSpreadSetting.cols.push(newCol);
+                }
+                for (const col of posSpreadSetting.extraCols) {
+                    const newCol = JSON.parse(JSON.stringify(col));
+                    if (newCol.formatTitle) newCol.title = newCol.formatTitle.replace('%s', tender.name).replace('%f', tender.filter);
+                    if (newCol.formatField) newCol.field = newCol.formatField.replace('%d', i + 1);
+                    posSpreadSetting.cols.push(newCol)
+                }
+            }
+            billsSpreadSetting.cols.push(...billsSpreadSetting.endCols);
+            posSpreadSetting.cols.push(...posSpreadSetting.endCols);
+        },
+        refreshShowData: function() {
+            const field = $('[name=compare-data]:checked').val();
+            if (field === this.compareData) return;
+
+            this.loadShowData();
+            SpreadJsObj.reloadColData(billsSheet, 5, billsSpreadSetting.cols.length - 5);
+            SpreadJsObj.reloadColData(posSheet, 1, posSpreadSetting.cols.length - 1);
+        }
+    };
+    billsSpread.bind(spreadNS.Events.SelectionChanged, billsTreeSpreadObj.selectionChanged);
+    const posSpreadObj = {
+        loadCurPosData: function () {
+            const node = SpreadJsObj.getSelectObject(billsSheet);
+            if (node) {
+                console.log(node);
+                SpreadJsObj.loadSheetData(posSheet, 'data', node.pos || []);
+            } else {
+                SpreadJsObj.loadSheetData(posSheet, 'data', []);
+            }
+        },
+    };
+
+    const tenderSelect = TenderSelectMulti({
+        title: '汇总标段',
+        type: 'gather',
+        dataType: 'stage',
+        afterSelect: function(select) {
+            const data = { filter: 'stage', tender: select };
+            postData(`/sp/${spid}/spss/load`, data, function(result) {
+                billsTreeSpreadObj.rebuildSpreadSetting(result);
+                SpreadJsObj.reLoadSheetHeader(billsSheet, true);
+                SpreadJsObj.reLoadSheetHeader(posSheet, true);
+
+                treeSetting.calcFields = ['sum_total_price'];
+                for (let i = 1; i <= result.length; i++) {
+                    treeSetting.calcFields.push('total_price_' + i, 'contract_tp_' + i, 'qc_tp_' + i, 'gather_tp_' + i, 'end_contract_tp_' + i, 'end_qc_tp_' + i, 'end_gather_tp_' + i);
+                }
+
+                const tenderTreeSetting = {
+                    id: 'ledger_id', pid: 'ledger_pid', order: 'order', level: 'level',
+                    rootId: -1,
+                    keys: ['id', 'tender_id', 'ledger_id'],
+                    calcFields: ['total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp'],
+                    calcFun: function(node) {
+                        if (!node.children || node.children.length === 0) {
+                            node.pre_gather_qty = ZhCalc.add(node.pre_contract_qty, node.pre_qc_qty);
+                            node.gather_qty = ZhCalc.add(node.contract_qty, node.qc_qty);
+                            node.end_contract_qty = ZhCalc.add(node.pre_contract_qty, node.contract_qty);
+                            node.end_gather_qty = ZhCalc.add(node.pre_gather_qty, node.gather_qty);
+                            node.end_qc_qty = ZhCalc.add(node.pre_qc_qty, node.qc_qty);
+                            node.end_qc_minus_qty = ZhCalc.add(node.pre_qc_minus_qty, node.qc_minus_qty);
+                        }
+                        node.pre_gather_tp = ZhCalc.add(node.pre_contract_tp, node.pre_qc_tp);
+                        node.gather_tp = ZhCalc.sum([node.contract_tp, node.qc_tp, node.pc_tp]);
+                        node.end_contract_tp = ZhCalc.sum([node.pre_contract_tp, node.contract_tp, node.contract_pc_tp]);
+                        node.end_qc_tp = ZhCalc.sum([node.pre_qc_tp, node.qc_tp, node.qc_pc_tp]);
+                        node.end_gather_tp = ZhCalc.add(node.pre_gather_tp, node.gather_tp);
+                    }
+                };
+                const posSetting = {
+                    id: 'id', ledgerId: 'lid',
+                    calcFun: function(pos) {
+                        pos.pre_gather_qty = ZhCalc.add(pos.pre_contract_qty, pos.pre_qc_qty);
+                        pos.gather_qty = ZhCalc.add(pos.contract_qty, pos.qc_qty);
+                        pos.end_contract_qty = ZhCalc.add(pos.pre_contract_qty, pos.contract_qty);
+                        pos.end_qc_qty = ZhCalc.add(pos.pre_qc_qty, pos.qc_qty);
+                        pos.end_qc_minus_qty = ZhCalc.add(pos.pre_qc_minus_qty, pos.qc_minus_qty);
+                        pos.end_gather_qty = ZhCalc.add(pos.pre_gather_qty, pos.gather_qty);
+                    }
+                };
+                const tenders = [];
+                for (const t of result) {
+                    const tender = {
+                        billsTree: createNewPathTree('ledger', tenderTreeSetting),
+                        pos: new PosData(posSetting),
+                    };
+                    tender.billsTree.loadDatas(t.bills);
+                    treeCalc.calculateAll(tender.billsTree);
+                    tender.pos.loadDatas(t.pos);
+                    tender.pos.calculateAll();
+                    tenders.push(tender);
+                }
+
+                billsTree.clearDatas();
+                billsTree.loadGatherData(tenders);
+                treeCalc.calculateAll(billsTree);
+                billsTreeSpreadObj.loadShowData();
+                SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+                SpreadJsObj.locateRow(billsSheet, 0);
+                posSpreadObj.loadCurPosData();
+                SpreadJsObj.locateRow(posSheet, 0);
+            });
+        },
+    });
+    $('#gather-select').click(tenderSelect.showSelect);
+
+    $('[name=compare-data]').click(function() {
+        billsTreeSpreadObj.refreshShowData();
+    });
+
+    $('#export-excel').click(function() {
+        const excelData = [];
+        for (const node of billsTree.nodes) {
+            const data = {};
+            for (const c of billsSpreadSetting.cols) {
+                data[c.field] = node[c.field];
+            }
+            excelData.push(data);
+            for (const p of node.pos) {
+                const pData = {};
+                for (const c of posSpreadSetting.cols) {
+                    pData[c.field] = p[c.field];
+                }
+                excelData.push(pData);
+            }
+        }
+        SpreadExcelObj.exportSimpleXlsxSheet(billsSpreadSetting, excelData, "台账汇总.xlsx");
+    });
+    $.divResizer({
+        select: '#pos-resize',
+        callback: function () {
+            billsSpread.refresh();
+            let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
+            $(".sp-wrap").height(bcontent-30);
+            posSpread.refresh();
+        }
+    });
+    // 显示层次
+    (function (select, sheet) {
+        $(select).click(function () {
+            if (!sheet.zh_tree) return;
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "differ":
+                    tree.expandByCustom(function (x) {
+                        const posterity = tree.getPosterity(x);
+                        if (posterity.length === 0) return x.differ_qty || x.differ_tp;
+                        for (const p of posterity) {
+                            if (x.differ_qty || x.differ_tp) return true;
+                        }
+                        return false;
+                    });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+                $('.c-body table thead').css('left', '56px');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+                $('.c-body table thead').css('left', '176px');
+            }
+            autoFlashHeight();
+            billsSpread.refresh();
+            posSpread.refresh();
+        }
+    });
+});

app/view/spss/gather_select_modal.ejs → app/public/js/spss_gather_stage_extra.js


+ 0 - 0
app/public/js/spss_gather_stage_info.js


+ 0 - 38
app/view/spss/compare_stage_modal.ejs

@@ -1,38 +0,0 @@
-<!--弹出对比标段-->
-<div class="modal fade" id="data-select" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择对比标段</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group">
-                    <label>对比标段1<b class="text-danger">*</b></label>
-                    <div class="row ml-1 mr-1">
-                        <div class="col-6">
-                            <input class="form-control form-control-sm"  placeholder="输入标段1的ID" type="text" name="id1">
-                        </div>
-                        <div class="col-6">
-                            <input class="form-control form-control-sm"  placeholder="输入标段1的对比期序号" type="text" name="sorder1">
-                        </div>
-                    </div>
-                </div>
-                <div class="form-group">
-                    <label>对比标段2<b class="text-danger">*</b></label>
-                    <div class="row ml-1 mr-1">
-                        <div class="col-6">
-                            <input class="form-control form-control-sm"  placeholder="输入标段2的ID" type="text" name="id2">
-                        </div>
-                        <div class="col-6">
-                            <input class="form-control form-control-sm"  placeholder="输入标段2的对比期序号" type="text" name="sorder2">
-                        </div>
-                    </div>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
-            </div>
-        </div>
-    </div>
-</div>

+ 0 - 55
app/view/spss/compare_tz.ejs

@@ -1,55 +0,0 @@
-<% include ./sub_menu.ejs %>
-<div class="panel-content">
-    <div class="panel-title">
-        <div class="title-main d-flex">
-            <% include ./sub_mini_menu.ejs %>
-            <div>
-                <div class="d-inline-block">
-                    <div class="dropdown">
-                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                            <i class="fa fa-list-ol"></i> 显示层级
-                        </button>
-                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
-                            <a class="dropdown-item" name="showLevel" tag="1" href="javascirpt: void(0);">第一层</a>
-                            <a class="dropdown-item" name="showLevel" tag="2" href="javascirpt: void(0);">第二层</a>
-                            <a class="dropdown-item" name="showLevel" tag="3" href="javascirpt: void(0);">第三层</a>
-                            <a class="dropdown-item" name="showLevel" tag="4" href="javascirpt: void(0);">第四层</a>
-                            <a class="dropdown-item" name="showLevel" tag="5" href="javascirpt: void(0);">第五层</a>
-                            <a class="dropdown-item" name="showLevel" tag="last" href="javascirpt: void(0);">最底层</a>
-                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascirpt: void(0);">只显示项目节</a>
-                        </div>
-                    </div>
-                </div>
-                <div class="d-inline-block">
-                    <button href="#data-select" class="btn btn-sm btn-light text-primary" data-toggle="modal" data-target="#data-select"><i class="fa fa-clone"></i>
-                        选择<%- ctx.url.indexOf('compare') > 0 ? '对比' : '汇总' %>标段
-                    </button>
-                </div>
-            </div>
-            <div class="ml-auto"></div>
-        </div>
-    </div>
-    <div class="content-wrap">
-        <div class="c-header p-0"></div>
-        <div class="c-body">
-            <div class="sjs-height-1" id="bills-spread">
-            </div>
-            <div class="bcontent-wrap" id="main-bottom">
-                <div id="main-resize" class="resize-y"  r-Type="height" div1="#bills-spread" div2="#main-bottom" store-id="compare-main" store-version="1.0.0" min="100"></div>
-                <div class="bc-bar mb-1">
-                    <ul class="nav nav-tabs">
-                        <li class="nav-item">
-                            <a class="nav-link active" data-toggle="tab" href="#xmujie" role="tab">计量单元</a>
-                        </li>
-                    </ul>
-                </div>
-                <div class="tab-content">
-                    <div class="tab-pane active" id="xmujie">
-                        <div class="sp-wrap" id="pos-spread">
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-</div>

+ 0 - 24
app/view/spss/compare_tz_modal.ejs

@@ -1,24 +0,0 @@
-<!--弹出对比标段-->
-<div class="modal fade" id="data-select" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择对比标段</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group">
-                    <label>对比标段1<b class="text-danger">*</b></label>
-                    <input class="form-control form-control-sm"  placeholder="输入标段1的ID" type="text" name="id1">
-                </div>               
-                <div class="form-group">
-                    <label>对比标段2<b class="text-danger">*</b></label>
-                    <input class="form-control form-control-sm"  placeholder="输入标段2的ID" type="text" name="id2">
-                </div>               
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
-            </div>
-        </div>
-    </div>
-</div>

+ 20 - 2
app/view/spss/gather_ledger.ejs

@@ -32,8 +32,26 @@
         </div>
     </div>
     <div class="content-wrap">
-        <div class="sjs-height-0" style="background-color: #fff">
-            <div class="c-body">
+        <div class="c-header p-0 col-12"></div>
+        <!--核心内容(两栏)-->
+        <div class="row w-100 sub-content">
+            <!--左栏-->
+            <div class="c-body" id="left-view" style="width: 100%">
+                <!--0号台账模式-->
+                <div class="sjs-height-1" style="overflow: hidden" id="bills-spread">
+                </div>
+                <div class="bcontent-wrap">
+                    <div id="pos-resize" class="resize-y" id="top-spr" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="javascript:void(0)">计量单元</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="sp-wrap" id="pos-spread">
+                    </div>
+                </div>
             </div>
         </div>
     </div>

+ 50 - 3
app/view/spss/gather_stage.ejs

@@ -6,7 +6,7 @@
             <div class="title-main  d-flex justify-content-between">
                 <div>
                     <div class="d-inline-block">
-                        <div class="btn-group btn-group-toggle group-tab" data-toggle="buttons">
+                        <div class="btn-group btn-group-toggle group-tab">
                             <a href="/sp/<%- ctx.subProject.id %>/spss/gather/stage" class="btn btn-sm btn-light active">计量台账 </a>
                             <a href="/sp/<%- ctx.subProject.id %>/spss/gather/se" class="btn btn-sm btn-light">其他台账 </a>
                         </div>
@@ -30,6 +30,35 @@
                         <button class="btn btn-primary btn-sm mr-2" id="export-excel">导出Excel</button>
                         <button class="btn btn-sm btn-primary" id="gather-select">汇总标段</button>
                     </div>
+                    <div class="d-inline-block ml-2">
+                        <span>汇总数据:</span>
+                        <div class="d-inline-block" style="vertical-align: middle">
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input pt-1" type="radio" id="radio_contract" value="contract" name="compare-data">
+                                <label class="form-check-label" for="radio_contract">合同计量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="radio_qc" value="qc" name="compare-data">
+                                <label class="form-check-label" for="radio_qc">变更计量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="radio_gather" value="gather" checked="" name="compare-data">
+                                <label class="form-check-label" for="radio_gather">完成计量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input pt-1" type="radio" id="radio_end_contract" value="end_contract" name="compare-data" datatype="end">
+                                <label class="form-check-label" for="radio_end_contract">截止合同计量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="radio_end_qc" value="end_qc" name="compare-data" datatype="end">
+                                <label class="form-check-label" for="radio_end_qc">截止变更计量</label>
+                            </div>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="radio" id="radio_end_gather" value="end_gather" name="compare-data" datatype="end">
+                                <label class="form-check-label" for="radio_end_gather">截止完成计量</label>
+                            </div>
+                        </div>
+                    </div>
                 </div>
                 <div class="">
 
@@ -38,8 +67,26 @@
         </div>
     </div>
     <div class="content-wrap">
-        <div class="sjs-height-0" style="background-color: #fff">
-            <div class="c-body">
+        <div class="c-header p-0 col-12"></div>
+        <!--核心内容(两栏)-->
+        <div class="row w-100 sub-content">
+            <!--左栏-->
+            <div class="c-body" id="left-view" style="width: 100%">
+                <!--0号台账模式-->
+                <div class="sjs-height-1" style="overflow: hidden" id="bills-spread">
+                </div>
+                <div class="bcontent-wrap">
+                    <div id="pos-resize" class="resize-y" id="top-spr" r-Type="height" div1=".sjs-height-1" div2=".bcontent-wrap" title="调整大小"><!--调整上下高度条--></div>
+                    <div class="bc-bar mb-1">
+                        <ul class="nav nav-tabs">
+                            <li class="nav-item">
+                                <a class="nav-link active" href="javascript:void(0)">计量单元</a>
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="sp-wrap" id="pos-spread">
+                    </div>
+                </div>
             </div>
         </div>
     </div>

+ 0 - 35
app/view/spss/gather_stage_modal.ejs

@@ -1,35 +0,0 @@
-<!--弹出汇总标段-->
-<div class="modal fade" id="data-select" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择汇总标段</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group">
-                    <div class="row">
-                        <div class="col-10 d-inline-flex">
-                            <input class="form-control form-control-sm"  placeholder="输入需要汇总的标段ID" type="text" name="tid">
-                            <input class="form-control form-control-sm ml-1"  placeholder="输入需要汇总的标段的期序号" type="text" name="sorder">
-                            <a href="javascript: void(0);" id="add-t" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus text-primary" aria-hidden="true"></i></a>
-                        </div>
-                    </div>
-                </div>
-                <div class="form-group">
-                    <label>汇总标段列表<b class="text-danger">*</b></label>
-                    <table class="table table-bordered table-sm mt-1">
-                        <thead>
-                        <th style="width: 40%">标段id</th><th style="width: 40%">第几期</th><th></th>
-                        </thead>
-                        <tbody>
-                        </tbody>
-                    </table>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
-            </div>
-        </div>
-    </div>
-</div>

+ 0 - 34
app/view/spss/gather_tz_modal.ejs

@@ -1,34 +0,0 @@
-<!--弹出汇总标段-->
-<div class="modal fade" id="data-select" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择汇总标段</h5>
-            </div>
-            <div class="modal-body">
-                <div class="form-group">
-                    <div class="row">
-                        <div class="col-6 d-inline-flex">
-                            <input class="form-control form-control-sm"  placeholder="输入需要汇总的标段ID" type="text" name="tid">
-                            <a href="javascript: void(0);" id="add-t" class="btn btn-sm" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus text-primary" aria-hidden="true"></i></a>
-                        </div>
-                    </div>
-                </div>
-                <div class="form-group">
-                    <label>汇总标段列表<b class="text-danger">*</b></label>
-                    <table class="table table-bordered table-sm mt-1">
-                        <thead>
-                        <th style="width: 80%">标段id</th><th></th>
-                        </thead>
-                        <tbody>
-                        </tbody>
-                    </table>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary btn-sm" id="select-ok">确定</button>
-            </div>
-        </div>
-    </div>
-</div>

app/view/spss/compare_select_modal.ejs → app/view/spss/spss_select_modal.ejs


+ 4 - 0
config/web.js

@@ -1916,6 +1916,8 @@ const JsFiles = {
                     '/public/js/decimal.min.js',
                     '/public/js/math.min.js',
                     '/public/js/file-saver/FileSaver.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
                 ],
                 mergeFiles: [
                     '/public/js/sub_menu.js',
@@ -1986,6 +1988,8 @@ const JsFiles = {
                     '/public/js/decimal.min.js',
                     '/public/js/math.min.js',
                     '/public/js/file-saver/FileSaver.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
                 ],
                 mergeFiles: [
                     '/public/js/sub_menu.js',