MaiXinRong 3 дней назад
Родитель
Сommit
6bf2395647

+ 23 - 3
app/controller/cost_controller.js

@@ -457,6 +457,20 @@ module.exports = app => {
                         case 'att':
                             responseData.data[f] = await ctx.service.costStageFile.getData(ctx.costStage.id, 'DESC');
                             break;
+                        case 'relaStageBills':
+                            const relaStage = ctx.costStage.relaStage;
+                            if (relaStage.stage_type === 'book') relaStage.relaStage = await this.ctx.service.costStage.getStage(relaStage.rela_stage.sid);
+                            responseData.data[f] = relaStage.relaStage
+                                ? await this.ctx.service.costStageLedger.getReadData(relaStage.relaStage)
+                                : await this.ctx.service.costStageLedger.getReadData(relaStage);
+                            const detail = relaStage.relaStage
+                                ? await this.ctx.service.costStageDetail.getReadData(relaStage.relaStage)
+                                : await this.ctx.service.costStageDetail.getReadData(relaStage);
+                            responseData.data[f].forEach(x => {
+                                const relaDetail = detail.filter(d => { return d.ledger_id === x.id; });
+                                x.detail_type = relaDetail.length === 0 ? '' : (relaDetail[0].is_deal ? '合同' : '一般');
+                            });
+                            break;
                         default:
                             responseData.data[f] = [];
                             break;
@@ -555,14 +569,18 @@ module.exports = app => {
                 ctx.body = this.ajaxErrorBody(err, '数据错误');
             }
         }
