Browse Source

附属工程量相关

MaiXinRong 1 year ago
parent
commit
0a5cc38589

+ 1 - 0
app/const/tender_info.js

@@ -135,6 +135,7 @@ const defaultInfo = {
             deal: false,
             dgnQty: false,
             clQty: false,
+            ancillaryGcl: false,
         },
         exMemo: true,
         thousandth: false,

+ 16 - 2
app/controller/ledger_controller.js

@@ -461,9 +461,11 @@ module.exports = app => {
                         ? await ctx.helper.loadLedgerDataFromOss(ctx.tender.his.pos_file)
                         : await ctx.service.pos.getPosData({tid: ctx.tender.id}, posColumn))
                     : [];
+                const ancillaryGclData = this.ctx.tender.data.measure_type === measureType.tz.value
+                    ? await ctx.service.ancillaryGcl.getAllDataByCondition({ where: { tid: ctx.tender.id } })
+                    : [];
                 const ledgerTags = await this.ctx.service.ledgerTag.getDatas(ctx.tender.id);
-                //ctx.body = { err: 0, msg: '', data: { bills: this.ctx.helper.hpackArr(ledgerData), pos: this.ctx.helper.hpackArr(posData), tags: ledgerTags }, hpack: ['bills', 'pos'] };
-                ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, tags: ledgerTags } };
+                ctx.body = { err: 0, msg: '', data: { bills: ledgerData, pos: posData, ancGcl: ancillaryGclData, tags: ledgerTags } };
             } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: [] };
@@ -495,6 +497,18 @@ module.exports = app => {
             }
         }
 
