Pārlūkot izejas kodu

feat: 限价功能

vian 5 gadi atpakaļ
vecāks
revīzija
6e4f22643b

+ 4 - 0
modules/all_models/bills.js

@@ -96,6 +96,10 @@ let billsSchema = new Schema({
     default: 0
   }, // 1 true 0 false 单价分析
   specialProvisional: String,
+  outPutMaxPrice:{type:Schema.Types.Mixed,default:false},//输出最高限价 true 是,false否,null 不确定,三个状态
+  outPutLimitPrice:{type:Schema.Types.Mixed,default:false},//输出限价 true 是,false否,null 不确定,三个状态
+  maxPrice:String,//最高限价
+  minPrice:String,//最低限价
   remark: String,
   engineeringContent: String, //工程内容
   serviceContent: String, //服务内容

+ 1 - 0
modules/all_models/projects.js

@@ -18,6 +18,7 @@ const ProjectSchema = new Schema({
     "ParentID": Number,
     "NextSiblingID": Number,
     "userID": String,
+    "importedByInterface": {type: Boolean, default: false},
     "name": String,
     "projType": String,
     "recentDateTime": Date,

+ 2 - 0
modules/pm/facade/pm_facade.js

@@ -1970,6 +1970,8 @@ async function importInterfaceProject(importObj, userID, compilation, overWriteU
     }
     //给单位工程设置一些数据
     async function setupTender(data) {
+        // 将项目标记为通过接口导入的项目
+        data.importedByInterface = true;
         if (!data.property.decimal) {
             //小数位数 需要修改,所以深拷贝
             data.property.decimal = JSON.parse(JSON.stringify(defaultDecimal));

+ 67 - 6
public/web/sheet/sheet_common.js

@@ -400,10 +400,19 @@ var sheetCommonObj = {
         sheet.getCell(row, col).hAlign(GC.Spread.Sheets.HorizontalAlign.center);
 
     },
-    getCheckBox(threeState = false) {
-      var c = new GC.Spread.Sheets.CellTypes.CheckBox();
-      c.isThreeState(threeState);
-      return c
+    getCheckBox(threeState = false){
+        var c = new GC.Spread.Sheets.CellTypes.CheckBox();
+        c.isThreeState(threeState);
+        return c
+    },
+    // 无法勾选的复选框
+    getReadOnlyCheckBox () {
+        function ReadOnlyCheckBox() {}
+        ReadOnlyCheckBox.prototype = new GC.Spread.Sheets.CellTypes.CheckBox();
+        ReadOnlyCheckBox.prototype.processMouseUp = function () {
+            return;
+        };
+        return new ReadOnlyCheckBox();
     },
     setComboBox(row, col, sheet, options, editorValueType, editable, maxDropDownItems) {
         //let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
@@ -763,7 +772,59 @@ var sheetCommonObj = {
         };
         return new ComboCellForActiveCell();
     },
-    getTipsCombo: function (forLocked, tips, setting, node) {
+    getTipsCell: function (baseCell, cellPrototype, tips, setting, node) {
+        let getTipsCell = function () {
+            this.clickCom=false;
+        };
+        getTipsCell.prototype = baseCell;
+        if(tips && tips !=""){
+            getTipsCell.prototype.processMouseEnter = function(hitinfo){
+                if(this.clickCom == true){ //点击了下拉框的三角形,则不用再显示悬浮框了
+                    this.clickCom = false;
+                    return;
+                }
+                let text =  typeof tips == 'function'?tips(node):tips;
+                TREE_SHEET_HELPER.delayShowTips(hitinfo,setting,text);
+            };
+            getTipsCell.prototype.processMouseLeave = function (hitinfo) {
+                TREE_SHEET_HELPER.hideTipsDiv();
+            };
+            getTipsCell.prototype.processMouseDown = function (hitinfo){
+                if(hitinfo.isReservedLocation == true){//这里是点击了下拉框的三角形才会有这个属性
+                    TREE_SHEET_HELPER.hideTipsDiv();
+                    this.clickCom = true;
+                }
+                cellPrototype.processMouseDown.apply(this, arguments);
+            };
+
+            getTipsCell.prototype.updateEditor = function (editorContext, cellStyle, cellRect, context){
+                TREE_SHEET_HELPER.hideTipsDiv();
+                cellPrototype.updateEditor.apply(this, arguments);
+            };
+        }
+        return new getTipsCell();
+    },
+    getTipsText: function (tips, setting, node) {
+        function baseTextCell() {}
+        baseTextCell.prototype = new GC.Spread.Sheets.CellTypes.Text();
+        baseTextCell.prototype.getHitInfo =  function (x, y, cellStyle, cellRect, context) {
+            return {
+                x: x,
+                y: y,
+                row: context.row,
+                col: context.col,
+                cellStyle: cellStyle,
+                cellRect: cellRect,
+                sheetArea: context.sheetArea
+            };
+        };
+        return this.getTipsCell(new baseTextCell(), GC.Spread.Sheets.CellTypes.Text.prototype, tips, setting, node);
+    },
+    getTipsCombo:function (forLocked,tips,setting,node) {
+        const baseCell = sheetCommonObj.getDynamicCombo(forLocked);
+        return this.getTipsCell(baseCell, GC.Spread.Sheets.CellTypes.ComboBox.prototype, tips, setting, node);
+    },
+    /* getTipsCombo: function (forLocked, tips, setting, node) {
         let getTipsCombo = function () {
             this.clickCom = false;
         };
@@ -794,7 +855,7 @@ var sheetCommonObj = {
             };
         }
         return new getTipsCombo();
-    },
+    }, */
     getTreeNodeCellType: function (datas, row, parentMap,treeCol, paintFunc) {// 2018-09-26  不用spreadjs默认的树结构,自定义控件
         var ns = GC.Spread.Sheets;
         let rectW = 10;

+ 8 - 1
public/web/tree_sheet/tree_sheet_helper.js

@@ -158,6 +158,7 @@ var TREE_SHEET_HELPER = {
                     }
                     return data;
                 };
+                var cell = sheet.getCell(iRow, iCol, GC.Spread.Sheets.SheetArea.viewport);
                 if(sheet.name()=="mainSheet"){
                  /*   if(colSetting.data.field=="quantity"){  2018-08-06 去掉工程量列表达式
                         let tag = node.data.quantityEXP?node.data.quantityEXP:'';
@@ -172,15 +173,21 @@ var TREE_SHEET_HELPER = {
                         }*/
                         if(tag!=null) sheet.setTag(iRow, iCol,tag);
                     }
+                    // 单元格字体颜色
+                    const foreColorFunc = MainTreeCol.foreColor[colSetting.data.field];
+                    if (foreColorFunc) {
+                        cell.foreColor(foreColorFunc(node));
+                    }
                     /*if(colSetting.data.field=="name"){ 2018-08-06 改成在编号列悬浮提示
                         let tag = node.data.itemCharacterText?node.data.itemCharacterText:'';
                         sheet.setTag(iRow, iCol,tag);
                     }*/
                 }
                 if(colSetting.visible == false) return;//隐藏列不做其它操作
-                var cell = sheet.getCell(iRow, iCol, GC.Spread.Sheets.SheetArea.viewport);
                 if (colSetting.data.getText && Object.prototype.toString.apply(colSetting.data.getText) === "[object Function]") {
                     cell.value(colSetting.data.getText(node));
+                } else if(['outPutMaxPrice', 'outPutLimitPrice'].includes(colSetting.data.field) && node.sourceType === projectObj.project.Bills.getSourceType()){//主要清单有三种状态,所以直接显示就好,不走最后的逻辑
+                    cell.value(node.data[colSetting.data.field]===undefined?false:node.data[colSetting.data.field]);
                 } else {
                     cell.value(getFieldText2());
                 }

+ 5 - 0
web/building_saas/css/custom.css

@@ -607,4 +607,9 @@ input.text-right {
 
 .add-select-bills-btn {
   margin: 5px 0;
+}
+
+.limit-price-input {
+  display: inline-block;
+  width: 80px;
 }

+ 13 - 0
web/building_saas/main/html/main.html

@@ -803,6 +803,19 @@
                                             </div>
 
                                         </fieldset>
+                                        <% if (!projectData.importedByInterface) { %>
+                                            <fieldset class="form-group">
+                                              <h5>清单限价</h5>
+                                              <div class="mt-1">
+                                                最高限价<input id="max-price-rate" data-limit="max" class="form-control form-control-sm limit-price-input" value="0" type="text"> %
+                                              </div>
+                                              <% if (compilationName !== '重庆定额(2018)') { %>
+                                                <div class="mt-1">
+                                                  最低限价<input id="min-price-rate" data-limit="min" class="form-control form-control-sm limit-price-input" value="0" type="text"> %
+                                                </div>
+                                              <% } %>
+                                            </fieldset>
+                                            <% } %>
                                     </div>
                                 </div>
                                 <!--清单工程精度-->

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

@@ -1046,6 +1046,32 @@
      let arr = Object.keys(obj);
      return arr.length == 0;
    },
+       // 清单价格是否大于最高限价
+   unitFeeGTMaxPrice: function (node, feeField) {
+     if (!this.isBill(node)) {
+       return false;
+     }
+     const totalFee = this.getFee(node, feeField);
+     const maxPrice = node.data.maxPrice;
+     // 最高限价有值才对比
+     if (!commonUtil.isDef(maxPrice)) {
+       return false;
+     }
+     return totalFee > maxPrice;
+   },
+   // 清单价格是否小于最低限价
+   unitFeeLTMinPrice: function (node, feeField) {
+     if (!this.isBill(node)) {
+       return false;
+     }
+     const totalFee = this.getFee(node, feeField);
+     const minPrice = node.data.minPrice;
+     // 最低限价有值才对比
+     if (!commonUtil.isDef(minPrice)) {
+       return false;
+     }
+     return totalFee < minPrice;
+   },
    isBudgetProject() {
      return projectObj.project.property.valuationType == 'bill';
    },

+ 18 - 6
web/building_saas/main/js/models/project.js

@@ -459,7 +459,7 @@ var PROJECT = {
               type:node.sourceType,
               data:{ID:node.data.ID}
           };
-          setData(data.data,newval,fieldName);
+          setData(data.data,newval,fieldName, node);
           datas.push(data);
           setChildren(node,newval,datas);//同步设置所有子项
           if(needSetParent) setParent(node,newval,datas);//设置父节点
@@ -491,7 +491,7 @@ var PROJECT = {
                       };
                       //有基数计算的子项值清空
                       let val = fieldName == "lockUnitPrice" && c.data.calcBase && c.data.calcBase != ""?null:newval;
-                      setData(data.data,val,fieldName);
+                      setData(data.data,val,fieldName, c);
                       datas.push(data);
                       setChildren(c,newValue,datas)
                   }
@@ -499,7 +499,7 @@ var PROJECT = {
           }
           function setParent(cnode,newValue,datas) {
               let diferrent = false;
-              if(cnode.parent && !projectObj.project.Bills.isMeasureNode(cnode.parent)){//排除措施项目节点
+              if(cnode.parent){//排除措施项目节点
                   for(b of cnode.parent.children){
                       if(b == cnode) continue
                       if(b.data[fieldName]!== newValue){//有兄弟节点的值和本节点不一样,则父节点设置为null
@@ -512,14 +512,26 @@ var PROJECT = {
                       type:cnode.parent.sourceType,
                       data:{ID:cnode.parent.data.ID}
                   };
-                  setData(data.data,pvalue,fieldName);
+                  setData(data.data,pvalue,fieldName, cnode.parent);
                   datas.push(data);
                   setParent(cnode.parent,pvalue,datas);
               }
           }
-          function setData(data,avalue,fieldName) {
+          function setData(data,avalue,fieldName, node) {
               data[fieldName] = avalue;
-              if(fieldName == "outPutMaxPrice") data.maxPrice = null;
+               // 二次修改项目属性上下限设置,前端的最高、最低限价不重算,要去掉输出限价列的√,再重新勾选才计算。因此输出最高限价、输出限价时,需要给节点的maxPrice、minPrice设置一个算好的值
+               if (['outPutLimitPrice', 'outPutMaxPrice'].includes(fieldName)) {
+                if (avalue) {
+                    const unitFee = node.data && node.data.feesIndex && node.data.feesIndex.common && node.data.feesIndex.common.unitFee || 0;
+                    const maxPriceRate = projectObj.project.property.maxPriceRate || 0;
+                    data.maxPrice = scMathUtil.roundForObj(unitFee * (1 + maxPriceRate * 0.01), decimalObj.bills.unitPrice) || null;
+                    const minPriceRate = projectObj.project.property.minPriceRate || 0;
+                    data.minPrice = scMathUtil.roundForObj(unitFee * (1 - minPriceRate * 0.01), decimalObj.bills.unitPrice) || null;
+                } else {
+                    data.maxPrice = null;
+                    data.minPrice = null;
+                }
+            }
           }
       };
         project.prototype.syncUpdateNodesAndRefresh =async function (datas) {

+ 64 - 2
web/building_saas/main/js/views/main_tree_col.js

@@ -71,7 +71,23 @@ let MainTreeCol = {
         if (rate) return rate.rate;
       }
       return node.data.feeRate;
-    }
+    },
+    maxPrice: function (node) {
+      if (node.data.outPutMaxPrice == true || node.data.outPutLimitPrice == true) {
+        if (node.data.maxPrice === null && node.data.feesIndex && node.data.feesIndex.common) {
+          return node.data.feesIndex.common.unitFee ? node.data.feesIndex.common.unitFee : ""
+        };
+        return node.data.maxPrice ? node.data.maxPrice : "";
+      }
+      return "";
+    },
+    minPrice: function (node) {
+      if (node.data.outPutLimitPrice == true) {
+        if (node.data.minPrice === null && node.data.feesIndex && node.data.feesIndex.common) return node.data.feesIndex.common.unitFee ? node.data.feesIndex.common.unitFee : "";
+        return node.data.minPrice ? node.data.minPrice : "";
+      }
+      return "";
+    },
   },
   readOnly: {
     // Vincent, 2018-01-09
@@ -277,7 +293,14 @@ let MainTreeCol = {
         }
       }
       return true;
-    }
+    },
+    maxPrice: function (node) {
+
+      return projectObj.project.projectInfo.importedByInterface || (node.data.outPutMaxPrice === false && node.data.outPutLimitPrice === false);
+    },
+    minPrice: function (node) {
+      return projectObj.project.projectInfo.importedByInterface || node.data.outPutLimitPrice === false;
+    },
   },
   cellType: {
     unit: function (node, setting) {
@@ -387,6 +410,34 @@ let MainTreeCol = {
         return dynamicCombo;
       }
 
+    },
+    outPutMaxPrice: function (node) {
+      if (node.sourceType === projectObj.project.Bills.getSourceType()) {
+        return projectObj.project.projectInfo.importedByInterface
+          ? sheetCommonObj.getReadOnlyCheckBox()
+          : sheetCommonObj.getCheckBox(true);
+      }
+    },
+    outPutLimitPrice: function (node) {
+      if (node.sourceType === projectObj.project.Bills.getSourceType()) {
+        return projectObj.project.projectInfo.importedByInterface
+          ? sheetCommonObj.getReadOnlyCheckBox()
+          : sheetCommonObj.getCheckBox(true);
+      }
+    },
+    maxPrice: function (node, setting) {
+      const tips = () => {
+        const maxPriceRate = projectObj.project.property.maxPriceRate || 0;
+        return node.data.maxPrice ? `最高限价=清单综合单价*(1+${maxPriceRate}%)` : '';
+      };
+      return sheetCommonObj.getTipsText(tips, setting, node);
+    },
+    minPrice: function (node, setting) {
+      const tips = () => {
+        const minPriceRate = projectObj.project.property.minPriceRate || 0;
+        return node.data.minPrice ? `最低限价=清单综合单价*(1-${minPriceRate}%)` : '';
+      };
+      return sheetCommonObj.getTipsText(tips, setting, node);
     }
   },
   editChecking: function (node) {
@@ -492,6 +543,14 @@ let MainTreeCol = {
       tips += `${node.data.annotation.replace(/\n/g, "<br>")}<br>`;
     }
     return tips
+  },
+  // 字体颜色w
+  foreColor: {
+    // 清单综合单价>最高限价 清单综合单价<最低限价 时,标红显示
+    'feesIndex.common.unitFee': function (node) {
+      const color = 'red';
+      return calcTools.unitFeeGTMaxPrice(node, 'common.unitFee') || calcTools.unitFeeLTMinPrice(node, 'common.unitFee') ? color : null;
+    }
   }
 };
 
@@ -672,6 +731,9 @@ $('#poj-set').on('shown.bs.modal', function (e) {
       }
     });
   }