-        async _analysisBillsBase(stage, type, data) {
+        async _analysisBillsBase(stage, type, data, addType) {
             if (isNaN(data.id) || data.id <= 0) throw '数据错误';
             if (type !== 'add') {
                 if (isNaN(data.count) || data.count <= 0) data.count = 1;
             }
             switch (type) {
                 case 'add':
-                    return await this.ctx.service.costStageAnalysis.addAnalysisNode(stage, data.id, data.count);
+                    if (addType === 'child') {
+                        return await this.ctx.service.costStageAnalysis.addAnalysisChild(stage, data.id, data.count);
+                    } else {
+                        return await this.ctx.service.costStageAnalysis.addAnalysisNode(stage, data.id, data.count);
+                    }
                 case 'delete':
                     return await this.ctx.service.costStageAnalysis.delete(stage.id, data.id, data.count);
                 case 'up-move':
@@ -590,7 +608,7 @@ module.exports = app => {
                         case 'down-move':
                         case 'up-level':
                         case 'down-level':
-                            responseData.data = await this._analysisBillsBase(ctx.costStage, data.postType, data.postData);
+                            responseData.data = await this._analysisBillsBase(ctx.costStage, data.postType, data.postData, data.addType);
                             break;
                         case 'update':
                             responseData.data = await this.ctx.service.costStageAnalysis.updateCalc(ctx.costStage, data.postData);
@@ -602,6 +620,8 @@ module.exports = app => {
                     responseData.data = await this.ctx.service.costStageAnalysisDetail.updateDatas(data.update);
                 } else if (data.target === 'importPay') {
                     responseData.data = await this.ctx.service.costStageAnalysis.importPay(ctx.costStage, data.pay_order, data.pay_tax);
+                } else if (data.target === 'importCost') {
+                    responseData.data = await this.ctx.service.costStageAnalysis.importCost(ctx.costStage, data.select_id, data.cost_id);
                 }
                 ctx.body = responseData;
             } catch (err) {

+ 1 - 1
app/lib/ybp.js

@@ -18,7 +18,7 @@ const key = CryptoJS__default.default.enc.Utf8.parse('7563850888ABCDEF'); // 十
 const iv = CryptoJS__default.default.enc.Utf8.parse('ABCDEF7563850888'); // 十六位十六进制数作为密钥偏移量
 
 const fs = require('fs');
-const ybpSpr = '|----|', rationSpr = '|====|';
+const ybpSpr = '|----|', rationSpr = '|====|', billsSpr = '|++++|', qtyCalcSpr = '|~~~~|';
 
 const Decrypt = function(str) {
     const encryptedHexStr = CryptoJS__default.default.enc.Hex.parse(str);

+ 112 - 31
app/public/js/cost_stage_analysis.js

@@ -103,10 +103,9 @@ $(document).ready(function() {
             }
             const preNode = tree.getPreSiblingNode(first);
             const valid = !this.sheet.zh_setting.readOnly;
-            const topParent = tree.getTopParent(first);
-            const isFixed = first.tree_level === 1 || [2,3,4].indexOf(topParent.node_type) >= 0;
+            const isFixed = first.tree_level === 1;
 
-            setObjEnable($('a[name=base-opr][type=add]'), valid && first && !isFixed);
+            setObjEnable($('a[name=base-opr][type=add]'), valid && first && (first.tree_level !== 1 || first.node_type === 2));
             setObjEnable($('a[name=base-opr][type=delete]'), valid && first && sameParent && !isFixed && !specNode);
             setObjEnable($('a[name=base-opr][type=up-move]'), valid && first && sameParent && preNode && !isFixed);
             setObjEnable($('a[name=base-opr][type=down-move]'), valid && first && sameParent  && !tree.isLastSibling(last) && !isFixed);
@@ -255,7 +254,7 @@ $(document).ready(function() {
             }
 
             const updateData = {
-                target: 'ledger', postType: type,
+                target: 'ledger', postType: type, addType: type === 'add' ? (node.node_type === 2 ? 'child' : 'next') : '',
                 postData: {
                     id: node.tree_id,
                     count: type === 'add' ? addCount : count,
@@ -312,32 +311,27 @@ $(document).ready(function() {
         editStarting(e, info) {
             if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
 
-            const tree = info.sheet.zh_tree;
             const col = info.sheet.zh_setting.cols[info.col];
             const node = info.sheet.zh_tree.nodes[info.row];
             if (!node) {
                 info.cancel = true;
                 return;
             }
-            const topParent = tree.getTopParent(node);
+            const detailRange = detailObj.data.getPartData(node.id);
 
             switch (col.field) {
                 case 'code':
                 case 'name':
-                    info.cancel = node.tree_level === 1 || node.node_type > 0 || node.calc_type === 2;
+                    info.cancel = node.tree_level === 1 || node.node_type > 0;
                     break;
                 case 'in_excl_tax_tp':
-                    info.cancel = node.children && node.children.length > 0;
-                    if (!info.cancel) {
-                        info.cancel = node.tree_level === 1 || node.node_type > 1  || topParent.node_type === 2;
-                    }
+                    info.cancel = node.tree_level === 1 || (node.children && node.children.length > 0);
+                    if (!info.cancel) info.cancel = node.node_type > 1 || !!node.import_cost_id;
                     break;
                 case 'yf_excl_tax_tp':
                 case 'sf_excl_tax_tp':
-                    info.cancel = node.children && node.children.length > 0;
-                    if (!info.cancel) {
-                        info.cancel = node.tree_level === 1 || node.node_type > 0  || topParent.node_type === 2;
-                    }
+                    info.cancel = node.tree_level === 1 || (node.children && node.children.length > 0);
+                    if (!info.cancel) info.cancel = node.node_type > 0 || !!node.import_cost_id;
                     break;
                 case 'num_a':
                 case 'num_b':
@@ -355,11 +349,8 @@ $(document).ready(function() {
                 case 'num_n':
                 case 'num_o':
                 case 'num_p':
-                    info.cancel = node.children && node.children.length > 0;
-                    if (!info.cancel) {
-                        const detailRange = detailObj.data.getPartData(node.id);
-                        info.cancel = detailRange && detailRange.length > 0;
-                    }
+                    info.cancel = node.tree_level === 1 || (node.children && node.children.length > 0);
+                    if (!info.cancel) info.cancel = !!node.import_cost_id || (detailRange && detailRange.length > 0);
                     break;
             }
         }
@@ -500,7 +491,12 @@ $(document).ready(function() {
         constructor() {
             this.spread = SpreadJsObj.createNewSpread($('#detail-spread')[0]);
             this.sheet = this.spread.getActiveSheet();
-            this.data = createAncillaryGcl({ id: 'id', masterId: 'ledger_id', sort: [['d_order', 'asc']] });
+            this.data = createAncillaryGcl({
+                id: 'id', masterId: 'ledger_id', sort: [['d_order', 'asc']],
+                calcFun: function(node) {
+                    node.sf_percent = node.yf_excl_tax_tp ? ZhCalc.mul(ZhCalc.div(node.sf_excl_tax_tp, node.yf_excl_tax_tp), 100 , 2) : 0;
+                },
+            });
             this.dealSpreadSetting = dealSpreadSetting;
             this.dealSpreadSetting.readOnly = readOnly;
             this.dealSpreadSetting.tipsSum = true;
@@ -528,6 +524,7 @@ $(document).ready(function() {
         }
         loadData(datas) {
             this.data.loadDatas(datas);
+            this.data.calculateAll();
             this.loadDetailData(SpreadJsObj.getSelectObject(billsObj.sheet));
         }
         refreshSheet() {
@@ -719,6 +716,80 @@ $(document).ready(function() {
     }
     const detailObj = new DetailObj();
 
+    const importCost = (function() {
+        const spread = SpreadJsObj.createNewSpread($('#import-cost-spread')[0]);
+        const sheet = spread.getActiveSheet();
+        SpreadJsObj.initSheet(sheet, {
+            cols: [
+                { title: '选择', colSpan: '1', rowSpan: '1', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox' },
+                { title: '编号', colSpan: '1', rowSpan: '1', field: 'code', hAlign: 0, width: 120, formatter: '@', cellType: 'tree' },
+                { title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 200, formatter: '@' },
+                { title: '类型', colSpan: '1', rowSpan: '1', field: 'detail_type', hAlign: 1, width: 40, formatter: '@' },
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            readOnly: true,
+        });
+        const tree = createNewPathTree('ledger', {
+            id: 'tree_id', pid: 'tree_pid', order: 'tree_order', level: 'tree_level',
+            isLeaf: 'tree_is_leaf', fullPath: 'tree_full_path', rootId: -1,
+        });
+        spread.bind(spreadNS.Events.ButtonClicked, function(e, info) {
+            if (!info.sheet.zh_setting || !info.sheet.zh_tree) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            if (col.field !== 'selected') return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            if (!node.children || node.children.length === 0) {
+                const selected = tree.nodes.find(x => { return x.selected; });
+                if (selected && selected.detail_type !== node.detail_type) {
+                    toastr.warning('勾选节点类型不一致,不允许勾选');
+                    return;
+                }
+                node.selected = !node.selected;
+                SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+            } else {
+                toastr.warning('父项不可勾选');
+            }
+        });
+        let curNode;
+        const initCostTree = function(data) {
+            tree.loadDatas(data);
+            SpreadJsObj.loadSheetData(sheet, SpreadJsObj.DataType.Tree, tree);
+        };
+        const show = function(bills) {
+            curNode = bills;
+            const orgSelect = curNode.import_cost_id.split(',');
+            tree.nodes.forEach(x => {
+                x.selected = orgSelect.indexOf(x.cost_id) >= 0;
+            });
+            SpreadJsObj.reloadColData(sheet, 0);
+            $('#import-cost').modal('show');
+        };
+        $('#import-cost').on('shown.bs.modal', function() {
+            spread.refresh();
+        });
+        $('#import-cost-ok').click(function() {
+            const updateData = { target: 'importCost', select_id: curNode.id, cost_id: tree.nodes.filter(x => { return x.selected; }).map(x => { return x.cost_id; }) };
+            if (updateData.cost_id.length === 0) {
+                toastr.warning('请选择要导入的成本节点');
+                return;
+            }
+            postData('update', updateData, function (result) {
+                const refreshNode = billsObj.tree.loadPostData(result);
+                billsObj.refreshTree(refreshNode);
+                detailObj.data.updateDatas(result.detail);
+                detailObj.loadDetailData(curNode);
+                $('#import-cost').modal('hide');
+            });
+        });
+        return { show, initCostTree };
+    })();
     // 清单右键菜单
     const billsContextMenuOptions = {
         selector: '#bills-spread',
@@ -770,9 +841,8 @@ $(document).ready(function() {
                 if (!tree) return true;
                 const first = sheet.zh_tree.nodes[row];
                 const valid = !sheet.zh_setting.readOnly;
-                const topParent = tree.getTopParent(first);
-                const isFixed = first.tree_level === 1 || [2,3,4].indexOf(topParent.node_type) >= 0;
-                return !valid || !first || isFixed;
+                const isFixed = first.tree_level === 1;
+                return !valid || !first || (isFixed && first.node_type !== 2);
             }
         };
         billsContextMenuOptions.items.delete = {
@@ -807,8 +877,7 @@ $(document).ready(function() {
                     }
                 }
                 const valid = !sheet.zh_setting.readOnly;
-                const topParent = tree.getTopParent(first);
-                const isFixed = first.tree_level === 1 || [2,3,4].indexOf(topParent.node_type) >= 0;
+                const isFixed = first.tree_level === 1;
                 return !valid || !first || !sameParent || nodeUsed || isFixed || specNode;
             }
         };
@@ -838,9 +907,8 @@ $(document).ready(function() {
                 if (!tree) return true;
                 const first = sheet.zh_tree.nodes[row];
                 const valid = !sheet.zh_setting.readOnly;
-                const topParent = tree.getTopParent(first);
-                const isFixed = first.tree_level === 1 || [2,3,4].indexOf(topParent.node_type) >= 0;
-                return !valid || !first || isFixed;
+                const isFixed = first.tree_level === 1;
+                return !valid || !first || (isFixed && first.node_type !== 2);
             },
             visible: function (key, opt) {
                 return !readOnly;
@@ -859,6 +927,18 @@ $(document).ready(function() {
                 return !node || (node.children && node.children.length > 0) || node.node_type !== 1;
             }
         };
+        billsContextMenuOptions.items.importCost = {
+            name: '导入成本数据',
+            callback: function() {
+                const node = SpreadJsObj.getSelectObject(billsObj.sheet);
+                importCost.show(node);
+            },
+            disabled: function() {
+                const node = SpreadJsObj.getSelectObject(billsObj.sheet);
+                const topNode = billsObj.tree.getTopParent(node);
+                return !node || (node.children && node.children.length > 0) || topNode.node_type !== 2;
+            }
+        };
         $('#import-pay-ok').click(function() {
             const updateData = { target: 'importPay', pay_order: $('#pay-order').val(), pay_tax: parseFloat($('#pay-tax').val()) };
             if (updateData.pay_tax < 0 || updateData.pay_tax > 100) {
@@ -870,7 +950,7 @@ $(document).ready(function() {
                 billsObj.refreshTree(refreshNode);
                 $('#import-pay').modal('hide');
             });
-        })
+        });
     }
     $.contextMenu(billsContextMenuOptions);
 
@@ -938,7 +1018,7 @@ $(document).ready(function() {
         }
     });
     // 加载数据
-    postData('load', { filter: 'bills;detail;att;tags' }, function(result) {
+    postData('load', { filter: 'bills;detail;att;tags;relaStageBills' }, function(result) {
         billsObj.loadData(result.bills);
         detailObj.loadData(result.detail);
 
@@ -948,6 +1028,7 @@ $(document).ready(function() {
         }
         costFile.loadDatas(result.att);
         costFile.getCurAttHtml(SpreadJsObj.getSelectObject(billsObj.sheet));
+        importCost.initCostTree(result.relaStageBills);
     });
 
     // 展开收起标准清单

+ 12 - 1
app/public/js/path_tree.js

@@ -2216,6 +2216,7 @@ const createAncillaryGcl = function (setting) {
                 if (!this.masterIndex[masterKey]) this.masterIndex[masterKey] = [];
                 this.masterIndex[masterKey].push(d);
                 if (resort.indexOf(masterKey) < 0) resort.push(masterKey);
+                if (this.setting.calcFun) this.setting.calcFun(d);
             }
         }
 
@@ -2233,8 +2234,8 @@ const createAncillaryGcl = function (setting) {
                 }
                 const masterKey = this.itemPre + item[this.setting.masterId];
                 if (resort.indexOf(masterKey) < 0) resort.push(masterKey);
+                if (this.setting.calcFun) this.setting.calcFun(d);
             }
-
         }
 
         /**
@@ -2330,6 +2331,16 @@ const createAncillaryGcl = function (setting) {
                 fun(prop.substring(3, prop.length), this.masterIndex[prop]);
             }
         }
+
+        /**
+         * 计算全部
+         */
+        calculateAll() {
+            if (!this.setting.calcFun)  return;
+            for (const d of this.datas) {
+                this.setting.calcFun(d);
+            }
+        }
     }
 
     return new AncillaryGcl(setting);

+ 133 - 138
app/service/cost_stage_analysis.js

@@ -17,7 +17,7 @@ const costFields = {
     hisFields: ['calc_his'],
     taxFields: ['tax'],
     treeFields: ['tree_id', 'tree_pid', 'tree_level', 'tree_order', 'tree_full_path', 'tree_is_leaf'],
-    baseFields: ['id', 'cost_id', 'tender_id', 'stage_id', 'node_type', 'calc_type'],
+    baseFields: ['id', 'cost_id', 'tender_id', 'stage_id', 'node_type', 'calc_type', 'has_detail', 'import_cost_id'],
 };
 costFields.sumFields = [...costFields.calcFields, ...costFields.selfCalcFields];
 costFields.preCopyFields = [...costFields.treeFields, ...costFields.textFields, ...costFields.selfTextFields, ...costFields.calcFields, ...costFields.selfCalcFields, ...costFields.taxFields];
@@ -140,14 +140,13 @@ module.exports = app => {
         async init(stage, transaction) {
             if (!this.ctx.subProject.cost_analysis_template) throw '未设置成本分析模板,请联系管理员设置';
             if (!stage || !transaction) throw '成本分析数据错误';
-            const userId = this.ctx.session.sessionUser.accountId;
 
             const templateData = await this.ctx.service.tenderNodeTemplate.getData(this.ctx.subProject.cost_analysis_template);
             if (templateData.length === 0) throw '台账模板无数据,连请联系管理员修改';
             const calcTemplate = await this.ctx.service.calcTmpl.getTemplate(stage.calc_template);
             if (!calcTemplate) throw '计算模板未定义,请联系管理员设置';
 
-            const insertTemplateData = [], insertLedgerData = [], insertDetailData = [];
+            const insertTemplateData = [];
             let maxId = 0;
             for (const b of templateData) {
                 const bills = {
@@ -160,68 +159,12 @@ module.exports = app => {
                 this._getDefaultData(bills, stage);
                 insertTemplateData.push(bills);
             }
-            const relaStage = await this.service.costStage.getStage(JSON.parse(stage.rela_stage).sid);
-            const costStageLedger = await this.getCostStageData(relaStage);
-            const costNode = insertTemplateData.find(x => { return x.node_type === 2 && x.tree_level === 1; });
-            if (costStageLedger.nodes.length > 0) costNode.tree_is_leaf = 0;
-            for (const [i, node] of costStageLedger.nodes.entries()) {
-                const parent = costStageLedger.getParent(node);
-                const newParent = parent ? insertLedgerData.find(x => { return x.tree_id === parent.new_tree_id; }) : costNode;
-                const ln = { calc_type: 2 };
-                this._getDefaultData(ln, stage);
-                ln.node_type = 0;
-                ln.cost_id = node.cost_id;
-                ln.tree_id = maxId + i + 1;
-                node.new_tree_id = ln.tree_id;
-                ln.tree_pid = newParent.tree_id;
-                ln.tree_order = node.tree_order;
-                ln.tree_level = newParent.tree_level + 1;
-                ln.tree_full_path = newParent.tree_full_path + '-' + ln.tree_id;
-                ln.tree_is_leaf = node.tree_is_leaf;
-                ln.code = node.code;
-                ln.name = node.name;
-                ln.unit = node.unit;
-                ln.tax = 0;
-                for (const prop of costFields.sumFields) {
-                    ln[prop] = 0;
-                }
-                insertLedgerData.push(ln);
-                if (node.detail && node.detail.length > 0) {
-                    for (const [i, d] of node.detail.entries()) {
-                        const idd = {
-                            id: this.uuid.v4(), tender_id: stage.tid, stage_id: stage.id,
-                            add_user_id: userId, update_user_id: userId,
-                            ledger_id: ln.id, cost_id: ln.cost_id, source_id: d.source_cid,
-                            d_order: i+1,
-                            code: d.code, name: d.name, party_b: d.party_b || '', tax: d.tax || 0,
-                            yf_excl_tax_tp: d.yf_excl_tax_tp || 0, in_excl_tax_tp: d.in_excl_tax_tp || 0, sf_excl_tax_tp: d.sf_excl_tax_tp || 0,
-                            is_deal: d.is_deal,
-                        };
-                        this.ctx.service.calcTmpl.calcByTemplate(idd, idd, idd, calcTemplate.calc_expr);
-                        idd.sf_percent = idd.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(idd.sf_excl_tax_tp, idd.yf_excl_tax_tp), 100, 2) : 0;
-                        insertDetailData.push(idd);
-                        for (const prop of costFields.sumFields) {
-                            ln[prop] = this.ctx.helper.add(ln[prop], idd[prop]);
-                        }
-                    }
-                } else {
-                    ln.tax = node.tax;
-                    ln.yf_excl_tax_tp = node.end_yf_excl_tax_tp || 0;
-                    ln.in_excl_tax_tp = node.end_in_excl_tax_tp || 0;
-                    ln.sf_excl_tax_tp = node.end_sf_excl_tax_tp || 0;
-                    this.ctx.service.calcTmpl.calcByTemplate(ln, ln, ln, calcTemplate.calc_expr);
-                }
-                ln.sf_percent = ln.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(ln.sf_excl_tax_tp, ln.yf_excl_tax_tp), 100, 2) : 0;
-            }
 
             await transaction.insert(this.tableName, insertTemplateData);
-            await transaction.insert(this.tableName, insertLedgerData);
-            if (insertDetailData.length > 0) await transaction.insert(this.ctx.service.costStageAnalysisDetail.tableName, insertDetailData);
         }
         async initByPre(stage, preStage, transaction) {
             if (!stage || !preStage || !transaction) throw '成本分析数据错误';
-            const userId = this.ctx.session.sessionUser.accountId;
-            const insertData = [], insertDetailData = [];
+            const insertData = [];
             let maxId = 0;
             if (!stage.calcTemplate) stage.calcTemplate = await this.service.calcTmpl.getTemplate(stage.calc_template);
 
@@ -235,93 +178,23 @@ module.exports = app => {
             const preDetails = await this.ctx.service.costStageAnalysisDetail.getReadData(preStage);
             for (const pl of preLedger.nodes) {
                 if (pl.tree_is_leaf) pl.detail = preDetails.filter(x => { return x.ledger_id === pl.id; });
-                if (pl.tree_level > 1) {
-                    const parent = preLedger.getTopParent(pl);
-                    if (parent.node_type === 2) continue;
-                }
                 const idata = {};
                 this._getDefaultData(idata, stage);
                 idata.cost_id = pl.cost_id;
                 idata.node_type = pl.node_type;
                 idata.calc_type = pl.calc_type;
-                for (const prop of costFields.preCopyFields) {
-                    idata[prop] = pl[prop];
+                idata.has_detail = pl.has_detail && pl.detail.length > 0;
+                idata.import_cost_id = pl.import_cost_id;
+                if (!idata.has_detail) {
+                    for (const prop of costFields.preCopyFields) {
+                        idata[prop] = pl[prop];
+                    }
                 }
                 maxId = Math.max(maxId, idata.tree_id);
                 insertData.push(idata);
             }
 
-            const relaStage = await this.service.costStage.getStage(JSON.parse(stage.rela_stage).sid);
-            const costStageLedger = await this.getCostStageData(relaStage);
-            const costNode = insertData.find(x => { return x.node_type === 2 && x.tree_level === 1; });
-            if (costStageLedger.nodes.length > 0) costNode.tree_is_leaf = 0;
-            for (const [i, node] of costStageLedger.nodes.entries()) {
-                const parent = costStageLedger.getParent(node);
-                const newParent = parent ? insertData.find(x => { return x.tree_id === parent.new_tree_id; }) : costNode;
-                const ln = { calc_type: 2 };
-                this._getDefaultData(ln, stage);
-                ln.node_type = 0;
-                ln.cost_id = node.cost_id;
-                ln.tree_id = maxId + i + 1;
-                node.new_tree_id = ln.tree_id;
-                ln.tree_pid = newParent.tree_id;
-                ln.tree_order = node.tree_order;
-                ln.tree_level = newParent.tree_level + 1;
-                ln.tree_full_path = newParent.tree_full_path + '-' + ln.tree_id;
-                ln.tree_is_leaf = node.tree_is_leaf;
-                ln.code = node.code;
-                ln.name = node.name;
-                ln.unit = node.unit;
-                ln.tax = node.tax;
-                ln.yf_excl_tax_tp = node.end_yf_excl_tax_tp || 0;
-                ln.in_excl_tax_tp = node.end_in_excl_tax_tp || 0;
-                ln.sf_excl_tax_tp = node.end_sf_excl_tax_tp || 0;
-                ln.postil = '';
-                ln.sf_percent = 0;
-                insertData.push(ln);
-                const preSource = preLedger.nodes.find(x => { return x.cost_id === node.cost_id; });
-                for (const prop of costFields.selfTextFields) {
-                    ln[prop] = preSource ? preSource[prop] : '';
-                }
-                if (!node.detail || node.detail.length === 0) {
-                    for (const prop of costFields.selfCalcFields) {
-                        ln[prop] = preSource ? preSource[prop] : 0;
-                    }
-                } else {
-                    for (const prop of costFields.sumFields) {
-                        ln[prop] = 0;
-                    }
-                    for (const [i, d] of node.detail.entries()) {
-                        const ld = {
-                            id: this.uuid.v4(), tender_id: stage.tid, stage_id: stage.id,
-                            add_user_id: userId, update_user_id: userId,
-                            ledger_id: ln.id, cost_id: ln.cost_id, source_id: d.source_cid,
-                            d_order: i+1,
-                            code: d.code, name: d.name, party_b: d.party_b || '', tax: ln.tax || 0,
-                            yf_excl_tax_tp: d.yf_excl_tax_tp || 0, in_excl_tax_tp: d.in_excl_tax_tp || 0, sf_excl_tax_tp: d.sf_excl_tax_tp || 0,
-                            is_deal: d.is_deal,
-                        };
-                        const preDetailSource = preSource ? preSource.detail.find(x => { return x.source_id === d.source_cid; }) : undefined;
-                        for (const prop of costFields.selfTextFields) {
-                            ld[prop] = preDetailSource ? preDetailSource[prop] : '';
-                        }
-                        for (const prop of costFields.selfCalcFields) {
-                            ld[prop] = preDetailSource ? preDetailSource[prop] || 0 : 0;
-                        }
-                        this.ctx.service.calcTmpl.calcByTemplate(ld, ld, ld, stage.calcTemplate.calc_expr);
-                        ld.sf_percent = ld.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(ld.sf_excl_tax_tp, ld.yf_excl_tax_tp), 100, 2) : 0;
-                        for (const prop of costFields.sumFields) {
-                            ln[prop] = this.ctx.helper.add(ln[prop], ld[prop]);
-                        }
-                        insertDetailData.push(ld);
-                    }
-                }
-                this.ctx.service.calcTmpl.calcByTemplate(ln, ln, ln, stage.calcTemplate.calc_expr);
-                ln.sf_percent = ln.yf_excl_tax_tp ? this.ctx.helper.mul(this.ctx.helper.div(ln.sf_excl_tax_tp, ln.yf_excl_tax_tp), 100, 2) : 0;
-            }
-
             await transaction.insert(this.tableName, insertData);
-            if (insertDetailData.length > 0) await transaction.insert(this.ctx.service.costStageAnalysisDetail.tableName, insertDetailData);
         }
         async initStageData(transaction, stage, preStage) {
             try {
@@ -440,7 +313,7 @@ module.exports = app => {
             const ids = id instanceof Array ? id : [id];
             if (ids.length === 0) return [];
 
-            const data = await this.db.getAllDataByCondition({ where: { id: ids } });
+            const data = await this.getAllDataByCondition({ where: { id: ids } });
             return data;
         }
 
@@ -489,7 +362,7 @@ module.exports = app => {
                         ? select[this.setting.fullPath].replace('-' + select[this.setting.kid], '-' + newData[this.setting.kid])
                         : newData[this.setting.kid] + '';
                     newData[this.setting.isLeaf] = true;
-                    newData.calc_type = calcType.in;
+                    newData.calc_type = select ? select.calc_type : calcType.in;
                     this._getDefaultData(newData, stage);
                     newDatas.push(newData);
                 }
@@ -515,6 +388,54 @@ module.exports = app => {
             }
         }
 
+        async addAnalysisChild(stage, targetId, count) {
+            if (!stage) return null;
+
+            const select = targetId ? await this.getDataByKid(stage.id, targetId) : null;
+            if (targetId && !select) throw '新增节点数据错误';
+            const children = await this.getChildrenByParentId(stage.id, select[this.setting.kid]);
+            const lastChild = children.length > 0 ? children[children.length - 1] : null;
+
+            this.transaction = await this.db.beginTransaction();
+            const newDatas = [];
+            try {
+                const maxId = await this._getMaxLid(stage.id);
+                for (let i = 1; i < count + 1; i++) {
+                    const newData = {};
+                    newData[this.setting.kid] = maxId + i;
+                    newData[this.setting.pid] = select[this.setting.kid];
+                    newData[this.setting.mid] = stage.id;
+                    newData[this.setting.level] = select[this.setting.level] + 1;
+                    newData[this.setting.order] = lastChild ? lastChild[this.setting.order] + i : i;
+                    newData[this.setting.fullPath] = select[this.setting.fullPath] + '-' + newData[this.setting.kid];
+                    newData[this.setting.isLeaf] = true;
+                    newData.calc_type = select.calc_type;
+                    this._getDefaultData(newData, stage);
+                    newDatas.push(newData);
+                }
+                const insertResult = await this.transaction.insert(this.tableName, newDatas);
+                this._cacheMaxLid(stage.id, maxId + count);
+
+                if (insertResult.affectedRows !== count) throw '新增节点数据错误';
+                if (!lastChild) {
+                    const updateParent = { id: select.id };
+                    updateParent[this.setting.isLeaf] = false;
+                    this.clearParentingData(updateParent);
+                    await this.transaction.update(this.tableName, updateParent);
+                }
+                await this.transaction.commit();
+                this.transaction = null;
+            } catch (err) {
+                await this.transaction.rollback();
+                this.transaction = null;
+                throw err;
+            }
+
+            const createData = await this.getDataByIds(newDatas.map(x => { return x.id; }));
+            const updateData = lastChild ? await this.getDataById(select.id) : [];
+            return {create: createData, update: updateData};
+        }
+
         async getProfitUpdateData(updateData) {
             const leafNode = await this.getAllDataByCondition({ where: { stage_id: this.ctx.costStage.id, tree_is_leaf: 1 } });
             const profit = leafNode.find(x => { return x.node_type === 3; });
@@ -623,6 +544,80 @@ module.exports = app => {
             }
             return { update: [payUpdate, ...profitUpdate] };
         }
+        async importCost(stage, select_id, cost_id) {
+            const userId = this.ctx.session.sessionUser.accountId;
+            const select = await this.getDataById(select_id);
+            if (!select) throw '选择的节点不存在,请刷新后重试';
+
+            const costStageLedger = await this.getCostStageData(stage.relaStage);
+            const nodes = costStageLedger.nodes.filter(x => { return cost_id.indexOf(x.cost_id) >= 0; });
+            if(nodes.length === 0 || nodes.length !== cost_id.length) throw '选择的成本数据不存在,请刷新后重试';
+
+            const orgDetailDatas = await this.ctx.service.costStageAnalysisDetail.getAllDataByCondition({ where: { ledger_id: select.id, stage_id: stage.id } });
+            const updateData = {
+                id: select.id, tree_id: select.tree_id, update_user_id: userId,
+                import_cost_id: cost_id.join(',')
+            };
+            const insertDetails = [];
+            if (nodes[0].detail.length > 0) {
+                const preStage = stage.stage_order > 1 ? await this.ctx.service.costStage.getStageByOrder(stage.tid, stage.stage_type, stage.stage_order -1) : null;
+                const preDetail = preStage ? await this.ctx.service.costStageAnalysisDetail.getAllDataByCondition({ where: { stage_id: preStage.id, cost_id: select.cost_id } }) : [];
+                for (const node of nodes) {
+                    for (const d of node.detail) {
+                        let id = insertDetails.find(x => { return x.source_id === d.source_cid && x.is_deal === d.is_deal; });
+                        if (!id) {
+                            id = {
+                                id: this.uuid.v4(), tender_id: stage.tid, stage_id: stage.id,
+                                add_user_id: userId, update_user_id: userId,
+                                ledger_id: select.id, cost_id: select.cost_id, source_id: d.source_cid, is_deal: d.is_deal,
+                                d_order: insertDetails.length + 1,
+                                code: d.code, name: d.name, party_b: d.party_b || '', tax: d.tax || 0,
+                            };
+                            insertDetails.push(id);
+                        }
+                        id.yf_excl_tax_tp = this.ctx.helper.add(id.yf_excl_tax_tp, d.yf_excl_tax_tp || 0);
+                        id.sf_excl_tax_tp = this.ctx.helper.add(id.sf_excl_tax_tp, d.sf_excl_tax_tp || 0);
+                        id.in_excl_tax_tp = this.ctx.helper.add(id.in_excl_tax_tp, d.in_excl_tax_tp || 0);
+                    }
+                }
+                for (const id of insertDetails) {
+                    let sourceDetail = orgDetailDatas.find(x => { return x.source_id === id.source_id && x.is_deal === id.is_deal; });
+                    if (!sourceDetail) sourceDetail = preDetail.find(x => { return x.source_id === id.source_id && x.is_deal === id.is_deal; });
+                    for (const prop of costFields.selfCalcFields) {
+                        id[prop] = sourceDetail ? sourceDetail[prop] || 0 : 0;
+                    }
+                    this.ctx.service.calcTmpl.calcByTemplate(id, id, id, stage.calcTemplate.calc_expr);
+                    for (const prop of costFields.sumFields) {
+                        updateData[prop] = this.ctx.helper.add(updateData[prop], id[prop]);
+                    }
+                }
+            } else {
+                for (const node of nodes) {
+                    updateData.yf_excl_tax_tp = this.ctx.helper.add(updateData.yf_excl_tax_tp, node.end_yf_excl_tax_tp || 0);
+                    updateData.sf_excl_tax_tp = this.ctx.helper.add(updateData.sf_excl_tax_tp, node.end_sf_excl_tax_tp || 0);
+                    updateData.in_excl_tax_tp = this.ctx.helper.add(updateData.in_excl_tax_tp, node.end_in_excl_tax_tp || 0);
+                }
+            }
+            this.ctx.service.calcTmpl.calcByTemplate(updateData, updateData, select, stage.calcTemplate.calc_expr);
+            const profitUpdate = await this.getProfitUpdateData([updateData]);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                await conn.update(this.tableName, updateData);
+                await conn.updateRows(this.tableName, profitUpdate);
+                await conn.delete(this.ctx.service.costStageAnalysisDetail.tableName, { stage_id: stage.id, ledger_id: select.id });
+                if (insertDetails.length > 0) await conn.insert(this.ctx.service.costStageAnalysisDetail.tableName, insertDetails);
+                await conn.commit();
+            } catch(err) {
+                this.ctx.log(err);
+                await conn.rollback();
+                throw '导入成本数据错误';
+            }
+            return {
+                update: [updateData, ...profitUpdate],
+                detail: { add: insertDetails, del: orgDetailDatas.map(x => { return x.id; }) }
+            };
+        }
         async reCalcProfit(stageId) {
             if (!this.ctx.costStage) {
                 const costStage = await this.service.costStage.getStage(stageId);

+ 17 - 1
app/view/cost/analysis_modal.ejs

@@ -22,10 +22,26 @@
                 </div>
             </div>
             <div class="modal-footer">
-                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
                 <button type="button" class="btn btn-sm btn-primary" id="import-pay-ok">确定</button>
             </div>
         </div>
     </div>
+</div>
+<div class="modal" id="import-cost" data-backdrop="static" aria-modal="true" role="dialog">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">导入成本数据</h5>
+            </div>
+            <div class="modal-body">
+                <div id="import-cost-spread" class="modal-height-500">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary" id="import-cost-ok">确定</button>
+            </div>
+        </div>
+    </div>
 </div>

+ 3 - 0
sql/update.sql

@@ -20,6 +20,9 @@ ADD COLUMN `csid` int NULL DEFAULT NULL COMMENT '补充合同id' AFTER `cpid`;
 ALTER TABLE `zh_sub_project`
 ADD COLUMN `cost_ledger_col_set` json NULL COMMENT '成本报审-列设置' AFTER `cost_calc_template`;
 
+ALTER TABLE `zh_cost_stage_analysis`
+ADD COLUMN `has_detail` int(1) NOT NULL DEFAULT 0 COMMENT '是否含有明细' AFTER `calc_type`,
+ADD COLUMN `import_cost_id` varchar(5000) NOT NULL DEFAULT '' COMMENT '导入的成本id' AFTER `has_detail`;
 
 ------------------------------------
 -- 表数据