Quellcode durchsuchen

附属工程量调整

MaiXinRong vor 1 Monat
Ursprung
Commit
7f6a78243e

+ 69 - 58
app/public/js/ledger.js

@@ -128,7 +128,7 @@ $(document).ready(function() {
     const posSheet = posSpread.getActiveSheet();
 
     // 初始化 附属工程量
-    const ancGcl = createAncillaryGcl({ id: 'id', masterId: 'lid', sort: [['g_order', 'asc']] });
+    const ancGcl = createAncillaryGcl({ id: 'id', masterId: 'pid', topId: 'lid', sort: [['g_order', 'asc']] });
     const ancGclSpread = SpreadJsObj.createNewSpread($('#anc-gcl-spread')[0]);
     const ancGclSheet = ancGclSpread.getActiveSheet();
 
@@ -531,7 +531,7 @@ $(document).ready(function() {
         };
         const loadCurDetailData = function() {
             const curAncGcl = SpreadJsObj.getSelectObject(ancGclSheet);
-            template = node ? posCalcTemplate.find(x => { return x.id === node.calc_template }) : null;
+            template = curAncGcl ? posCalcTemplate.find(x => { return x.id === curAncGcl.calc_template }) : null;
             if (template) {
                 const specCol = template.spread_cache.cols.find(x => { return x.field === 'spec'; });
                 if (specCol) {
@@ -791,7 +791,7 @@ $(document).ready(function() {
             spread.bind(spreadNS.Events.EditEnded, ctrlObj.editEnded);
             spread.bind(spreadNS.Events.ClipboardPasting, ctrlObj.clipboardPasting);
             $.contextMenu({
-                selector: '#pos-detail-spread',
+                selector: '#anc-gcl-detail-spread',
                 build: function ($trigger, e) {
                     const target = SpreadJsObj.safeRightClickSelection($trigger, e, spread);
                     return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
@@ -888,7 +888,6 @@ $(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; });
@@ -910,7 +909,6 @@ $(document).ready(function() {
         storeKey: 'ledger-error-' + getTenderId(),
         afterLocated:  function () {
             posOperationObj.loadCurPosData();
-            ancGclObj.loadCurAncillaryGcl();
         },
         afterShow: function () {
             ledgerSpread.refresh();
@@ -929,7 +927,6 @@ $(document).ready(function() {
         checkType: getCheckType(checkOption),
         afterLocated:  function () {
             posOperationObj.loadCurPosData();
-            ancGclObj.loadCurAncillaryGcl();
         },
         afterShow: function () {
             ledgerSpread.refresh();
@@ -1190,7 +1187,6 @@ $(document).ready(function() {
                         self.refreshTree(sheet, refreshNode);
                         self.refreshOperationValid(sheet);
                         posOperationObj.loadCurPosData();
-                        ancGclObj.loadCurAncillaryGcl();
                         billsTag.afterDeleteBills(refreshNode.delete);
                     });
                 });
@@ -1710,7 +1706,6 @@ $(document).ready(function() {
         },
         loadRelaData: function () {
             posOperationObj.loadCurPosData();
-            ancGclObj.loadCurAncillaryGcl();
             posSearch.search($('#pos-keyword').val());
             treeOperationObj.loadExprToInput(ledgerSpread.getActiveSheet());
         },
@@ -2165,7 +2160,7 @@ $(document).ready(function() {
     }
 
     const calcTemplateSelect = (function(){
-        let ctrlBills = {}, first = 1;
+        let ctrlNode = {}, first = 1, nodeType = 'bills';
         const tree = createNewPathTree('gather', {
             id: 'tree_id', pid: 'tree_pid', order: 'tree_order',
             level: 'tree_level', isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path',
@@ -2200,12 +2195,13 @@ $(document).ready(function() {
                 return !data.is_calc_template ? defaultFont : 'bold ' + defaultFont;
             },
             getColor: function(sheet, data, row, col, defaultColor) {
-                return data && data.id === ctrlBills.calc_template ? spreadColor.common.invalid : defaultColor;
+                return data && data.id === ctrlNode.calc_template ? spreadColor.common.invalid : defaultColor;
             }
         });
         SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Tree, tree);
-        const select = function(node) {
-            ctrlBills = node;
+        const select = function(node, type) {
+            nodeType = type || 'bills';
+            ctrlNode = node;
             SpreadJsObj.reloadRowBackColor(sheet, 0, tree.nodes.length);
             $('#select-calc-template').modal('show');
         };
@@ -2216,17 +2212,32 @@ $(document).ready(function() {
                 toastr.warning('请勿选择文件夹');
                 return;
             }
-            if (ct.id === ctrlBills.calc_template) {
+            if (ct.id === ctrlNode.calc_template) {
                 toastr.warning('选择的计算模板与之前相同');
                 return;
             }
-            const updateData = {postType: 'extra', postData: { id: ctrlBills.id, tender_id: ctrlBills.tender_id, ledger_id: ctrlBills.ledger_id, calc_template: ct.id }};
-            postData(window.location.pathname + '/update', updateData, function (result) {
-                const refreshNode = ledgerTree.loadPostData(result);
-                treeOperationObj.refreshTree(ledgerSheet, refreshNode);
-                posCalcDetail.loadCurDetailData();
-                $('#select-calc-template').modal('hide');
-            });
+            switch (nodeType) {
+                case 'ancGcl':
+                    const ancGclUpdateData = { calcTmpl: { id: ctrlNode.id, calc_template: ct.id } };
+                    postData('/tender/' + getTenderId() + '/anc-gcl/update', ancGclUpdateData, function(result) {
+                        ancGcl.updateDatas(result);
+                        ancGclObj.loadCurAncillaryGcl();
+                        ancGclDetail.detail.updateDatas({ update: result.detail });
+                        ancGclDetail.reloadCurDetailData();
+                        $('#select-calc-template').modal('hide');
+                    });
+                    break;
+                case 'bills':
+                default:
+                    const billsUpdateData = {postType: 'extra', postData: { id: ctrlNode.id, tender_id: ctrlNode.tender_id, ledger_id: ctrlNode.ledger_id, calc_template: ct.id }};
+                    postData(window.location.pathname + '/update', billsUpdateData, function (result) {
+                        const refreshNode = ledgerTree.loadPostData(result);
+                        treeOperationObj.refreshTree(ledgerSheet, refreshNode);
+                        posCalcDetail.loadCurDetailData();
+                        $('#select-calc-template').modal('hide');
+                    });
+                    break;
+            }
         });
         $('#select-calc-template').on('shown.bs.modal', function() {
             if (first) {
@@ -2673,7 +2684,6 @@ $(document).ready(function() {
                             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
                             pos.loadDatas(result.pos);
                             posOperationObj.loadCurPosData();
-                            ancGclObj.loadCurAncillaryGcl();
                             checkShowLast(result.bills.length);
                         }, null);
                     },
@@ -2984,6 +2994,7 @@ $(document).ready(function() {
             posOperationObj.loadExprToInput();
             posOperationObj.refreshOperationValid(posSpread.getActiveSheet());
             if (refreshDetail) posCalcDetail.loadCurDetailData();
+            ancGclObj.loadCurAncillaryGcl();
         },
         baseOpr: function (sheet, type) {
             const data = {
@@ -3034,6 +3045,7 @@ $(document).ready(function() {
                         const loadResult = ledgerTree.loadPostData(result.ledger);
                         treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
                         treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
+                        posOperationObj.loadCurPosData();
                     } else {
                         const updateRst = pos.updateDatas(result.pos);
                         billsTag.refreshPosTagView(updateRst.update);
@@ -3230,6 +3242,7 @@ $(document).ready(function() {
                     const loadResult = ledgerTree.loadPostData(result.ledger);
                     treeOperationObj.refreshTree(ledgerSpread.getActiveSheet(), loadResult);
                     treeOperationObj.refreshOperationValid(ledgerSpread.getActiveSheet());
+                    posOperationObj.loadCurPosData();
                 });
             }
         },
@@ -3358,6 +3371,7 @@ $(document).ready(function() {
             posOperationObj.loadExprToInput();
             posOperationObj.refreshOperationValid(posSpread.getActiveSheet());
             posCalcDetail.loadCurDetailData();
+            ancGclObj.loadCurAncillaryGcl();
         },
         addPegs: function (pegs) {
             if (!pegs || pegs.length <= 0) return;
@@ -3591,6 +3605,10 @@ $(document).ready(function() {
             {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: 'calc_template', hAlign: 1, width: 80, formatter: '@', readOnly: true, getValue: function(data) {
+                const ct = data ? posCalcTemplate.find(x => { return x.id === data.calc_template }) : null;
+                return ct ? ct.name : '';
+            }},
             {title: '备注', colSpan: '1', rowSpan: '1', field: 'memo', hAlign: 0, width: 80, formatter: '@'},
         ],
         emptyRows: 3,
@@ -3605,9 +3623,10 @@ $(document).ready(function() {
     SpreadJsObj.initSheet(ancGclSheet, ancGclSpreadSetting);
     const ancGclObj = {
         loadCurAncillaryGcl: function () {
-            const node = treeOperationObj.getSelectNode(ledgerSheet);
-            const gclData = node ? ancGcl.getPartData(node.id) || [] : [];
+            const posNode = SpreadJsObj.getSelectObject(posSheet);
+            const gclData = posNode ? ancGcl.getPartData(posNode.id) || [] : [];
             SpreadJsObj.loadSheetData(ancGclSheet, SpreadJsObj.DataType.Data, gclData);
+            ancGclDetail.loadCurDetailData();
         },
         baseOpr: function (type) {
             const data = {};
@@ -3621,8 +3640,8 @@ $(document).ready(function() {
             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 }];
+                const posNode = SpreadJsObj.getSelectObject(posSheet);
+                data.add = [{ lid: posNode.lid, pid: posNode.id, g_order: gclRange.length + 1 }];
             } else if (type === 'delete') {
                 data.del = [];
                 for (let iRow = 0; iRow < count; iRow++) {
@@ -3673,6 +3692,7 @@ $(document).ready(function() {
         },
         editStarting: function (e, info) {
             ancGclObj.ledgerTreeNode = SpreadJsObj.getSelectObject(ledgerSheet);
+            ancGclObj.posNode = SpreadJsObj.getSelectObject(posSheet);
         },
         editEnded: function (e, info) {
             const setting = info.sheet.zh_setting;
@@ -3683,19 +3703,9 @@ $(document).ready(function() {
             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('项目节不可插入附属工程量');
+            const posNode = ancGclObj.posNode;
+            if (!posNode) {
+                toastr.error('数据错误,请选择计量单元后再试');
                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 return;
             }
@@ -3728,7 +3738,7 @@ $(document).ready(function() {
             } 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 };
+                const addData = { lid: posNode.lid, pid: posNode.id, g_order: order };
                 if (col.type === 'Number') {
                     const num = _.toNumber(newText);
                     if (!_.isFinite(num)) {
@@ -3801,19 +3811,9 @@ $(document).ready(function() {
         },
         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('项目节不可插入附属工程量');
+            const posNode = ancGclObj.posNode;
+            if (!posNode) {
+                toastr.error('数据错误,请选择计量单元后再试');
                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 return;
             }
@@ -3861,7 +3861,7 @@ $(document).ready(function() {
                     data.update.push(updateData);
                 } else {
                     if (!data.add) data.add = [];
-                    const addData = { lid: node.id, g_order: curRow + 1};
+                    const addData = { lid: posNode.lid, pid: posNode.id, g_order: curRow + 1};
                     analysisData(pasteData[iRow], addData);
                     data.add.push(addData);
                 }
@@ -3896,7 +3896,11 @@ $(document).ready(function() {
                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
             });
         },
+        selectionChanged: function(e, info) {
+            ancGclDetail.loadCurDetailData();
+        },
     };
+    ancGclSpread.bind(spreadNS.Events.SelectionChanged, ancGclObj.selectionChanged);
     if (!readOnly) {
         SpreadJsObj.addDeleteBind(ancGclSpread, ancGclObj.deletePress);
         ancGclSpread.bind(spreadNS.Events.EditStarting, ancGclObj.editStarting);
@@ -3960,6 +3964,18 @@ $(document).ready(function() {
                         ancGclObj.baseOpr('up-move');
                     }
                 },
+                editSpr: '----',
+                selectCalcTemplate: {
+                    name: '选择计算模板',
+                    callback: function(key, opt) {
+                        const node = SpreadJsObj.getSelectObject(ancGclSheet);
+                        calcTemplateSelect.select(node, 'ancGcl');
+                    },
+                    disabled: function(key, opt) {
+                        const node = SpreadJsObj.getSelectObject(ancGclSheet);
+                        return !node;
+                    }
+                }
             }
         });
     }
@@ -4507,7 +4523,6 @@ $(document).ready(function() {
                         treeOperationObj.refreshOperationValid(mainSheet);
                         ledgerSpread.focus();
                         posOperationObj.loadCurPosData();
-                        ancGclObj.loadCurAncillaryGcl();
                     });
                 });
             }
@@ -4926,7 +4941,6 @@ $(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 {
@@ -5823,7 +5837,6 @@ $(document).ready(function() {
             SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
             pos.loadDatas(result.pos);
             posOperationObj.loadCurPosData();
-            ancGclObj.loadCurAncillaryGcl();
             checkShowLast(result.bills.length);
         });
     });
@@ -5835,7 +5848,6 @@ $(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;
             }
@@ -6089,7 +6101,6 @@ $(document).ready(function() {
           if (Object.keys(att).length) {
               SpreadJsObj.locateTreeNode(ledgerSpread.getActiveSheet(), att.ledger_id, true);
               posOperationObj.loadCurPosData();
-              ancGclObj.loadCurAncillaryGcl();
           }
       }
   });

+ 58 - 13
app/service/ancillary_gcl.js

@@ -29,11 +29,11 @@ module.exports = app => {
             const datas = data instanceof Array ? data : [data];
             const insertData = [];
             for (const d of datas) {
-                if (!d.lid || !d.g_order) throw '新增其他数据,提交的数据错误';
+                if (!d.lid || !d.pid || !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,
+                    lid: d.lid, pid: d.pid, g_order: d.g_order,
                 };
                 if (d.is_aux !== undefined) nd.is_aux = d.is_aux;
                 if (d.name) nd.name = d.name || '';
@@ -49,23 +49,22 @@ module.exports = app => {
                 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 } });
+            const pos = await this.ctx.service.pos.getDataById(orgDatas[0].pid);
+            let posAnc = await this.getAllDataByCondition({ where: { tid: pos.tid, pid: pos.id } });
 
-            billsAnc = billsAnc.filter(ba => {
+            posAnc = posAnc.filter(ba => {
                 return data.indexOf(ba.id) < 0;
             });
-            billsAnc.sort((x, y) => { return x.g_order - y.g_order; });
+            posAnc.sort((x, y) => { return x.g_order - y.g_order; });
 
             const updateData = [];
-            billsAnc.forEach((x, i) => {
+            posAnc.forEach((x, i) => {
                 if (x.g_order !== i + 1) updateData.push({ id: x.id, g_order: i + 1});
             });
 
@@ -73,6 +72,7 @@ module.exports = app => {
             try {
                 await conn.delete(this.tableName, { id: data });
                 if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
+                await this.ctx.service.ancillaryGclDetail.deleteAncGclPartData(conn, orgDatas[0].tid, data);
                 await conn.commit();
                 return [data, updateData];
             } catch (err) {
@@ -80,7 +80,6 @@ module.exports = app => {
                 throw err;
             }
         }
-
         async _updateDatas (data) {
             if (!data || data.length === 0) throw '提交数据错误';
             const qtyDecimal = this.ctx.tender.info.decimal.qty;
@@ -102,8 +101,10 @@ module.exports = app => {
                 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 (!od.calc_template) {
+                    if (d.quantity !== undefined) nd.quantity = this.ctx.helper.round(d.quantity, qtyDecimal);
+                    if (d.expr !== undefined) nd.expr = d.expr || '';
+                }
                 if (d.drawing_code !== undefined) nd.drawing_code = d.drawing_code;
                 if (d.memo !== undefined) nd.memo = d.memo || '';
                 uDatas.push(nd);
@@ -115,9 +116,47 @@ module.exports = app => {
                 return [];
             }
         }
+        async _setCalcTemplate(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 = [], detailUpdate = [];
+            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, calc_template: d.calc_template, expr: '', quantity: 0 };
+                uDatas.push(nd);
+            }
+            const conn = await this.db.beginTransaction();
+            try {
+                if (datas[0].calc_template) {
+                    for (const ud of uDatas) {
+                        const [du, quantity] = await this.ctx.service.ancillaryGclDetail.resetAncGclCalcTemplate(conn, ud.id, ud.calc_template);
+                        detailUpdate.push(...du);
+                        ud.quantity = this.ctx.helper.round(quantity, qtyDecimal);
+                    }
+                } else {
+                    await this.ctx.service.ancillaryGcl.deleteAncGclPartData(conn, orgDatas[0].tid, uDatas.map(x => { return x.id; }));
+                }
+                await conn.updateRows(this.tableName, uDatas);
+                await conn.commit();
+                return [uDatas, detailUpdate];
+            } catch (err) {
+                this.ctx.log(err);
+                await conn.rollback();
+                throw '修改计算模板失败';
+            }
+        }
         async updateDatas(data) {
-            const result = {add: [], del: [], update: []};
+            const result = {add: [], del: [], update: [], details: []};
             try {
                 if (data.add) {
                     result.add = await this._addDatas(data.add);
@@ -128,6 +167,9 @@ module.exports = app => {
                 if (data.del) {
                     [result.del, result.update] = await this._delDatas(data.del);
                 }
+                if (data.calcTmpl) {
+                    [result.update, result.details] = await this._setCalcTemplate(data.calcTmpl);
+                }
                 return result;
             } catch (err) {
                 if (err.stack) {
@@ -139,9 +181,12 @@ module.exports = app => {
             }
         }
 
-        async deletePartData(transaction, tid, lid) {
+        async deleteBillsPartData(transaction, tid, lid) {
             await transaction.delete(this.tableName, { tid: tid, lid: lid });
         }
+        async deletePosPartData(transaction, tid, pid) {
+            await transaction.delete(this.tableName, { tid: tid, pid: pid });
+        }
     }
 
     return AncillaryGcl;

+ 329 - 0
app/service/ancillary_gcl_detail.js

@@ -0,0 +1,329 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const strField = ['str1', 'str2', 'str3', 'str4'];
+const numField = ['num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i', 'num_j', 'num_k', 'num_l', 'num_m', 'num_n', 'num_o', 'num_p', 'num_q', 'num_r', 'num_s', 'num_t', 'num_u'];
+const specialField = ['spec', 'qty', 'expr'];
+const math = require('mathjs');
+math.config({
+    number: 'BigNumber',
+});
+
+module.exports = app => {
+
+    class AncillaryGclDetail extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'ancillary_gcl_detail';
+        }
+
+        // ---------- 校验&计算数据相关 ------------------
+        getDecimal(decimal, field) {
+            return decimal[field] !== undefined ? decimal[field] || 0 : decimal.def;
+        }
+        _calcExpr(formula) {
+            const percentReg = /((\d+)|((\d+)(\.\d+)))%/g;
+            const percent = formula.match(percentReg);
+            if (percent) {
+                for (const p of percent) {
+                    const v = math.eval(p.replace(new RegExp('%', 'gm'), '/100'));
+                    formula = formula.replace(p, v);
+                }
+            }
+            try {
+                // 使用mathjs计算 math.eval('17259401.95*0.9') = 15533461.754999999,正确应为 15533461.755
+                // const value = math.eval(formula);
+                // 使用逆波兰法四则运算,可防止出现误差,但是只支持四则运算,不支持科学运算
+                // const value = this.ctx.helper.calcExprStrRpn(formula);
+                // 使用mathjs的大数运算,可支持所有
+                const value = parseFloat(math.eval(formula));
+                return Number.isFinite(value) ? value : 0;
+            } catch(err) {
+                return 0;
+            }
+        }
+        _reCalcQty(data, template) {
+            const qtyExpr = template.calc_expr.find(x => { return x.field === 'qty'; });
+            const result = { id: data.id, expr: qtyExpr ? qtyExpr.expr : '' };
+            for (const nf of numField) {
+                result[nf] = this.ctx.helper.round(data[nf] || 0, this.getDecimal(template.decimal, nf));
+                result.expr = result.expr.replace(new RegExp(nf, 'gm'), data[nf] || 0);
+            }
+            result.qty = this.ctx.helper.round(this._calcExpr(result.expr), this.getDecimal(template.decimal, 'qty'));
+            return result;
+        }
+        // 后端计算
+        _loadDataAndCalc(data, updateData, orgData, template) {
+            let calc = false;
+            for (const sf of strField) {
+                if (updateData[sf] !== undefined) data[sf] = updateData[sf] || '';
+            }
+            if (updateData.spec !== undefined) {
+                calc = true;
+                data.spec = updateData.spec;
+                if (template.spec_rela) {
+                    const sv = template.specValue.find(x => { return x.spec === data.spec; });
+                    data[template.spec_rela] = sv ? sv.value : 0;
+                }
+            }
+            if (updateData[template.spec_rela] !== undefined) delete updateData[template.spec_rela];
+            this.ctx.service.calcTmpl.calcByTemplate(data, updateData, orgData, template.calc_expr, { qty: 'expr' });
+            return calc || (data.qty !== undefined && orgData.qty !== data.qty);
+        }
+        // 依赖前端计算
+        _loadDataResult(data, updateData) {
+            for (const sf of strField) {
+                if (updateData[sf] !== undefined) data[sf] = updateData[sf] || '';
+            }
+            if (updateData.spec !== undefined) data.spec = updateData.spec;
+            if (updateData.qty !== undefined) data.qty = updateData.qty;
+            if (updateData.expr !== undefined) data.expr = updateData.expr;
+            for (const nf of numField) {
+                if (updateData[nf] !== undefined) data[nf] = updateData[nf];
+            }
+        }
+        // --------------------------------------------
+
+        async _getAncGclUpdateData(data, ag_id, calc = true) {
+            if (!calc) return [null, null];
+
+            try {
+                const ancGclData = await this.ctx.service.ancillaryGcl.getDataById(ag_id);
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, ledgerData.unit);
+                const posDatas = await this.ctx.service.pos.getAllDataByCondition({ where: { lid } });
+                const detailDatas = await this.getAllDataByCondition({ columns: ['id', 'lid', 'pid', 'qty'], where: { pid } });
+
+                let sumPosQty = 0;
+                for (const d of data) {
+                    sumPosQty = this.ctx.helper.add(d.qty, sumPosQty);
+                }
+                for (const d of detailDatas) {
+                    if (data.findIndex(x => { return x.id === d.id; }) >= 0) continue;
+                    sumPosQty = this.ctx.helper.add(d.qty, sumPosQty);
+                }
+                const op = posDatas.find(x => { return x.id === pid; });
+                if (!op) throw '所属计量单元不存在,请刷新页面后再试';
+                const ppd = { id: op.id, sgfh_qty: this.ctx.helper.round(sumPosQty, precision.value) };
+                ppd.quantity = this.ctx.helper.sum([ppd.sgfh_qty, op.sjcl_qty, op.qtcl_qty]);
+
+                let sumBillsQty = 0;
+                for (const pd of posDatas) {
+                    if (pd.id === pid) {
+                        sumBillsQty = this.ctx.helper.add(sumBillsQty, ppd.sgfh_qty);
+                    } else {
+                        sumBillsQty = this.ctx.helper.add(sumBillsQty, pd.sgfh_qty);
+                    }
+                }
+                const bpd = { id: ledgerData.id, sgfh_qty: sumBillsQty };
+                bpd.sgfh_tp = this.ctx.helper.mul(bpd.sgfh_qty, ledgerData.unit_price, this.ctx.tender.info.decimal.tp);
+                bpd.quantity = this.ctx.helper.sum([bpd.sgfh_qty, ledgerData.sjcl_qty, ledgerData.qtcl_qty]);
+                bpd.total_price = this.ctx.helper.mul(bpd.quantity, ledgerData.unit_price, this.ctx.tender.info.decimal.tp);
+                return [ppd, bpd];
+            } catch (err) {
+                return [null, null];
+            }
+        }
+
+        async _addDatas(data, selectId) {
+            const user_id = this.ctx.session.sessionUser.accountId;
+
+            const datas = data instanceof Array ? data : [data];
+            const le = await this.ctx.service.ledgerExtra.getDataById(datas[0].lid);
+            if (!le.calc_template) throw '未定义计算模板,请先在清单处选择计算模板';
+            const calcTemplate = await this.ctx.service.calcTmpl.getTemplate(le.calc_template);
+            if (!calcTemplate) throw '计算模板不存在';
+            const posDetail = await this.getAllDataByCondition({ where: { tid: this.ctx.tender.id, pid: datas[0].pid }, orders: [['pcd_order', 'ASC']] });
+            const selectIndex = selectId ? posDetail.findIndex(x => { return x.id === selectId; }) : -1;
+            if (selectId && selectIndex < 0) throw '选择的数据不存在';
+            const startOrder = selectId
+                ? (selectIndex > 0 ? posDetail[selectIndex - 1].pcd_order : 0)
+                : (posDetail.length > 0 ? posDetail[posDetail.length - 1].pcd_order : 0);
+
+            const insertData = [], updateData = [];
+            let isCalc = false;
+            for (const [i, d] of datas.entries()) {
+                if (!d.lid || !d.pid || !d.pcd_order) throw '新增其他数据,提交的数据错误';
+                const nd = {
+                    id: this.uuid.v4(), tid: this.ctx.tender.id,
+                    create_user_id: user_id, update_user_id: user_id,
+                    lid: d.lid, pid: d.pid, pcd_order: startOrder + i + 1,
+                };
+                if (this._loadDataAndCalc(nd, d, {}, calcTemplate)) isCalc = true;
+                insertData.push(nd);
+            }
+            const [posUpdate, billsUpdate] = await this._getBillsPosUpdateData(insertData, insertData[0].pid, insertData[0].lid, isCalc);
+            if (selectIndex >= 0) {
+                const maxOrder = insertData[insertData.length - 1].pcd_order;
+                for (const p of posDetail) {
+                    if (p.pcd_order > startOrder) {
+                        updateData.push({ id: p.id, pcd_order: maxOrder + updateData.length + 1 });
+                    }
+                }
+            }
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.insert(this.tableName, insertData);
+                if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
+                if (posUpdate) await conn.update(this.ctx.service.pos.tableName, posUpdate);
+                if (billsUpdate) await conn.update(this.ctx.service.ledger.tableName, billsUpdate);
+                await conn.commit();
+            } catch(err) {
+                await conn.rollback();
+                throw err;
+            }
+            const addData = await this.getAllDataByCondition({
+                where: { id: this.ctx.helper._.map(insertData, 'id') }
+            });
+            const posData = isCalc ? await this.ctx.service.pos.getDataById(addData[0].pid) : null;
+            const ledgerData = isCalc ? await this.ctx.service.ledger.getDataById(addData[0].lid) : null;
+            return [addData, updateData, posData, ledgerData]
+        }
+        async _delDatas (data) {
+            if (!data || data.length === 0) throw '提交数据错误';
+            const orgDatas = await this.getAllDataByCondition({ where: { id: data } });
+
+            if (!orgDatas || orgDatas.length === 0) throw '删除的设计量明细不存在';
+
+            let posDetail = await this.getAllDataByCondition({ where: { tid: orgDatas[0].tid, pid: orgDatas[0].pid } });
+            posDetail = posDetail.filter(pa => {
+                return data.indexOf(pa.id) < 0;
+            });
+            posDetail.sort((x, y) => { return x.pcd_order - y.pcd_order; });
+
+            const updateData = [];
+            posDetail.forEach((x, i) => {
+                if (x.pcd_order !== i + 1) updateData.push({ id: x.id, pcd_order: i + 1});
+            });
+            const [posUpdate, billsUpdate] = await this._getBillsPosUpdateData(data.map(x => { return { id: x, qty: 0 }; }), orgDatas[0].pid, orgDatas[0].lid);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.delete(this.tableName, { id: data });
+                if (updateData.length > 0) await conn.updateRows(this.tableName, updateData);
+                if (posUpdate) await conn.update(this.ctx.service.pos.tableName, posUpdate);
+                if (billsUpdate) await conn.update(this.ctx.service.ledger.tableName, billsUpdate);
+                await conn.commit();
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+            const posData = await this.ctx.service.pos.getDataById(orgDatas[0].pid);
+            const ledgerData = await this.ctx.service.ledger.getDataById(orgDatas[0].lid);
+            return [data, updateData, posData, ledgerData];
+        }
+        async _updateDatas (data) {
+            if (!data || data.length === 0) throw '提交数据错误';
+            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 le = await this.ctx.service.ledgerExtra.getDataById(orgDatas[0].lid);
+            if (!le.calc_template) throw '未定义计算模板,请先在清单处选择计算模板';
+            const calcTemplate = await this.ctx.service.calcTmpl.getTemplate(le.calc_template);
+            if (!calcTemplate) throw '计算模板不存在';
+
+            const uDatas = [];
+            let isCalc = false;
+            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.pcd_order) nd.pcd_order = d.pcd_order;
+                if (this._loadDataAndCalc(nd, d, od, calcTemplate)) isCalc = true;
+                uDatas.push(nd);
+            }
+            const [posUpdate, billsUpdate] = await this._getBillsPosUpdateData(uDatas, orgDatas[0].pid, orgDatas[0].lid, isCalc);
+
+            if (uDatas.length > 0) {
+                const conn = await this.db.beginTransaction();
+                try {
+                    await conn.updateRows(this.tableName, uDatas);
+                    if (posUpdate) await conn.update(this.ctx.service.pos.tableName, posUpdate);
+                    if (billsUpdate) await conn.update(this.ctx.service.ledger.tableName, billsUpdate);
+                    await conn.commit();
+                } catch (err) {
+                    await conn.rollback();
+                    throw err;
+                }
+                const posData = isCalc ? await this.ctx.service.pos.getDataById(orgDatas[0].pid) : null;
+                const ledgerData = isCalc ? await this.ctx.service.ledger.getDataById(orgDatas[0].lid) : null;
+                return [uDatas, posData, ledgerData];
+            } else {
+                return [];
+            }
+        }
+        async updateDatas(data) {
+            const result = { detail: {add: [], del: [], update: []}, pos: null, bills: null };
+            try {
+                if (data.add) {
+                    [result.detail.add, result.detail.update, result.pos, result.bills] = await this._addDatas(data.add, data.select);
+                }
+                if (data.update) {
+                    const orgUpdate = result.detail.update;
+                    [result.detail.update, result.pos, result.bills] = await this._updateDatas(data.update);
+                    if (orgUpdate.length > 0) result.detail.update.push(...orgUpdate);
+                }
+                if (data.del) {
+                    [result.detail.del, result.detail.update, result.pos, result.bills] = await this._delDatas(data.del);
+                }
+                return result;
+            } catch (err) {
+                if (err.stack) {
+                    throw err;
+                } else {
+                    result.err = err.toString();
+                    return result;
+                }
+            }
+        }
+
+        async resetAncGclCalcTemplate(transaction, ag_id, templateId) {
+            const user_id = this.ctx.session.sessionUser.accountId;
+            const calcTemplate = await this.ctx.service.calcTmpl.getTemplate(templateId);
+            if (!calcTemplate) throw '计算模板不存在';
+
+            const detailDatas = await this.getAllDataByCondition({ where: { ag_id: ag_id } });
+            const detailUpdateDatas = [];
+            let qty = 0;
+            for (const pd of detailDatas) {
+                const ud = this._reCalcQty(pd, calcTemplate);
+                ud.update_user_id = user_id;
+                detailUpdateDatas.push(ud);
+                qty = this.ctx.helper.add(ud.qty, qty);
+            }
+
+            if (detailUpdateDatas.length > 0) await transaction.updateRows(this.tableName, detailUpdateDatas);
+            return [detailUpdateDatas, qty];
+        }
+
+        async deleteBillsPartData(transaction, tid, lid) {
+            await transaction.delete(this.tableName, { tid: tid, lid: lid });
+        }
+        async deletePosPartData(transaction, tid, pid) {
+            await transaction.delete(this.tableName, { tid: tid, lid: pid });
+        }
+        async deleteAncGclPartData(transaction, tid, ag_id) {
+            await transaction.delete(this.tableName, { tid: tid, ag_id: ag_id });
+        }
+    }
+
+    return AncillaryGclDetail;
+};

+ 3 - 2
app/service/ledger.js

@@ -290,10 +290,11 @@ module.exports = app => {
          * @private
          */
         async _deleteRelaData(mid, deleteData) {
-            await this.ctx.service.pos.deletePosData(this.transaction, mid, this._.map(deleteData, 'id'));
-            await this.ctx.service.ancillaryGcl.deletePartData(this.transaction, mid, this._.map(deleteData, 'id'));
             await this.transaction.delete(this.ctx.service.ledgerExtra.tableName, { tid: mid, id: this._.map(deleteData, 'id') });
+            await this.ctx.service.pos.deletePosData(this.transaction, mid, this._.map(deleteData, 'id'));
             await this.ctx.service.posCalcDetail.deleteBillsPartData(this.transaction, mid, this._.map(deleteData, 'id'));
+            await this.ctx.service.ancillaryGcl.deleteBillsPartData(this.transaction, mid, this._.map(deleteData, 'id'));
+            await this.ctx.service.ancillaryGclDetail.deleteBillsPartData(this.transaction, mid, this._.map(deleteData, 'id'));
         }
 
         _checkField(data, field) {

+ 2 - 0
app/service/pos.js

@@ -357,6 +357,8 @@ module.exports = app => {
                 await transaction.delete(this.tableName, {tid: tid, id: data});
                 await transaction.update(this.ctx.service.ledger.tableName, updateBills);
                 await this.ctx.service.posCalcDetail.deletePosPartData(transaction, tid, data);
+                await this.ctx.service.ancillaryGcl.deletePosPartData(transaction, tid, data);
+                await this.ctx.service.ancillaryGclDetail.deletePosPartData(transaction, tid, data);
                 await transaction.commit();
                 updateBills.ledger_id = bills.ledger_id;
                 return { ledger: { update: [updateBills] }, pos: data };

+ 1 - 1
app/service/pos_calc_detail.js

@@ -8,7 +8,7 @@
  * @version
  */
 const strField = ['str1', 'str2', 'str3', 'str4'];
-const numField = ['num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i'];
+const numField = ['num_a', 'num_b', 'num_c', 'num_d', 'num_e', 'num_f', 'num_g', 'num_h', 'num_i', 'num_j', 'num_k', 'num_l', 'num_m', 'num_n', 'num_o', 'num_p', 'num_q', 'num_r', 'num_s', 'num_t', 'num_u'];
 const specialField = ['spec', 'qty', 'expr'];
 const math = require('mathjs');
 math.config({

+ 8 - 8
app/view/ledger/explode.ejs

@@ -153,21 +153,21 @@
                         </div>
                     </div>
                     <div class="sp-wrap" style="display: flex; flex-wrap: wrap;">
-                        <div class="c-body" id="pos-spread" style="width: 100%">
+                        <div class="c-body h-100" id="pos-spread" style="width: 100%">
                         </div>
-                        <div class="c-body" id="pos-right" style="display: none; width: 40%;">
+                        <div class="c-body h-100" 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="tab-content sp-wrap">
-                                <div id="anc-gcl" class="tab-pane table-select-show">
-                                    <div id="anc-gcl-spread" style="height: 235px;">
+                            <div class="tab-content h-100">
+                                <div id="anc-gcl" class="tab-pane table-select-show h-100">
+                                    <div id="anc-gcl-spread" class="h-50">
                                     </div>
-                                    <div id="anc-gcl-detail" style="height: 235px;">
+                                    <div id="anc-gcl-detail" class="h-50">
                                         <div id="anc-gcl-spr" class="resize-y" r-Type="height" div1="#anc-gcl-spread" div2="#anc-gcl-detail" r-parent="div2" title="调整大小"><!--调整上下高度条--></div>
                                         <div id="anc-gcl-detail-spread" class="w-100 h-100"></div>
                                     </div>
                                 </div>
-                                <div id="pos-detail" class="tab-pane table-select-show">
-                                    <div class="sp-wrap" id="pos-detail-spread">
+                                <div id="pos-detail" class="tab-pane table-select-show h-100">
+                                    <div class="h-100" id="pos-detail-spread">
                                     </div>
                                 </div>
                             </div>

+ 42 - 0
sql/update.sql

@@ -483,6 +483,48 @@ ALTER TABLE `zh_ancillary_gcl`
 ADD COLUMN `pid` varchar(36) NOT NULL DEFAULT '' COMMENT '计量单元id' AFTER `lid`,
 ADD COLUMN `calc_template` varchar(36) NOT NULL DEFAULT '' COMMENT '明细计算模板' AFTER `update_time`;
 
+CREATE TABLE `zh_ancillary_gcl_detail`  (
+  `id` varchar(36) NOT NULL COMMENT 'uuid',
+  `tid` int(11) NOT NULL COMMENT '标段id(zh_tender.id)',
+  `lid` varchar(36) NOT NULL COMMENT '台账id(zh_ledger.id)',
+  `pid` varchar(36) NOT NULL COMMENT '计量单元id(zh_pos.id)',
+  `ag_id` varchar(36) NOT NULL COMMENT '附属工程量id(zh_ancillary_gcl.id)',
+  `agd_order` int(11) NOT NULL COMMENT '排序',
+  `create_user_id` int(11) NOT NULL COMMENT '新增用户id(zh_project_account.id)',
+  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间',
+  `update_user_id` int(11) NOT NULL COMMENT '最后修改用户id(zh_project_account.id)',
+  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
+  `qty` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数量',
+  `expr` varchar(255) NOT NULL DEFAULT '' COMMENT '数量计算式',
+  `str1` varchar(255) NOT NULL DEFAULT '' COMMENT '文本1',
+  `str2` varchar(255) NOT NULL DEFAULT '' COMMENT '文本2',
+  `str3` varchar(255) NOT NULL DEFAULT '' COMMENT '文本3',
+  `str4` varchar(255) NOT NULL DEFAULT '' COMMENT '文本4',
+  `spec` varchar(50) NOT NULL DEFAULT '' COMMENT '规格',
+  `num_a` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值a',
+  `num_b` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值b',
+  `num_c` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值c',
+  `num_d` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值d',
+  `num_e` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值e',
+  `num_f` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值f',
+  `num_g` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值g',
+  `num_h` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值h',
+  `num_i` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值i',
+  `num_j` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值j',
+  `num_k` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值k',
+  `num_l` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值l',
+  `num_m` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值m',
+  `num_n` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值n',
+  `num_o` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值o',
+  `num_p` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值p',
+  `num_q` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值q',
+  `num_r` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值r',
+  `num_s` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值s',
+  `num_t` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值t',
+  `num_u` decimal(24, 8) NOT NULL DEFAULT 0 COMMENT '数值u',
+  PRIMARY KEY (`id`)
+);
+
 ------------------------------------
 -- 表数据
 ------------------------------------