Forráskód Böngészése

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

vian 4 éve
szülő
commit
5a1f9ae336

+ 2 - 0
config/gulpConfig.js

@@ -182,6 +182,8 @@ module.exports = {
         'web/building_saas/main/js/views/zmhs_view.js',
         'web/building_saas/main/js/views/mbzm_view.js',
         'web/building_saas/main/js/views/tender_price_view.js',
+        'web/building_saas/main/js/views/equipment_purchase_view.js',
+        'web/building_saas/main/js/views/gather_fees_view.js',
         'web/building_saas/main/js/views/billsElf.js',
         'web/building_saas/main/js/views/sub_view.js',
         'web/building_saas/main/js/views/fee_rate_view.js',

+ 26 - 0
modules/all_models/equipment_purchase.js

@@ -0,0 +1,26 @@
+
+let mongoose = require('mongoose'),
+    Schema = mongoose.Schema;
+
+
+let equipment = {
+    ID:String,
+    code:String,
+    name:String,
+    unit:String,
+    quantity:Number,
+    unitPrice:Number,//单价
+    totalPrice:Number,//金额
+    remark:String
+}
+
+
+let equipment_purchase = new Schema({
+    projectID:{
+        type: Number,
+        index: true
+    },
+    total:Number,//合计
+    equipments :[equipment]
+    },{versionKey:false})
+mongoose.model('equipment_purchase', equipment_purchase,'equipment_purchase');

+ 37 - 0
modules/equipment_purchase/controllers/equipment_purchase_controller.js

@@ -0,0 +1,37 @@
+let equipment_purchase_facade = require('../facade/equipment_purchase_facade');
+let logger = require("../../../logs/log_helper").logger;
+let controller = {
+    insertData:async function(req){
+        let data = req.body.data;
+        data = JSON.parse(data);
+        return await equipment_purchase_facade.insertData(data.projectID,data.equipments);
+    },
+    deleteEquipment:async function(req){
+        let data = req.body.data;
+        data = JSON.parse(data);
+        return await equipment_purchase_facade.deleteEquipment(data.projectID,data.ID); 
+    },
+    updateEquipments:async function(req){
+        let data = req.body.data;
+        data = JSON.parse(data);
+        return await equipment_purchase_facade.updateEquipments(data.projectID,data.updateData); 
+    }
+}
+
+
+module.exports = {   
+    action:async function(req,res){//自动跳转到URL对应的controller方法
+        let result={
+            error:0
+        }
+        try {
+            let functionName = req.url.replace(/\//g,"");
+            result.data = controller[functionName]?await controller[functionName](req):"";
+        }catch (err){
+            logger.err(err.stack);
+            result.error=1;
+            result.message = err.message;
+        }
+        res.json(result);
+    }
+};

+ 51 - 0
modules/equipment_purchase/facade/equipment_purchase_facade.js

@@ -0,0 +1,51 @@
+
+module.exports={
+    getData:getData,
+    insertData:insertData,
+    deleteEquipment:deleteEquipment,
+    updateEquipments:updateEquipments
+};
+
+let mongoose = require('mongoose');
+let equipmentPurchaseModel = mongoose.model("equipment_purchase");
+let consts = require('../../main/models/project_consts');
+
+function getPreUpdate (pre, doc){
+    const update = {};
+    for (const key in doc) {
+      update[`${pre}.${key}`] = doc[key];
+    }
+    return update;
+  };
+
+async function deleteEquipment(projectID,ID){
+    await equipmentPurchaseModel.update({projectID},{'$pull':{equipments:{ ID }}});
+}
+
+async function insertData (projectID,data){
+    await equipmentPurchaseModel.update({projectID},{'$push':{equipments:{ $each: data }}});
+    return data
+}
+
+async function updateEquipments (projectID,updateData){
+    let tasks = [];
+    for(let data of updateData){
+        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
+}
+
+function getData(projectID, callback) {
+    equipmentPurchaseModel.findOne({'projectID': projectID}).lean().exec((err, data) => {
+        if (err) {
+            callback(1, '', null);
+        } else {
+            callback(0, consts.projectConst.EQUIPMENT_PURCHASE, data);
+        }
+    })
+}

+ 10 - 0
modules/equipment_purchase/routes/equipment_purchase_route.js

@@ -0,0 +1,10 @@
+let express = require('express');
+let controller = require('../controllers/equipment_purchase_controller');
+
+module.exports = function (app) {
+    var eqRouter = express.Router();
+    eqRouter.post('/insertData', controller.action);
+    eqRouter.post('/deleteEquipment', controller.action);
+    eqRouter.post('/updateEquipments', controller.action);
+    app.use('/equipmentPurchase',eqRouter);
+}

+ 4 - 0
modules/main/models/project.js

@@ -26,6 +26,9 @@ let pmController = require('../../pm/controllers/pm_controller');
 const commonFacade = require('../../main/facade/common_facade');
 const GLJListModel = require("../../glj/models/glj_list_model");
 
+
+var equipment_purchase_facade = require('../../equipment_purchase/facade/equipment_purchase_facade');
+
 var consts = require('./project_consts');
 var projectConsts = consts.projectConst;
 var asyncTool = require("async");
@@ -51,6 +54,7 @@ moduleMap[projectConsts.PROJECT_INFO] = pmController;
 moduleMap[projectConsts.EVALUATE_LIST] = evaluate_facade;
 moduleMap[projectConsts.BID_EVALUATION_LIST] = bid_facade;
 moduleMap[projectConsts.CONTRACTOR_LIST] = contractor_facade;
+moduleMap[projectConsts.EQUIPMENT_PURCHASE] = equipment_purchase_facade;
 
 var Project = function (){};
 

+ 4 - 1
modules/main/models/project_consts.js

@@ -1,3 +1,5 @@
+const { EQUIPMENT } = require("../../common/const/glj_type_const");
+
 /**
  * Created by jimiz on 2017/4/18.
  */
@@ -24,7 +26,8 @@ let projectConst = {
     PROJECT_INFO: 'project_info',
     EVALUATE_LIST:'evaluate_list',
     CONTRACTOR_LIST:'contractor_list',
-    BID_EVALUATION_LIST:'bid_evaluation_list'
+    BID_EVALUATION_LIST:'bid_evaluation_list',
+    EQUIPMENT_PURCHASE:'equipment_purchase'
 };
 
 let projectConstList = [

+ 3 - 0
modules/pm/models/project_model.js

@@ -52,6 +52,7 @@ let projSettingModel = mongoose.model('proj_setting');
 let optionModel = mongoose.model('options');
 const { constructionBillsTemplate } = require('../../main/templates/constructionBillsTemplate');
 
+let equipmentPurchaseModel = mongoose.model('equipment_purchase');
 function ProjectsDAO() {
 }
 
@@ -271,6 +272,8 @@ ProjectsDAO.prototype.updateUserProjects = async function (userId, compilationId
                     // 新建计算程序文件 CSL, 2017.10.23
                     let cpFile = await calcProgramFacade.newProjectCalcProgramFile(data.updateData);
                     newProject.property.calcProgramFile = cpFile ? cpFile : null;
+                    //新建设备购置计录行
+                    await equipmentPurchaseModel.create({projectID:newProject.ID,total:0,equipments:[]});    
                 }
                 newProject.save(async function (err, result) {
                     if (!err && result._doc.projType === projectType.tender) {

+ 1 - 1
public/web/gljUtil.js

@@ -368,7 +368,7 @@ let gljUtil = {
             let pglj = calcTools.getProjectGLJ(glj);
             glj.tenderQuantity = this.getRationGLJTenderQuantity(glj, ration, gd, scMathUtil,pglj);
 
-            return scMathUtil.roundToString(quantity * glj.quantity, gd);
+            return scMathUtil.roundForObj(quantity * glj.quantity, gd)+'';
         }
     },
     getEngineerCostData:function(property,bills,fixedFlag,scMathUtil){

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

@@ -24,6 +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-not").height($(window).height()-headerHeight-1);
     $(".main-data-side-search").height($(window).height()-headerHeight-toolsbarHeight-64);
     $(".side-content").height($(window).height()-headerHeight );

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

@@ -0,0 +1,9 @@
+
+<div class="container-fluid">
+    <div class="row">
+        <div class="col-lg-12 p-0">
+            <div class="main-data-full-equipment" id="equipmentSpread">
+            </div>
+        </div>
+    </div>
+</div>

+ 26 - 0
web/building_saas/main/html/gather_fees.html

@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <script src="../../../../public/web/common_util.js"></script>
+</head>
+
+<body>
+    <div class="toolsbar px-1" style="height:30px;width:100%;line-height:30px;font-size:15px;">费用汇总</div>
+    <div class="container-fluid">
+        <div class="row">
+            <div class="col-lg-2 p-0">
+                <div class="grid main-data-full" id="gfMainSpread"></div>
+            </div>
+            <div class="col-lg-10 p-0">
+                <div class="grid main-data-full" id="gfDetailSpread"></div>
+            </div>
+        </div>
+    </div>
+</body>
+
+
+</html>

+ 20 - 14
web/building_saas/main/html/main.html

@@ -76,8 +76,9 @@
         <li class="nav-item"><a data-toggle="tab" href="#fee_rates" id="tab_fee_rate" role="tab">费率</a></li>
         <li class="nav-item"><a data-toggle="tab" href="#calc_program_manage" id="tab_calc_program_manage" role="tab"
             style="display:none">总计算程序</a></li>
-        <!--<li class="nav-item"><a data-toggle="tab" href="#tender_price" id="tab_tender_price" role="tab" style="display:none">调价</a></li>-->
-        <li class="nav-item"><a data-toggle="tab" href="#tender_price" id="tab_tender_price" role="tab">调价</a></li>
+        <li class="nav-item"><a data-toggle="tab" href="#tender_price" id="tab_tender_price" role="tab" style="display:none">调价</a></li>
+     <li class="nav-item"><a data-toggle="tab" href="#equipment_purchase" id="tab_equipment_purchase" role="tab">设备购置</a></li>
+              <li class="nav-item"><a data-toggle="tab" href="#gather_fees" id="tab_gather_fees" role="tab">费用汇总</a></li>
         <li class="nav-item"><a data-toggle="tab" href="#reports" role="tab" id="tab_report"
             onclick="rptTplObj.iniPage();">报表</a></li>
         <li class="nav-item"><a data-toggle="tab" href="#index" id="tab_index" role="tab" style="display:none">指标信息</a>
@@ -144,7 +145,7 @@
                     <a class="dropdown-item btn-sm" href="javascript:void(0);" id="displayZD">最底层</a>
                   </div>
                   <!-- <a href="javascript:void(0);" id="ZLFB_btn" class="dropdown-item" data-placement="bottom"><i class="fa fa-retweet" aria-hidden="true"></i> 整理清单</a> -->
-                  <a  href="javascript:void(0);" id="ZLFB_MENU" data-toggle="dropdown"  class="dropdown-item dropdown-toggle"><i class="fa fa-retweet" aria-hidden="true"></i> 整理清单</a>
+                 <!--  <a  href="javascript:void(0);" id="ZLFB_MENU" data-toggle="dropdown"  class="dropdown-item dropdown-toggle"><i class="fa fa-retweet" aria-hidden="true"></i> 整理清单</a>
                   <div style="position: relative;">
                     <div id="ZLFB_MENU_sub" class="dropdown-menu dropdown-menu-left"
                     style="min-width: 6.5rem; position: absolute; transform: translate3d(158px, 3px, 0px); top: -40px; left: 0px; will-change: transform;"
@@ -153,7 +154,7 @@
                     <a class="dropdown-item btn-sm" href="javascript:void(0);" id="ZLQD_btn" >清单整理</a>
                   
                   </div>
-                  </div>
+                  </div> -->
 
                   <% if (region === '重庆市' || region === '广东省') { %>
                   <a id="interface-dropdown" href="javascript:void(0);" data-toggle="dropdown"
@@ -216,10 +217,10 @@
                 <!-- <li class="nav-item">
                               <a class="nav-link px-3 right-nav-link"  href="javascript:void(0)" id = 'locateTab' relaPanel="#locate">查找定位</a>
                           </li>-->
-                <li class="nav-item">
+             <!--    <li class="nav-item">
                   <a class="nav-link px-1 right-nav-link" href="javascript:void(0)" id='stdBillsGuidanceTab'
                     relaPanel="#zy">标准清单</a>
-                </li>
+                </li> -->
                 <!-- <li class="nav-item">
                               <a class="nav-link px-3" href="javascript:void(0)" id = 'stdBillsTab' relaPanel="#qd">清单规则</a>
                           </li>-->
@@ -228,13 +229,12 @@
                     relaPanel="#de">定额库</a>
                 </li>
 
-                <li class="nav-item dropdown">
+            <!--     <li class="nav-item dropdown">
                   <a class="nav-link dropdown-toggle more" data-toggle="dropdown" href="#" role="button"
                     aria-haspopup="true" aria-expanded="false">更多</a>
                   <div class="dropdown-menu" id="div_more_dropdown_right">
                     <a class="dropdown-item  right-nav-link" href="javascript:void(0)" id='locateTab'
                       relaPanel="#locate">查找定位</a>
-                    <!--<a class="dropdown-item" data-toggle="tab" href="#sqpz" role="tab">书签批注</a>-->
                     <script>
                       //2018-11-23  zhang 模板库移动到更多下拉框
                       if (G_SHOW_BLOCK_LIB) {
@@ -244,7 +244,7 @@
                       }
                     </script>
                   </div>
-                </li>
+                </li> -->
               </ul>
             </div>
           </div>
@@ -998,6 +998,12 @@
         <div class="tab-pane" id="tender_price" role="tabpanel">
           <%include tender_price.html %>
         </div>
+         <div class="tab-pane" id="equipment_purchase" role="tabpanel">
+          <%include equipment_purchase.html %>
+        </div>
+        <div class="tab-pane" id="gather_fees" role="tabpanel">
+          <%include gather_fees.html %>
+        </div>
         <div class="tab-pane" id="calc_program_manage" role="tabpanel">
           <%include calc_program_manage.html %>
         </div>
@@ -1933,11 +1939,9 @@
         <div class="modal-body" style="padding-top: 0px;padding-bottom: 0px;">
           <div class="row" style="height:210px">
             <!--sjs id设置在这个div-->
-            <div class=" col-8" style="overflow: hidden" id="feeItemSheet">
-            </div>
-            <div class=" col-4" style="overflow: hidden" id="install_setting">
+            <div class=" col-12" style="overflow: hidden" id="feeItemSheet"></div>
+      <!-- <div class=" col-4" style="overflow: hidden" id="install_setting">
               <div style="height: 100px;">
-                <!--<div class="setting_title">整个项目统一计取</div>-->
                 <fieldset class="form-group" style="border:1px solid #b3b3b3;padding: 15px">
                   <legend class="legend">分项费用:</legend>
                   <div class="form-check">
@@ -1955,7 +1959,7 @@
                   </div>
                 </fieldset>
               </div>
-            </div>
+            </div> -->
           </div>
           <div class="row" style="height:270px; margin-top: 5px">
             <!--sjs id设置在这个div-->
@@ -2964,6 +2968,8 @@
   <script type="text/javascript" src="/web/building_saas/main/js/views/zmhs_view.js"></script>
   <script type="text/javascript" src="/web/building_saas/main/js/views/mbzm_view.js"></script>
   <script type="text/javascript" src="/web/building_saas/main/js/views/tender_price_view.js"></script>
+   <script type="text/javascript" src="/web/building_saas/main/js/views/equipment_purchase_view.js"></script>
+  <script type="text/javascript" src="/web/building_saas/main/js/views/gather_fees_view.js"></script>
   <script type="text/javascript" src="/web/building_saas/main/js/views/billsElf.js"></script>
   <script type="text/javascript" src="/web/building_saas/main/js/views/sub_view.js"></script>
   <script type="text/javascript" src="/web/building_saas/main/js/views/fee_rate_view.js"></script>

+ 329 - 76
web/building_saas/main/js/models/calc_program.js

@@ -159,7 +159,8 @@ let calcTools = {
     },
     forceSelect: function (treeNode, rowsCount = 1, colsCount = 2){
         projectObj.mainController.tree.selected = treeNode;
-        let idx = projectObj.project.mainTree.items.indexOf(treeNode);
+        let tree = treeNode.tree;
+        let idx = tree.items.indexOf(treeNode);
         let sheet = projectObj.mainSpread.getActiveSheet();
         sheet.setSelection(idx, 0, rowsCount, colsCount);
         sheet.showRow(idx, GC.Spread.Sheets.VerticalPosition.center);
@@ -502,7 +503,7 @@ let calcTools = {
 
         // 总造价暂估费
         if (me.isTotalCostBill(treeNode)){
-            let nodes = projectObj.project.mainTree.roots;
+            let nodes = treeNode.tree.roots;
             for (let node of nodes){
                 if (me.isTotalCostBill(node)) break;
                 let eU = 0, eT = 0, eTU = 0, eTT = 0;
@@ -949,7 +950,7 @@ let calcTools = {
         return (treeNode.data.quantity && parseFloat(treeNode.data.quantity));
     },
 
-    getRationsByProjectGLJ(PGLJID){
+    getRationsByProjectGLJ(PGLJID, tree = projectObj.project.mainTree){
         let rationIDs = [];
         let RGs = projectObj.project.ration_glj.datas;
         let PGLJIDs = [];
@@ -965,13 +966,13 @@ let calcTools = {
         };
 
         let rationNodes = [];
-        let nodes = projectObj.project.mainTree.nodes;
+        let nodes = tree.nodes;
         for (let rID of rationIDs){
             rationNodes.push(nodes['id_' + rID]);
         };
 
         // 工料机形式的定额
-        let items = projectObj.project.mainTree.items;
+        let items = tree.items;
         for (let item of items){
             if (PGLJIDs.indexOf(item.data.projectGLJID)  !== -1)
                 rationNodes.push(item);
@@ -979,9 +980,9 @@ let calcTools = {
 
         return rationNodes;
     },
-    getNodesByProgramID(programID){
+    getNodesByProgramID(programID, tree = projectObj.project.mainTree){
         let discreteNodes = [];
-        let nodes = projectObj.project.mainTree.items;
+        let nodes = tree.items;
         for (let node of nodes){
             if (node.data.programID == programID)
                 discreteNodes.push(node);
@@ -1056,13 +1057,32 @@ let calcTools = {
         return totalFee < +minPrice;
     },
 
-    getTenderCalcType: function () {
+    getTenderTypeStr: function () {
         let tenderSetting = projectObj.project.property.tenderSetting;
-        let ct = tenderSetting && tenderSetting.calcPriceOption? tenderSetting.calcPriceOption : "priceBase_RCJ";
-        if (ct == 'priceBase') ct = 'priceBase_RCJ';   // 兼容旧项目
+        let ct = tenderSetting && tenderSetting.calcPriceOption ? tenderSetting.calcPriceOption : "priceBase_RCJ";
+        if (ct == 'priceBase') ct = 'priceBase_RCJ'; // 兼容旧项目
         return ct;
+      },
+    getTenderType: function () {
+    let rst;
+    let sOption = calcTools.getTenderTypeStr();
+    if (sOption == 'coeBase')
+        rst = tenderTypes.ttCalc
+    else if (sOption == 'priceBase_RCJ')
+        rst = tenderTypes.ttReverseGLJ
+    else if (sOption == 'priceBase_ZM')
+        rst = tenderTypes.ttReverseRation;
+    return rst;
+    },
+    // 取树结点的调价系数。
+    getCoe: function (node, tender) {
+    if (tender == tenderTypes.ttReverseGLJ)
+        return calcTools.isVP_or_GLJR(node) ? node.data.rationQuantityCoe : (node.data.quantityCoe ? node.data.quantityCoe.labour : 0)
+    else if (tender == tenderTypes.ttReverseRation)
+        return node.data.rationQuantityCoe;
     }
 
+
 };
 
 let rationCalcBases = {
@@ -1478,8 +1498,8 @@ let analyzer = {
             return true
         else return false;
     },
-    templateIsUsed: function (ID){
-        let nodes = projectObj.project.mainTree.items;
+    templateIsUsed: function (ID, tree = projectObj.project.mainTree){
+        let nodes = tree.items;
         for (let node of nodes){
               if (node.data && node.data.programID && node.data.programID == ID) {
                   return true;
@@ -2001,7 +2021,7 @@ class CalcProgram {
             };
 
             if (tenderType == tenderTypes.ttReverseRation || tenderType == tenderTypes.ttReverseGLJ)
-                this.calcTenderReverse(treeNode, tenderType);
+              this.reverseTenderCalc(treeNode, tenderType);
 
             me.deleteUselessFees(treeNode, fnArr);
         };
@@ -2055,11 +2075,12 @@ class CalcProgram {
             projectObj.project.projectInfo.lastFileVer = VERSION;
             // 批量树结点计算后,计算程序早已物是人非,所以这里要重新计算一下。警告:第二个参数千万不能改成3,否则死循环!
             if (activeSubSheetIsCalcProgram())
-                calcProgramObj.refreshCalcProgram(projectObj.project.mainTree.selected, 2);
+                calcProgramObj.refreshCalcProgram(treeNodes[0].tree.selected, 2);
             $.bootstrapLoading.end();
         });
     };
-    // 计算本节点及所有会被影响到的节点,如:所有父节点(默认,可选)、公式引用节点(默认,可选)。修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点。
+    // 计算本节点及所有会被影响到的节点,如:所有父节点(默认,可选)、公式引用节点(默认,可选)。
+    // 修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点。
     calculate(treeNode, calcParents = true, calcFormulas = true, tender){
         let me = this;
         let changedNodes = [];
@@ -2096,7 +2117,8 @@ class CalcProgram {
         调价相关参数:
         tender:                 null:不调价(普通计算)。 1: 正向调价   2:反向调价-调子目    3: 反向调价-调工料机
     */
-    calcAllNodes(calcType = calcAllType.catAll, tender){
+
+    calcAllNodes(calcType = calcAllType.catAll, tender, tree = projectObj.project.mainTree){
         let me = this;
         let changedNodes = [];
         function calcNodes(nodes) {
@@ -2117,11 +2139,11 @@ class CalcProgram {
         if (calcType == calcAllType.catRations)
             calcType == calcAllType.catAll;
 
-        calcNodes(me.project.mainTree.roots);
+        calcNodes(tree.roots);
         me.calcFormulaNodes(changedNodes, tender);
-
-        if (tender){    // 普通计算不执行,只有3种调价计算才清。
-            for(let node of projectObj.project.mainTree.items){
+        debugger;
+        if (tender){
+            for(let node of tree.items){
                 this.clearTenderCache(node);
             };
         };
@@ -2240,6 +2262,19 @@ class CalcProgram {
         return me.getTotalFee(baseNodes, excludeNodes, tender);
     };
 
+    // 清理调价缓存数据
+    clearTenderCache(treeNode){
+        // 这些属性值为什么不定义在一个对象里?因为每次要判断对象是否存在,十分麻烦。不如直接写简单。分散书写,统一处理也很好用。
+        if (treeNode.data.tender_activeTotal) delete treeNode.data.tender_activeTotal;
+        if (treeNode.data.tender_activeTarget) delete treeNode.data.tender_activeTarget;
+        if (treeNode.data.tender_fullLoad) delete treeNode.data.tender_fullLoad;
+        if (treeNode.data.tender_distribute){
+            delete treeNode.data.targetTotalFee;
+            delete treeNode.data.targetUnitFee;
+            delete treeNode.data.tender_distribute;
+        };
+    };
+
     initGljPriceTenderCoe (){
         if (projectObj.project.property.tenderSetting && projectObj.project.property.tenderSetting.gljPriceTenderCoe
             && (projectObj.project.property.tenderSetting.gljPriceTenderCoe != 1)){
@@ -2248,8 +2283,8 @@ class CalcProgram {
         }
     };
 
-    // 反向调价需初始化调价树、缓存数据等
-    initReverseTenderDatas (){
+    // 反向调价-初始化调价树、清理缓存数据等
+    reverseTenderInitDatas(){
         for(let node of tender_obj.tenderTree.items){
             if (node.data.rationQuantityCoe) node.data.rationQuantityCoe = null;
             let qcObj = node.data.quantityCoe;
@@ -2264,8 +2299,10 @@ class CalcProgram {
         this.initGljPriceTenderCoe();
     };
 
-    // 反向调价
-    calcTenderReverse(treeNode, tender){
+    // 反向调价简单计算:只计算当前单一结点,由目标金额计算定额量系数或工料机量系数coe,再根据此coe计算定额的调后量、调后合价。
+    // ⑴只计算参数指定的一个单一的结点,连父结点、引用结点都不计算。此结点只会是定额、量价、工料机款定额,不会是清单。
+    // ⑵执行此方法的前提:已在其它地方把总的目标金额分摊好了,此结点已获取目标金额。
+    reverseTenderCalc(treeNode, tender) {
         if (tender == tenderTypes.ttReverseRation) {
             if (treeNode.data.feesIndex.common && treeNode.data.feesIndex.common.tenderUnitFee != treeNode.data.feesIndex.common.unitFee) {
                 treeNode.data.feesIndex.common.tenderUnitFee = treeNode.data.feesIndex.common.unitFee;
@@ -2273,7 +2310,7 @@ class CalcProgram {
             }
         };
 
-        if (!treeNode.data.targetTotalFee){
+        if (!treeNode.data.targetTotalFee){     // 没有目标金额、但有输入目标单价的:根据目标单价算出目标金额。
             if (treeNode.data.targetUnitFee){
                 treeNode.data.targetTotalFee = (treeNode.data.targetUnitFee * treeNode.data.quantity).toDecimal(decimalObj.decimal('totalPrice', treeNode));
                 treeNode.changed = true;
@@ -2291,16 +2328,18 @@ class CalcProgram {
             }
         };
 
+        // 经过前面的一通折腾还是没有目标单价的话,那么就要通过目标金额算出来。
         if (!treeNode.data.targetUnitFee || (parseFloat(treeNode.data.targetUnitFee) == 0)){
             if (calcTools.hasQuantity(treeNode))
                 treeNode.data.targetUnitFee = (treeNode.data.targetTotalFee / treeNode.data.quantity).toDecimal(decimalObj.decimal('unitPrice', treeNode));
         }
 
+        // 系数=调后单价/调前单价
         let coe = 1;
         if (treeNode.data.feesIndex.common.totalFee != 0)
             coe = (treeNode.data.targetUnitFee / treeNode.data.feesIndex.common.unitFee).toDecimal(decimalObj.process);
 
-        // 调价情况之————量价反调工料机(量价无工料机可调,还是按定额来调,即直接调树结点的消耗量)
+        // 量价、工料机款式的定额:它们无下挂工料机可调,直接调树结点的消耗量。
         let isVP_RevGLJ =(tender == tenderTypes.ttReverseGLJ) && calcTools.isVP_or_GLJR(treeNode);
 
         if ((tender == tenderTypes.ttReverseRation) || isVP_RevGLJ){
@@ -2359,32 +2398,17 @@ class CalcProgram {
         };
     };
 
-    // 清理调价缓存数据
-    clearTenderCache(treeNode){
-        // 这些属性值为什么不定义在一个对象里?因为每次要判断对象是否存在,十分麻烦。不如直接写简单。分散书写,统一处理也很好用。
-        if (treeNode.data.tender_activeTotal) delete treeNode.data.tender_activeTotal;
-        if (treeNode.data.tender_activeTarget) delete treeNode.data.tender_activeTarget;
-        if (treeNode.data.tender_fullLoad) delete treeNode.data.tender_fullLoad;
-        if (treeNode.data.tender_distribute){
-            delete treeNode.data.targetTotalFee;
-            delete treeNode.data.targetUnitFee;
-            delete treeNode.data.tender_distribute;
-        };
-    };
-
-    // 调价:分摊前的准备工作。主要标记满载的结点。
+   // 反向调价-分摊前的准备工作。包括:
+   // ⑴结点是否满载
+   // ⑵若是满载,汇总孩子的金额、目标金额给它
+   // ⑶标记此结点的目标金额来自孩子,未来它不往下分摊(汇上来再摊下去无用功)
     prepareForDistribute(treeNode){
         if (!treeNode) return;
 
         if (treeNode.firstChild())
             this.prepareForDistribute(treeNode.firstChild());
 
-        if (treeNode.children.length == 0){
-            // if (!treeNode.data.feesIndex['common'])
-            //     treeNode.data.tender_fullLoad = true
-            // else
-            //     treeNode.data.tender_fullLoad = false;
-        }
+        if (treeNode.children.length == 0){}
         else{
             let full = true;
             for (let i = 0; i < treeNode.children.length; i++) {
@@ -2410,20 +2434,12 @@ class CalcProgram {
             treeNode.data.tender_fullLoad = full;
         };
 
-        /*if (calcTools.isBill(treeNode) && (treeNode.data.tender_fullLoad != undefined)){
-            console.log(treeNode.data.name + ',tender_fullLoad: ' + treeNode.data.tender_fullLoad + ', target: ' + calcTools.hasTargetTotalFee(treeNode));
-        };*/
-
-/*        if (calcTools.isRationItem(treeNode)){
-            console.log(treeNode.data);
-        }*/
-
         if (treeNode.nextSibling)
             this.prepareForDistribute(treeNode.nextSibling);
     };
 
 
-    // 调价之分摊目标合价:从父到子往下分摊。
+   // 反向调价-分摊目标合价:从父到子往下分摊。
     distributeTargetTotalFee(treeNode){
         if (!treeNode) return;
 
@@ -2431,7 +2447,7 @@ class CalcProgram {
             // 默认能够执行到这里时每个节点已经被初始化,缓存已删除
             treeNode.data.tender_activeTotal = treeNode.data.feesIndex['common'].totalFee;
 
-            // 开始分摊:只分摊自有目标金额、从父结点分摊到的金额(不分摊子结点上来的金额)
+            // 开始分摊:只分摊自有目标金额、从父结点分摊到的金额(不分摊子结点汇总上来的金额)
             if (calcTools.hasTargetTotalFee(treeNode) && !(treeNode.data.tender_distribute && treeNode.data.tender_distribute == 2)){
                 treeNode.data.tender_activeTarget = treeNode.data.targetTotalFee;
 
@@ -2475,6 +2491,198 @@ class CalcProgram {
             this.distributeTargetTotalFee(treeNode.nextSibling);
     };
 
+       // 反向调价逼近
+    reverseTenderApproach(callback, tender){
+        let me = this;
+
+        let G_DIGIT = 0.01;      // 系数调整步距(0.1最终结果误差大。0.001目标金额与逼前金额差距大时无法有效逼近)
+        let MaxDiffValue = 0.1;  // 可接受的最大差值。(如果实际差值比这个大:说明列表结点耗尽,无法达到指定精度。公路设为1,建筑设为0.1)
+        let times = 300;         // 逼近计算的极限次数。正常情况下“单位系数金额”列表中的结点耗尽即退出,这里指定轮数是最后保险阀,防止无限死循环。
+        let calcModel = 1;       // 计算模式:1 精度优先(差值不接近0不停,直到结点用完或极限次数用完。时间长精度高)2 速度优先(达到指定差值范围即熔断逼近。时间短差值大)
+        let diffProp = 0.0001;   // 计算模式=2(速度优先)时有效。 通过这个金额比例值计算可接受的最大差值D。差值D = 根结点金额 * diffProp
+        let isTest = false;      // 测试
+
+        // 按指定的比例获取可接受的差值:如目标金额的万分之一。
+        function getPropV(node){
+        let v = parseFloat((node.data.targetTotalFee * diffProp).toFixed(0));   // node.data.feesIndex.common.totalFee
+        return Math.max(v, MaxDiffValue);
+        }
+
+        // 取根结点的:调后金额跟目标金额差值,看还有多少误差需要处理。
+        function  getRootDiff() {
+        let root = tender_obj.tenderTree.roots[0];
+        return (root.data.feesIndex.common.tenderTotalFee - root.data.targetTotalFee).toDecimal(3);
+        }
+
+        // 生成每单位差值的定额结点列表。
+        function getNodeDiffs() {
+        let arr = [];
+        for (let i = 0; i < tender_obj.tenderTree.items.length; i++) {
+            let node = tender_obj.tenderTree.items[i];
+            // 量价还是要参与,因为它贡献了金额,如果它的金额比重很大,它退出了,会导致其它结点过调。
+            // if (calcTools.isRationCategory(node) && (!calcTools.isVP_or_GLJR(node))){
+            if (calcTools.isRationCategory(node)){
+            let coe = calcTools.getCoe(node, tender);
+            if (coe!= 0) {
+                let diff = Math.abs(node.data.feesIndex.common.tenderTotalFee - node.data.feesIndex.common.totalFee);
+                node.data.tender_diffValuePerCoe  = (diff * G_DIGIT / coe).toDecimal(decimalObj.process);
+                node.data.tender_rowNo = i + 1;       // node在UI上显示的行号
+                arr.push(node);
+            }
+            }
+        };
+        arr.sort(function sortArr(a, b) { return a.data.tender_diffValuePerCoe - b.data.tender_diffValuePerCoe});
+
+        if (isTest){
+            let arr2 = [];
+            for (let i = 0; i < arr.length; i++) {
+            arr2.push({row: arr[i].data.tender_rowNo, code: arr[i].data.code + ' ' + arr[i].data.name, diff: arr[i].data.tender_diffValuePerCoe});
+            }
+            console.log(arr2);
+        }
+
+        return arr;
+        }
+
+        // 每单位差值的定额结点列表中,取离给定值最近的定额结点。
+        function getCloseNode(arr, value) {
+        let index = 0;
+        let d_value = Number.MAX_VALUE;
+        for (let i = 0; i < arr.length; i++) {
+            let new_d_value = Math.abs(arr[i].data.tender_diffValuePerCoe - value);
+            if (new_d_value <= d_value) {
+            if (new_d_value === d_value && arr[i].data.tender_diffValuePerCoe < arr[index].data.tender_diffValuePerCoe) {
+                continue;
+            }
+            index = i;
+            d_value = new_d_value;
+            }
+        }
+        return {idx: index, node: arr[index]}
+        }
+
+        // 逼近(单轮)。arr 参考取值列表。返回{type, node, nodeIdx}。
+        // type:1正常,2无结点,3结点过调。node:结点。nodeIdx:结点在列表中的索引位置。node.data.tender_rowNo 在UI上的行号。
+        function approach(arr){
+        let v = getRootDiff();
+        let obj = getCloseNode(arr, Math.abs(v));    // {idx, node}  极端:{0, undefind}
+        let closeNode = obj.node;
+        if (!closeNode)
+            return {type: 2, node: undefined, nodeIdx: -1};    // arr 被清空了
+
+        let d = (v > 0) ? -G_DIGIT : G_DIGIT;
+        let coe = calcTools.getCoe(closeNode, tender);
+        if ((coe + d) < 0)
+            return {type: 3, node: obj.node, nodeIdx: obj.idx};  // 再调的话,系数就变负数了,过调
+
+        if (tender == tenderTypes.ttReverseRation){
+            closeNode.data.tender_previousCoe = closeNode.data.rationQuantityCoe;  // tender_previousCoe: 上一次的调整系数,用于撤回
+            closeNode.data.rationQuantityCoe = (closeNode.data.rationQuantityCoe + d).toDecimal(decimalObj.process);
+        }
+        else if (tender == tenderTypes.ttReverseGLJ){
+            if (calcTools.isVP_or_GLJR(closeNode)){
+            closeNode.data.tender_previousCoe = closeNode.data.rationQuantityCoe;
+            closeNode.data.rationQuantityCoe = (closeNode.data.rationQuantityCoe + d).toDecimal(decimalObj.process);
+            }
+            else {
+            closeNode.data.tender_previousCoe = closeNode.data.quantityCoe.labour;
+            closeNode.data.quantityCoe.labour = (closeNode.data.quantityCoe.labour + d).toDecimal(decimalObj.process)
+            closeNode.data.quantityCoe.material = (closeNode.data.quantityCoe.material + d).toDecimal(decimalObj.process)
+            closeNode.data.quantityCoe.machine = (closeNode.data.quantityCoe.machine + d).toDecimal(decimalObj.process)
+            closeNode.data.quantityCoe.main = (closeNode.data.quantityCoe.main + d).toDecimal(decimalObj.process)
+            closeNode.data.quantityCoe.equipment = (closeNode.data.quantityCoe.equipment + d).toDecimal(decimalObj.process)
+            };
+        }
+
+        me.calculate(closeNode, true, true, tenderTypes.ttCalc);
+        return {type: 1, node: obj.node, nodeIdx: obj.idx};
+        }
+
+        // 撤消最后一轮逼近(调过头了,回退一步)
+        function undoLastApproach(obj){
+        let closeNode = obj.node;
+        let coe = closeNode.data.tender_previousCoe;
+        if (tender == tenderTypes.ttReverseRation){
+            closeNode.data.rationQuantityCoe = coe
+        } else if (tender == tenderTypes.ttReverseGLJ){
+            if (calcTools.isVP_or_GLJR(closeNode)){
+            closeNode.data.rationQuantityCoe = coe
+            }
+            else {
+            closeNode.data.quantityCoe.labour = coe
+            closeNode.data.quantityCoe.material = coe
+            closeNode.data.quantityCoe.machine = coe
+            closeNode.data.quantityCoe.main = coe
+            closeNode.data.quantityCoe.equipment = coe
+            };
+        }
+
+        me.calculate(closeNode, true, true, tenderTypes.ttCalc);
+        if (isTest) {
+            let _sp = `        `;   // 保留空格,打印对齐
+            let _node = `[行${obj.node.data.tender_rowNo} 索引${obj.nodeIdx}]`;
+            console.log(`${_sp} ${_node} 过调,已回退`);
+        }
+        }
+
+        let root = tender_obj.tenderTree.roots[0];
+        let propV = getPropV(root);
+        let vArr = getNodeDiffs();
+        let d1 = getRootDiff();
+
+        if (isTest){
+        let _tq = `调前${root.data.feesIndex.common.totalFee}`;
+        let _mb = `目标${root.data.targetTotalFee}`;
+        let _wbj = `调后无逼近${root.data.feesIndex.common.tenderTotalFee}`;
+        let _cz = `差值${d1}`;
+        let _js = `轮${times} 系数步距${G_DIGIT}`;
+        let _yx = (calcModel == 2) ? `速度优先(差比${diffProp} 差域0~${propV})` : "精度优先";
+        console.log(`${_tq}|${_mb}|${_wbj}|${_cz}|${_yx}|${_js}`);
+        }
+
+        // 多轮逼进
+        for (let i = 1; i <= times; i++) {
+        // 与目标金额差值在1以内,整数部分已相同,结果很棒。很多时候能达到完美的差值0,但不能用0判断,因为金额取小数时有问题。
+        if (Math.abs(d1) < MaxDiffValue) break;
+        // 速度优先模式下,结果到达差值范围内,即熔断逼近。
+        if (calcModel == 2){
+            if (Math.abs(d1) < propV) break;
+        };
+
+        let obj = approach(vArr);
+        if (obj.type == 2) break;        // 结点耗完,列表已清空,无结点可调。
+        if (obj.type == 3) {             // 结点过调,该结点任务已完成,不再参与调节,从列表中清除
+            vArr.splice(obj.nodeIdx, 1);
+            continue;
+        };
+
+        let d2 = getRootDiff();
+
+        if (isTest){
+            let _time = `【第 ${i} 轮】调整`;
+            let _node = `[行${obj.node.data.tender_rowNo} 索引${obj.nodeIdx}]`;
+            let _coe = `${obj.node.data.tender_previousCoe} → ${calcTools.getCoe(obj.node, tender)}`;
+            let _d = `差值${d1} → ${d2}`;
+            console.log(`${_time}${_node} ${_coe},${_d}`);
+        }
+
+        if (Math.abs(d2) > Math.abs(d1)){    // 逼近后差值反而变大,证明此轮调节不合适,回退一步,并将结点从列表清除
+            undoLastApproach(obj);
+            vArr.splice(obj.nodeIdx, 1);
+        }
+        else if (Math.abs(d2) == Math.abs(d1)){  // 调前调后差距相同时,会死循环,也从列表中清除
+            vArr.splice(obj.nodeIdx, 1);
+        }
+        else {d1 = d2}
+        };
+
+        // 重要注释:到这里,所有系数已处理就绪,最后直接全局正算即可。
+        // 到这一步,被处理的定额、及其受影响的父结点、引用结点等均已正算完成(approach方法、undoLastApproach方法),但都未保存。
+        // 最后全局正算目的:⑴计算除第一部以外的其它部分 ⑵保存。正算在调用外面完成。
+        // this.calcAllNodesAndSave(calcAllType.catAll, callback, tenderTypes.ttCalc);
+    };
+
+
     setRationMap(){
         if(this.rationMap == null){
             this.rationMap = {};
@@ -2507,27 +2715,72 @@ class CalcProgram {
         }
     };
 
-    doTenderCalc(callback){
-        let sOption = calcTools.getTenderCalcType();
-        let tender;
-        if (sOption =='coeBase')
-            tender = tenderTypes.ttCalc
-        else if (sOption =='priceBase_RCJ')
-            tender = tenderTypes.ttReverseGLJ
-        else if (sOption =='priceBase_ZM')
-            tender = tenderTypes.ttReverseRation;
-        if (tender == tenderTypes.ttReverseGLJ || tender == tenderTypes.ttReverseRation){
-          if (!tender_obj.tenderTree){
-            tender_obj.createTree();
-            tender_obj.createTreeNodes();
-          }
-
-          this.initReverseTenderDatas();
-          this.prepareForDistribute(tender_obj.tenderTree.roots[0]);
-          this.distributeTargetTotalFee(tender_obj.tenderTree.roots[0]);
-        };
-        this.calcAllNodesAndSave(calcAllType.catAll, callback, tender);
+    doTenderCalc(callback) {
+        $.bootstrapLoading.start();
+        setTimeout(()=>{
+            let tender = calcTools.getTenderType();
+            if (tender == tenderTypes.ttReverseGLJ || tender == tenderTypes.ttReverseRation) {
+            // 调价计算必须依赖调价树。
+            if (!tender_obj.tenderTree){
+                tender_obj.createTree();
+                tender_obj.createTreeNodes();
+            }
+            // 反向调价+逼近原理:
+            // 清理调价缓存 → 分摊目标金额(从子往父处理满载、从父往子分摊) → 全局反算 → 逼近 → 全局正算、存储
+            this.reverseTenderInitDatas();
+            this.prepareForDistribute(tender_obj.tenderTree.roots[0]);
+            this.distributeTargetTotalFee(tender_obj.tenderTree.roots[0]);
+            this.calcAllNodes(calcAllType.catAll, tender);    // 先全局反算:得到每定额的coe、基础调后金额(误差大,需逼近)
+            this.reverseTenderApproach(callback, tender);     // 逼近上述基础调后金额
+            }
+
+            // 全局正算(用的是主树,无需调价树)
+            this.calcAllNodesAndSave(calcAllType.catAll, callback, tenderTypes.ttCalc);
+        })
+
     };
+
+    gatherRationFees(programID){
+        // $.bootstrapLoading.start();
+        // setTimeout(()=>{
+            // 先把符合的定额筛选出来,以提高速度。
+            let rations = []; 
+            for (const r of projectObj.project.Ration.datas) {
+                if (r.programID == programID) rations.push(r);
+            };    
+
+            let gatherObj = {};
+            gatherObj.programID = programID;
+            for (let ft of cpFeeTypes) {
+                let ftObj = {};
+                ftObj.name = ft.name;
+    
+                let sum_uf = 0, sum_tuf = 0, sum_tf = 0, sum_ttf = 0;
+                for (const r of rations) {
+                    let uf = 0, tuf = 0, tf = 0, ttf = 0;
+                    if (r.feesIndex && r.feesIndex[ft.type]) {
+                        uf = parseFloatPlus(r.feesIndex[ft.type].unitFee).toDecimal(decimalObj.bills.unitPrice);
+                        tuf = parseFloatPlus(r.feesIndex[ft.type].tenderUnitFee).toDecimal(decimalObj.bills.unitPrice);
+                        tf = parseFloatPlus(r.feesIndex[ft.type].totalFee).toDecimal(decimalObj.bills.totalPrice);
+                        ttf = parseFloatPlus(r.feesIndex[ft.type].tenderTotalFee).toDecimal(decimalObj.bills.totalPrice);
+                    };
+                    sum_uf = (sum_uf + uf).toDecimal(decimalObj.process);
+                    sum_tuf = (sum_tuf + tuf).toDecimal(decimalObj.process);
+                    sum_tf = (sum_tf + tf).toDecimal(decimalObj.process);
+                    sum_ttf = (sum_ttf + ttf).toDecimal(decimalObj.process);
+                };
+
+                ftObj.totalFee = sum_tf.toDecimal(decimalObj.bills.totalPrice);
+                ftObj.tenderTotalFee = sum_ttf.toDecimal(decimalObj.bills.totalPrice);
+                ftObj.unitFee = sum_uf.toDecimal(decimalObj.bills.unitPrice);
+                ftObj.tenderUnitFee = sum_tuf.toDecimal(decimalObj.bills.unitPrice);
+
+                gatherObj[ft.type] = ftObj;
+            };
+            return gatherObj;
+        // });
+        // $.bootstrapLoading.end();    
+    }
 };
 
 // export default analyzer;

+ 2 - 1
web/building_saas/main/js/models/main_consts.js

@@ -20,7 +20,8 @@ const ModuleNames = {
     projectInfo: 'project_info',
     evaluate_list:'evaluate_list',
     contractor_list:'contractor_list',
-    bid_evaluation_list:'bid_evaluation_list'
+    bid_evaluation_list:'bid_evaluation_list',
+    equipment_purchase:'equipment_purchase'
 };
 
 let gljType = gljUtil.gljType;

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

@@ -109,6 +109,7 @@ var PROJECT = {
             this.evaluate_list = new EvaluateList(this);
             this.bid_evaluation_list = new BidEvaluationList(this);
             this.contractor_list = new ContractorList(this);
+            this.equipment_purchase = new EquipmentPurchase(this);
             this.calcBase = calcBase;
 
             // this.masterField = {ration: 'billsItemID', volumePrice: 'billsItemID'};

+ 11 - 0
web/building_saas/main/js/models/project_glj.js

@@ -1048,4 +1048,15 @@ class ContractorList {
     loadData (datas) {
         this.datas = datas;
     };
+}
+
+class EquipmentPurchase {
+    constructor (project) {
+        this.project = project;
+        this.datas = {};
+        project.registerModule(ModuleNames.equipment_purchase, this);
+    };
+    loadData (data) {
+        this.datas = data;
+    };
 }

+ 5 - 7
web/building_saas/main/js/models/ration.js

@@ -465,9 +465,6 @@ var Ration = {
                 } else if (selected.data.calcBase&&selected.data.calcBase!="") {
                     alert('当前有基数计算,不能插入定额/量价/人材机。');
                 } else {
-                    if(selected.data.type === billType.FB){
-                        return;
-                    }
                     billItemID = selected.source.getID();
                     nextID = selected.tree.rootID();
                     br = this.getBillsSortRation(billItemID);
@@ -581,10 +578,11 @@ var Ration = {
             let needInstall = false;
             if (selected === null) { return null; }
             if (selected.sourceType === project.Bills.getSourceType() && selected.depth() > 0) {
-                if(selected.data.type === billType.FB){
+               /*  if(selected.data.type === billType.FB){
                     return null;
-                }
-                else if (selected.source.children.length > 0) {
+                } 
+                else */
+                if (selected.source.children.length > 0) {
                     alert('当前清单已有清单子项,不能套用定额。');
                 } else if (selected.data.calcBase&&selected.data.calcBase!="") {
                     alert('当前有基数计算,不能插入定额/量价/人材机。');
@@ -852,9 +850,9 @@ var Ration = {
             if (calcTools.isRationCategory(node)) return true;
             if (calcTools.isBill(node)){
                 if (node.data.type == billType.FX || node.data.type == billType.BX) return true;  // 分项、补项
+                if (calcTools.isLeafBill(node) && node.data.type == billType.FB) return true; //叶子分部,可添加
                 if (calcTools.isLeafBill(node)
                     && (node.data.type != billType.DXFY)
-                    && (node.data.type != billType.FB)
                     && project.Bills.isMeasure(node)) return true;  // 叶子清单项
             };
 

+ 205 - 0
web/building_saas/main/js/views/equipment_purchase_view.js

@@ -0,0 +1,205 @@
+
+let equipmentPurchaseObj  = {
+    setting:{
+        header: [
+            {headerName: "编号", headerWidth: 160, dataCode: "code", dataType: "String",formatter: "@"},
+            {headerName: "名称", headerWidth: 200, dataCode: "name", dataType: "String"},
+            {headerName: "单位", headerWidth: 100, dataCode: "unit",  dataType: "String"},
+            {headerName: "数量", headerWidth: 160, dataCode: "quantity", hAlign: "right", dataType: "Number",validator:'number'},
+            {headerName: "单价", headerWidth: 160, dataCode: "unitPrice", hAlign: "right", dataType: "Number",validator:'number'},
+            {headerName: "金额", headerWidth: 160, dataCode: "totalPrice", hAlign: "right", dataType: "Number"},
+        ],
+        view: {
+            lockColumns: ["totalPrice"],
+            rowHeaderWidth:40,
+            colHeaderHeight:35
+        }
+    
+    },
+    sheet:null,
+    initSpread:function () {
+        if(this.sheet == null){
+            this.spread = SheetDataHelper.createNewSpread($("#equipmentSpread")[0]);
+            sheetCommonObj.spreadDefaultStyle(this.spread);
+            this.sheet = this.spread.getSheet(0);
+            sheetCommonObj.initSheet(this.sheet, this.setting, 0);
+            this.sheet.bind(GC.Spread.Sheets.Events.ValueChanged,this.onValueChange);
+            this.sheet.bind(GC.Spread.Sheets.Events.RangeChanged, this.onSheetRangeChange);
+            if (projectReadOnly) {
+                sheetCommonObj.disableSpread(this.spread);
+              } else {
+                this.initRightClick();
+              }
+        }
+    },
+    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);
+    },
+
+    onValueChange:function (e,info) {
+        let me = equipmentPurchaseObj,row = info.row, col = info.col;
+        let dataCode = me.setting.header[col].dataCode;
+        let value = info.newValue;
+        let equipment = me.data[row];
+
+        if (value&&! sheetCommonObj.checkData(col,me.setting,value)) {
+            alert('输入的数据类型不对,请重新输入!');
+            return me.showData();
+        }
+        let data = {doc:{},ID:equipment.ID};
+        if(dataCode == 'quantity' || dataCode == 'unitPrice'){
+            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;
+        if(newValue){
+            if(dataCode === 'quantity') {
+                newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.quantity'));
+                quantity = newValue;
+                if(gljUtil.isDef(doc.unitPrice)) unitPrice = doc.unitPrice;
+            }
+            if(dataCode === 'unitPrice') {
+                newValue =  scMathUtil.roundForObj(newValue,getDecimal('glj.unitPrice'));
+                unitPrice = newValue;
+                if(gljUtil.isDef(doc.quantity)) quantity = doc.quantity;
+            }
+            doc.totalPrice = scMathUtil.roundForObj(quantity * unitPrice,getDecimal('glj.unitPrice'));
+        }
+    },
+    onSheetRangeChange:function(e,args){
+        let updateMap = {};
+        let updateData = []
+        let me = equipmentPurchaseObj;
+        for(let c of args.changedCells){
+            let dataCode = me.setting.header[c.col].dataCode;
+            let value= args.sheet.getCell(c.row, c.col).text();
+            let equipment = me.data[c.row];
+            if (value&&!sheetCommonObj.checkData(c.col,me.setting,value)) {
+                alert('输入的数据类型不对,请重新输入!');
+                me.showData();
+                return ;
+            }
+            let tem = updateMap[equipment.ID]?updateMap[equipment.ID]:{};
+            if(dataCode == 'quantity' || dataCode == 'unitPrice'){
+                me.calcTotalPrice(value,dataCode,tem,equipment);
+            } 
+            tem[dataCode] = value;
+            updateMap[equipment.ID] = tem;
+        }
+        for(let ID in updateMap){
+            let data = {doc:updateMap[ID],ID:ID};
+            updateData.push(data);
+        }
+        if(updateData.length > 0)  me.updateEquipments(updateData);
+    },
+    newEquipment:function(){
+        return {ID:uuid.v1()}
+    },
+    updateEquipments:async function(updateData){
+        try {
+            $.bootstrapLoading.start();
+            let projectID = projectObj.project.ID();
+            await ajaxPost('/equipmentPurchase/updateEquipments', { projectID, updateData });
+            for(let data of updateData){
+                let equipment = _.find(this.data,{ID:data.ID});
+                if(equipment){
+                    Object.assign(equipment,data.doc);
+                }
+            }
+        } catch (error) {
+            alert('更新失败,请重试');
+        }
+        this.showData();
+        $.bootstrapLoading.end();
+    },
+    insertEquipments:async function(equipments){
+        try {
+            $.bootstrapLoading.start();
+            let projectID = projectObj.project.ID();
+            await ajaxPost('/equipmentPurchase/insertData', { projectID, equipments });
+            this.data.push(...equipments)  
+            this.showData();
+        } catch (error) {
+            alert('插入失败,请重试');
+        }
+        $.bootstrapLoading.end();
+    },
+    deleteEquipment:async function(ID){
+        try {
+            let projectID = projectObj.project.ID();
+            await ajaxPost('/equipmentPurchase/deleteEquipment', { projectID, ID });
+            _.remove(this.data,{ID});
+            this.showData();
+        } catch (error) {
+            alert('删除失败,请重试');
+        } 
+    },
+    registerInputContextMenuItem:function(){
+        const insertEquipmentHtml = `<span>插入&nbsp;&nbsp;<input id='insert-equipment-number' class="menu-input" type="text" value="1" onfocus="this.select()">&nbsp;&nbsp;行</span>`;
+        let me = this;    
+        return sheetCommonObj.registerInputContextMenuItem('insertEquipment', insertEquipmentHtml, 'fa-sign-in', async function () {  
+            const number = +$('#insert-equipment-number').val();
+            if (!number) {
+                return;
+            }  
+            const newData = [];
+            for (let i = 0; i < number; i++) {
+                newData.push(me.newEquipment());
+            }
+            me.insertEquipments(newData)   
+        });
+    },
+
+    initRightClick: function () {
+        let me = this;
+        $.contextMenu({
+          selector: '#equipmentSpread',
+          build: function ($trigger, e) {
+            me.rightClickTarget = SheetDataHelper.safeRightClickSelection($trigger, e, me.spread);
+            return me.rightClickTarget.hitTestType === GC.Spread.Sheets.SheetArea.viewport ||
+              me.rightClickTarget.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+          },
+          items: {
+            "insert": {
+              type:  me.registerInputContextMenuItem(),
+              disabled: function () {
+                return false;
+              },
+             /*  callback: function (key, opt) {
+                me.insertEquipments([me.newEquipment()])   
+              } */
+            },
+            "delete": {
+                name: "删除",
+                icon: 'fa-times',
+                disabled: function () {
+                  return me.rightClickTarget.row === undefined;
+                },
+                callback: function (key, opt) {
+                  let row = me.rightClickTarget.row;
+                  me.deleteEquipment(me.data[row].ID);
+                  //me.preApplyInfoPrice(row);
+                }
+              }
+          }
+        });
+      },
+}
+
+$(function () {
+    $('#tab_equipment_purchase').on('shown.bs.tab', function (e) {
+        sessionStorage.setItem('mainTab', '#tab_equipment_purchase');
+        $(e.relatedTarget.hash).removeClass('active');
+        equipmentPurchaseObj.initSpread();
+        equipmentPurchaseObj.showData();
+    })
+})

+ 584 - 0
web/building_saas/main/js/views/gather_fees_view.js

@@ -0,0 +1,584 @@
+let gatherFeesView = {
+    datas: [],
+    mainSpread: null,
+    detailSpread: null,
+    mainSheet: null,
+    detailSheet: null,
+    mainSetting: {
+        header:[
+            // {headerName:"ID",headerWidth:80,dataCode:"ID", hAlign: "center"},
+            {headerName:"名称",headerWidth:300,dataCode:"name", dataType: "String"}
+        ],
+        view:{
+            comboBox:[],
+            lockColumns:[0,1],
+            colHeaderHeight: CP_Col_Width.colHeader,
+            rowHeaderWidth: CP_Col_Width.rowHeader
+        }
+    },
+    detailSetting: {
+        header:[
+            // {headerName:"ID",headerWidth:80,dataCode:"ID", hAlign: "center"},
+            {headerName:"费用代号",headerWidth:CP_Col_Width.code, dataCode:"code", dataType: "String"},
+            {headerName:"费用名称",headerWidth:CP_Col_Width.name, dataCode:"name", dataType: "String"},
+            {headerName:"计算基数",headerWidth:CP_Col_Width.dispExprUser, dataCode:"dispExprUser", dataType: "String"},
+            {headerName:"费率",headerWidth:CP_Col_Width.feeRate, dataCode:"feeRate", dataType: "Number",hAlign: "right",decimalField:"feeRate"},
+            {headerName: "金额", headerWidth: CP_Col_Width.totalFee, dataCode: "totalFee", dataType: "Number"},
+            {headerName:"费用类别",headerWidth:CP_Col_Width.displayFieldName, dataCode:"displayFieldName", dataType: "String", hAlign: "center"},
+            {headerName:"基数说明",headerWidth:CP_Col_Width.statement, dataCode:"statement", dataType: "String"},
+            {headerName:"备注",headerWidth:CP_Col_Width.memo, dataCode:"memo", dataType: "String"}
+        ],
+        view:{
+            comboBox:[],
+            lockColumns:[0,1,2,3,4,5,6,7],
+            colHeaderHeight: CP_Col_Width.colHeader,
+            rowHeaderWidth: CP_Col_Width.rowHeader
+        },
+        //callback 和disable都是和 cusButton配套使用的
+        callback:{
+            calcBase:function (hitinfo) {
+                calcBaseView.onCalcBaseButtonClick(hitinfo,'ration');
+            }
+        },
+        disable:{
+            calcBase:function (row,col) {
+                return projectReadOnly || !calcBaseView.ifEdit('ration', row)
+            }
+        }
+    },
+
+    buildSheet: function (){
+        let me = this;
+        me.datas = projectObj.project.calcProgram.datas.templates;
+        if (me.mainSpread) {
+            me.mainSpread.destroy();
+            me.mainSpread = null;
+        };
+        if (me.detailSpread) {
+            me.detailSpread.destroy();
+            me.detailSpread = null;
+        };
+        me.mainSpread = sheetCommonObj.buildSheet($('#gfMainSpread')[0], me.mainSetting, me.datas.length);
+        me.mainSheet = me.mainSpread.getSheet(0);
+        sheetCommonObj.spreadDefaultStyle(me.mainSpread);
+        me.detailSpread = sheetCommonObj.buildSheet($('#gfDetailSpread')[0], me.detailSetting, me.datas[0].calcItems.length);
+        me.detailSheet = me.detailSpread.getSheet(0);
+        sheetCommonObj.spreadDefaultStyle(me.detailSpread);
+        let arr = projectObj.project.calcProgram.compiledFeeTypeNames.slice();
+        // arr.delete('暂估费');
+        // let fieldName = new GC.Spread.Sheets.CellTypes.ComboBox();
+        // fieldName.items(arr);
+        // me.detailSheet.getRange(-1, 4, -1, 1).cellType(fieldName);
+
+        me.mainSheet.bind(GC.Spread.Sheets.Events.EnterCell, me.onMainEnterCell);
+        me.detailSheet.bind(GC.Spread.Sheets.Events.ValueChanged, me.onDetailValueChanged);
+        me.detailSheet.bind(GC.Spread.Sheets.Events.RangeChanged, me.onRangeChanged);
+        me.detailSheet.bind(GC.Spread.Sheets.Events.EnterCell, me.onDetailEnterCell);
+        me.detailSheet.bind(GC.Spread.Sheets.Events.ClipboardPasting, me.onClipboardPasting);
+        sheetCommonObj.showData(me.mainSheet, me.mainSetting, me.datas);
+        me.mainSheet.setRowCount(me.datas.length);
+
+        me.detailSheet.name('gatherfee_detail');
+        feeRateObject.setFeeRateCellCol(me.detailSheet, _.findIndex(me.detailSetting.header,{'dataCode':'feeRate'}));
+        sheetCommonObj.showData(me.detailSheet, me.detailSetting, me.datas[0].calcItems);
+        me.getfeeRateColor(me.datas[0].calcItems);
+        customRowHeader(me.detailSheet, me.datas[0].calcItems.length);
+        if(!projectReadOnly){
+            // me.loadMainContextMenu();
+            // me.loadDetailContextMenu();
+        }
+        else {
+            if(me.mainSetting.view.lockColumns){
+                me.mainSetting.view.lockColumns = null;
+            }
+            if(me.detailSetting.view.lockColumns){
+                me.detailSetting.view.lockColumns = null;
+            }
+            sheetCommonObj.disableSpread(me.mainSpread);
+            sheetCommonObj.disableSpread(me.detailSpread);
+        }
+    },
+    getStdCalcProgramFiles: function(){
+        function getStdCPFilesHtml(stdCPLibs) {
+            let result = '<option value="">请选择计算程序标准库</option>';
+            if (stdCPLibs.length <= 0) {
+                return result;
+            };
+
+            for (let lib of stdCPLibs){
+                result += '<option value='+ lib.id +'>'+ lib.name +'</option>';
+            };
+            return result;
+        };
+
+        let stdCPHtml = getStdCPFilesHtml(projectObj.project.projectInfo.engineeringInfo.program_lib);
+        $("#calcProgramFileSelect").html(stdCPHtml);
+    },
+    onMainEnterCell: function(sender, args) {
+        var me = gatherFeesView;
+        var row = args.sheet.getActiveRowIndex();
+
+        me.detailSpread.suspendPaint();
+        var dData = me.datas[row].calcItems;
+        me.detailSheet.setRowCount(dData.length, GC.Spread.Sheets.SheetArea.viewport);
+        let gatherObj = projectObj.project.calcProgram.gatherRationFees(me.datas[row].ID);
+        for (const d of dData) {
+           d['totalFee'] = gatherObj[d.fieldName]?.totalFee;
+        }
+        sheetCommonObj.showData(me.detailSheet, me.detailSetting, dData);
+        me.getfeeRateColor(dData);
+        customRowHeader(me.detailSheet, dData.length);
+        me.detailSpread.resumePaint();
+    },
+    onRangeChanged:function (sender,args) {
+        let me = gatherFeesView;
+        let editInfo= me.getSelectionInfo();
+        let dataCode = me.detailSetting.header[args.col].dataCode;
+        // if (args.action == GC.Spread.Sheets.RangeChangedAction.clear&&args.changedCells.length == 1) {//清除操作
+        if (args.action == GC.Spread.Sheets.RangeChangedAction.clear) {//清除操作
+            if (dataCode == 'feeRate') {
+                $.bootstrapLoading.start();
+                projectObj.project.FeeRate.updateFeeRateFromCalc(null, editInfo);
+            }
+            else if (dataCode == 'displayFieldName'){
+                $.bootstrapLoading.start();
+                for (let cell of args.changedCells){
+                    let curCalcItem = editInfo.template.calcItems[cell.row];
+                    curCalcItem.fieldName = '';
+                    curCalcItem.displayFieldName = '';
+                };
+
+                let data = {
+                    'projectID': projectObj.project.ID(),
+                    'ID': editInfo.template.ID,
+                    'calcItems': editInfo.template.calcItems
+                };
+                gatherFeesView.updateTemplate(data, function (rst) {
+                    if (rst){
+                        gatherFeesView.refreshDetailSheet();
+                        $.bootstrapLoading.end();
+                    }
+                });
+            }
+        }
+    },
+    onDetailValueChanged: function(sender, args) {
+        $.bootstrapLoading.start();
+        let me = gatherFeesView;
+        let editInfo= me.getSelectionInfo();
+        let curCalcItem = editInfo.calcItem;
+        let dataCode = me.detailSetting.header[args.col].dataCode;
+
+        if (dataCode == 'feeRate') {
+            projectObj.project.FeeRate.updateFeeRateFromCalc(args.newValue, editInfo);
+            $.bootstrapLoading.end();
+            return;
+        }
+
+        let template = me.getSelectionInfo().template;
+        if (dataCode == 'displayFieldName') {
+            if (curCalcItem.displayFieldName == args.newValue) {
+                $.bootstrapLoading.end();
+                return;
+            }
+
+            if (analyzer.fieldNameIsUsed(template, args.newValue)){
+                let sheet = me.detailSpread.getActiveSheet();
+                sheet.suspendEvent();
+                sheet.setValue(args.row, args.col, args.oldValue);
+                sheet.resumeEvent();
+                $.bootstrapLoading.end();
+                let s = hintBox.font(template.fieldNameTempUsed + 1);
+                hintBox.infoBox('系统提示', `“${args.newValue}” 已被第 ${s} 行使用,不允许重复选择!`, 1);
+                delete template.fieldNameTempUsed;
+                return;
+            }
+
+            curCalcItem.fieldName = projectObj.project.calcProgram.compiledFeeTypeMaps[args.newValue];
+        }
+        else if (dataCode == 'dispExprUser'){  // 除非直接改单元格,弹窗不会走这里
+            hintBox.infoBox('系统提示', '用户直接在sheet中修改了dispExprUser值,触发onDetailEditEnded事件。', 1);
+        };
+
+        curCalcItem[dataCode] = args.newValue;
+
+        if (dataCode == 'name'){
+            analyzer.refreshUsedCalcItemsStatement(template, curCalcItem);
+            let data = {
+                'projectID': projectObj.project.ID(),
+                'ID': template.ID,
+                'calcItems': template.calcItems
+            };
+            gatherFeesView.updateTemplate(data, function (rst) {
+                if (rst){
+                    gatherFeesView.refreshDetailSheet();
+                    $.bootstrapLoading.end();
+                }
+            });
+            if (activeSubSheetIsCalcProgram())
+                calcProgramObj.refreshCalcProgram(projectObj.project.mainTree.selected, 1);
+        }
+        else{
+            let data = {
+                'projectID': projectObj.project.ID(),
+                'templatesID': template.ID,
+                'calcItem': curCalcItem
+            };
+            me.saveCalcItem(data, function (rst) {
+                if (rst){
+                    let relationNodes = calcTools.getNodesByProgramID(template.ID);
+                    projectObj.project.calcProgram.calcNodesAndSave(relationNodes);
+                    $.bootstrapLoading.end();
+                }
+            });
+        }
+    },
+    onDetailEnterCell: function (sender, args) {
+        // if (args.col == 2)    // 加这句,切换单元格后,原单元格的三点图片不会消失。
+            gatherFeesView.detailSheet.repaint();   // 这句是为了触发sheetCommonObj → getCusButtonCellType → CusButtonCellType → paint方法。
+        // for test.
+        // let t = gatherFeesView.getSelectionInfo().template;
+        // let c = gatherFeesView.getSelectionInfo().calcItem;
+        // let lc = analyzer.calcItemLabourCoe(c);
+        // c.dispExpr = analyzer.getDispExpr(c.expression, t);
+        // c.dispExprUser = analyzer.getDispExprUser(c.dispExpr, lc);
+        // c.compiledExpr = analyzer.getCompiledExpr(c.expression, lc);
+        // let e = `ID:${c.ID} ${c.expression} ${c.dispExpr} ${c.dispExprUser} ${c.compiledExpr} ${c.custom}`;
+        // projectObj.testDisplay('', e);
+    },
+    onClipboardPasting: function (sender, args) {
+         args.cancel = true;
+    },
+    loadMainContextMenu: function () {
+        $.contextMenu({
+            selector: '#gfMainSpread',
+            build: function ($trigger, e) {
+                SheetDataHelper.safeRightClickSelection($trigger, e, gatherFeesView.mainSpread);
+            },
+            items: {
+                "copyTemplate": {
+                    name: "另存为",
+                    icon: 'copy',
+                    callback: function (key, opt) {
+                        $.bootstrapLoading.start();
+                        let template = gatherFeesView.getSelectionInfo().template;
+                        let idx = gatherFeesView.mainSpread.getActiveSheet().getActiveRowIndex();
+
+                        let newTemplate = {};
+                        newTemplate.ID = analyzer.templateMaxID() + 1;
+                        newTemplate.name = analyzer.templateNewName(template.name);
+                        newTemplate.custom = true;
+                        newTemplate.calcItems = [];
+                        $.extend(true, newTemplate.calcItems, template.calcItems);
+                        // 清理掉费率ID关联
+                        for (let ci of newTemplate.calcItems){
+                            if (ci.feeRateID || ci.feeRateID == null)
+                                delete ci.feeRateID;
+                        };
+
+                        let data = {
+                            'projectID': projectObj.project.ID(),
+                            'ID': newTemplate.ID,
+                            'name': newTemplate.name,
+                            'custom': newTemplate.custom,
+                            'calcItems': newTemplate.calcItems
+                        };
+
+                        gatherFeesView.addTemplate(data, function (rst) {
+                            if (rst){
+                                let ts = projectObj.project.calcProgram.templates;
+                                ts.push(newTemplate);
+                                projectObj.project.calcProgram.compileTemplateMaps();
+                                projectObj.project.calcProgram.compileTemplate(newTemplate);
+                                gatherFeesView.buildSheet();
+                                gatherFeesView.mainSheet.setSelection(ts.length - 1, 0, 1, 1);
+                                gatherFeesView.mainSheet.showRow(ts.length - 1, GC.Spread.Sheets.VerticalPosition.center);
+                                gatherFeesView.refreshDetailSheet();
+                                $.bootstrapLoading.end();
+                            }
+                        });
+                    }
+                },
+                "reNameTemplate": {
+                    name: "重命名...",
+                    icon: 'edit',
+                    disabled: function () {
+                        let custom = gatherFeesView.getSelectionInfo().template.custom;
+                        let canReName = custom ? custom : false;
+                        return !canReName;
+                    },
+                    callback: function (key, opt) {
+                        $.bootstrapLoading.start();
+                        let template = gatherFeesView.getSelectionInfo().template;
+                        let idx = gatherFeesView.mainSpread.getActiveSheet().getActiveRowIndex();
+
+                        let newName = '';
+                        hintBox.valueBox('重命名', template.name, function () {
+                            newName = hintBox.value;
+
+                            if (!newName){
+                                hintBox.error(`名称不能为空!`);
+                                return false;
+                            };
+
+                            if (newName == template.name) {
+                                $.bootstrapLoading.end();
+                                return;
+                            }
+
+                            if (analyzer.templateNameIsExist(newName)){
+                                hintBox.error(`“${newName}” 已存在,请重新输入!`);
+                                return false;
+                            };
+
+                            template.name = newName;
+                            let data = {
+                                'projectID': projectObj.project.ID(),
+                                'ID': template.ID,
+                                'name': template.name
+                            };
+                            gatherFeesView.updateTemplate(data, function (rst) {
+                                if (rst){
+                                    projectObj.project.calcProgram.compileTemplateMaps();
+                                    projectObj.mainController.refreshTreeNode(calcTools.getNodesByProgramID(template.ID));
+                                    sheetCommonObj.showData(gatherFeesView.mainSpread.getSheet(0), gatherFeesView.mainSetting, gatherFeesView.datas);
+                                    $.bootstrapLoading.end();
+                                }
+                            });
+                        });
+                    }
+                },
+                "spr1": '--------',
+                "deleteTemplate": {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    disabled: function () {
+                        let custom = gatherFeesView.getSelectionInfo().template.custom;
+                        let canDelete = custom ? custom : false;
+                        return !canDelete;
+                    },
+                    callback: function () {
+                        $.bootstrapLoading.start();
+                        let template = gatherFeesView.getSelectionInfo().template;
+                        if (analyzer.templateIsUsed(template.ID)) {
+                            $.bootstrapLoading.end();
+                            hintBox.infoBox('系统提示', `计算模板“${template.name}”已被使用,不允许删除!`, 1);
+                            return;
+                        };
+                        hintBox.infoBox('系统提示', `确定要删除计算模板“${template.name}”吗?`, 2, cbYes);
+                        function cbYes() {
+                            let data = {
+                                'projectID': projectObj.project.ID(),
+                                'ID': template.ID
+                            };
+                            gatherFeesView.deleteTemplate(data, function (rst) {
+                                if (rst){
+                                    let idx = gatherFeesView.mainSheet.getActiveRowIndex();
+                                    projectObj.project.calcProgram.templates.splice(idx, 1);
+                                    projectObj.project.calcProgram.compileTemplateMaps();
+                                    gatherFeesView.buildSheet();
+                                    gatherFeesView.mainSheet.setSelection(idx - 1, 0, 1, 1);
+                                    gatherFeesView.mainSheet.showRow(idx - 1, GC.Spread.Sheets.VerticalPosition.center);
+                                    gatherFeesView.refreshDetailSheet();
+                                    $.bootstrapLoading.end();
+                                }
+                            });
+                        };
+                    }
+                }
+            }
+        });
+    },
+    loadDetailContextMenu: function () {
+        $.contextMenu({
+            selector: '#gfDetailSpread',
+            build: function ($triggerElement, event) {
+                SheetDataHelper.safeRightClickSelection($triggerElement, event, gatherFeesView.detailSpread);
+            },
+            items: {
+                "newCalcItem": {
+                    name: "插入行",
+                    icon: 'fa-sign-in',
+                    callback: function () {
+                        $.bootstrapLoading.start();
+                        let template = gatherFeesView.getSelectionInfo().template;
+                        let idx = gatherFeesView.detailSpread.getActiveSheet().getActiveRowIndex();
+
+                        let newItem = {};
+                        newItem.ID = analyzer.calcItemMaxID(template) + 1;
+                        // newItem.name = '新建';
+                        newItem.memo = '自定义';
+                        newItem.custom = true;
+                        newItem.expression = '0';                           // 这里必须得有值,否则程序内部公式解析会出现诸多问题
+                        template.calcItems.splice(idx + 1, 0, newItem);
+                        let data = {
+                            'projectID': projectObj.project.ID(),
+                            'ID': template.ID,
+                            'calcItems': template.calcItems
+                        };
+                        gatherFeesView.updateTemplate(data, function (rst) {
+                            if (rst){
+                                projectObj.project.calcProgram.compileTemplate(template);
+                                gatherFeesView.refreshDetailSheet();
+                                gatherFeesView.detailSpread.getActiveSheet().setSelection(idx + 1, 0, 1, 1);
+                                let relationNodes = calcTools.getNodesByProgramID(template.ID);
+                                projectObj.project.calcProgram.calcNodesAndSave(relationNodes);
+                            }
+                        });
+                        $.bootstrapLoading.end();
+                    }
+                },
+                "spr1": '--------',
+                "deleteCalcItem": {
+                    name: '删除行',
+                    icon: 'fa-remove',
+                    callback: function () {
+                        $.bootstrapLoading.start();
+                        let template = gatherFeesView.getSelectionInfo().template;
+                        let idx = gatherFeesView.detailSpread.getActiveSheet().getActiveRowIndex();
+                        let item = template.calcItems[idx];
+
+                        if (item.fieldName == 'common'){
+                            $.bootstrapLoading.end();
+                            hintBox.infoBox('系统提示', `费用类别为“工程造价”的行不允许删除!`, 1);
+                            return;
+                        };
+
+                        if (analyzer.calcItemIsUsed(template, item)){
+                            $.bootstrapLoading.end();
+                            let s = hintBox.font(item.tempUsed + 1);
+                            hintBox.infoBox('系统提示', `第 ${idx + 1} 行“${item.name}”已被第 ${s} 行引用,不允许删除!`, 1);
+                            delete item.tempUsed;
+                            return;
+                        };
+
+                        hintBox.infoBox('系统提示', `确定要删除计算规则“${item.name}”吗?`, 2, cbYes);
+                        function cbYes() {
+                            template.calcItems.splice(idx, 1);
+                            let data = {
+                                'projectID': projectObj.project.ID(),
+                                'ID': template.ID,
+                                'calcItems': template.calcItems
+                            };
+                            gatherFeesView.updateTemplate(data, function (rst) {
+                                if (rst){
+                                    projectObj.project.calcProgram.compileTemplate(template);
+                                    gatherFeesView.refreshDetailSheet();
+                                    let relationNodes = calcTools.getNodesByProgramID(template.ID);
+                                    projectObj.project.calcProgram.calcNodesAndSave(relationNodes);
+                                    $.bootstrapLoading.end();
+                                }
+                            });
+                        };
+                    }
+                }
+            }
+        });
+
+    },
+    saveCalcItem: function (data, callback) {//data
+        CommonAjax.post('/calcProgram/saveCalcItem', data,
+            function (result) {
+                if(callback){
+                    callback(result);
+                }
+            }
+        );
+    },
+    updateTemplate: function (data, callback) {
+        CommonAjax.post('/calcProgram/updateTemplate', data,
+            function (result) {
+                if(callback){
+                    callback(result);
+                }
+            }
+        );
+    },
+    addTemplate: function (data, callback) {
+        CommonAjax.post('/calcProgram/addTemplate', data,
+            function (result) {
+                if(callback){
+                    callback(result);
+                }
+            }
+        );
+    },
+    deleteTemplate: function (data, callback) {
+        $.bootstrapLoading.start();
+        CommonAjax.post('/calcProgram/deleteTemplate', data,
+            function (result) {
+                if(callback){
+                    callback(result);
+                }
+                $.bootstrapLoading.end();
+            }
+        );
+    },
+    getSelectionInfo:function () {
+        var templateIndex = this.mainSpread.getActiveSheet().getActiveRowIndex();
+        var dIndex = this.detailSpread.getActiveSheet().getActiveRowIndex();
+        var info = {
+            template:this.datas[templateIndex],
+            calcItem:this.datas[templateIndex].calcItems[dIndex]
+        }
+        return info;
+    },
+    refreshDetailSheet:function () {
+        var me = this;
+        if(me.mainSpread && me.detailSpread){
+            let mainRowIdx = me.mainSpread.getActiveSheet().getActiveRowIndex();
+            let calcItems = me.datas[mainRowIdx].calcItems;
+            let detailSheet = me.detailSpread.getActiveSheet();
+            detailSheet.setRowCount(calcItems.length);
+            sheetCommonObj.showData(detailSheet, me.detailSetting, calcItems);
+            me.getfeeRateColor(calcItems);
+            customRowHeader(detailSheet, calcItems.length);
+        }
+    },
+    getfeeRateColor: function (calcItems) {    // 有费率ID关联的变个色
+        var me = this;
+        for (let i = 0; i < calcItems.length; i++) {
+            if (calcItems[i].feeRateID != undefined && calcItems[i].feeRateID != null)
+                me.detailSheet.getCell(i, 3).foreColor("#0aa8ea")
+            else
+                me.detailSheet.getCell(i, 3).foreColor("black");
+        }
+    }
+};
+
+$(document).ready(function(){
+    $('#tab_gather_fees').on('shown.bs.tab', function (e) {
+        sessionStorage.setItem('mainTab', '#tab_gather_fees');
+        $(e.relatedTarget.hash).removeClass('active');
+        if (!gatherFeesView.mainSpread)
+            gatherFeesView.buildSheet()
+        else
+            gatherFeesView.mainSpread.refresh();
+
+        let count = gatherFeesView.datas[gatherFeesView.mainSheet.getActiveRowIndex()].calcItems.length;
+        gatherFeesView.detailSheet.setRowCount(count, GC.Spread.Sheets.SheetArea.viewport);
+    });
+
+    $("#calcProgramFileSelect").change(function() {
+        // 取标准库数据过来显示。
+        let libID = $(this).val();
+        if (libID == ''){
+            gatherFeesView.mainSpread.getSheet(0).setRowCount(0);
+            gatherFeesView.detailSpread.getSheet(0).setRowCount(0);
+            return false;
+        };
+
+        $.bootstrapLoading.start();
+        libID = parseFloat(libID);
+        let projectID = projectObj.project.ID();
+        CommonAjax.post('/calcProgram/updateTemplateFile', {"projectID": projectID, "libID": libID}, function (data) {
+            projectObj.project.calcProgram.datas.templates = data;
+            projectObj.project.calcProgram.compileAllTemps();
+            projectObj.project.calcProgram.calcAllNodesAndSave();
+            gatherFeesView.buildSheet();
+            $.bootstrapLoading.end();
+        });
+    });
+});
+
+

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

@@ -754,7 +754,7 @@ var gljOprObj = {
     calcMixRationTotalQuantity(mList,pTotal){ //计算组成物的总消耗量
         for (let subG of mList) {
             subG.rationItemQuantity = scMathUtil.roundForObj(subG.rationItemQuantity, getDecimal("glj.quantity"));
-            subG.totalQuantity = scMathUtil.roundToString(subG.rationItemQuantity * pTotal, getDecimal("glj.quantity"));
+            subG.totalQuantity = scMathUtil.roundForObj(subG.rationItemQuantity * pTotal, getDecimal("glj.quantity"))+'';
         }
     },
     addMixRatioToShow: function () {

+ 1 - 0
web/building_saas/main/js/views/glj_view_contextMenu.js

@@ -373,6 +373,7 @@ var gljContextMenu = {
                         installationFeeObj.applyRuleTo('FX',gljContextMenu.selectedRow);
                     },
                     visible: function(key, opt){
+                        return false
                         return subSpread.getActiveSheet().name()=='rationInstallSheet';
                     }
                 },

+ 4 - 4
web/building_saas/main/js/views/installation_fee_view.js

@@ -17,7 +17,7 @@ let installationFeeObj={
             {headerName: "材料(%)", headerWidth: 70, dataCode: "material", hAlign: "right", dataType: "String"},
             {headerName: "机械(%)", headerWidth: 70, dataCode: "machine", hAlign: "right", dataType: "String"},
             {headerName: "费用类型", headerWidth: 100, dataCode: "feeType", hAlign: "center", dataType: "String",cellType:'comboBox',options:installFeeType},
-            {headerName: "记取位置", headerWidth: 200, dataCode: "position", hAlign: "left", dataType: "String",cellType:'selectButton',getText:'forPosition'}
+            /* {headerName: "记取位置", headerWidth: 200, dataCode: "position", hAlign: "left", dataType: "String",cellType:'selectButton',getText:'forPosition'} */
         ],
         view: {
             lockColumns: [0,1,3,4,5,6,7,8,9,10]
@@ -49,7 +49,7 @@ let installationFeeObj={
             {headerName: "计取", headerWidth: 50, dataCode: "isCal", dataType: "String",cellType: "checkBox"},
             {headerName: "费用项", headerWidth: 300, dataCode: "feeItem", hAlign: "left", dataType: "String"},
             {headerName: "费用类型", headerWidth: 100, dataCode: "feeType", hAlign: "center", dataType: "String",cellType:'comboBox',options:installFeeType},
-            {headerName: "记取位置", headerWidth: 180, dataCode: "displayPosition", hAlign: "left", dataType: "String",cellType:'selectButton'}
+            /* {headerName: "记取位置", headerWidth: 180, dataCode: "displayPosition", hAlign: "left", dataType: "String",cellType:'selectButton'} */
         ],
         view: {
             lockColumns: [0,1]
@@ -68,7 +68,7 @@ let installationFeeObj={
             {headerName: "人工(%)", headerWidth: 75, dataCode: "labour", hAlign: "right", dataType: "String"},
             {headerName: "材料(%)", headerWidth: 75, dataCode: "material", hAlign: "right", dataType: "String"},
             {headerName: "机械(%)", headerWidth: 75, dataCode: "machine", hAlign: "right", dataType: "String"},
-            {headerName: "记取位置", headerWidth: 180, dataCode: "position", hAlign: "left", dataType: "String",cellType:'selectButton'}
+           /*  {headerName: "记取位置", headerWidth: 180, dataCode: "position", hAlign: "left", dataType: "String",cellType:'selectButton'} */
         ],
         view: {
             lockColumns: [0, 2]
@@ -118,7 +118,7 @@ let installationFeeObj={
             {headerName: "其中人工(%)", headerWidth: 100, dataCode: "labour", hAlign: "right", dataType: "String"},
             {headerName: "其中材料(%)", headerWidth: 100, dataCode: "material", hAlign: "right", dataType: "String"},
             {headerName: "其中机械(%)", headerWidth: 100, dataCode: "machine", hAlign: "right", dataType: "String"},
-            {headerName: "记取位置", headerWidth: 100, dataCode: "position", hAlign: "left", dataType: "String",cellType:'selectButton',getText:'forPosition'}
+            /* {headerName: "记取位置", headerWidth: 100, dataCode: "position", hAlign: "left", dataType: "String",cellType:'selectButton',getText:'forPosition'} */
         ],
         view: {
             lockColumns: [0, 1]

+ 19 - 16
web/building_saas/main/js/views/project_view.js

@@ -301,7 +301,7 @@ var projectObj = {
                 let libId = projectObj.project.projectInfo.engineeringInfo.bill_lib[0].id;
                 CommonAjax.post('/stdBillsEditor/getStdBillsByCode', {userId: userID, billsLibId: libId, code: stdCode}, function (data) {
                     if (data) {
-                        
+
                         function updateBeforeInsert(node, data) {
                             node.data.name = data.name;
                             if(node.data.type == billType.BX){//从清单库中找到标准清单的话,要把补项改成分项
@@ -520,7 +520,8 @@ var projectObj = {
                 };
                 //计算基数赋值要经过解析和标准化,已在calculate里赋值
                 if(fieldName !== 'calcBase'){
-                    calcTools.setFieldValue(node, fieldName, value);
+                  calcTools.setFieldValue(node, fieldName, value);
+                  projectObj.mainController.refreshTreeNode([node]);  // 量价,输入单价x,反算单价x1,第二次输入单价x,单价不再反算。(实际反算了,没刷新)
                 };
 
                 project.calcProgram.calcAndSave(node);
@@ -562,7 +563,7 @@ var projectObj = {
         projectObj.lastCell = {row: info.row, col: info.col};
         if(colSetting.data.field === 'quantity'){
             SheetDataHelper.hideMoreButton()
-        }  
+        }
     },
     //滚动造价书鼠标时,工程量的更多按钮跟着移动
     TopRowChanged:function(type,info){
@@ -570,7 +571,7 @@ var projectObj = {
         if(colSetting.data.field === 'quantity'){
             SheetDataHelper.moveMoreButton(info.sheet)
         }
-        
+
     },
 
     //repaint 动态下拉框
@@ -639,7 +640,7 @@ var projectObj = {
             }
         }
         if(node.sourceType == ModuleNames.ration){ //在定额编码中双击,如果右侧定额库没有展开,则自动展开。
-            if(node.data.type != rationType.ration) return ;//只有定额类型才需要自动展开,其它都不用 
+            if(node.data.type != rationType.ration) return ;//只有定额类型才需要自动展开,其它都不用
             let libID = node.data.libID;
             if (node.data.from === 'cpt') {
                 libID = node.data.fromUser ? `${rationLibObj.compleRationLibId}*${node.data.fromUser}` : rationLibObj.compleRationLibId;
@@ -978,7 +979,7 @@ var projectObj = {
                 });
                 let startShowTime = +new Date();
                 that.mainController = TREE_SHEET_CONTROLLER.createNew(that.project.mainTree, that.mainSpread.getActiveSheet(), that.project.projSetting.mainGridSetting);
-               
+
                 let endShowTime = +new Date();
                 console.log(`show data时间——${endShowTime - startShowTime}`);
                 that.mainController.bind('refreshBaseActn', that.refreshBaseActn);
@@ -1013,12 +1014,12 @@ var projectObj = {
 
 
                 //工程量悬浮窗移动事件
-                that.mainSpread.bind(GC.Spread.Sheets.Events.TopRowChanged, that.TopRowChanged);    
+                that.mainSpread.bind(GC.Spread.Sheets.Events.TopRowChanged, that.TopRowChanged);
                 //let loadOtherStartTime = +new Date();
                 //if(!projectReadOnly){
                     that.loadMainSpreadContextMenu();
                 //}
-                
+
                 socketObject.connect('main', { projectReadOnly: !!projectReadOnly, user: { ...projectObj.project.projectInfo.opener } });//连接socket服务器
                 let endTime = +new Date();
                 console.log(`其它时间——${endTime - endShowTime}`);
@@ -1183,7 +1184,7 @@ var projectObj = {
                 //如果其后有定额/量价/人材机(空行:编码为空不算),焦点应跳动至下一行定额/量价/人材机的工程量,不需处理
 
                 //如果其后没有定额/量价/人材机(空行也没有),则自动在其后插入一行定额空行,焦点跳动至定额空行的编码单元格
-                if(!nextSibling){
+             /*    if(!nextSibling){
                     let codeCol = colSettingObj.getColByField('code');
                     let codeVisible = colSettingObj.getVisible('code');
                     me.project.Ration.addNewRation(null, rationType.ration, function () {
@@ -1193,9 +1194,9 @@ var projectObj = {
                         }
                         sheet.setActiveCell(newRow, newCol);
                     }, true);
-                }
+                } */
                 //如果其后有定额空行,焦点跳动至定额空行的编码单元格
-                else if(nextSibling && !(isDef(nextSibling.data.code) && nextSibling.data.code.toString().trim() !== '')) {
+                if(nextSibling && !(isDef(nextSibling.data.code) && nextSibling.data.code.toString().trim() !== '')) {
                     let codeCol = colSettingObj.getColByField('code');
                     let codeVisible = colSettingObj.getVisible('code');
                     if(codeCol !== null && codeVisible !== false){
@@ -1342,16 +1343,16 @@ var projectObj = {
                         return true;//除了清单,其它类型都只读
                     },
                     callback: function (key, opt) {
-                        /* 2020-12-28 插入前先弹窗选择 
+                        /* 2020-12-28 插入前先弹窗选择
                         let selected = project.mainTree.selected;
                         if(selected.data.type==billType.FX || selected.data.type==billType.BX){
                              //添加成分项的父亲
-                             ProjectController.addFXParent(selected);   
+                             ProjectController.addFXParent(selected);
                         }else{//正常添加分部
                             ProjectController.addFB(project, controller);
                             projectObj.selectColAndFocus(project.mainTree.selected);
                         } */
-                        $("#selectFBFor").val("add");   
+                        $("#selectFBFor").val("add");
                         $('#selectFBDiv').modal('show');
                     },
                     visible: function(key, opt){
@@ -1391,6 +1392,7 @@ var projectObj = {
                         return true;//除了清单,其它类型都只读
                     },
                     visible: function(key, opt){
+                        return false
                         if(project.mainTree.selected){
                             return project.Bills.isFBFX(project.mainTree.selected );//不属于分部分项的话隐藏
                         }else {
@@ -1411,6 +1413,7 @@ var projectObj = {
                         return true;
                     },
                     visible: function(key, opt){
+                        return false
                         if(project.mainTree.selected){
                             return  project.Bills.isFBFX(project.mainTree.selected)==true?false:true;
                         }else {
@@ -1784,7 +1787,7 @@ var projectObj = {
                             project.calcProgram.calcAllNodesAndSave();
                             $.bootstrapLoading.end();
                         },100)
-                        
+
                     },
                     disabled: function () {
                         if (projectReadOnly) {
@@ -3503,7 +3506,7 @@ $(function () {
         if ($('#clone_option_unit').prop("checked")) options.checkUnit = true;
         if ($('#clone_option_zeroQuantity').prop("checked")) options.zeroQuantity = true;
         if ($('#clone_option_cover').prop("checked")) options.overwriteRations = true;
-        
+
         let nodes = [];
         let calcNodes = [];
         switch (blockLibObj.cloneType) {

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

@@ -428,7 +428,7 @@ let tender_obj={
         let tenderSetting = projectObj.project.property.tenderSetting;
         let gljPriceTenderCoe = tenderSetting && tenderSetting.gljPriceTenderCoe?tenderSetting.gljPriceTenderCoe:1;
         let showTenderFields = tenderSetting && tenderSetting.showTenderFields?tenderSetting.showTenderFields:false;
-        let calcPriceOption = calcTools.getTenderCalcType();
+        let calcPriceOption = calcTools.getTenderTypeStr();
         $('#calcPriceOption').val(calcPriceOption);
         $('#gljPriceTenderCoe').val(gljPriceTenderCoe);
         $('#cbShowTenderFields').prop("checked", showTenderFields);