+        async ancGclUpdate(ctx) {
+            try {
+                await this.checkMeasureType(measureType.tz.value);
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = await ctx.service.ancillaryGcl.updateDatas(data);
+                ctx.body = { err: 0, msg: '', data: responseData };
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         /**
          * 更新 部位明细数据
          *

+ 444 - 42
app/public/js/ledger.js

@@ -63,6 +63,7 @@ $(document).ready(function() {
     autoFlashHeight();
     // 初始化台账
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const ledgerSheet = ledgerSpread.getActiveSheet();
     removeLocalCache('bills-fold');
     const treeSetting = {
         id: 'ledger_id',
@@ -94,6 +95,11 @@ $(document).ready(function() {
     });
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
 
+    // 初始化 附属工程量
+    const ancGcl = createAncillaryGcl({ id: 'id', masterId: 'lid', sort: [['g_order', 'asc']] });
+    const ancGclSpread = SpreadJsObj.createNewSpread($('#anc-gcl-spread')[0]);
+    const ancGclSheet = ancGclSpread.getActiveSheet();
+
     const billsTag = $.billsTag({
         selector: '#bills-tag',
         relaSpread: ledgerSpread,
@@ -104,6 +110,7 @@ $(document).ready(function() {
         },
         afterLocated:  function (lid, pos_id) {
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
             if (pos_id) {
                 const posSheet = posSpread.getActiveSheet();
                 const relaPos = posSheet.zh_data.find(x => { return x.id === pos_id; });
@@ -113,6 +120,7 @@ $(document).ready(function() {
         afterShow: function () {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
+            if (ancGclSpread) ancGclSpread.refresh();
         },
     });
     const errorList = $.cs_errorList({
@@ -122,10 +130,12 @@ $(document).ready(function() {
         storeKey: 'ledger-error-' + getTenderId(),
         afterLocated:  function () {
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
         },
         afterShow: function () {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
+            if (ancGclSpread) ancGclSpread.refresh();
         },
     });
     const checkList = $.ledger_checkList({
@@ -137,10 +147,12 @@ $(document).ready(function() {
         checkType: getCheckType(checkOption),
         afterLocated:  function () {
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
         },
         afterShow: function () {
             ledgerSpread.refresh();
             if (posSpread) posSpread.refresh();
+            if (ancGclSpread) ancGclSpread.refresh();
         },
     });
 
@@ -165,27 +177,14 @@ $(document).ready(function() {
             }
             autoFlashHeight();
             ledgerSpread.refresh();
-            if (posSpread) {
-                posSpread.refresh();
-            }
-            if (stdXmj) {
-                stdXmj.spread.refresh();
-            }
-            if (stdGcl) {
-                stdGcl.spread.refresh();
-            }
-            if (dealBills) {
-                dealBills.spread.refresh();
-            }
-            if (searchLedger) {
-                searchLedger.spread.refresh();
-            }
-            if (errorList) {
-                errorList.spread.refresh();
-            }
-            if (checkList) {
-                checkList.spread.refresh();
-            }
+            if (posSpread) posSpread.refresh();
+            if (ancGclSpread) ancGclSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
+            if (stdGcl) stdGcl.spread.refresh();
+            if (dealBills) dealBills.spread.refresh();
+            if (searchLedger) searchLedger.spread.refresh();
+            if (errorList) errorList.spread.refresh();
+            if (checkList) checkList.spread.refresh();
             if (gclGather) gclGather.spread.refresh();
         }
     });
@@ -425,6 +424,7 @@ $(document).ready(function() {
                         self.refreshTree(sheet, refreshNode);
                         self.refreshOperationValid(sheet);
                         posOperationObj.loadCurPosData();
+                        ancGclObj.loadCurAncillaryGcl();
                         billsTag.afterDeleteBills(refreshNode.delete);
                     });
                 });
@@ -900,6 +900,7 @@ $(document).ready(function() {
         },
         loadRelaData: function () {
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
             posSearch.search($('#pos-keyword').val());
             treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
         },
@@ -1595,6 +1596,7 @@ $(document).ready(function() {
                             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
                             pos.loadDatas(result.pos);
                             posOperationObj.loadCurPosData();
+                            ancGclObj.loadCurAncillaryGcl();
                             checkShowLast(result.bills.length);
                         }, null);
                     },
@@ -1745,6 +1747,7 @@ $(document).ready(function() {
                 let bcontent = $(".bcontent-wrap") ? $(".bcontent-wrap").height() : 0;
                 $(".sp-wrap").height(bcontent-30);
                 posSpread.refresh();
+                if (ancGclSpread) ancGclSpread.refresh();
             }
         });
         sjsSettingObj.setGridSelectStyle(posSpreadSetting);
@@ -2386,11 +2389,369 @@ $(document).ready(function() {
         }
     });
 
+    // 附属工程量相关
+    const ancGclSpreadSetting = {
+        cols: [
+            {title: '辅材', colSpan: '1', rowSpan: '1', field: 'is_aux', hAlign: 1, width: 40, cellType: 'checkbox'},
+            {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 120, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 40, formatter: '@', cellType: 'unit'},
+            {title: '设计量', colSpan: '1', rowSpan: '1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+            {title: '设计量公式', colSpan: '1', rowSpan: '1', field: 'expr', hAlign: 0, width: 80, formatter: '@'},
+            {title: '图册号', colSpan: '1', rowSpan: '1', field: 'drawing_code', hAlign: 0, width: 80, formatter: '@'},
+            {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 80, formatter: '@'},
+        ],
+        emptyRows: 3,
+        headRows: 1,
+        headRowHeight: [32],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+    };
+    SpreadJsObj.initSheet(ancGclSheet, ancGclSpreadSetting);
+    const ancGclObj = {
+        loadCurAncillaryGcl: function () {
+            const node = treeOperationObj.getSelectNode(ledgerSheet);
+            const gclData = node ? ancGcl.getPartData(node.id) || [] : [];
+            SpreadJsObj.loadSheetData(ancGclSheet, SpreadJsObj.DataType.Data, gclData);
+        },
+        baseOpr: function (type) {
+            const data = {};
+
+            const gclRange = ancGclSheet.zh_data;
+            if (type !== 'insert' && (!gclRange || gclRange.length === 0)) return;
+
+            const sel = ancGclSheet.getSelections();
+            if (!sel[0]) return;
+
+            const row = sel[0].row, count = sel[0].rowCount;
+            const first = gclRange[row];
+            if (type === 'insert') {
+                const node = SpreadJsObj.getSelectObject(ledgerSheet);
+                data.add = [{ lid: node.id, g_order: gclRange.length + 1 }];
+            } else if (type === 'delete') {
+                data.del = [];
+                for (let iRow = 0; iRow < count; iRow++) {
+                    const gclData = gclRange[row + iRow];
+                    if (!gclData) continue;
+                    data.del.push(gclData.id);
+                }
+
+                if (data.del.length === 0) return;
+            } else if (type === 'up-move') {
+                data.update = [];
+                const pre = gclRange[row - 1];
+                if (!pre) return;
+
+                const preUpdate = { id: pre.id };
+                for (let iRow = 0; iRow < count; iRow++) {
+                    const gclData = gclRange[iRow + row];
+                    if (!gclData) continue;
+                    data.update.push({ id: gclData.id, g_order: gclRange[iRow + row - 1].g_order });
+                    preUpdate.g_order = gclData.g_order;
+                }
+                data.update.push(preUpdate);
+
+                if (data.update <= 1) return;
+            } else if (type === 'down-move') {
+                data.update = [];
+                const next = gclRange[row + count];
+                if (!next) return;
+
+                const nextUpdate = { id: next.id };
+                for (let iRow = count - 1; iRow >= 0; iRow--) {
+                    const gclData = gclRange[iRow + row];
+                    if (!gclData) continue;
+
+                    data.update.push({ id: gclData.id, g_order: gclRange[iRow + row + 1].g_order});
+                    nextUpdate.g_order = gclData.g_order;
+                }
+                data.update.push(nextUpdate);
+
+                if (data.update <= 1) return;
+            }
+
+            postData('/tender/' + getTenderId() + '/anc-gcl/update', data, function(result) {
+                ancGcl.updateDatas(result);
+                ancGclObj.loadCurAncillaryGcl();
+                if (type !== 'delete') SpreadJsObj.locateData(ancGclSheet, first);
+            });
+        },
+        editStarting: function (e, info) {
+            ancGclObj.ledgerTreeNode = SpreadJsObj.getSelectObject(ledgerSheet);
+        },
+        editEnded: function (e, info) {
+            const setting = info.sheet.zh_setting;
+            if (!setting) return;
+            const gclData = SpreadJsObj.getSelectObject(info.sheet);
+
+            const col = setting.cols[info.col];
+            const orgText = gclData ? gclData[col.field] : '', newText = trimInvalidChar(info.editingText);
+            if (orgText === newText || (!orgText && !newText)) return;
+
+            const node = ancGclObj.ledgerTreeNode;
+            if (!node) {
+                toastr.error('数据错误,请选择台账节点后再试');
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if (!!newText && node.children && node.children.length > 0) {
+                toastr.error('父节点不可添加附属工程量');
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if (!!newText && (!node.b_code || node.b_code === '')) {
+                toastr.error('项目节不可插入附属工程量');
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+
+            const data = {};
+            if (gclData) {
+                const updateData = { id: gclData.id };
+                if (col.type === 'Number') {
+                    const num = _.toNumber(newText);
+                    if (!_.isFinite(num)) {
+                        toastr.error('输入的数字非法');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    updateData[col.field] = num;
+                    if (gclData.expr) updateData.expr = '';
+                } else if (col.field === 'expr') {
+                    try {
+                        updateData.expr = newText;
+                        updateData.quantity = math.evaluate(transExpr(newText));
+                    } catch(err) {
+                        toastr.error('输入的计算式非法');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                } else {
+                    updateData[col.field] = newText;
+                }
+                data.update = [ updateData ];
+            } else {
+                const sortData = info.sheet.zh_data;
+                const order = (!sortData || sortData.length === 0) ? 1 : Math.max(sortData[sortData.length - 1].g_order + 1, sortData.length + 1);
+                const addData = { lid: node.id, g_order: order };
+                if (col.type === 'Number') {
+                    const num = _.toNumber(newText);
+                    if (!_.isFinite(num)) {
+                        toastr.error('输入的数字非法');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    addData[col.field] = num;
+                    if (gclData.expr) addData.expr = '';
+                } else if (col.field === 'expr') {
+                    try {
+                        addData.expr = newText;
+                        addData.quantity = math.evaluate(transExpr(newText));
+                    } catch(err) {
+                        toastr.error('输入的计算式非法');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                } else {
+                    addData[col.field] = newText;
+                }
+                data.add = [addData];
+            }
+            postData('/tender/' + getTenderId() + '/anc-gcl/update', data, function (result) {
+                ancGcl.updateDatas(result);
+                ancGclObj.loadCurAncillaryGcl();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            });
+        },
+        deletePress: function (sheet) {
+            const setting = sheet.zh_setting;
+            if (!setting) return;
+
+            const sortData = sheet.zh_data;
+            if (!sortData || sortData.length === 0) return;
+
+            const sel = sheet.getSelections()[0];
+            const data = { update: [] };
+            for (let iRow = sel.row; iRow < sel.row + sel.rowCount; iRow++) {
+                let bDel = false;
+                const node = sortData[iRow];
+                if (!node) continue;
+
+                const updateData = { id: node.id };
+                for (let iCol = sel.col; iCol < sel.col + sel.colCount; iCol++) {
+                    const style = sheet.getStyle(iRow, iCol);
+                    if (style.locked) continue;
+
+                    const col = setting.cols[iCol];
+                    if (col.field === 'expr') {
+                        updateData.expr = '';
+                        updateData.quantity = 0;
+                    } else {
+                        updateData[col.field] = col.type === 'Number' ? 0 : '';
+                        if (col.field === 'quantity') updateDataexpr = '';
+                    }
+                    bDel = true;
+                }
+                if (bDel) data.update.push(updateData);
+            }
+            if (data.update.length === 0) return;
+
+            postData('/tender/' + getTenderId() + '/anc-gcl/update', data, function (result) {
+                ancGcl.updateDatas(result);
+                ancGclObj.loadCurAncillaryGcl();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            });
+        },
+        clipboardPasting: function(e, info) {
+            info.cancel = true;
+            const node = SpreadJsObj.getSelectObject(ledgerSheet);
+            if (!node) {
+                toastr.error('数据错误,请选择台账节点后再试');
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if (node.children && node.children.length > 0) {
+                toastr.error('父节点不可添加附属工程量');
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+            if ((!node.b_code || node.b_code === '')) {
+                toastr.error('项目节不可插入附属工程量');
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                return;
+            }
+
+            const hint = {
+                expr: {type: 'warning', msg: '输入的 计算式 非法,已过滤'},
+                num: {type: 'warning', msg: '输入的 数字 非法,已过滤'},
+            };
+            const setting = info.sheet.zh_setting;
+            const sortData = info.sheet.zh_data || [];
+            const pasteData = SpreadJsObj.analysisPasteText(info.pasteData.text);
+            const data = {};
+            const analysisData = function(pasteRow, targetData) {
+                pasteRow.forEach((value, iCol) => {
+                    const col = setting.cols[info.cellRange.col + iCol];
+                    if (col.field === 'expr') {
+                        try {
+                            targetData.expr = trimInvalidChar(value);
+                            targetData.quantity = math.evaluate(transExpr(targetData.expr));
+                        } catch(err) {
+                            toastMessageUniq(hint.expr);
+                            targetData.expr = '';
+                            return;
+                        }
+                    } else if (col.type === 'Number') {
+                        const num = _.toNumber(value);
+                        if (!_.isFinite(num)) {
+                            toastMessageUniq(hint.num);
+                            return;
+                        }
+                        targetData[col.field] = num;
+                        if (col.field === 'quantity') targetData.expr = '';
+                    } else {
+                        targetData[col.field] = value;
+                    }
+                });
+            };
+            for (let iRow = 0; iRow < pasteData.length; iRow++) {
+                const curRow = iRow + info.cellRange.row;
+                const gclData = sortData[curRow];
+                if (gclData) {
+                    if (!data.update) data.update = [];
+                    const updateData = { id: gclData.id };
+                    analysisData(pasteData[iRow], updateData);
+                    data.update.push(updateData);
+                } else {
+                    if (!data.add) data.add = [];
+                    const addData = { lid: node.id, g_order: curRow + 1};
+                    analysisData(pasteData[iRow], addData);
+                    data.add.push(addData);
+                }
+            }
+            if ((!data.update || data.update.length === 0) && (!data.add || data.add.length === 0)) return;
+
+            postData('/tender/' + getTenderId() + '/anc-gcl/update', data, function (result) {
+                ancGcl.updateDatas(result);
+                ancGclObj.loadCurAncillaryGcl();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.row);
+            });
+        },
+    };
+    SpreadJsObj.addDeleteBind(ancGclSpread, ancGclObj.deletePress);
+    ancGclSpread.bind(spreadNS.Events.EditStarting, ancGclObj.editStarting);
+    ancGclSpread.bind(spreadNS.Events.EditEnded, ancGclObj.editEnded);
+    ancGclSpread.bind(spreadNS.Events.ClipboardPasting, ancGclObj.clipboardPasting);
+    $.contextMenu({
+        selector: '#anc-gcl-spread',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, ancGclSpread);
+            return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+        },
+        items: {
+            'insert': {
+                name: '插入',
+                icon: 'fa-plus',
+                disabled: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(ledgerSheet);
+                    return !node || !node.b_code || (node.children && node.children.length > 0);
+                },
+                callback: function (key, opt) {
+                    ancGclObj.baseOpr('insert');
+                }
+            },
+            'delete': {
+                name: '删除',
+                icon: 'fa-remove',
+                disabled: function (key, opt) {
+                    const gclData = SpreadJsObj.getSelectObject(ancGclSheet);
+                    return !gclData;
+                },
+                callback: function (key, opt) {
+                    ancGclObj.baseOpr('delete');
+                }
+            },
+            'down-move': {
+                name: '下移',
+                icon: 'fa-arrow-down',
+                disabled: function(key, opt) {
+                    const sel = ancGclSheet.getSelections()[0];
+                    const row = sel ? sel.row : -1;
+                    const first = ancGclSheet.zh_data[row];
+                    const next = ancGclSheet.zh_data[sel.row + sel.rowCount];
+                    return !first || !next;
+                },
+                callback: function(key, opt) {
+                    ancGclObj.baseOpr('down-move');
+                }
+            },
+            'up-move': {
+                name: '上移',
+                icon: 'fa-arrow-up',
+                disabled: function(key, opt) {
+                    const sel = ancGclSheet.getSelections()[0];
+                    const row = sel ? sel.row : -1;
+                    const first = ancGclSheet.zh_data[row];
+                    const preNode = ancGclSheet.zh_data[row - 1];
+                    return !first || !preNode;
+                },
+                callback: function(key, opt) {
+                    ancGclObj.baseOpr('up-move');
+                }
+            },
+        }
+    });
+
     postData(window.location.pathname + '/load', {}, function (data) {
         ledgerTree.loadDatas(data.bills);
         treeCalc.calculateAll(ledgerTree);
         checkShowLast(data.bills.length);
         pos.loadDatas(data.pos);
+        ancGcl.loadDatas(data.ancGcl);
 
         for (const t of data.tags) {
             t.node = ledgerTree.datas.find(x => { return x.id === t.lid; });
@@ -2406,6 +2767,9 @@ $(document).ready(function() {
         posOperationObj.loadCurPosData();
         SpreadJsObj.resetTopAndSelect(posSpread.getActiveSheet());
 
+        ancGclObj.loadCurAncillaryGcl();
+        SpreadJsObj.resetTopAndSelect(ancGclSheet);
+
         treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
         treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
 
@@ -2416,27 +2780,14 @@ $(document).ready(function() {
         select: '#right-spr',
         callback: function () {
             ledgerSpread.refresh();
-            if (posSpread) {
-                posSpread.refresh();
-            }
-            if (stdXmj) {
-                stdXmj.spread.refresh();
-            }
-            if (stdGcl) {
-                stdGcl.spread.refresh();
-            }
-            if (dealBills) {
-                dealBills.spread.refresh();
-            }
-            if (searchLedger) {
-                searchLedger.spread.refresh();
-            }
-            if (errorList) {
-                errorList.spread.refresh();
-            }
-            if (checkList) {
-                checkList.spread.refresh();
-            }
+            if (posSpread) posSpread.refresh();
+            if (ancGclSpread) ancGclSpread.refresh();
+            if (stdXmj) stdXmj.spread.refresh();
+            if (stdGcl) stdGcl.spread.refresh();
+            if (dealBills) dealBills.spread.refresh();
+            if (searchLedger) searchLedger.spread.refresh();
+            if (errorList) errorList.spread.refresh();
+            if (checkList) checkList.spread.refresh();
             if (gclGather) gclGather.spread.refresh();
         }
     });
@@ -2487,6 +2838,7 @@ $(document).ready(function() {
             treeOperationObj.refreshOperationValid(mainSheet);
             ledgerSpread.focus();
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
         });
     };
     const stdXmjSetting = {
@@ -2627,6 +2979,7 @@ $(document).ready(function() {
                         },
                         afterLocated: function () {
                             posOperationObj.loadCurPosData();
+                            ancGclObj.loadCurAncillaryGcl();
                         },
                         calcSum: function (result) {
                             const sum = { name: '合计' };
@@ -2661,6 +3014,7 @@ $(document).ready(function() {
         if (posSpread) {
             posSpread.refresh();
         }
+        if (ancGclSpread) ancGclSpread.refresh();
     });
     class DealBills {
         constructor (selector, spreadSetting) {
@@ -2882,6 +3236,7 @@ $(document).ready(function() {
                         treeOperationObj.refreshOperationValid(mainSheet);
                         ledgerSpread.focus();
                         posOperationObj.loadCurPosData();
+                        ancGclObj.loadCurAncillaryGcl();
                     });
                 });
             }
@@ -3285,6 +3640,7 @@ $(document).ready(function() {
                             SpreadJsObj.reloadRowsBackColor(sheet, [sel.row, result.create[0].index]);
                             treeOperationObj.refreshOperationValid(sheet);
                             posOperationObj.loadCurPosData();
+                            ancGclObj.loadCurAncillaryGcl();
                             self.obj.modal('hide');
                         }, null, true);
                     } else {
@@ -3586,6 +3942,49 @@ $(document).ready(function() {
         }
     }
 
+    $('a', '#anc-gcl-nav').bind('click', function(e) {
+        e.preventDefault();
+        const tab = $(this);
+        const showSideTab = function (show) {
+            const left = $('#pos-spread'), right = $('#pos-right'), parent = left.parent();
+            if (show) {
+                right.show();
+                autoFlashHeight();
+                /**
+                 * right.show()后, parent被撑开成2倍left.height, 导致parent.width减少了10px
+                 * 第一次left.width调整后,parent的缩回left.height, 此时parent.width又增加了10px
+                 * 故需要通过最终的parent.width再计算一次left.width
+                 *
+                 * Q: 为什么不通过先计算left.width的宽度,以避免计算两次left.width?
+                 * A: 右侧工具栏不一定显示,当右侧工具栏显示过一次后,就必须使用parent和right来计算left.width
+                 *
+                 */
+                const percent = 100 - right.outerWidth() /parent.width() * 100;
+                left.css('width', percent + '%');
+            } else {
+                left.width(parent.width());
+                right.hide();
+            }
+
+        };
+        if (!tab.hasClass('active')) {
+            tab.addClass('active');
+            showSideTab(tab.hasClass('active'));
+        } else {
+            tab.removeClass('active');
+            showSideTab(tab.hasClass('active'));
+        }
+        if (posSpread) posSpread.refresh();
+        if (ancGclSpread) ancGclSpread.refresh();
+    });
+    $.divResizer({
+        select: '#pos-right-spr',
+        callback: function () {
+            if (posSpread) posSpread.refresh();
+            if (ancGclSpread) ancGclSpread.refresh();
+        }
+    });
+
     // $('#searchAccount').click(() => {
     //     const data = {
     //         keyword: $('#searchName').val(),