+  // 清单限价
+  $('#max-price-rate') && $('#max-price-rate').val(projectObj.project.property.maxPriceRate || 0);
+  $('#min-price-rate') && $('#min-price-rate').val(projectObj.project.property.minPriceRate || 0);
 });
 
 $('#poj-set').on('hidden.bs.modal', function (e) {

+ 2 - 2
web/building_saas/main/js/views/project_info.js

@@ -106,9 +106,9 @@ var projectInfoObj = {
             $("[data-toggle='tooltip']").tooltip();
             if (data.property.valuationType == BUDGET) {
                 $("#tab_tender_price").css('display', 'none');
-            } else {
+            }/*  else {
                 $("#about-calc").css('display', 'none');
-            };
+            }; */
         }
     },
     // 刷新总造价显示span

+ 41 - 1
web/building_saas/main/js/views/project_view.js

@@ -1890,7 +1890,7 @@ var projectObj = {
             projectObj.onEvaluationProjectClic(node,info);
         }else if(fieldName == "unitPriceAnalysis"){
             projectObj.onUnitPriceAnalysisClick(node,info);
-        }else if(fieldName == "lockUnitPrice"){
+        }else if(fieldName == "lockUnitPrice"||fieldName == "outPutMaxPrice"||fieldName=="outPutLimitPrice"){
           projectObj.onCasCadeButtonClick(node,info,fieldName);
       }
     },
