瀏覽代碼

feat: 限价功能

vian 4 年之前
父節點
當前提交
20acdae0c0

+ 2 - 0
modules/all_models/bills.js

@@ -68,7 +68,9 @@ let billsSchema = new Schema({
     //是否记取面积增加费
     areaIncreaseFee:{type:Schema.Types.Mixed,default:false},//true 是,false否,null 不确定,三个状态
     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},
     "code": {type: String, default: ''},
     "name": String,
     "projType": String,

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

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

+ 30 - 10
public/web/sheet/sheet_common.js

@@ -990,13 +990,13 @@ var sheetCommonObj = {
         };
         return new ComboCellForActiveCell();
     },
-    getTipsCombo:function (forLocked,tips,setting,node) {
-        let getTipsCombo = function () {
+    getTipsCell: function (baseCell, cellPrototype, tips, setting, node) {
+        let getTipsCell = function () {
             this.clickCom=false;
         };
-        getTipsCombo.prototype = sheetCommonObj.getDynamicCombo(forLocked);
+        getTipsCell.prototype = baseCell;
         if(tips && tips !=""){
-            getTipsCombo.prototype.processMouseEnter = function(hitinfo){
+            getTipsCell.prototype.processMouseEnter = function(hitinfo){
                 if(this.clickCom == true){ //点击了下拉框的三角形,则不用再显示悬浮框了
                     this.clickCom = false;
                     return;
@@ -1004,23 +1004,43 @@ var sheetCommonObj = {
                 let text =  typeof tips == 'function'?tips(node):tips;
                 TREE_SHEET_HELPER.delayShowTips(hitinfo,setting,text);
             };
-            getTipsCombo.prototype.processMouseLeave = function (hitinfo) {
+            getTipsCell.prototype.processMouseLeave = function (hitinfo) {
                 TREE_SHEET_HELPER.hideTipsDiv();
             };
-            getTipsCombo.prototype.processMouseDown = function (hitinfo){
+            getTipsCell.prototype.processMouseDown = function (hitinfo){
                 if(hitinfo.isReservedLocation == true){//这里是点击了下拉框的三角形才会有这个属性
                     TREE_SHEET_HELPER.hideTipsDiv();
                     this.clickCom = true;
                 }
-                GC.Spread.Sheets.CellTypes.ComboBox.prototype.processMouseDown.apply(this, arguments);
+                cellPrototype.processMouseDown.apply(this, arguments);
             };
 
-            getTipsCombo.prototype.updateEditor = function (editorContext, cellStyle, cellRect, context){
+            getTipsCell.prototype.updateEditor = function (editorContext, cellStyle, cellRect, context){
                 TREE_SHEET_HELPER.hideTipsDiv();
-                GC.Spread.Sheets.CellTypes.ComboBox.prototype.updateEditor.apply(this, arguments);
+                cellPrototype.updateEditor.apply(this, arguments);
             };
         }
-        return new getTipsCombo();
+        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);
     },
     // paintFunc,需要追加到paint方法中的自定义paint方法
     getTreeNodeCellType:function (datas,row,parentMap,paintFunc) {// 2018-09-26  不用spreadjs默认的树结构,自定义控件

+ 3 - 2
public/web/tree_sheet/tree_sheet_helper.js

@@ -166,6 +166,8 @@ var TREE_SHEET_HELPER = {
                             tag = node.data.adjustState?node.data.adjustState:'';
                         }
                         if(tag!=null) sheet.setTag(iRow, iCol,tag);
+                    } else if (colSetting) {
+
                     }
                     // 单元格字体颜色
                     const foreColorFunc = MainTreeCol.foreColor[colSetting.data.field];
@@ -176,8 +178,7 @@ var TREE_SHEET_HELPER = {
                 if(colSetting.visible == false) return;//隐藏列不做其它操作
                 if (colSetting.data.getText && Object.prototype.toString.apply(colSetting.data.getText) === "[object Function]") {
                     cell.value(colSetting.data.getText(node));
-                }else if((colSetting.data.field=="mainBills"||(colSetting.data.field=="outPutMaxPrice" && $("#fileKind").val() != '1'))&&MainTreeCol.mainBillsEnable(node)){//主要清单有三种状态,所以直接显示就好,不走最后的逻辑
-                   //outPutMaxPrice 对于投标项目 即 fileKind = 1 时只读,不进这个逻辑
+                }else if((colSetting.data.field=="mainBills"||(['outPutMaxPrice', 'outPutLimitPrice'].includes(colSetting.data.field)))&&MainTreeCol.mainBillsEnable(node)){//主要清单有三种状态,所以直接显示就好,不走最后的逻辑
                     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

@@ -513,4 +513,9 @@ margin-right: 100px !important;
 
 .fee_detail_height{
   height:270px
+}
+
+.limit-price {
+  display: inline-block;
+  width: 80px;
 }

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

@@ -1114,6 +1114,19 @@
                         </label>
                       </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" style="display: inline-block; width: 90px;"> %
+                      </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" style="display: inline-block; width: 90px;"> %
+                        </div>
+                      <% } %>
+                    </fieldset>
+                    <% } %>
                   </div>
                 </div>
                 <!--清单工程精度-->

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

@@ -1042,6 +1042,19 @@ let calcTools = {
         }
         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;
+    },
     
     getTenderCalcType: function () {
         let tenderSetting = projectObj.project.property.tenderSetting;

+ 17 - 12
web/building_saas/main/js/models/exportStdInterfaceBase.js

@@ -829,27 +829,32 @@ const XML_EXPORT_BASE = (() => {
      */
     function softCheck() {
         const tenderDetailMap = _cache.tenderDetailMap;
-        // 检查清单综合单价是否大于最高限价,大于则提示
-        function checkMaxPrice(tenderDetail) {
-            return tenderDetail.mainTree.items
-                .filter(node => calcTools.unitFeeGTMaxPrice(node, 'common.unitFee'))
-                .map(node => {
-                    const code = node.data.code || '';
-                    const name = node.data.name || '';
-                    return `第${node.serialNo()}行“${code + name}”,清单综合单价 > 最高限价`;
-                });
+        // 检查清单综合单价
+        function checkPrice(tenderDetail) {
+            const rst = [];
+            tenderDetail.mainTree.items.forEach(node => {
+                const code = node.data.code || '';
+                const name = node.data.name || '';
+                if (calcTools.unitFeeGTMaxPrice(node, 'common.unitFee')) {
+                    rst.push(`第${node.serialNo()}行“${code + name}”,清单综合单价 > 最高限价`);
+                }
+                if (calcTools.unitFeeLTMinPrice(node, 'common.unitFee')) {
+                    rst.push(`第${node.serialNo()}行“${code + name}”,清单综合单价 < 最低限价`);
+                }
+            });
+            return rst;
         }
         const infos = [];
         // 按照获取顺序serialNo(根据树结构)排序
         Object.values(tenderDetailMap)
             .sort((a, b) => a.serialNo - b.serialNo)
             .forEach(tenderDetail => {
-                const maxPriceInfos = checkMaxPrice(tenderDetail);
-                if (!maxPriceInfos.length) {
+                const priceInfos = checkPrice(tenderDetail);
+                if (!priceInfos.length) {
                     return;
                 }
                 infos.push(`<span style="font-weight: bold">单位工程“${tenderDetail.projectInfo.name}”下:</span>`);
-                infos.push(...maxPriceInfos);
+                infos.push(...priceInfos);
             });
         return infos;
     }

+ 17 - 5
web/building_saas/main/js/models/project.js

@@ -432,7 +432,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);//设置父节点
@@ -464,7 +464,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, caches);
                         datas.push(data);
                         setChildren(c,newValue,datas)
                     }
@@ -485,14 +485,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.updateNodesAndRefresh=function (datas,callback) {

+ 45 - 11
web/building_saas/main/js/views/main_tree_col.js

@@ -69,12 +69,21 @@ let MainTreeCol = {
             return node.data.feeRate;
         },
         maxPrice:function (node) {
-            if(node.data.outPutMaxPrice == true){
-                if(node.data.maxPrice === null&&node.data.feesIndex &&node.data.feesIndex.common) return node.data.feesIndex.common.unitFee?node.data.feesIndex.common.unitFee:"";
+            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
@@ -308,10 +317,13 @@ let MainTreeCol = {
         },
         maxPrice:function (node) {
             // 对于投标项目只读
-            if(+$('#fileKind').val() === _fileKind.tender) {
+            /* if(+$('#fileKind').val() === _fileKind.tender) {
                 return true;
-            }
-            return node.data.outPutMaxPrice !== true;
+            } */
+            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;
         },
         // 超高降效
         forOverHeight: function (node) {
@@ -444,11 +456,16 @@ let MainTreeCol = {
         },
         outPutMaxPrice:function (node) {
             if(MainTreeCol.mainBillsEnable(node)) {
-                // 投标项目,复选框不可改变
-                const checkBox = +$('#fileKind').val() === _fileKind.tender
+                return projectObj.project.projectInfo.importedByInterface
                     ? sheetCommonObj.getReadOnlyCheckBox()
                     : sheetCommonObj.getCheckBox(true);
-                return checkBox;
+            }
+        },
+        outPutLimitPrice:function (node) {
+            if(MainTreeCol.mainBillsEnable(node)) {
+                return projectObj.project.projectInfo.importedByInterface
+                ? sheetCommonObj.getReadOnlyCheckBox()
+                : sheetCommonObj.getCheckBox(true);
             }
         },
         overHeight: function (node) {
@@ -464,6 +481,20 @@ let MainTreeCol = {
             dynamicCombo._maxDropDownItems = 10;
             dynamicCombo.items(items);
             return dynamicCombo;
+        },
+        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);
         }
     },
     mainBillsEnable:function (node) {
@@ -582,10 +613,10 @@ let MainTreeCol = {
     },
     // 字体颜色w
     foreColor: {
-        // 清单综合单价>最高限价时,标红显示
+        // 清单综合单价>最高限价 清单综合单价<最低限价 时,标红显示
         'feesIndex.common.unitFee': function (node) {
             const color = 'red';
-            return calcTools.unitFeeGTMaxPrice(node, 'common.unitFee') ? color : null;
+            return calcTools.unitFeeGTMaxPrice(node, 'common.unitFee') || calcTools.unitFeeLTMinPrice(node, 'common.unitFee') ? color : null;
         }
     }
 };
@@ -767,6 +798,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) {

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

@@ -2055,7 +2055,7 @@ var projectObj = {
             projectObj.onIsEstimateClick(node,info);
         }else if(fieldName == "evaluationProject"){
             projectObj.onEvaluationProjectClic(node,info);
-        }else if(fieldName == "mainBills"||fieldName == "outPutMaxPrice"||fieldName=="areaIncreaseFee"||fieldName == "lockUnitPrice"){
+        }else if(fieldName == "mainBills"||fieldName == "outPutMaxPrice"||fieldName=="outPutLimitPrice"||fieldName=="areaIncreaseFee"||fieldName == "lockUnitPrice"){
             projectObj.onCasCadeButtonClick(node,info,fieldName);
         }
     },
@@ -2757,6 +2757,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)){
@@ -2859,6 +2868,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']);
                 }
@@ -3487,6 +3502,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');
     //呈现选项
@@ -3685,6 +3702,28 @@ $('#locate-other').click(() => {
     fastLocate(FastLocateType.OTHER);
 });
 
+// 清单限价
+$('.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);
+    }
+});
 
 //导出接口
 ExportView.exportListener();

+ 1 - 1
web/over_write/js/chongqing_2018_export.js

@@ -1289,7 +1289,7 @@ const XMLStandard = (function () {
                 decimal = detail.projectInfo.property.decimal;
             // 处理最高限价,输出最高限价时,若最高限价为空,则取综合单价,否则取最高限价
             let maxPrice = 0;
-            if (exportKind === _config.EXPORT_KIND.Control && node.data.outPutMaxPrice) {
+            if (exportKind === _config.EXPORT_KIND.Control && (node.data.outPutMaxPrice || node.data.outPutLimitPrice)) {
                 maxPrice = _util.isDef(node.data.maxPrice)
                     ? node.data.maxPrice
                     : _util.getFee(node.data.fees, 'common.unitFee')

+ 1 - 0
web/over_write/js/chongqing_2018_import.js

@@ -278,6 +278,7 @@ const importXML = (() => {
             //不为0才输出
             if (maxPrice && maxPrice !== '0') {
                 obj.outPutMaxPrice = true;
+                obj.outPutLimitPrice = true;
                 obj.maxPrice = maxPrice;
             }
         }

+ 1 - 0
web/over_write/js/guangdong_2018_import.js

@@ -394,6 +394,7 @@ const importXML = (() => {
             const maxPrice = getValue(workElementSrc, ['_PriceHigh']);
             //不为0才输出
             if (maxPrice && maxPrice !== '0') {
+                bills.outPutLimitPrice = true;
                 bills.outPutMaxPrice = true;
                 bills.maxPrice = maxPrice;
             }