@@ -3871,6 +4270,7 @@ $(document).ready(function() {
             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
             pos.loadDatas(result.pos);
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
             checkShowLast(result.bills.length);
         });
     });
@@ -3882,6 +4282,7 @@ $(document).ready(function() {
             const loadResult = ledgerTree.loadPostData({update: data.source.bills});
             treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
             posOperationObj.loadCurPosData();
+            ancGclObj.loadCurAncillaryGcl();
             for (const e of data.error) {
                 e.serialNo = ledgerTree.getNodeIndex(ledgerTree.getItems(e.ledger_id)) + 1;
             }
@@ -4110,6 +4511,7 @@ $(document).ready(function() {
           if (Object.keys(att).length) {
               SpreadJsObj.locateTreeNode(ledgerSpread.getActiveSheet(), att.ledger_id, true);
               posOperationObj.loadCurPosData();
+              ancGclObj.loadCurAncillaryGcl();
           }
       }
   });

+ 148 - 0
app/public/js/path_tree.js

@@ -2058,6 +2058,154 @@ const createNewPathTree = function (type, setting) {
     }
 };
 
+const createAncillaryGcl = function (setting) {
+    class AncillaryGcl {
+        /**
+         * 构造函数
+         * @param {id|Number, masterId|Number} setting
+         */
+        constructor(setting) {
+            this.itemPre = 'id_';
+            // 无索引
+            this.datas = [];
+            // 以key为索引
+            this.items = {};
+            // 以分类id为索引的有序
+            this.masterIndex = {};
+            // 设置
+            this.setting = setting;
+        }
+
+        resortPart(partData) {
+            const sortRule = this.setting.sort || [['g_order', 'asc']];
+            if (partData instanceof Array) {
+                partData.sort(function (a, b) {
+                    for (const sr of sortRule) {
+                        const iSort = sr[1] === 'asc' ? a[sr[0]] - b[sr[0]] : b[sr[0]] - a[sr[0]];
+                        if (iSort) return iSort;
+                    }
+                })
+            }
+        }
+
+        /**
+         * 加载数据
+         * @param datas
+         */
+        loadDatas(datas) {
+            this.datas = datas;
+            this.items = {};
+            this.masterIndex = {};
+            for (const data of this.datas) {
+                const key = this.itemPre + data[this.setting.id];
+                this.items[key] = data;
+
+                const masterKey = this.itemPre + data[this.setting.masterId];
+                if (!this.masterIndex[masterKey]) {
+                    this.masterIndex[masterKey] = [];
+                }
+                this.masterIndex[masterKey].push(data);
+            }
+            for (const prop in this.masterIndex) {
+                this.resortPart(this.masterIndex[prop]);
+            }
+        }
+
+        _addDatas(data, resort) {
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                const key = this.itemPre + d[this.setting.id];
+                this.items[key] = d;
+
+                const masterKey = this.itemPre + d[this.setting.masterId];
+                if (!this.masterIndex[masterKey]) this.masterIndex[masterKey] = [];
+                this.masterIndex[masterKey].push(d);
+                if (resort.indexOf(masterKey) < -1) resort.push(masterKey);
+            }
+        }
+
+        /**
+         * 更新数据
+         * @param datas
+         */
+        _updateDatas(data, resort) {
+            const datas = data instanceof Array ? data : [data];
+            for (const d of datas) {
+                const item = this.getItem(d[this.setting.id]);
+                if (!item) continue;
+                for (const prop in d) {
+                    item[prop] = d[prop];
+                }
+                const masterKey = this.itemPre + item[this.setting.masterId];
+                if (resort.indexOf(masterKey) === -1) resort.push(masterKey);
+            }
+
+        }
+
+        /**
+         * 移除数据
+         * @param datas
+         */
+        _removeDatas(data, resort) {
+            if (!data) { return; }
+            const datas = data instanceof Array ? data : [data];
+            for (let i = datas.length - 1; i >= 0; i--) {
+                const id = datas[i];
+                const d = this.getItem(id);
+                this.datas.splice(this.datas.indexOf(d), 1);
+                const key = this.itemPre + d[this.setting.id];
+                delete this.items[key];
+                const range = this.getPartData(d[this.setting.masterId]);
+                range.splice(range.indexOf(d), 1);
+            }
+        }
+
+        updateDatas(data) {
+            const resort = [];
+            if (data.add) this._addDatas(data.add, resort);
+            if (data.del) this._removeDatas(data.del, resort);
+            if (data.update) this._updateDatas(data.update, resort);
+            for (const s of resort) {
+                this.resortPart(this.masterIndex[s]);
+            }
+        }
+
+        /**
+         * 移除数据 - 根据分类id
+         * @param mid
+         */
+        removeDatasByMasterId(mid) {
+            const masterKey = this.itemPre + mid;
+            const range = this.masterIndex[masterKey];
+            if (range) {
+                delete this.masterIndex[masterKey];
+                for (const r of range) {
+                    this.datas.splice(this.datas.indexOf(r), 1);
+                    const key = this.itemPre + r[this.setting.id];
+                    delete this.items[key];
+                }
+            }
+        }
+
+        getItem(id) {
+            return this.items[this.itemPre + id];
+        }
+
+        getPartData(mid) {
+            return this.masterIndex[this.itemPre + mid];
+        }
+
+        set sort(sort) {
+            this.setting.sort = sort;
+            for (const key in this.masterIndex) {
+                this.resortPart(this.masterIndex[key]);
+            }
+        }
+    }
+
+    return new AncillaryGcl(setting);
+};
+
 const treeCalc = {
     mapTreeNode: function (tree) {
         const setting = tree.setting;

+ 21 - 0
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -3219,3 +3219,24 @@ const SpreadJsObj = {
     })(),
 };
 
