|  | @@ -1056,12 +1056,31 @@ 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;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +   
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -2001,7 +2020,7 @@ class CalcProgram {
 | 
	
		
			
				|  |  |              };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (tenderType == tenderTypes.ttReverseRation || tenderType == tenderTypes.ttReverseGLJ)
 | 
	
		
			
				|  |  | -                this.calcTenderReverse(treeNode, tenderType);
 | 
	
		
			
				|  |  | +              this.reverseTenderCalc(treeNode, tenderType);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              me.deleteUselessFees(treeNode, fnArr);
 | 
	
		
			
				|  |  |          };
 | 
	
	
		
			
				|  | @@ -2059,7 +2078,8 @@ class CalcProgram {
 | 
	
		
			
				|  |  |              $.bootstrapLoading.end();
 | 
	
		
			
				|  |  |          });
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  | -    // 计算本节点及所有会被影响到的节点,如:所有父节点(默认,可选)、公式引用节点(默认,可选)。修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点。
 | 
	
		
			
				|  |  | +    // 计算本节点及所有会被影响到的节点,如:所有父节点(默认,可选)、公式引用节点(默认,可选)。
 | 
	
		
			
				|  |  | +    // 修改一个树节点,实际上要计算和保存的是一批树结点:层层父结点、被其它结点(的公式)引用的公式结点。
 | 
	
		
			
				|  |  |      calculate(treeNode, calcParents = true, calcFormulas = true, tender){
 | 
	
		
			
				|  |  |          let me = this;
 | 
	
		
			
				|  |  |          let changedNodes = [];
 | 
	
	
		
			
				|  | @@ -2120,7 +2140,7 @@ class CalcProgram {
 | 
	
		
			
				|  |  |          calcNodes(me.project.mainTree.roots);
 | 
	
		
			
				|  |  |          me.calcFormulaNodes(changedNodes, tender);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        if (tender){    // 普通计算不执行,只有3种调价计算才清。
 | 
	
		
			
				|  |  | +        if (tender){   
 | 
	
		
			
				|  |  |              for(let node of projectObj.project.mainTree.items){
 | 
	
		
			
				|  |  |                  this.clearTenderCache(node);
 | 
	
		
			
				|  |  |              };
 | 
	
	
		
			
				|  | @@ -2240,6 +2260,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 +2281,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 +2297,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 +2308,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 +2326,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 +2396,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 +2432,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 +2445,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 +2489,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 +2713,31 @@ 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){
 | 
	
		
			
				|  |  | +    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);
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -          this.initReverseTenderDatas();
 | 
	
		
			
				|  |  | -          this.prepareForDistribute(tender_obj.tenderTree.roots[0]);
 | 
	
		
			
				|  |  | -          this.distributeTargetTotalFee(tender_obj.tenderTree.roots[0]);
 | 
	
		
			
				|  |  | -        };
 | 
	
		
			
				|  |  | -        this.calcAllNodesAndSave(calcAllType.catAll, callback, tender);
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  | +   
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // export default analyzer;
 |