Browse Source

Merge branch 'budget' of http://192.168.1.41:3000/SmartCost/ConstructionCost into budget

chenshilong 4 years ago
parent
commit
f1b0d0740a

+ 2 - 0
modules/all_models/equipment_purchase.js

@@ -5,6 +5,8 @@ let mongoose = require('mongoose'),
 
 let equipment = {
     ID:String,
+    ParentID:String,
+    seq:Number,
     code:String,
     name:String,
     unit:String,

+ 1 - 1
modules/equipment_purchase/controllers/equipment_purchase_controller.js

@@ -14,7 +14,7 @@ let controller = {
     updateEquipments:async function(req){
         let data = req.body.data;
         data = JSON.parse(data);
-        return await equipment_purchase_facade.updateEquipments(data.projectID,data.updateData); 
+        return await equipment_purchase_facade.updateEquipments(data.projectID,data.updateData,data.total); 
     }
 }
 

+ 11 - 6
modules/equipment_purchase/facade/equipment_purchase_facade.js

@@ -27,14 +27,19 @@ async function insertData (projectID,data){
     return data
 }
 
-async function updateEquipments (projectID,updateData){
+async function updateEquipments (projectID,updateData,total){
     let tasks = [];
+    if(total !== undefined || total !== null) tasks.push({updateOne :{filter:{projectID},update:{$set:{total:total}}}});
     for(let data of updateData){
-        let task = {updateOne : {
-            filter:{projectID,'equipments.ID':data.ID},
-            update:{$set:getPreUpdate('equipments.$',data.doc)}
-        }}
-        tasks.push(task)
+        if(data.type === 'insert' ){
+            tasks.push({updateOne:{ filter:{projectID},update:{'$push':{equipments:{ $each: data.documents }}}}})
+        }else {
+            let task = {updateOne : {
+                filter:{projectID,'equipments.ID':data.ID},
+                update:{$set:getPreUpdate('equipments.$',data.doc)}
+            }}
+            tasks.push(task)
+        }   
     }
     if(tasks.length > 0) await equipmentPurchaseModel.bulkWrite(tasks);
     return updateData

+ 80 - 2
modules/main/facade/bill_facade.js

@@ -13,13 +13,15 @@ let Bills_Lib = mongoose.model('std_bills_lib_bills');
 let ration_Model = mongoose.model('ration');
 let ration_glj_Model = mongoose.model('ration_glj');
 let ration_coe_Model = mongoose.model('ration_coe');
+const projectModel = mongoose.model('projects');
 let ration_installation_Model = mongoose.model('ration_installation');
 let ration_template_Model = mongoose.model('ration_template');
 let bill_Model = require('../models/bills').model;
 let billsLibDao = require("../../bills_lib/models/bills_lib_interfaces");
 const pmFacade = require('../../pm/facade/pm_facade');
-const { getSortedTreeData } = require('../../../public/common_util');
+const { getSortedTreeData, getEngineeringFeeType } = require('../../../public/common_util');
 const { billType, constructionFeeNodeID, constructionEquipmentFeeNodeID, BudgetArea, fixedFlag } = require('../../../public/common_constants');
+const scMathUtil = require('../../../public/scMathUtil').getUtil();
 const uuidV1 = require('uuid/v1');
 
 const GLJController = require("../../glj/controllers/glj_controller");
@@ -127,6 +129,18 @@ module.exports={
             await bill_Model.bulkWrite(bulks);
         }
     },
+    // 获取单位工程ID 工程费用 映射表
+    getUnitsBudgetMap: async function (unitIDs) {
+        const rst = {};
+        unitIDs.forEach(unitID => {
+            rst[unitID] = 1000000;
+        });
+        return rst;
+    },
+    // 获取设备购置费
+    getEquipmentFee: async function (unitIDs) {
+        return 5000.15;
+    },
     // 获取概算汇总数据(拍好序的)
     getBudgetSummary: async function (constructionID) {
         // 获取建设项目清单数据(工程建设其他费用 - 建设项目总概算)部分
@@ -135,16 +149,20 @@ module.exports={
         sortedOtherFeeBills.forEach(item => item.area = BudgetArea.CONSTRUCTION_OTHER_FEE);
         // 获取工程费用数据
         const constructionFeeBills = await this.getConstructionFeeData(constructionID, sortedOtherFeeBills[0].ID);
+        // 汇算工程费用
+        await this.summarizeData(constructionFeeBills);
         return [...constructionFeeBills, ...sortedOtherFeeBills];
     },
     // 获取工程费用数据,作为概算汇总数据的拼接树数据
     getConstructionFeeData: async function (constructionID, nextID) {
-        const projects = await pmFacade.getPosterityProjects([constructionID], true, { _id: 0, ID: 1, ParentID: 1, NextSiblingID: 1, name: 1, 'property.feeStandardName': 1});
+        const projects = await pmFacade.getPosterityProjects([constructionID], true, { _id: 0, ID: 1, ParentID: 1, NextSiblingID: 1, name: 1, projType: 1, 'property.engineeringName': 1});
         const construction = projects.find(p => p.ID === constructionID);
         const items = getSortedTreeData(construction.ParentID, projects);
         // 转换为uuid
         const IDMap = {};
         items.forEach((item, index) => {
+            //保留项目ID
+            item.orgProjectID = item.ID;
             if (index == 0) {
                 IDMap[item.ID] = constructionFeeNodeID;
             } else {
@@ -199,6 +217,66 @@ module.exports={
         items.push(constructionEquipmentNode);
         return items;
     },
+    // 汇算数据
+    summarizeData: async function (items) {
+        const processDecimal = 6;
+        const construction = items.find(item => item.projType === pmFacade.projectType.project);
+        if (!construction) {
+            return;
+        }
+        const singles = items.filter(item => item.projType === pmFacade.projectType.engineering);
+        const units = items.filter(item => item.projType === pmFacade.projectType.tender);
+        if (!singles.length || !units.length) {
+            return;
+        }
+        const unitProjectIDs = units.map(unit => unit.orgProjectID);
+        // 获取第一个单位工程的小数位数(清单合价)
+        const theUnit = await projectModel.findOne({ ID: unitProjectIDs[0] }, { _id: 0, 'property.decimal': 1 }).lean();
+        const decimal = theUnit && theUnit.property && theUnit.property.decimal && theUnit.property.decimal.bills && theUnit.property.decimal.bills.totalPrice || 2;
+        // 获取单位工程 工程费用映射表
+        const unitBudgetMap = await this.getUnitsBudgetMap(unitProjectIDs);
+        // 汇算
+        const constructionFeeObj = { total: 0, building: 0, installation: 0 };
+        for (const single of singles) {
+            const singleFeeObj = { total: 0, building: 0, installation: 0 };
+            const refUnits = units.filter(unit => unit.ParentID === single.ID);
+            for (const unit of refUnits) {
+                const unitFee = unitBudgetMap[unit.orgProjectID];
+                // 汇算到单项工程
+                singleFeeObj.total = scMathUtil.roundForObj(singleFeeObj.total + unitFee, processDecimal);
+                const unitFeeObj = { total: unitFee || 0, building: 0, installation: 0 };
+                // 建筑工程费、安装工程费
+                const feeType = getEngineeringFeeType(unit.property && unit.property.engineeringName || '');
+                if (feeType) {
+                    unitFeeObj[feeType] = unitFee;
+                    singleFeeObj[feeType] = scMathUtil.roundForObj(singleFeeObj[feeType] + unitFee, processDecimal);
+                }
+                unit.fees = feeObj2Fees(unitFeeObj);
+            }
+            single.fees = feeObj2Fees(singleFeeObj);
+            // 汇算到建设项目
+            constructionFeeObj.total = scMathUtil.roundForObj(constructionFeeObj.total + singleFeeObj.total, processDecimal);
+            constructionFeeObj.building = scMathUtil.roundForObj(constructionFeeObj.building + singleFeeObj.building, processDecimal);
+            constructionFeeObj.installation = scMathUtil.roundForObj(constructionFeeObj.installation + singleFeeObj.installation, processDecimal);
+        }
+        construction.fees = feeObj2Fees(constructionFeeObj);
+        // 获取设备购置费
+        const equipmentFee = await this.getEquipmentFee(unitProjectIDs);
+        const equipmentItem = items.find(item => item.flags && item.flags[0] && item.flags[0].flag === fixedFlag.CONSTRUCTION_EQUIPMENT_FEE);
+        const equipmentFeeObj = { fieldName: 'equipment', totalFee: equipmentFee };
+        if (equipmentItem) {
+            equipmentItem.fees = [equipmentFeeObj];
+        }
+        construction.fees.push(equipmentFeeObj);
+
+        function feeObj2Fees(feeObj) {
+            return [
+                { fieldName: 'common', totalFee: scMathUtil.roundForObj(feeObj.total, decimal) },
+                { fieldName: 'building', totalFee: scMathUtil.roundForObj(feeObj.building, decimal) },
+                { fieldName: 'installation', totalFee: scMathUtil.roundForObj(feeObj.installation, decimal) },
+            ];
+        }
+    },
     bulkOperation: async function (bulkData) {
         const bulks = [];
         bulkData.forEach(item => {

+ 8 - 8
modules/main/templates/constructionBillsTemplate.js

@@ -28,7 +28,7 @@ const buildingTemplate = [
   { ID: 227, ParentID: 22, NextSiblingID: 228, code: '2.2.7', name: '研究试验费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 228, ParentID: 22, NextSiblingID: 229, code: '2.2.8', name: '勘察设计费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 2281, ParentID: 228, NextSiblingID: 2282, code: '2.2.8.1', name: '勘察费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
-  { ID: 2282, ParentID: 228, NextSiblingID: -1, code: '2.2.8.2', name: '设计费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
+  { ID: 2282, ParentID: 228, NextSiblingID: -1, code: '2.2.8.2', name: '设计费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.DESIGN_FEE }], type: billType.BILL },
   { ID: 229, ParentID: 22, NextSiblingID: 2210, code: '2.2.9', name: '咨询费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 2291, ParentID: 229, NextSiblingID: 2292, code: '2.2.9.1', name: '设计咨询费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 2292, ParentID: 229, NextSiblingID: 2293, code: '2.2.9.2', name: '工程造价咨询费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
@@ -52,11 +52,11 @@ const buildingTemplate = [
   { ID: 3, ParentID: -1, NextSiblingID: 4, code: '3', name: '预备费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.BUDGET_RESERVE }], type: billType.DXFY },
   { ID: 31, ParentID: 3, NextSiblingID: 32, code: '3.1', name: '基本预备费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.BASIC_BUDGET_RESERVE }], type: billType.BILL },
   { ID: 32, ParentID: 3, NextSiblingID: -1, code: '3.2', name: '价差预备费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.DIFF_BUDGET_RESERVE }], type: billType.BILL },
-  { ID: 4, ParentID: -1, NextSiblingID: 5, code: '4', name: '专项费用', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_SPECIAL_FEE }], type: billType.BILL },
+  { ID: 4, ParentID: -1, NextSiblingID: 5, code: '4', name: '专项费用', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_SPECIAL_FEE }], type: billType.DXFY },
   { ID: 41, ParentID: 4, NextSiblingID: 42, code: '4.1', name: '车辆购置费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
-  { ID: 42, ParentID: 4, NextSiblingID: 43, code: '4.2', name: '建设期贷款利息', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
+  { ID: 42, ParentID: 4, NextSiblingID: 43, code: '4.2', name: '建设期贷款利息', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.LOAN_INTEREST }], type: billType.BILL },
   { ID: 43, ParentID: 4, NextSiblingID: -1, code: '4.3', name: '铺底流动资金', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
-  { ID: 5, ParentID: -1, NextSiblingID: -1, code: '5', name: '建设项目总概算', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_BUDGET }], type: billType.BILL },
+  { ID: 5, ParentID: -1, NextSiblingID: -1, code: '5', name: '建设项目总概算', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_BUDGET }], type: billType.DXFY },
 ];
 
 /* 概算汇总,城市轨道交通工程,建设其他费清单模板 */
@@ -91,7 +91,7 @@ const railTemplate = [
   { ID: 227, ParentID: 22, NextSiblingID: 228, code: '2.2.7', name: '研究试验费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 228, ParentID: 22, NextSiblingID: 229, code: '2.2.8', name: '勘察设计费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 2281, ParentID: 228, NextSiblingID: 2282, code: '2.2.8.1', name: '勘察费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
-  { ID: 2282, ParentID: 228, NextSiblingID: -1, code: '2.2.8.2', name: '设计费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
+  { ID: 2282, ParentID: 228, NextSiblingID: -1, code: '2.2.8.2', name: '设计费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.DESIGN_FEE }], type: billType.BILL },
   { ID: 229, ParentID: 22, NextSiblingID: 2210, code: '2.2.9', name: '咨询费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 2291, ParentID: 229, NextSiblingID: 2292, code: '2.2.9.1', name: '设计咨询费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
   { ID: 2292, ParentID: 229, NextSiblingID: 2293, code: '2.2.9.2', name: '工程造价咨询费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
@@ -116,11 +116,11 @@ const railTemplate = [
   { ID: 3, ParentID: -1, NextSiblingID: 4, code: '3', name: '预备费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.BUDGET_RESERVE }], type: billType.DXFY },
   { ID: 31, ParentID: 3, NextSiblingID: 32, code: '3.1', name: '基本预备费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.BASIC_BUDGET_RESERVE }], type: billType.BILL },
   { ID: 32, ParentID: 3, NextSiblingID: -1, code: '3.2', name: '价差预备费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.DIFF_BUDGET_RESERVE }], type: billType.BILL },
-  { ID: 4, ParentID: -1, NextSiblingID: 5, code: '4', name: '专项费用', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_SPECIAL_FEE }], type: billType.BILL },
+  { ID: 4, ParentID: -1, NextSiblingID: 5, code: '4', name: '专项费用', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_SPECIAL_FEE }], type: billType.DXFY },
   { ID: 41, ParentID: 4, NextSiblingID: 42, code: '4.1', name: '车辆购置费', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
-  { ID: 42, ParentID: 4, NextSiblingID: 43, code: '4.2', name: '建设期贷款利息', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
+  { ID: 42, ParentID: 4, NextSiblingID: 43, code: '4.2', name: '建设期贷款利息', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.LOAN_INTEREST }], type: billType.BILL },
   { ID: 43, ParentID: 4, NextSiblingID: -1, code: '4.3', name: '铺底流动资金', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: null }], type: billType.BILL },
-  { ID: 5, ParentID: -1, NextSiblingID: -1, code: '5', name: '建设项目总概算', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_BUDGET }], type: billType.BILL },
+  { ID: 5, ParentID: -1, NextSiblingID: -1, code: '5', name: '建设项目总概算', unit: '', calcBase: '', quantity: '', flags: [{ fieldName: 'fixed', flag: fixedFlag.CONSTRUCTION_BUDGET }], type: billType.DXFY },
 ];
 
 module.exports = {

+ 9 - 5
public/common_constants.js

@@ -93,16 +93,20 @@
         CONSTRUCTION_EQUIPMENT_FEE: 1002,
         // 工程建设其他费用
         CONSTRUCTION_OTHER_FEE: 1003,
+        // 设计费
+        DESIGN_FEE: 1004,
         // 预备费
-        BUDGET_RESERVE: 1004,
+        BUDGET_RESERVE: 1005,
         // 基本预备费
-        BASIC_BUDGET_RESERVE: 1005,
+        BASIC_BUDGET_RESERVE: 1006,
         // 价差预备费
-        DIFF_BUDGET_RESERVE: 1006,
+        DIFF_BUDGET_RESERVE: 1007,
         // 专项费用
-        CONSTRUCTION_SPECIAL_FEE: 1007,
+        CONSTRUCTION_SPECIAL_FEE: 1008,
+        // 建设期贷款利息
+        LOAN_INTEREST: 1009,
         // 建设项目总概算
-        CONSTRUCTION_BUDGET: 1008,
+        CONSTRUCTION_BUDGET: 1010,
 
     };
     // 清单类型

+ 19 - 7
public/common_util.js

@@ -70,7 +70,7 @@ function deleteEmptyObject(arr) {
     // 将树数据排序好
     function getSortedTreeData(rootID, items) {
         return sortSameDedth(rootID, items).reverse();
-        
+
         function sortSameDedth(parentID, items) {
             const sameDepthItems = items.filter(item => item.ParentID === parentID);
             if (!sameDepthItems.length) {
@@ -88,7 +88,7 @@ function deleteEmptyObject(arr) {
             return sorted;
         }
     }
-    
+
     // 控制全屏(浏览器有限制)
     // Element.requestFullscreen的全屏和“F11”的全屏是不一样的。前者是将相关Element变成全屏显示。后者是将浏览器导航、标签等隐藏。
     // Fullscreen API对于全屏的判断和监听都是基于Element.requestFullscreen的,比如Document.fullscreenElement。通过F11触发的全屏Document.fullscreenElement返回null,无法正确返回全屏状态。
@@ -120,17 +120,28 @@ function deleteEmptyObject(arr) {
         }
     }
     function exitFullscreen() {
-        if(document.exitFullscreen) {
+        if (document.exitFullscreen) {
             return document.exitFullscreen();
-        } else if(document.mozCancelFullscreen) {
+        } else if (document.mozCancelFullscreen) {
             return document.mozCancelFullscreen();
-        } else if(document.webkitExitFullscreen) {
+        } else if (document.webkitExitFullscreen) {
             return document.webkitExitFullscreen();
-        } else if(document.msExitFullscreen) {
+        } else if (document.msExitFullscreen) {
             return document.msExitFullscreen();
         }
     }
 
+    // 判断单位工程的工程专业的金额所属是“建筑工程费”还是“安装工程费”
+    const getEngineeringFeeType = (engineeringName) => {
+        if (['土建工程', '装饰工程', '市政工程', '城市轨道交通工程', '装配式建筑工程', '城市地下综合管廊工程'].includes(engineeringName)) {
+            return 'building';
+        }
+        if (['安装工程']) {
+            return 'installation'
+        }
+        return null;
+    };
+
     return {
         isDef,
         isEmptyVal,
@@ -139,6 +150,7 @@ function deleteEmptyObject(arr) {
         getRequired,
         getSortedTreeData,
         isNotEmptyObject,
-        handleFullscreen
+        handleFullscreen,
+        getEngineeringFeeType
     };
 });

+ 3 - 0
public/web/id_tree.js

@@ -276,6 +276,9 @@ var idTree = {
                 if (this.preSibling) {
                     tools.addUpdateDataForNextSibling(data, this.preSibling, this.tree.setting.rootId);
                 }
+                if (this.lastChild() && this.nextSibling) {
+                    tools.addUpdateDataForNextSibling(data, this.lastChild(), this.getNextSiblingID());
+                }
                 tools.addUpdateDataForNextSibling(data, this.parent, this.getID());
                 data.push({type: 'update', data: this.tree.getDataTemplate(this.getID(), this.parent.getParentID(), this.parent.getNextSiblingID())});
             }

+ 7 - 5
public/web/tree_sheet/tree_sheet_controller.js

@@ -3,7 +3,7 @@
  */
 
 var TREE_SHEET_CONTROLLER = {
-    createNew: function (tree, sheet, setting, loadSheetHeader = true) {
+    createNew: function (tree, sheet, setting, loadSheetHeader = true, customEvent = false) {
         var controller = function () {
             this.tree = tree;
             this.sheet = sheet;
@@ -21,10 +21,12 @@ var TREE_SHEET_CONTROLLER = {
             //
             var that = this;
             TREE_SHEET_HELPER.showTreeData(this.setting, this.sheet, this.tree);
-            this.sheet.unbind(GC.Spread.Sheets.Events.SelectionChanged);
-            this.sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
-                that.setTreeSelected(that.tree.items[info.newSelections[0].row]);
-            });
+            if (!customEvent) {
+                this.sheet.unbind(GC.Spread.Sheets.Events.SelectionChanged);
+                this.sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+                    that.setTreeSelected(that.tree.items[info.newSelections[0].row]);
+                });
+            }
         };
 
         controller.prototype.insert = function () {

+ 13 - 0
web/building_saas/budget-summary/html/budget-summary.html

@@ -1,3 +1,16 @@
 <div class="budget-summary">
+  <div class="toolsbar px-1 d-flex justify-content-between">
+    <div class="tools-btn btn-group align-top">
+      <a href="javascript:void(0)" class="btn btn-light btn-sm" id="budget-upLevel" data-toggle="tooltip"
+        data-placement="bottom" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+      <a href="javascript:void(0)" class="btn btn-light btn-sm" id="budget-downLevel" data-toggle="tooltip"
+        data-placement="bottom" data-original-title="降级"><i class="fa fa-arrow-right"
+          aria-hidden="true"></i></a>
+      <a href="javascript:void(0)" class="btn btn-light btn-sm" id="budget-upMove" data-toggle="tooltip"
+        data-placement="bottom" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+      <a href="javascript:void(0)" class="btn btn-light btn-sm" id="budget-downMove" data-toggle="tooltip"
+        data-placement="bottom" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+    </div>
+  </div>
   <div class="sheet-wrapper" id="budget-summary-sheet"></div>
 </div>

+ 239 - 63
web/building_saas/budget-summary/js/budgetSummarySheet.js

@@ -1,7 +1,7 @@
 /* 建设其他费表格相关 */
 const budgetSummaryObj = (() => {
 
-  const { isDef, isNumber } = window.commonUtil;
+  const { isEmptyVal, isDef, isNumber } = window.commonUtil;
   const { fixedFlag, BudgetArea } = window.commonConstants;
 
   // 原始数据
@@ -117,11 +117,74 @@ const budgetSummaryObj = (() => {
       $.bootstrapLoading.end();
     }
   }
+  // 是否是属于工程费用区域的节点
+  const isConstructionFeeArea = (node) => {
+    return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE;
+  }
+  // 工具栏可操作性
+  let upLevelDisabled = false;
+  let downLevelDisabled = false;
+  let upMoveDisabled = false;
+  let downMoveDisabled = false;
+  const refreshToolsBar = (node) => {
+    upLevelDisabled = !node || !node.canUpLevel() || isConstructionFeeArea(node) || (node.nextSibling && node.data.calcBase);
+    downLevelDisabled = !node || !node.canDownLevel() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling) || (node.preSibling && node.preSibling.data.calcBase);
+    upMoveDisabled = !node || !node.canUpMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling);
+    downMoveDisabled = !node || !node.canDownMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.nextSibling);
+    if (upLevelDisabled) {
+      $('#budget-upLevel').addClass('disabled');
+    } else {
+      $('#budget-upLevel').removeClass('disabled');
+    }
+    if (downLevelDisabled) {
+      $('#budget-downLevel').addClass('disabled');
+    } else {
+      $('#budget-downLevel').removeClass('disabled');
+    }
+    if (upMoveDisabled) {
+      $('#budget-upMove').addClass('disabled');
+    } else {
+      $('#budget-upMove').removeClass('disabled');
+    }
+    if (downMoveDisabled) {
+      $('#budget-downMove').addClass('disabled');
+    } else {
+      $('#budget-downMove').removeClass('disabled');
+    }
+  };
+  // 表格选中相关
+  const selectCell = (row, col, setCell = false) => {
+    const node = tree.items[row];
+    refreshToolsBar(node);
+    tree.selected = node;
+    console.log(node);
+    const sheet = spread.getSheet(0);
+    if (setCell) {
+      sheet.setActiveCell(row, col);
+    }
+    //设置选中行底色和恢复前选中行底色
+    const refreshNodes = [node];
+    if (!tree.preSelected) {
+      refreshNodes.push(tree.items[0]);
+    } else {
+      refreshNodes.push(tree.preSelected);
+    }
+    tree.preSelected = node;
+    projectObj.setNodesStyle(sheet, refreshNodes, tree);
+  };
+  // 事件列表
   const events = {
     EnterCell(sender, args) {
       args.sheet.repaint();
     },
+    SelectionChanged(sender, args) {
+      const newSel = args.newSelections[0] ? { row: args.newSelections[0].row, col: args.newSelections[0].col } : { row: 0, col: 0 };
+      selectCell(newSel.row, newSel.col);
+    },
     ValueChanged(sender, args) {
+      if (isEmptyVal(args.oldValue) && isEmptyVal(args.newValue)) {
+        return;
+      }
       edit(args.sheet, [{ row: args.row, col: args.col }]);
     },
     RangeChanged(sender, args) {
@@ -130,19 +193,61 @@ const budgetSummaryObj = (() => {
   }
   const bindEvents = (sheet) => {
     Object.entries(events).forEach(([ev, evFunc]) => {
-      sheet.bind(GC.Spread.Sheets.Events[ev], evFunc)
-    })
+      sheet.bind(GC.Spread.Sheets.Events[ev], evFunc);
+    });
   }
 
   /* 只读相关 */
-  const lockData = (sheet, nodes) => {
-    TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
+  // 单元格锁定判断
+  const lockFactory = {
+    code(node) {
+      return !!(node && node.getFlag());
+    },
+    name(node) {
+      return !!(node && node.getFlag());
+    },
+    calcBase(node) {
+      return !!(node && node.children.length);
+    },
+    feeRate(node) {
+      return !!(node && node.children.length);
+    }
+  };
+  const lockData = (sheet, nodes, isMass = true) => {
+    const lock = () => {
+      if (projectReadOnly) {
+        sheet.getRange(0, 0, nodes.length, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
+        return;
+      }
       // 工程费用区域,只读
       const equipmentNode = nodes.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_EQUIPMENT_FEE);
-      if (equipmentNode) {
-        sheet.getRange(0, 0, equipmentNode.serialNo() + 1, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
+      if (!equipmentNode) {
+        return;
       }
-    });
+      sheet.getRange(0, 0, equipmentNode.serialNo() + 1, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true);
+      // 剩下的区域,根据单元格锁定方法,按字段进行锁定判断
+      const startIndex = equipmentNode.serialNo() + 1;
+      for (let row = startIndex; row < nodes.length; row ++) {
+        const node = nodes[row];
+        budgetSummaryTreeSetting.cols.forEach((item, col) => {
+          const field = item.data.field;
+          const lockFunc = lockFactory[field];
+          if (!lockFunc) {
+            return;
+          }
+          const isLocked = lockFunc(node);
+          sheet.getCell(row, col).locked(isLocked);
+        });
+      }
+
+    }
+    if (isMass) {
+      TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
+        lock();
+      });
+    } else {
+      lock();
+    }
   }
 
   /* 初始化表格 */
@@ -156,6 +261,7 @@ const budgetSummaryObj = (() => {
       bindEvents(sheet);
       const headers = sheetCommonObj.getHeadersFromTreeSetting(budgetSummaryTreeSetting);
       sheetCommonObj.setHeader(sheet, headers);
+      // 右键菜单
       initContextMenu();
     } else {
       spread.refresh();
@@ -166,7 +272,7 @@ const budgetSummaryObj = (() => {
   // 初始化树
   const initTree = (data, sheet, setting) => {
     tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
-    const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false);
+    const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false, true);
     tree.loadDatas(data);
     tree.items.forEach(node => {
       node.source = node;
@@ -175,7 +281,14 @@ const budgetSummaryObj = (() => {
     controller.showTreeData();
     sheet.setRowCount(data.length);
     setUnitCombo(sheet, data);
-    lockData(sheet, tree.items);
+    TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
+      lockData(sheet, tree.items, false);
+      // 表格格式化
+      budgetSummaryTreeSetting.cols.forEach((item, index) => {
+        sheet.setFormatter(-1, index, item.formatter || '@', GC.Spread.Sheets.SheetArea.viewport);
+      });
+    });
+    selectCell(sheet.getActiveRowIndex(), sheet.getActiveColumnIndex());
   }
 
   /* 右键菜单 */
@@ -200,28 +313,42 @@ const budgetSummaryObj = (() => {
     // 重新初始化树
     initTree(rawData, sheet, budgetSummaryTreeSetting);
   }
+  let loading = false;
   // 插入
   const insert = async (sheet, selected) => {
     try {
+      if (loading) {
+        return;
+      }
+      loading = true;
       $.bootstrapLoading.start();
       const updateData = tree.getInsertData(selected.data.ParentID, selected.data.NextSiblingID, uuid.v1());
       const newData = updateData.filter(item => item.type === 'new');
       newData.forEach(item => {
+        item.data.fees = [];
+        item.data.flags = [];
+        item.feesIndex = {};
+        item.flagsIndex = {};
         item.data.type = selected.data.type;
         item.data.projectID = projectObj.project.property.rootProjectID;
       });
       await bulkOperation(updateData);
       updateTree(sheet, updateData);
-      sheet.setActiveCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex())
+      selectCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex(), true);
     } catch (err) {
       alert(err);
     } finally {
       $.bootstrapLoading.end();
+      loading = false;
     }
   }
   // 删除
   const remove = async (sheet, selected) => {
     try {
+      if (loading) {
+        return;
+      }
+      loading = true;
       $.bootstrapLoading.start();
       const updateData = tree.getDeleteData(selected);
       await bulkOperation(updateData);
@@ -230,11 +357,21 @@ const budgetSummaryObj = (() => {
       alert(err);
     } finally {
       $.bootstrapLoading.end();
+      loading = false;
     }
   }
   // 升级
-  const upLevel = async (sheet, selected) => {
+  const upLevel = async (selected) => {
+    if (!spread || !tree) {
+      return;
+    }
+    const sheet = spread.getSheet(0);
+    selected = selected ? selected : tree.selected;
     try {
+      if (loading || upLevelDisabled) {
+        return;
+      }
+      loading = true;
       $.bootstrapLoading.start();
       const updateData = selected.getUpLevelData();
       await bulkOperation(updateData);
@@ -243,11 +380,21 @@ const budgetSummaryObj = (() => {
       alert(err);
     } finally {
       $.bootstrapLoading.end();
+      loading = false;
     }
   }
   // 降级
-  const downLevel = async (sheet, selected) => {
+  const downLevel = async (selected) => {
+    if (!spread || !tree) {
+      return;
+    }
+    const sheet = spread.getSheet(0);
+    selected = selected ? selected : tree.selected;
     try {
+      if (loading || downLevelDisabled) {
+        return;
+      }
+      loading = true;
       $.bootstrapLoading.start();
       const updateData = selected.getDownLevelData();
       await bulkOperation(updateData);
@@ -256,47 +403,64 @@ const budgetSummaryObj = (() => {
       alert(err);
     } finally {
       $.bootstrapLoading.end();
+      loading = false;
     }
   }
   // 上移
-  const upMove = async (sheet, selected) => {
+  const upMove = async (selected) => {
+    if (!spread || !tree) {
+      return;
+    }
+    const sheet = spread.getSheet(0);
+    selected = selected ? selected : tree.selected;
     try {
+      if (loading || upMoveDisabled) {
+        return;
+      }
+      loading = true;
       $.bootstrapLoading.start();
       const updateData = selected.getUpMoveData();
       await bulkOperation(updateData);
       updateTree(sheet, updateData);
       const prev = selected.preSibling;
       const row = sheet.getActiveRowIndex() - prev.posterityCount() - 1;
-      sheet.setActiveCell(row, sheet.getActiveColumnIndex());
+      selectCell(row, sheet.getActiveColumnIndex(), true);
       sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
     } catch (err) {
       alert(err);
     } finally {
       $.bootstrapLoading.end();
+      loading = false;
     }
   }
   // 下移
-  const downMove = async (sheet, selected) => {
+  const downMove = async (selected) => {
+    if (!spread || !tree) {
+      return;
+    }
+    const sheet = spread.getSheet(0);
+    selected = selected ? selected : tree.selected;
     try {
+      if (loading || downMoveDisabled) {
+        return;
+      }
+      loading = true;
       $.bootstrapLoading.start();
       const updateData = selected.getDownMoveData();
       await bulkOperation(updateData);
       updateTree(sheet, updateData);
       const next = selected.nextSibling;
       const row = sheet.getActiveRowIndex() + next.posterityCount() + 1;
-      sheet.setActiveCell(row, sheet.getActiveColumnIndex())
+      selectCell(row, sheet.getActiveColumnIndex(), true);
       sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center);
     } catch (err) {
       alert(err);
     } finally {
       $.bootstrapLoading.end();
+      loading = false;
     }
 
   }
-  // 是否是属于工程费用区域的节点
-  const isConstructionFeeArea = (node) => {
-    return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE;
-  }
   // 初始化右键菜单
   const initContextMenu = () => {
     if (!spread) {
@@ -311,7 +475,7 @@ const budgetSummaryObj = (() => {
         const target = SheetDataHelper.safeRightClickSelection($trigger, e, spread);
         curRow = target.row;
         curNode = tree && tree.items[curRow] || null;
-        sheet.setActiveCell(target.row, target.col);
+        selectCell(target.row, target.col, true);
         return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
       },
       items: {
@@ -329,52 +493,52 @@ const budgetSummaryObj = (() => {
           name: '删除行',
           icon: 'fa-remove',
           disabled() {
-            return !curNode || isConstructionFeeArea(curNode);
+            return !curNode || isConstructionFeeArea(curNode) || curNode.getFlag();
           },
           callback() {
             remove(sheet, curNode);
           }
         },
-        upLevel: {
-          name: '升级',
-          icon: 'fa-arrow-left',
-          disabled() {
-            return !curNode || !curNode.canUpLevel() || isConstructionFeeArea(curNode);
-          },
-          callback() {
-            upLevel(sheet, curNode);
-          }
-        },
-        downLevel: {
-          name: '降级',
-          icon: 'fa-arrow-right',
-          disabled() {
-            return !curNode || !curNode.canDownLevel() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
-          },
-          callback() {
-            downLevel(sheet, curNode);
-          }
-        },
-        upMove: {
-          name: '上移',
-          icon: 'fa-arrow-up',
-          disabled() {
-            return !curNode || !curNode.canUpMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
-          },
-          callback() {
-            upMove(sheet, curNode);
-          }
-        },
-        downMove: {
-          name: '下移',
-          icon: 'fa-arrow-down',
-          disabled() {
-            return !curNode || !curNode.canDownMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.nextSibling);
-          },
-          callback() {
-            downMove(sheet, curNode);
-          }
-        },
+        /*       upLevel: {
+                name: '升级',
+                icon: 'fa-arrow-left',
+                disabled() {
+                  return !curNode || !curNode.canUpLevel() || isConstructionFeeArea(curNode);
+                },
+                callback() {
+                  upLevel(sheet, curNode);
+                }
+              },
+              downLevel: {
+                name: '降级',
+                icon: 'fa-arrow-right',
+                disabled() {
+                  return !curNode || !curNode.canDownLevel() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
+                },
+                callback() {
+                  downLevel(sheet, curNode);
+                }
+              },
+              upMove: {
+                name: '上移',
+                icon: 'fa-arrow-up',
+                disabled() {
+                  return !curNode || !curNode.canUpMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling);
+                },
+                callback() {
+                  upMove(sheet, curNode);
+                }
+              },
+              downMove: {
+                name: '下移',
+                icon: 'fa-arrow-down',
+                disabled() {
+                  return !curNode || !curNode.canDownMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.nextSibling);
+                },
+                callback() {
+                  downMove(sheet, curNode);
+                }
+              }, */
         refresh: {
           name: '刷新数据',
           icon: 'fa-refresh',
@@ -420,6 +584,18 @@ const budgetSummaryObj = (() => {
       init(projectObj.project.property.rootProjectID);
     }
   });
+  $('#budget-upLevel').click(() => {
+    upLevel();
+  });
+  $('#budget-downLevel').click(() => {
+    downLevel();
+  });
+  $('#budget-upMove').click(() => {
+    upMove();
+  });
+  $('#budget-downMove').click(() => {
+    downMove();
+  });
 
   // 对外暴露
   return {

+ 1 - 1
web/building_saas/css/custom.css

@@ -531,7 +531,7 @@ margin-right: 100px !important;
 
 /* 概算汇总 */
 .budget-summary {
-  height: calc(100vh - 40px);
+  height: calc(100vh - 71px);
 }
 .budget-summary .sheet-wrapper {
   height: 100%;

+ 1 - 1
web/building_saas/js/global.js

@@ -24,7 +24,7 @@ function autoFlashHeight(){
     $(".main-data-full-fl").height($(window).height()-headerHeight-toolsbarHeight-37);
     $(".main-data-full-feeRate").height($(window).height()-headerHeight-78);
     $(".main-data-full-tender").height($(window).height()-headerHeight-btntoolsbarHeight-10-30);
-    $(".main-data-full-equipment").height($(window).height()-headerHeight);
+    $(".main-data-full-equipment").height($(window).height()-headerHeight-30);
     $(".main-data-not").height($(window).height()-headerHeight-1);
     $(".main-data-side-search").height($(window).height()-headerHeight-toolsbarHeight-64);
     $(".side-content").height($(window).height()-headerHeight );

+ 12 - 0
web/building_saas/main/html/equipment_purchase.html

@@ -1,6 +1,18 @@
 
 <div class="container-fluid">
     <div class="row">
+        <div class="toolsbar px-1 d-flex " style="width: 100%;height: 30px;">
+          <a href="javascript:void(0)" class="btn btn-light btn-sm" id="equipment_upLevel" data-toggle="tooltip"
+            data-placement="bottom" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+          <a href="javascript:void(0)" class="btn btn-light btn-sm" id="equipment_downLevel" data-toggle="tooltip"
+            data-placement="bottom" data-original-title="降级"><i class="fa fa-arrow-right"
+              aria-hidden="true"></i></a>
+          <a href="javascript:void(0)" class="btn btn-light btn-sm" id="equipment_upMove" data-toggle="tooltip"
+            data-placement="bottom" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+          <a href="javascript:void(0)" class="btn btn-light btn-sm" id="equipment_downMove" data-toggle="tooltip"
+            data-placement="bottom" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+          <label class="mx-2 form-inline py-1">设备购置费合计: <label id='equipment_total'></label></label>
+        </div>
         <div class="col-lg-12 p-0">
             <div class="main-data-full-equipment" id="equipmentSpread">
             </div>

+ 0 - 1
web/building_saas/main/js/models/calc_program.js

@@ -2209,7 +2209,6 @@ class CalcProgram {
 
         calcNodes(tree.roots);
         me.calcFormulaNodes(changedNodes, tender);
-        debugger;
         if (tender){
             for(let node of tree.items){
                 this.clearTenderCache(node);

+ 1 - 1
web/building_saas/main/js/views/calc_base_view.js

@@ -179,7 +179,7 @@ let calcBaseView = {
         me.curType = type;
         if (type === me.type.bills) {
             //锁定的清单不显示
-            if( MainTreeCol.lockBillChecking(projectObj.project.mainTree.selected,field)){
+            if(field && MainTreeCol.lockBillChecking(projectObj.project.mainTree.selected,field)){
                 return;
             }
             //显示清单基数分类

+ 285 - 47
web/building_saas/main/js/views/equipment_purchase_view.js

@@ -2,22 +2,24 @@
 let unitOptions = ['m', 'm2', 'm3', 'km', 't', 'kg', '台班', '工日', '昼夜', '元', '项', '处', '个', '件',
 '根', '组', '系统', '台', '套', '株', '丛', '缸', '支', '只', '块', '座', '对', '份', '樘', '攒', '榀']
 let equipmentPurchaseObj  = {
+    IDMap:{},
+    parentMap:{},
     setting:{
         header: [
-            {headerName: "编号", headerWidth: 160, dataCode: "code", dataType: "String",formatter: "@"},
-            {headerName: "设备名称", headerWidth: 200, dataCode: "name", dataType: "String"},
-            {headerName: "设备价格-设备原价", headerWidth: 160, dataCode: "originalPrice", hAlign: "right", dataType: "Number",validator:'number'},
-            {headerName: "设备运杂费", headerWidth: 160, dataCode: "freight", hAlign: "right", dataType: "Number",validator:'number'},
-            {headerName: "备品备件费", headerWidth: 160, dataCode: "sparePartCost", hAlign: "right", dataType: "Number",validator:'number'},
-            {headerName: "单价", headerWidth: 160, dataCode: "unitPrice", hAlign: "right", dataType: "Number",validator:'number'},
-            {headerName: "单位", headerWidth: 60, dataCode: "unit",  dataType: "String",hAlign: "center",cellType:'comboBox',editable:true,options:unitOptions},
-            {headerName: "数量", headerWidth: 160, dataCode: "quantity", hAlign: "right", dataType: "Number",validator:'number'},
-            {headerName: "金额", headerWidth: 160, dataCode: "totalPrice", hAlign: "right", dataType: "Number"},
+            {headerName: "编号", headerWidth: 160, dataCode: "code", dataType: "String",formatter: "@",spanRows: [2]},
+            {headerName: "设备名称", headerWidth: 200, dataCode: "name", dataType: "String",spanRows: [2]},
+            {headerName: "单位", headerWidth: 60, dataCode: "unit",  dataType: "String",hAlign: "center",cellType:'comboBox',editable:true,options:unitOptions,spanRows: [2]},
+            {headerName: ["设备价格","设备原价"], headerWidth: 160, dataCode: "originalPrice", hAlign: "right", dataType: "Number",validator:'number',spanCols: [4,1]},
+            {headerName: ["","设备运杂费"], headerWidth: 160, dataCode: "freight", hAlign: "right", dataType: "Number",validator:'number',spanCols: [0,1]},
+            {headerName: ["","备品备件费"], headerWidth: 160, dataCode: "sparePartCost", hAlign: "right", dataType: "Number",validator:'number',spanCols: [0,1]},
+            {headerName: ["","单价"], headerWidth: 160, dataCode: "unitPrice", hAlign: "right", dataType: "Number",validator:'number',spanCols: [0,1]},
+            {headerName: "数量", headerWidth: 160, dataCode: "quantity", hAlign: "right", dataType: "Number",validator:'number',spanRows: [2]},
+            {headerName: "合价", headerWidth: 160, dataCode: "totalPrice", hAlign: "right", dataType: "Number",spanRows: [2]},
         ],
+        headRows:2,
         view: {
-            lockColumns: ["totalPrice"],
+            lockColumns: ["totalPrice",'unitPrice'],
             rowHeaderWidth:40,
-            colHeaderHeight:35
         }
     
     },
@@ -32,6 +34,7 @@ let equipmentPurchaseObj  = {
             this.sheet.bind(GC.Spread.Sheets.Events.RangeChanged, this.onSheetRangeChange);
             this.sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e,args) {
                 args.sheet.repaint();
+                equipmentPurchaseObj.checkBtn();
             });
             if (projectReadOnly) {
                 sheetCommonObj.disableSpread(this.spread);
@@ -40,14 +43,110 @@ let equipmentPurchaseObj  = {
               }
         }
     },
+    checkBtn:function(){
+        let me = equipmentPurchaseObj;
+        let selected = me.getSelected();
+        let preNode = me.getPreNode(selected);
+        let afterNode = me.getAfterNode(selected);
+        //工具栏按钮的有效性
+        me.validateBtn($('#equipment_upMove'),preNode);
+        me.validateBtn($('#equipment_downMove'),afterNode);
+        me.validateBtn($('#equipment_upLevel'),selected && selected.ParentID !=='-1');
+        me.validateBtn($('#equipment_downLevel'),preNode);
+       
+    },
+
+    validateBtn:function(btn,validate){
+        if(validate){
+            btn.removeClass('disabled');
+        }else{
+            btn.addClass('disabled');
+        }
+    },
+    getTreeData:function(){
+        let treeData = [];
+        let roots = this.parentMap['-1'];
+        let me = this;
+        getChildren(roots,treeData);
+        return treeData;
+        
+        function getChildren(nodes,data){
+           if(nodes){
+               for(let n of nodes){
+                data.push(n);
+                getChildren(me.parentMap[n.ID],data)
+               }
+           } 
+        }
+    },
+    getSelected:function(){
+        let sel = this.sheet.getSelections()[0];
+        let row = sel.row == -1 || sel.row == "" ? 0 : sel.row;
+        if(this.data && this.data.length>0){
+            return this.data[row];
+        }
+        return null;
+    },
+    getParentNode:function(node){
+       if(node.ParentID === '-1') return null; 
+       return this.IDMap[node.ParentID]; 
+    },
+    //取前兄弟节点
+    getPreNode:function(node){
+        if(node){
+            let nodes = this.parentMap[node.ParentID];
+            let index = nodes.indexOf(node);
+            return nodes[index-1]
+        }
+       return null;
+    },
+    //取后兄弟节点
+    getAfterNode:function(node){
+        if(node){
+            let nodes = this.parentMap[node.ParentID];
+            let index = nodes.indexOf(node);
+            return nodes[index+1]
+        }
+       return null;
+    },
+    //取所有后兄弟节点
+    getAllAfterNodes:function(node){
+       let brothers = this.parentMap[node.ParentID];
+       let index = brothers.indexOf(node);
+       if(brothers.length >= index + 2){
+        return brothers.slice(index+1);
+       }
+       return [];
+
+    },
+    //插入为最后一个子节点的序号
+    getLastChildrenSeq:function(node){
+        let seq = 1;
+        let children = this.parentMap[node.ID];
+        if(children && children.length > 0){
+            return children[children.length-1].seq +1
+        }
+        return seq;
+    },
     showData:function(){
         let equipment_purchase = projectObj.project.equipment_purchase;
-        this.data = equipment_purchase.datas.equipments;
-        _.sortBy(this.data,['code']);
-        sheetCommonObj.showData(this.sheet, this.setting,this.data);
-        this.sheet.setRowCount(this.data.length);
-    },
+        this.sourceData = equipment_purchase.datas;
+        let data = this.sourceData.equipments;
+        this.IDMap = {};
+        this.parentMap = {};
+        data = _.sortBy(data,'seq');
+        for(let d of data){
+            let node = ({...d,collapsed:false})
+            this.IDMap[node.ID] = node;
+            this.parentMap[node.ParentID] ?this.parentMap[node.ParentID].push(node):this.parentMap[node.ParentID]=[node];
+        }
+        let treeData = this.getTreeData();
 
+        this.data = treeData;
+        sheetCommonObj.showTreeData(this.sheet, this.setting,treeData);
+        this.checkBtn();
+        $('#equipment_total').text(this.sourceData.total);
+    },
     onValueChange:function (e,info) {
         let me = equipmentPurchaseObj,row = info.row, col = info.col;
         let dataCode = me.setting.header[col].dataCode;
@@ -59,31 +158,65 @@ let equipmentPurchaseObj  = {
             return me.showData();
         }
         let data = {doc:{},ID:equipment.ID};
-        if(dataCode == 'quantity' || dataCode == 'unitPrice'){
-            me.calcTotalPrice(value,dataCode,data.doc,equipment);
+        if(dataCode == 'quantity' || dataCode == 'originalPrice'|| dataCode == 'freight'|| dataCode == 'sparePartCost'){
+            value = me.calcTotalPrice(value,dataCode,data.doc,equipment);
         }
         if(equipment[dataCode] == value) return me.showData();
         data.doc[dataCode] = value;
         me.updateEquipments([data]);
     },
-    calcTotalPrice:function(newValue,dataCode,doc,equipment){
-        let unitPrice = equipment.unitPrice?scMathUtil.roundForObj(equipment.unitPrice,getDecimal('glj.unitPrice')):0;
-        let quantity = equipment.quantity?scMathUtil.roundForObj(equipment.quantity,getDecimal('glj.quantity')):0;
-        let quantity = equipment.quantity?scMathUtil.roundForObj(equipment.quantity,getDecimal('glj.quantity')):0;
+    calcTotalPrice:function(newValue=0,dataCode,doc,equipment){   
+        //设备原价
+        let originalPrice = equipment.originalPrice?scMathUtil.roundForObj(equipment.originalPrice,getDecimal('glj.unitPrice')):0;
+        //设备运杂费
+        let freight = equipment.freight?scMathUtil.roundForObj(equipment.freight,getDecimal('glj.unitPrice')):0;
+        //备品备件费
+        let sparePartCost = equipment.sparePartCost?scMathUtil.roundForObj(equipment.sparePartCost,getDecimal('glj.quantity')):0;
         let quantity = equipment.quantity?scMathUtil.roundForObj(equipment.quantity,getDecimal('glj.quantity')):0;
-        if(newValue){
-            if(gljUtil.isDef(doc.unitPrice)) unitPrice = doc.unitPrice;
-            if(gljUtil.isDef(doc.quantity)) quantity = doc.quantity;
-            if(dataCode === 'quantity') {
-                newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.quantity'));
-                quantity = newValue;
-            }
-            if(dataCode === 'unitPrice') {
-                newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.unitPrice'));
-                unitPrice = newValue;
-            }
-            doc.totalPrice = scMathUtil.roundForObj(quantity * unitPrice,getDecimal('glj.unitPrice'));
+        
+        let unitPrice = 0;
+
+        if(gljUtil.isDef(doc.originalPrice)) originalPrice = doc.originalPrice;
+        if(gljUtil.isDef(doc.freight)) freight = doc.freight;
+        if(gljUtil.isDef(doc.sparePartCost)) sparePartCost = doc.sparePartCost;
+        if(gljUtil.isDef(doc.quantity)) quantity = doc.quantity;
+        if(dataCode === 'quantity') {
+            newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.quantity'));
+            quantity = newValue;
         }
+        if(dataCode === 'originalPrice') {
+            newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.unitPrice'));
+            originalPrice = newValue;
+        }
+        if(dataCode === 'freight') {
+            newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.unitPrice'));
+            freight = newValue;
+        }
+        if(dataCode === 'sparePartCost') {
+            newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.unitPrice'));
+            sparePartCost = newValue;
+        }
+        
+        unitPrice = scMathUtil.roundForObj(originalPrice + freight,getDecimal('glj.unitPrice'));
+        unitPrice = scMathUtil.roundForObj(unitPrice + sparePartCost,getDecimal('glj.unitPrice'));
+        doc.unitPrice = unitPrice;
+        doc.totalPrice = scMathUtil.roundForObj(quantity * unitPrice,getDecimal('glj.unitPrice'));
+        return newValue;
+    },
+    //计算序列号,返回要改变的兄弟节点数据
+    calcSeq:function(ParentID,seq){
+      let data = [];  
+      let nodes = this.parentMap[ParentID];
+      let temSeq = seq+1;
+      if(nodes && nodes.length > 0){
+        for(let n of nodes){
+           if(n.seq >= seq){
+             data.push({doc:{seq:temSeq},ID:n.ID}) 
+             temSeq+=1;
+           } 
+        }
+      }
+        return data;
     },
     onSheetRangeChange:function(e,args){
         let updateMap = {};
@@ -99,8 +232,8 @@ let equipmentPurchaseObj  = {
                 return ;
             }
             let tem = updateMap[equipment.ID]?updateMap[equipment.ID]:{};
-            if(dataCode == 'quantity' || dataCode == 'unitPrice'){
-                me.calcTotalPrice(value,dataCode,tem,equipment);
+            if(dataCode == 'quantity' || dataCode == 'originalPrice'|| dataCode == 'freight'|| dataCode == 'sparePartCost'){
+                value = me.calcTotalPrice(value,dataCode,tem,equipment);
             } 
             tem[dataCode] = value;
             updateMap[equipment.ID] = tem;
@@ -111,20 +244,47 @@ let equipmentPurchaseObj  = {
         }
         if(updateData.length > 0)  me.updateEquipments(updateData);
     },
-    newEquipment:function(){
-        return {ID:uuid.v1()}
+    newEquipment:function(ParentID='-1',seq=0){
+        return {ID:uuid.v1(),ParentID,seq}
+    },
+    sumTotal: function(updateData){
+        let dataMap = _.indexBy(updateData, 'ID');
+        let total = 0;
+        for(let d of  this.data){
+            let totalPrice = d.totalPrice?scMathUtil.roundForObj(d.totalPrice,getDecimal('glj.unitPrice')):0;
+            let data = dataMap[d.ID];
+            if(data && gljUtil.isDef(data.doc.totalPrice))totalPrice = data.doc.totalPrice;
+            total =scMathUtil.roundForObj(total + totalPrice,getDecimal('glj.unitPrice')) 
+        }
+        return total;
+    },
+    //上移
+    moveUp:async function(node){
+       let preNode = this.getPreNode(node); 
+       if(preNode){
+        let updateData = [];
+         updateData.push({doc:{seq:node.seq},ID:preNode.ID});
+         updateData.push({doc:{seq:preNode.seq},ID:node.ID});
+        await this.updateEquipments(updateData);
+       }
     },
     updateEquipments:async function(updateData){
         try {
             $.bootstrapLoading.start();
             let projectID = projectObj.project.ID();
-            await ajaxPost('/equipmentPurchase/updateEquipments', { projectID, updateData });
+            let total = this.sumTotal(updateData);
+            await ajaxPost('/equipmentPurchase/updateEquipments', { projectID, updateData,total });
             for(let data of updateData){
-                let equipment = _.find(this.data,{ID:data.ID});
-                if(equipment){
-                    Object.assign(equipment,data.doc);
+                if(data.type === 'insert'){
+                    this.sourceData.equipments.push(...data.documents);  
+                }else{
+                    let equipment = _.find(this.sourceData.equipments,{ID:data.ID});
+                    if(equipment){
+                        Object.assign(equipment,data.doc);
+                    }
                 }
             }
+            projectObj.project.equipment_purchase.datas.total = total;
         } catch (error) {
             alert('更新失败,请重试');
         }
@@ -136,7 +296,7 @@ let equipmentPurchaseObj  = {
             $.bootstrapLoading.start();
             let projectID = projectObj.project.ID();
             await ajaxPost('/equipmentPurchase/insertData', { projectID, equipments });
-            this.data.push(...equipments)  
+            this.sourceData.equipments.push(...equipments)  
             this.showData();
         } catch (error) {
             alert('插入失败,请重试');
@@ -147,7 +307,7 @@ let equipmentPurchaseObj  = {
         try {
             let projectID = projectObj.project.ID();
             await ajaxPost('/equipmentPurchase/deleteEquipment', { projectID, ID });
-            _.remove(this.data,{ID});
+            _.remove( this.sourceData.equipments,{ID});
             this.showData();
         } catch (error) {
             alert('删除失败,请重试');
@@ -162,10 +322,27 @@ let equipmentPurchaseObj  = {
                 return;
             }  
             const newData = [];
+            let row = me.rightClickTarget.row;
+            let seq = 0;
+            let ParentID = '-1';
+            let brotherNodes = [];
+            if(row == undefined){//没有选中节点的情况,添加到最后
+                brotherNodes = me.parentMap['-1'];
+                if(brotherNodes && brotherNodes.length > 0){
+                    seq = brotherNodes[brotherNodes.length -1].seq;   
+                } 
+            }else{
+                let node = me.data[row];//选中节点时插入为选中的下一行
+                seq = node.seq;
+                ParentID = node.ParentID;
+            }
             for (let i = 0; i < number; i++) {
-                newData.push(me.newEquipment());
+                seq+=1;
+                newData.push(me.newEquipment(ParentID,seq));
             }
-            me.insertEquipments(newData)   
+            let updateData = me.calcSeq(ParentID,seq);
+            updateData.push({documents:newData,type:'insert'});
+            me.updateEquipments(updateData)   
         });
     },
 
@@ -173,8 +350,9 @@ let equipmentPurchaseObj  = {
         let me = this;
         $.contextMenu({
           selector: '#equipmentSpread',
-          build: function ($trigger, e) {
+          build: function ($trigger, e) { 
             me.rightClickTarget = SheetDataHelper.safeRightClickSelection($trigger, e, me.spread);
+            me.checkBtn();
             return me.rightClickTarget.hitTestType === GC.Spread.Sheets.SheetArea.viewport ||
               me.rightClickTarget.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
           },
@@ -212,4 +390,64 @@ $(function () {
         equipmentPurchaseObj.initSpread();
         equipmentPurchaseObj.showData();
     })
+
+    //降级
+    $('#equipment_downLevel').click(function(){
+        let me = equipmentPurchaseObj;
+        let selected = me.getSelected();
+        if(selected){
+            let preNode = me.getPreNode(selected);
+            if(preNode){
+                let seq = me.getLastChildrenSeq(preNode);
+                me.updateEquipments([{doc:{ParentID:preNode.ID,seq},ID:selected.ID}])
+            }
+        }
+    })
+
+     //升级  - 后兄弟节点变成子节点
+     $('#equipment_upLevel').click(function(){
+        let me = equipmentPurchaseObj;
+        let selected = me.getSelected();
+        if(selected){
+            let parentNode = me.getParentNode(selected);
+            if(parentNode){
+                let seq = parentNode.seq+1;
+                let updateData =  me.calcSeq(parentNode.ParentID,seq);
+                updateData.push({doc:{ParentID:parentNode.ParentID,seq},ID:selected.ID})
+                let brothers = me.getAllAfterNodes(selected);
+                let subSeq = 0;
+                //后兄弟节点变成子节点
+                for(let b of brothers){
+                    subSeq+=1;
+                    updateData.push({doc:{ParentID:selected.ID,seq:subSeq},ID:b.ID})
+                }
+                me.updateEquipments(updateData)
+            }
+        }
+    })
+    
+    //上移
+    $('#equipment_upMove').click(async function (){
+        let me = equipmentPurchaseObj;
+        let selected = me.getSelected();
+        if(selected){
+           let sel = me.sheet.getSelections()[0];
+           await me.moveUp(selected);
+           me.sheet.setSelection(sel.row -1 , sel.col, sel.rowCount, sel.colCount);
+        }
+    })
+     //下移
+     $('#equipment_downMove').click(async function(){
+        let me = equipmentPurchaseObj;
+        let selected = me.getSelected();
+        if(selected){
+            let sel = me.sheet.getSelections()[0];
+           let node = me.getAfterNode(selected);
+           if(node){
+            await me.moveUp(node);
+            me.sheet.setSelection(sel.row +1 , sel.col, sel.rowCount, sel.colCount);
+           }
+           
+        }
+    })
 })

+ 15 - 2
web/building_saas/pm/html/project-management.html

@@ -384,7 +384,7 @@
                             </div>
                         </div>
                         <span class="form-text text-danger" id="valuation-info" style="display: none;">请选择计价规则</span>
-                        <div class="form-group row" id="tender-dialog-fileKind">
+                        <div class="form-group row" id="tender-dialog-fileKind" style="display: none;">
                             <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">文件类型</label>
                             <div class="col">
                                 <div class="custom-control custom-radio custom-control-inline">
@@ -404,12 +404,25 @@
                                     <input type="radio" value="1" checked id="radioCommon-tender" name="taxType-tender" class="custom-control-input">
                                     <label class="custom-control-label" for="radioCommon-tender">一般计税法</label>
                                 </div>
-                                <div class="custom-control custom-radio custom-control-inline">
+                                <div class="custom-control custom-radio custom-control-inline" style="display: none;">
                                     <input type="radio" value="2" id="radioSimple-tender" name="taxType-tender" class="custom-control-input">
                                     <label class="custom-control-label" for="radioSimple-tender">简易计税法</label>
                                 </div>
                             </div>
                         </div>
+                        <div class="form-group row">
+                            <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">项目类型</label>
+                            <div class="col">
+                                <div class="custom-control custom-radio custom-control-inline">
+                                    <input type="radio" value="1" checked id="building-tender" name="budgetType-tender" class="custom-control-input">
+                                    <label class="custom-control-label" for="building-tender">建筑安装工程</label>
+                                </div>
+                                <div class="custom-control custom-radio custom-control-inline">
+                                    <input type="radio" value="2" id="rail-tender" name="budgetType-tender" class="custom-control-input">
+                                    <label class="custom-control-label" for="rail-tender">城市轨道交通工程</label>
+                                </div>
+                            </div>
+                        </div>
                     </div>
                     <div class="form-group row">
                         <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">单项工程</label>

+ 9 - 1
web/building_saas/pm/js/pm_newMain.js

@@ -40,6 +40,7 @@ const FileKind = {
     bid: 2  //招标
 };
 let curTaxType = 1; //1:"一般计税",2:"简易计税"
+let curBudgetType = commonConstants.BudgetType.BUILDING;
 let curValutionType = 'bill',   //计价方式默认只有清单计价
     curValuation = billValuation[0].id, //计价规则初始取第一个
     curValuationName = billValuation[0].name; //计价规则初始取第一个
@@ -2066,6 +2067,9 @@ $(document).ready(function() {
         curTaxType = parseInt($(this).val());
         getStdCalcProgramFiles();
     });
+    $('input[name="budgetType-tender"]').click(function () {
+        curBudgetType = parseInt($(this).val());
+    });
     // 选择计价方式
     $("input[name='valuation_type']").click(function() {
         let type = $(this).val();
@@ -2127,7 +2131,9 @@ $(document).ready(function() {
                 $('#newProjectSet').show();
                 $('input[name="fileKind-tender"]:eq(0)').prop('checked', true);
                 $('input[name="taxType-tender"]:eq(0)').prop('checked', true);
+                $('input[name="budgetType-tender"]:eq(0)').prop('checked', true);
                 curTaxType = 1;
+                curBudgetType = commonConstants.BudgetType.BUILDING;
                 getStdCalcProgramFiles();
                 replaceClass($('#poj-name-info'), 'text-danger', 'text-info');
                 setDangerInfo($('#poj-name-info'), `新建“${pojName}”`);
@@ -2237,7 +2243,9 @@ $(document).ready(function() {
         $('#newProjectSet').hide(); //隐藏文件类型、计税方法选项
         $('input[name="fileKind-tender"]:eq(0)').prop('checked', true);
         $('input[name="taxType-tender"]:eq(0)').prop('checked', true);
+        $('input[name="budgetType-tender"]:eq(0)').prop('checked', true);
         curTaxType = 1;
+        curBudgetType = commonConstants.BudgetType.BUILDING;
         //获取建设项目
         let selected = projTreeObj.tree.selected;
         let projs = getProjs(selected);
@@ -3258,7 +3266,7 @@ function AddTenderItems(selected, projName, engName, tenderName, property, callb
             let fileKind = $('input[name="fileKind-tender"]:checked').val();
             let updateProjs = GetUpdateData(pre, parent, next, projName, {
                 basicInformation: needfulInfoData ? needfulInfoData : [],
-                fileKind: parseInt(fileKind), taxType: curTaxType, valuationType: curValutionType, valuation: curValuation, valuationName: curValuationName},
+                fileKind: parseInt(fileKind), taxType: curTaxType, budgetType: curBudgetType, valuationType: curValutionType, valuation: curValuation, valuationName: curValuationName},
                 projID, {updateType: 'new', projectType: projectType.project});
             let updateEng = {updateType: 'new', updateData: {ID: engID, ParentID: projID, NextSiblingID: -1, name: engName, projType: projectType.engineering}};
             property.rootProjectID = projID;