@@ -2624,6 +2624,15 @@ $('#property_ok').click(function () {
         projectObj.project.property.projectFeature = saveData;
         properties['property.projectFeature'] = saveData;
     }
+    // 清单限价
+    const maxPriceRate = $('#max-price-rate') && +$('#max-price-rate').val();
+    if (maxPriceRate && maxPriceRate !== projectObj.project.property.maxPriceRate) {
+        properties['property.maxPriceRate'] = maxPriceRate;
+    }
+    const minPriceRate = $('#min-price-rate') && +$('#min-price-rate').val();
+    if (minPriceRate && minPriceRate !== projectObj.project.property.minPriceRate) {
+        properties['property.minPriceRate'] = minPriceRate;
+    }
     //清单工程量精度
     let newBillsDecimalDatas = billsDecimalView.toBillsDecimalDatas(billsDecimalView.cache);
     if(billsDecimalView.toUpdate(billsQuanDecimal.datas, newBillsDecimalDatas)){
@@ -2721,6 +2730,12 @@ $('#property_ok').click(function () {
                 window.location.href = '/main?project=' + projectID;
             }
             else{
+                if(mixDatas.properties.hasOwnProperty('property.maxPriceRate')){
+                    projectObj.project.property.maxPriceRate = maxPriceRate;
+                }
+                if(mixDatas.properties.hasOwnProperty('property.minPriceRate')){
+                    projectObj.project.property.minPriceRate = minPriceRate;
+                }
                 if(mixDatas.properties.hasOwnProperty('property.basicInformation')){
                     basicInfoView.orgDatas = basicInfoView.toViewDatas(mixDatas.properties['property.basicInformation']);
                 }
@@ -3334,6 +3349,8 @@ function disableTools(){
     $('#poj-settings-4').find('input').prop('disabled', 'disabled');
     //小数位数
     $('#poj-settings-decimal').find('input').prop('disabled', 'disabled');
+    // 清单限价
+    $('.limit-price-input').prop('disabled', 'disabled');
     //人工单价调整
     $('#std_labour_coe_files').prop('disabled', 'disabled');
     //呈现选项
@@ -3473,3 +3490,26 @@ $('#menu_calc_program_manage').click(function () {
 $("#mainNav").on("click"," li a",function(e){
     projectObj.project.projectMarkChecking();
 });
+
+// 清单限价
+$('.limit-price-input').bind('input', function () {
+    const limitType = $(this).data('limit');
+    const orgVal = limitType === 'max'
+        ? (projectObj.project.property.maxPriceRate || 0)
+        : (projectObj.project.property.minPriceRate || 0);
+    try {
+        const val = $(this).val();
+        if (isNaN(val)) {
+            throw '只能输入两位小数数值!';
+        }
+        const splits = val.split('.');
+        const decimalDigits = splits[1] && splits[1].length || 0;
+        if (decimalDigits > 2) {
+            const roundVal = scMathUtil.roundForObj(val, 2);
+            $(this).val(roundVal);
+        }
+    } catch (err) {
+        alert(err);
+        $(this).val(orgVal);
+    }
+});