+
+const GridSpreadUtils = {
+    getDefaultSelectInfo: function (sheet, isMulti = true) {
+        const gridData = sheet.zh_data;
+        if (!gridData) return[gridData, null, -1, 0];
+
+        const sel = sheet.getSelections()[0];
+        const node = gridData[sel.row];
+        if (!node) return [gridData, node, sel.row, 0];
+
+        if (!isMulti || sel.rowCount === 1) return [gridData, node, sel.row, 1];
+
+        let count = 1;
+        for (let r = 1; r < sel.rowCount; r++) {
+            const rNode = gridData[sel.row + r];
+            if (!rNode) break;
+            count += 1;
+        }
+        return [gridData, node, sel.row, count];
+    },
+};

+ 1 - 0
app/router.js

@@ -234,6 +234,7 @@ module.exports = app => {
     app.post('/tender/:id/ledger/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.update');
     app.post('/tender/:id/ledger/upload-excel/:ueType', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.uploadExcel');
     app.get('/tender/:id/ledger/download/:file', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.download');
+    app.post('/tender/:id/anc-gcl/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.ancGclUpdate');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.posUpdate');
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.posPaste');
     app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, uncheckTenderCheck, 'ledgerController.deal2sgfh');

+ 140 - 0
app/service/ancillary_gcl.js

@@ -0,0 +1,140 @@
+'use strict';
+
+/**
+ * 附属工程量(ancillary gcl)
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class AncillaryGcl extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ancillary_gcl';
+        }
+
+        async _addDatas(data) {
+            const qtyDecimal = this.ctx.tender.info.decimal.qty;
+            const user_id = this.ctx.session.sessionUser.accountId;
+
+            const datas = data instanceof Array ? data : [data];
+            const insertData = [];
+            for (const d of datas) {
+                if (!d.lid || !d.g_order) throw '新增其他数据,提交的数据错误';
+                const nd = {
+                    id: this.uuid.v4(), tid: this.ctx.tender.id,
+                    add_user_id: user_id, update_user_id: user_id,
+                    lid: d.lid, g_order: d.g_order,
+                };
+                if (d.name) nd.name = d.name || '';
+                if (d.unit) nd.unit = d.unit || '';
+                if (d.quantity) nd.quantity = this.ctx.helper.round(d.quantity || 0, qtyDecimal);
+                if (d.expr) nd.expr = d.expr || '';
+                if (d.memo) nd.memo = d.memo || '';
+                insertData.push(nd);
+            }
+            await this.db.insert(this.tableName, insertData);
+            return await this.getAllDataByCondition({
+                where: { id: this.ctx.helper._.map(insertData, 'id') }
+            });
+        }
+
+        async _delDatas (data) {
+            if (!data || data.length === 0) throw '提交数据错误';
+            const orgDatas = await this.getAllDataByCondition({ where: { id: data } });
+
+            if (!orgDatas || orgDatas.length === 0) throw '删除的辅助工程量不存在';
+
+            const bills = await this.ctx.service.ledger.getDataById(orgDatas[0].lid);
+            let billsAnc = await this.getAllDataByCondition({ where: { tid: bills.tid, lid: bills.id } });
+
+            billsAnc = billsAnc.filter(ba => {
+                return data.indexOf(ba.id) < 0;
+            });
+            billsAnc.sort((x, y) => { return x.g_order - y.g_order; });
+
+            const updateData = [];
+            billsAnc.forEach((x, i) => {
+                if (x.g_order !== i + 1) updateData.push({ id: x.id, g_order: i + 1});
+            });
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.delete(this.tableName, { id: data });
+                if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
+                await conn.commit();
+                return [data, updateData];
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+
+        async _updateDatas (data) {
+            if (!data || data.length === 0) throw '提交数据错误';
+            const qtyDecimal = this.ctx.tender.info.decimal.qty;
+            const user_id = this.ctx.session.sessionUser.accountId;
+
+            const datas = data instanceof Array ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({
+                where: { id: this.ctx.helper._.map(datas, 'id') }
+            });
+            if (!orgDatas || orgDatas.length === 0) throw '修改的辅助工程量不存在';
+
+            const uDatas = [];
+            for (const d of datas) {
+                const od = orgDatas.find(x => { return x.id === d.id; });
+                if (!od) continue;
+
+                const nd = { id: od.id, update_user_id: user_id };
+                if (d.name !== undefined) nd.name = d.name;
+                if (d.g_order !== undefined) nd.g_order = d.g_order;
+                if (d.unit !== undefined) nd.unit = d.unit;
+                if (d.quantity !== undefined) nd.quantity = this.ctx.helper.round(d.quantity, qtyDecimal);
+                if (d.expr !== undefined) nd.expr = d.expr || '';
+                if (d.memo !== undefined) nd.memo = d.memo || '';
+                uDatas.push(nd);
+            }
+            if (uDatas.length > 0) {
+                await this.db.updateRows(this.tableName, uDatas);
+                return uDatas;
+            } else {
+                return [];
+            }
+        }
+
+        async updateDatas(data) {
+            const result = {add: [], del: [], update: []};
+            try {
+                if (data.add) {
+                    result.add = await this._addDatas(data.add);
+                }
+                if (data.update) {
+                    result.update = await this._updateDatas(data.update);
+                }
+                if (data.del) {
+                    [result.del, result.update] = await this._delDatas(data.del);
+                }
+                return result;
+            } catch (err) {
+                if (err.stack) {
+                    throw err;
+                } else {
+                    result.err = err.toString();
+                    return result;
+                }
+            }
+        }
+    }
+
+    return AncillaryGcl;
+};

+ 48 - 23
app/view/ledger/explode.ejs

@@ -86,32 +86,57 @@
                 <% if (tender.measure_type === measureType.tz.value) { %>
                 <div class="bcontent-wrap" id="main-bottom">
                     <div id="main-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>
-                            <li class="ml-2 nav-item">
-                                <div class="d-inline-flex">
-                                    <a href="javascript: void(0);" name="pos-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
-                                    <a href="javascript: void(0);" name="pos-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
-                                </div>
-                            </li>
-                            <li class="nav-item" id="pos-search">
-                            </li>
-                            <li class="nav-item">
-                                <div class="d-inline-block">
-                                    <div class="input-group input-group-sm ml-2">
-                                        <div class="input-group-prepend">
-                                            <span class="input-group-text" id="basic-addon1">表达式</span>
+                    <div class="bc-bar mb-1 d-flex">
+                        <div class="d-inline-block">
+                            <ul class="nav nav-tabs">
+                                <li class="nav-item">
+                                    <a class="nav-link active" href="javascript:void(0)">计量单元</a>
+                                </li>
+                                <li class="ml-2 nav-item">
+                                    <div class="d-inline-flex">
+                                        <a href="javascript: void(0);" name="pos-opr" type="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                                        <a href="javascript: void(0);" name="pos-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                                    </div>
+                                </li>
+                                <li class="nav-item" id="pos-search">
+                                </li>
+                                <li class="nav-item">
+                                    <div class="d-inline-block">
+                                        <div class="input-group input-group-sm ml-2">
+                                            <div class="input-group-prepend">
+                                                <span class="input-group-text" id="basic-addon1">表达式</span>
+                                            </div>
+                                            <input type="text" class="form-control form-control-sm m-0" id="pos-expr" readonly="">
                                         </div>
-                                        <input type="text" class="form-control form-control-sm m-0" id="pos-expr" readonly="">
                                     </div>
-                                </div>
-                            </li>
-                        </ul>
+                                </li>
+                            </ul>
+                        </div>
+                        <div class="ml-auto">
+                            <!--<a class="btn btn-sm btn-primary mr-1 mt-1" id="anc-gcl-btn" href="javascript: void(0);">附属工程量</a>-->
+                            <% if (tenderInfo.display.ledger.ancillaryGcl) {%>
+                            <ul class="nav nav-tabs" id="anc-gcl-nav" >
+                                <li class="nav-item">
+                                    <div class="form-check form-check-inline mt-1">
+                                        <input class="form-check-input pt-1" type="checkbox" id="filter-anc-gcl" value="0" name="is_aux">
+                                        <label class="form-check-label" for="filter-anc-gcl">不显示辅材</label>
+                                    </div>
+                                </li>
+                                <li class="nav-item">
+                                    <a class="nav-link" href="javascript:void(0)">附属工程量</a>
+                                </li>
+                            </ul>
+                            <% } %>
+                        </div>
                     </div>
-                    <div class="sp-wrap" id="pos-spread">
+                    <div class="sp-wrap" style="display: flex; flex-wrap: wrap;">
+                        <div class="c-body" id="pos-spread" style="width: 100%">
+                        </div>
+                        <div class="c-body" id="pos-right" style="display: none; width: 40%;">
+                            <div class="resize-x" id="pos-right-spr" r-Type="width" div1="#pos-spread" div2="#pos-right" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                            <div class="sp-wrap" id="anc-gcl-spread">
+                            </div>
+                        </div>
                     </div>
                 </div>
                 <% } %>

+ 6 - 1
app/view/tender/detail_modal.ejs

@@ -824,6 +824,10 @@
                         <label class="custom-control-label" for="ledger-cl-qty">错漏增减</label>
                     </div>
                     <div class="custom-control custom-checkbox mb-2">
+                        <input type="checkbox" class="custom-control-input" id="ancillary-gcl" checked="">
+                        <label class="custom-control-label" for="ancillary-gcl">附属工程量</label>
+                    </div>
+                    <div class="custom-control custom-checkbox mb-2">
                         <input type="checkbox" class="custom-control-input" id="thousandth" checked="">
                         <label class="custom-control-label" for="thousandth">千分位</label>
                     </div>
@@ -1649,6 +1653,7 @@
         $('#ledger-deal')[0].checked = property.display.ledger.deal;
         $('#ledger-dgn-qty')[0].checked = property.display.ledger.dgnQty;
         $('#ledger-cl-qty')[0].checked = property.display.ledger.clQty;
+        $('#ancillary-gcl')[0].checked = property.display.ledger.ancillaryGcl;
         $('#thousandth')[0].checked = property.display.thousandth;
         $('#stage-rc')[0].checked = property.display.stage.realComplete;
         $('#stage-priceDiff')[0].checked = property.display.stage.priceDiff;
@@ -1661,7 +1666,7 @@
     function post5 () {
         const prop = {
             display: {
-                ledger: { deal: $('#ledger-deal')[0].checked, dgnQty: $('#ledger-dgn-qty')[0].checked, clQty: $('#ledger-cl-qty')[0].checked, },
+                ledger: { deal: $('#ledger-deal')[0].checked, dgnQty: $('#ledger-dgn-qty')[0].checked, clQty: $('#ledger-cl-qty')[0].checked, ancillaryGcl: $('#ancillary-gcl')[0].checked, },
                 thousandth: $('#thousandth')[0].checked,
                 stage: { realComplete: $('#stage-rc')[0].checked, correct: $('#stage-correct')[0].checked, priceDiff: $('#stage-priceDiff')[0].checked },
                 dayMode: $('#dayMode')[0].checked,