Kaynağa Gözat

指数调差增加上涨下跌及现行价格指数和有效价格指数

ellisran 8 saat önce
ebeveyn
işleme
2999bf5125

+ 1 - 1
app/const/material.js

@@ -43,7 +43,7 @@ const decimal = {
     tp: 2, // 金额
 };
 const exponent_decimal = {
-    up: 3, // 单价, 基准价格指数,现行价格
+    up: 3, // 单价、基准价格指数、现行价格指数、有效价格
     tp: 0, // 金额
     qty: 3, // 计算值
 };

+ 22 - 0
app/controller/material_controller.js

@@ -1238,6 +1238,28 @@ module.exports = app => {
                 // 转换为整型,不然会无法获取
                 if (data.ms_id) data.ms_id = parseInt(data.ms_id);
                 if (data.mn_id) data.mn_id = parseInt(data.mn_id);
+                const isPastAdminRiskEdit = ctx.material.readOnly && ctx.session.sessionUser.is_admin && ctx.material.order < ctx.material.highOrder && (data.type === 'update' || data.type === 'paste');
+                if (isPastAdminRiskEdit) {
+                    const updateList = data.type === 'paste' ? data.updateData : [data.updateData];
+                    const riskUpdates = updateList.map(item => ({
+                        id: ctx.material.showStageExponent || ctx.material.exponent_node ? item.me_id : item.id,
+                        m_up_risk: item.m_up_risk,
+                        m_down_risk: item.m_down_risk,
+                    }));
+                    if (riskUpdates.some(item => !item.id)) throw '往期指数工料参数有误';
+                    if (ctx.material.showStageExponent || ctx.material.exponent_node) {
+                        responseData.data = data.type === 'paste' ?
+                            await ctx.service.materialExponent.saveDatas(riskUpdates, data.ms_id, data.mn_id) :
+                            await ctx.service.materialExponent.save(riskUpdates[0], data.ms_id, data.mn_id);
+                    } else {
+                        responseData.data = await ctx.service.materialExponent.saveHistoryRisks(riskUpdates);
+                    }
+                    ctx.body = responseData;
+                    return;
+                }
+                if (ctx.material.readOnly && ['add', 'del', 'update', 'paste', 'ex_calc', 'update_node'].includes(data.type)) {
+                    throw '无权操作';
+                }
                 switch (data.type) {
                     case 'add':
                         responseData.data = await ctx.service.materialExponent.add();

+ 3 - 2
app/lib/rm/material.js

@@ -314,7 +314,8 @@ class rptMemChange extends RptMemBase {
             });
         } else {
             const sql = 'SELECT me.id, me.tid, me.mid, me.type, me.symbol, me.symbol_desc, me.code, me.remark, me.in_time,' +
-                '    meh.type, meh.weight_num, meh.basic_price, meh.basic_times, meh.m_price, meh.calc_num, meh.is_summary' +
+                '    meh.type, meh.weight_num, meh.basic_price, meh.basic_times, meh.m_up_risk, meh.m_down_risk, ' +
+                '    meh.m_price, meh.effective_price, meh.calc_num, meh.is_summary' +
                 '  FROM ' + this.ctx.service.materialExponentHistory.tableName + ' meh ' +
                 '  LEFT JOIN ' + this.ctx.service.materialExponent.tableName + ' me ON meh.me_id = me.id ' +
                 '  WHERE meh.tid = ? And meh.mid = ?'+
@@ -516,4 +517,4 @@ class rptMemChange extends RptMemBase {
     }
 }
 
-module.exports = rptMemChange;
+module.exports = rptMemChange;

+ 3 - 2
app/lib/rm/tender_material.js

@@ -464,7 +464,8 @@ class ReportMemoryMaterial {
             });
         } else {
             const sql = 'SELECT me.id, me.tid, me.mid, me.type, me.symbol, me.symbol_desc, me.code, me.remark, me.in_time,' +
-                '    meh.type, meh.weight_num, meh.basic_price, meh.basic_times, meh.m_price, meh.calc_num, meh.is_summary' +
+                '    meh.type, meh.weight_num, meh.basic_price, meh.basic_times, meh.m_up_risk, meh.m_down_risk, ' +
+                '    meh.m_price, meh.effective_price, meh.calc_num, meh.is_summary' +
                 '  FROM ' + this.ctx.service.materialExponentHistory.tableName + ' meh ' +
                 '  LEFT JOIN ' + this.ctx.service.materialExponent.tableName + ' me ON meh.me_id = me.id ' +
                 '  WHERE meh.tid = ? And meh.mid = ?'+
@@ -485,4 +486,4 @@ class ReportMemoryMaterial {
     }
 }
 
-module.exports = ReportMemoryMaterial;
+module.exports = ReportMemoryMaterial;

+ 68 - 12
app/public/js/material_exponent.js

@@ -14,6 +14,7 @@ function getPasteHint (str, row = '') {
 }
 $(document).ready(() => {
     autoFlashHeight();
+    const canAdminEditHistoryRisk = isAdmin && materialOrder < materialHighOrder;
     const materialExponentSpread = SpreadJsObj.createNewSpread($('#material-exponent-spread')[0]);
     const materialExponentSpreadSetting = {
         cols: [
@@ -23,7 +24,10 @@ $(document).ready(() => {
             {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit'},
             {title: '基本价格指数(F0)', colSpan: '1', rowSpan: '2', field: 'basic_price', hAlign: 2, width: 120, readOnly: 'readOnly.isEdit'},
             {title: '基准时间', colSpan: '1', rowSpan: '2', field: 'basic_times', hAlign: 0, width: 80, formatter: '@', readOnly: 'readOnly.isEdit'},
-            {title: '现行价格指数(Ft)', colSpan: '1', rowSpan: '2', field: 'm_price', hAlign: 2, width: 120, type: 'Number', readOnly: 'readOnly.remark'},
+            {title: '上涨幅度(%)', colSpan: '1', rowSpan: '2', field: 'm_up_risk', hAlign: 2, width: 85, type: 'Number', readOnly: 'readOnly.risk'},
+            {title: '下跌幅度(%)', colSpan: '1', rowSpan: '2', field: 'm_down_risk', hAlign: 2, width: 85, type: 'Number', readOnly: 'readOnly.risk'},
+            {title: '现行价格指数(Fto)', colSpan: '1', rowSpan: '2', field: 'm_price', hAlign: 2, width: 125, type: 'Number', readOnly: 'readOnly.remark'},
+            {title: '有效价格指数(Ft)', colSpan: '1', rowSpan: '2', field: 'effective_price', hAlign: 2, width: 125, type: 'Number', readOnly: true, getValue: 'getValue.effective_price'},
             {title: '加权系数(B)', colSpan: '1', rowSpan: '2', field: 'weight_num', hAlign: 2, width: 120, formatter: '@', readOnly: 'readOnly.isConstant'},
             {title: '计算值(Ft/F0*B)', colSpan: '1', rowSpan: '2', field: 'calc_num', hAlign: 2, width: 100, formatter: '@', readOnly: true, getValue: 'getValue.calc_num'},
             {title: '备注', colSpan: '1', rowSpan: '2', field: 'remark', hAlign: 0, width: 60, formatter: '@', readOnly: 'readOnly.remark'},
@@ -35,7 +39,7 @@ $(document).ready(() => {
         defaultRowHeight: 21,
         headerFont: '12px 微软雅黑',
         font: '12px 微软雅黑',
-        readOnly: readOnly,
+        readOnly: readOnly && !canAdminEditHistoryRisk,
     };
     const materialExponentBase = {
         isUsed: function (data) {
@@ -54,9 +58,28 @@ $(document).ready(() => {
     };
     const materialExponentCol = {
         getValue: {
+            effective_price: function (data) {
+                if (data.type === 1 || data.m_price === null || data.m_price === undefined) return null;
+                const upRisk = Number(data.m_up_risk) || 0;
+                const downRisk = Number(data.m_down_risk) || 0;
+                let effectivePrice = data.m_price;
+                if ((upRisk !== 0 || downRisk !== 0) && data.basic_price > 0) {
+                    const upperLimit = ZhCalc.mul(data.basic_price, ZhCalc.add(1, ZhCalc.div(upRisk, 100)));
+                    const lowerLimit = ZhCalc.mul(data.basic_price, ZhCalc.sub(1, ZhCalc.div(downRisk, 100)));
+                    if (data.m_price > upperLimit) {
+                        effectivePrice = ZhCalc.sub(data.m_price, ZhCalc.mul(data.basic_price, ZhCalc.div(upRisk, 100)));
+                    } else if (data.m_price < lowerLimit) {
+                        effectivePrice = ZhCalc.add(data.m_price, ZhCalc.mul(data.basic_price, ZhCalc.div(downRisk, 100)));
+                    } else {
+                        effectivePrice = data.basic_price;
+                    }
+                }
+                return ZhCalc.round(effectivePrice, materialExponentDecimal.up);
+            },
             calc_num : function (data) {
                 if (data.type === 1) return null;
-                const calc_num = data.basic_price > 0 ? ZhCalc.mul(data.weight_num, ZhCalc.div(data.m_price, data.basic_price)) : 0;
+                const effectivePrice = materialExponentCol.getValue.effective_price(data);
+                const calc_num = data.basic_price > 0 ? ZhCalc.mul(data.weight_num, ZhCalc.div(effectivePrice, data.basic_price)) : 0;
                 return calc_num > 0 ? ZhCalc.round(calc_num, materialExponentDecimal.qty) : 0;
             },
         },
@@ -70,6 +93,9 @@ $(document).ready(() => {
             remark: function (data) {
                 return !(!readOnly && data.type === 2);
             },
+            risk: function (data) {
+                return !(data.type === 2 && (!readOnly || canAdminEditHistoryRisk));
+            },
             isConstant: function (data) {
                 // return !(!readOnly && materialExponentBase.isConstant(data));
                 return readOnly;
@@ -99,7 +125,7 @@ $(document).ready(() => {
     };
     SpreadJsObj.initSheet(stageTjSpread.getActiveSheet(), stageTjSpreadSetting);
 
-    const needUpdateArray = ['weight_num', 'm_price', 'remark'];
+    const needUpdateArray = ['weight_num', 'm_up_risk', 'm_down_risk', 'm_price', 'effective_price', 'remark'];
 
     const materialExponentSpreadObj = {
         getMaterialExponentData: function (msid = null, mnid = null) {
@@ -322,6 +348,15 @@ $(document).ready(() => {
                         validText = ZhCalc.round(num, materialExponentDecimal.up);
                     }
                 }
+                if (col.field === 'm_up_risk' || col.field === 'm_down_risk') {
+                    validText = validText === null ? 0 : validText;
+                    if (!Number.isInteger(Number(validText)) || Number(validText) < 0 || Number(validText) > 100) {
+                        toastr.error('上涨、下跌幅度必须为0~100的整数');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    validText = Number(validText);
+                }
                 if (col.field === 'weight_num') {
                     if (isNaN(validText)) {
                         toastr.error('不能输入其它非数字类型字符');
@@ -349,6 +384,7 @@ $(document).ready(() => {
                     }
                 }
                 select[col.field] = validText;
+                select.effective_price = materialExponentCol.getValue.effective_price(select);
                 select.calc_num = materialExponentCol.getValue.calc_num(select);
                 delete select.waitingLoading;
                 // console.log(select);
@@ -407,7 +443,7 @@ $(document).ready(() => {
             }
         },
         deletePress: function (sheet) {
-            if (!sheet.zh_setting || readOnly || sheet.zh_setting.readOnly) return;
+            if (!sheet.zh_setting || (readOnly && !canAdminEditHistoryRisk) || sheet.zh_setting.readOnly) return;
             // materialExponentBase.isUsed(select)
             if (sheet.zh_setting && sheet.zh_data) {
                 const sel = sheet.getSelections()[0];
@@ -436,12 +472,17 @@ $(document).ready(() => {
                             // bPaste = false;
                             continue;
                         }
-                        materialExData[colSetting.field] = null;
-                        sortData[iRow][colSetting.field] = null;
+                        const clearValue = colSetting.field === 'm_up_risk' || colSetting.field === 'm_down_risk' ? 0 : null;
+                        materialExData[colSetting.field] = clearValue;
+                        sortData[iRow][colSetting.field] = clearValue;
                     }
-                    if (exData.type !== materialType.ex_type[0].value) materialExData.calc_num = materialExponentCol.getValue.calc_num(sortData[iRow]);
                     const arr = Object.keys(materialExData);
                     if (arr.length !== 1) {
+                        if (exData.type !== materialType.ex_type[0].value) {
+                            materialExData.effective_price = materialExponentCol.getValue.effective_price(sortData[iRow]);
+                            materialExData.calc_num = materialExponentCol.getValue.calc_num(sortData[iRow]);
+                        }
+                        if (sortData[iRow].me_id) materialExData.me_id = sortData[iRow].me_id;
                         data.push(materialExData);
                     }
                 }
@@ -486,7 +527,7 @@ $(document).ready(() => {
                 SpreadJsObj.reLoadSheetData(materialExponentSpread.getActiveSheet());
                 return;
             }
-            if (sortData.length > 0 && range.col + range.colCount > 10) {
+            if (sortData.length > 0 && range.col + range.colCount > info.sheet.zh_setting.cols.length) {
                 toastMessageUniq(hint.cellError);
                 SpreadJsObj.reLoadSheetHeader(materialExponentSpread.getActiveSheet());
                 SpreadJsObj.reLoadSheetData(materialExponentSpread.getActiveSheet());
@@ -499,12 +540,15 @@ $(document).ready(() => {
                 const curRow = range.row + iRow;
                 // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
                 const materialExData = { id: sortData[curRow].id };
+                if (sortData[curRow].me_id) materialExData.me_id = sortData[curRow].me_id;
                 const hintRow = range.rowCount > 1 ? curRow : '';
                 let sameCol = 0;
                 for (let iCol = 0; iCol < range.colCount; iCol++) {
                     const curCol = range.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
                     if (!colSetting) continue;
+                    const colReadOnly = colSetting.readOnly === true || (typeof colSetting.readOnly === 'function' && colSetting.readOnly(sortData[curRow]));
+                    if (colReadOnly) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
                     validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
@@ -540,6 +584,15 @@ $(document).ready(() => {
                             validText = ZhCalc.round(num, materialExponentDecimal.up);
                         }
                     }
+                    if (colSetting.field === 'm_up_risk' || colSetting.field === 'm_down_risk') {
+                        validText = validText === null ? 0 : validText;
+                        if (!Number.isInteger(Number(validText)) || Number(validText) < 0 || Number(validText) > 100) {
+                            toastMessageUniq(getPasteHint({type: 'error', msg: '上涨、下跌幅度必须为0~100的整数'}, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        validText = Number(validText);
+                    }
                     if (colSetting.field === 'weight_num') {
                         if (isNaN(validText)) {
                             toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
@@ -570,6 +623,7 @@ $(document).ready(() => {
                     sortData[curRow][colSetting.field] = validText;
                 }
                 if (bPaste) {
+                    materialExData.effective_price = materialExponentCol.getValue.effective_price(sortData[curRow]);
                     materialExData.calc_num = materialExponentCol.getValue.calc_num(sortData[curRow]);
                     data.push(materialExData);
                     // rowData.push(curRow);
@@ -782,15 +836,17 @@ $(document).ready(() => {
         $('#calc_basic_select').html(html);
     }
 
+    if (!readOnly || canAdminEditHistoryRisk) {
+        materialExponentSpread.bind(spreadNS.Events.EditEnded, materialExponentSpreadObj.editEnded);
+        materialExponentSpread.bind(spreadNS.Events.ClipboardPasted, materialExponentSpreadObj.clipboardPasted);
+        if (!editForAudit) SpreadJsObj.addDeleteBind(materialExponentSpread, materialExponentSpreadObj.deletePress);
+    }
     if (!readOnly) {
         $('#add').click(materialExponentSpreadObj.add);
         $('#del').click(materialExponentSpreadObj.del);
         materialExponentSpread.bind(spreadNS.Events.SelectionChanged, materialExponentSpreadObj.selectionChanged);
-        materialExponentSpread.bind(spreadNS.Events.EditEnded, materialExponentSpreadObj.editEnded);
         materialExponentSpread.bind(spreadNS.Events.ButtonClicked, materialExponentSpreadObj.buttonClicked);
-        materialExponentSpread.bind(spreadNS.Events.ClipboardPasted, materialExponentSpreadObj.clipboardPasted);
         if (!editForAudit) {
-            SpreadJsObj.addDeleteBind(materialExponentSpread, materialExponentSpreadObj.deletePress);
             // 右键菜单
             $.contextMenu({
                 selector: '#material-exponent-spread',

+ 3 - 1
app/service/material.js

@@ -372,7 +372,9 @@ module.exports = app => {
                     const sql2 = 'UPDATE ' + this.ctx.service.materialExponent.tableName + ' as me, ' +
                         this.ctx.service.materialExponentHistory.tableName + ' as meh ' +
                         'SET me.`weight_num` = meh.`weight_num`, me.`basic_price` = meh.`basic_price`, ' +
-                        'me.`basic_times` = meh.`basic_times`, me.`m_price` = meh.`m_price`, ' +
+                        'me.`basic_times` = meh.`basic_times`, me.`m_up_risk` = meh.`m_up_risk`, ' +
+                        'me.`m_down_risk` = meh.`m_down_risk`, me.`m_price` = meh.`m_price`, ' +
+                        'me.`effective_price` = meh.`effective_price`, ' +
                         'me.`calc_num` = meh.`calc_num`, me.`is_summary` = meh.`is_summary` ' +
                         'WHERE meh.`tid` = ? AND meh.`order` = ? AND meh.`me_id` = me.`id`';
                     const sqlParam2 = [this.ctx.tender.id, materialInfo.order - 1];

+ 3 - 0
app/service/material_audit.js

@@ -512,7 +512,10 @@ module.exports = app => {
                                 weight_num: me.weight_num,
                                 basic_price: me.basic_price,
                                 basic_times: me.basic_times,
+                                m_up_risk: me.m_up_risk,
+                                m_down_risk: me.m_down_risk,
                                 m_price: me.m_price,
+                                effective_price: me.effective_price !== null ? me.effective_price : me.m_price,
                                 calc_num: me.calc_num,
                                 is_summary: me.is_summary,
                             };

+ 224 - 40
app/service/material_exponent.js

@@ -11,6 +11,7 @@
 const auditConst = require('../const/audit').material;
 const materialConst = require('../const/material');
 const MaterialCalculator = require('../lib/material_calc');
+const Decimal = require('decimal.js');
 
 module.exports = app => {
     class MaterialExponent extends app.BaseService {
@@ -25,6 +26,69 @@ module.exports = app => {
             this.tableName = 'material_exponent';
         }
 
+        _normalizeRisk(value) {
+            if (value === null || value === undefined || value === '') return 0;
+            const risk = Number(value);
+            if (!Number.isInteger(risk) || risk < 0 || risk > 100) {
+                throw '上涨、下跌幅度必须为0~100的整数';
+            }
+            return risk;
+        }
+
+        getEffectivePrice(data, decimal = (this.ctx.material.exponent_decimal ? this.ctx.material.exponent_decimal.up : materialConst.exponent_decimal.up)) {
+            if (data.m_price === null || data.m_price === undefined || data.m_price === '') return null;
+
+            const currentPrice = new Decimal(data.m_price);
+            const upRisk = this._normalizeRisk(data.m_up_risk);
+            const downRisk = this._normalizeRisk(data.m_down_risk);
+            let effectivePrice = currentPrice;
+
+            if ((upRisk !== 0 || downRisk !== 0) && data.basic_price !== null && data.basic_price !== undefined && data.basic_price !== '' && new Decimal(data.basic_price).gt(0)) {
+                const basicPrice = new Decimal(data.basic_price);
+                const upperLimit = basicPrice.mul(new Decimal(1).add(new Decimal(upRisk).div(100)));
+                const lowerLimit = basicPrice.mul(new Decimal(1).sub(new Decimal(downRisk).div(100)));
+
+                if (currentPrice.gt(upperLimit)) {
+                    effectivePrice = currentPrice.sub(basicPrice.mul(upRisk).div(100));
+                } else if (currentPrice.lt(lowerLimit)) {
+                    effectivePrice = currentPrice.add(basicPrice.mul(downRisk).div(100));
+                } else {
+                    effectivePrice = basicPrice;
+                }
+            }
+
+            return Number(effectivePrice.toDecimalPlaces(Number(decimal), Decimal.ROUND_HALF_UP).toString());
+        }
+
+        _normalizeUpdateData(data) {
+            const editableCols = [
+                'id', 'symbol', 'symbol_desc', 'code', 'basic_price', 'basic_times',
+                'm_up_risk', 'm_down_risk', 'm_price', 'weight_num', 'remark', 'is_summary',
+            ];
+            const updateData = this._.pick(data, editableCols);
+            if (!updateData.id) throw '指数工料参数有误';
+            if (updateData.m_up_risk !== undefined) updateData.m_up_risk = this._normalizeRisk(updateData.m_up_risk);
+            if (updateData.m_down_risk !== undefined) updateData.m_down_risk = this._normalizeRisk(updateData.m_down_risk);
+            return updateData;
+        }
+
+        _setEffectivePrice(currentData, updateData) {
+            const priceCols = ['basic_price', 'm_price', 'm_up_risk', 'm_down_risk'];
+            const needUpdatePrice = priceCols.some(col => updateData[col] !== undefined);
+            const needUpdateCalc = needUpdatePrice || updateData.weight_num !== undefined;
+            if (!needUpdateCalc) return updateData;
+            const calcData = Object.assign({}, currentData, updateData);
+            if (needUpdatePrice) {
+                updateData.effective_price = this.getEffectivePrice(calcData);
+                calcData.effective_price = updateData.effective_price;
+            }
+            const effectivePrice = calcData.effective_price !== null && calcData.effective_price !== undefined ? calcData.effective_price : calcData.m_price;
+            const calcNum = calcData.type === materialConst.ex_type[1].value && calcData.basic_price > 0 ?
+                this.ctx.helper.mul(calcData.weight_num, this.ctx.helper.div(effectivePrice, calcData.basic_price)) : null;
+            updateData.calc_num = calcNum ? this.ctx.helper.round(calcNum, this.ctx.material.exponent_decimal.qty) : calcNum;
+            return updateData;
+        }
+
         /**
          * 添加指数清单
          * @return {void}
@@ -156,7 +220,7 @@ module.exports = app => {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
-            delete data.in_time;
+            data = this._normalizeUpdateData(data);
             // delete data.m_tp;
             // 判断是否可修改
             // 判断t_type是否为费用
@@ -165,10 +229,15 @@ module.exports = app => {
                 const result = {};
                 const info = {};
                 switch (this.ctx.material.exponentStatus) {
-                    case materialConst.exponent_status.shared_noNode:
+                    case materialConst.exponent_status.shared_noNode: {
+                        const exponentInfo = await transaction.get(this.tableName, { id: data.id, tid: this.ctx.tender.id });
+                        if (!exponentInfo) throw '指数工料数据不存在';
+                        this._setEffectivePrice(exponentInfo, data);
                         await transaction.update(this.tableName, data);
                         [result.ex_tp, result.ex_tax_tp, result.ex_expr] = await this.calcMaterialExTp(transaction);
+                        result.exponentData = await transaction.select(this.tableName, { where: { id: data.id } });
                         break;
+                    }
                     case materialConst.exponent_status.shared_node:
                         if (!mn_id) {
                             throw '分项参数有误';
@@ -215,6 +284,9 @@ module.exports = app => {
                         break;
                     default: break;
                 }
+                if (this.ctx.material.order < this.ctx.material.highOrder && this.ctx.session.sessionUser.is_admin) {
+                    await this._syncHistoricalTotals(transaction);
+                }
                 await transaction.commit();
                 return result;
             } catch (err) {
@@ -248,6 +320,9 @@ module.exports = app => {
                 updateData.id = data.id;
                 await transaction.update(this.tableName, updateData);
             }
+            if (updateData.basic_price !== undefined) {
+                await this.ctx.service.materialExponentShard.resetEffectivePrice(transaction, [data.id]);
+            }
             if (needCalc) {
                 if (mn_id) {
                     await this.ctx.service.materialExponentShard.calcMaterialAllExTpByNode(transaction, this.ctx.material.id, mn_id);
@@ -271,17 +346,25 @@ module.exports = app => {
             // 判断t_type是否为费用
             const transaction = await this.db.beginTransaction();
             try {
+                datas = datas.map(data => this._normalizeUpdateData(data));
                 const result = { ex_tp: this.ctx.material.ex_tp };
                 const info = {};
                 if (this.ctx.material.exponentStatus !== materialConst.exponent_status.shared_noNode) {
                     info.meList = await transaction.select(this.tableName, { where: { id: this._.map(datas, 'id') } });
                 }
                 switch (this.ctx.material.exponentStatus) {
-                    case materialConst.exponent_status.shared_noNode:
+                    case materialConst.exponent_status.shared_noNode: {
+                        const exponentList = await transaction.select(this.tableName, { where: { id: this._.map(datas, 'id'), tid: this.ctx.tender.id } });
+                        for (const data of datas) {
+                            const exponentInfo = this._.find(exponentList, { id: data.id });
+                            if (!exponentInfo) throw '指数工料数据不存在';
+                            this._setEffectivePrice(exponentInfo, data);
+                        }
                         await transaction.updateRows(this.tableName, datas);
                         [result.ex_tp, result.ex_tax_tp, result.ex_expr] = await this.calcMaterialExTp(transaction);
                         result.exponentData = await transaction.select(this.tableName, { where: { id: this._.map(datas, 'id') } });
                         break;
+                    }
                     case materialConst.exponent_status.shared_node:
                         if (!mn_id) {
                             throw '参数有误';
@@ -316,6 +399,9 @@ module.exports = app => {
                         break;
                     default: break;
                 }
+                if (this.ctx.material.order < this.ctx.material.highOrder && this.ctx.session.sessionUser.is_admin) {
+                    await this._syncHistoricalTotals(transaction);
+                }
                 await transaction.commit();
                 return result;
             } catch (err) {
@@ -324,6 +410,105 @@ module.exports = app => {
             }
         }
 
+        async saveHistoryRisks(datas) {
+            if (!this.ctx.session.sessionUser.is_admin || this.ctx.material.order >= this.ctx.material.highOrder) {
+                throw '无权修改往期指数幅度';
+            }
+            const updateList = datas instanceof Array ? datas : [datas];
+            const transaction = await this.db.beginTransaction();
+            try {
+                const historyIds = this._.map(updateList, 'id');
+                const historyList = await transaction.select(this.ctx.service.materialExponentHistory.tableName, {
+                    where: { id: historyIds, mid: this.ctx.material.id, order: this.ctx.material.order },
+                });
+                const updates = [];
+                for (const data of updateList) {
+                    const historyInfo = this._.find(historyList, { id: data.id });
+                    if (!historyInfo || historyInfo.type !== materialConst.ex_type[1].value) throw '往期指数工料数据不存在';
+                    const updateData = { id: historyInfo.id };
+                    if (data.m_up_risk !== undefined) updateData.m_up_risk = this._normalizeRisk(data.m_up_risk);
+                    if (data.m_down_risk !== undefined) updateData.m_down_risk = this._normalizeRisk(data.m_down_risk);
+                    if (Object.keys(updateData).length === 1) continue;
+                    updateData.effective_price = this.getEffectivePrice(Object.assign({}, historyInfo, updateData));
+                    const calcNum = historyInfo.basic_price > 0 ? this.ctx.helper.mul(historyInfo.weight_num, this.ctx.helper.div(updateData.effective_price, historyInfo.basic_price)) : 0;
+                    updateData.calc_num = calcNum ? this.ctx.helper.round(calcNum, this.ctx.material.exponent_decimal.qty) : calcNum;
+                    updates.push(updateData);
+                }
+                if (updates.length === 0) throw '上涨、下跌幅度未发生变化';
+                await transaction.updateRows(this.ctx.service.materialExponentHistory.tableName, updates);
+                const [ex_tp, ex_tax_tp, ex_expr] = await this._calcHistoryExTp(transaction);
+                await this._syncHistoricalTotals(transaction);
+                const historyData = await transaction.select(this.ctx.service.materialExponentHistory.tableName, { where: { id: this._.map(updates, 'id') } });
+                await transaction.commit();
+                return { ex_tp, ex_tax_tp, ex_expr, historyData };
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _calcHistoryExTp(transaction) {
+            let basicCalc = 0;
+            const exCalc = this.ctx.material.ex_calc ? JSON.parse(this.ctx.material.ex_calc) : null;
+            if (exCalc) {
+                for (const calc of exCalc) {
+                    if (!calc.select) continue;
+                    basicCalc = this.ctx.helper.add(basicCalc, calc.code === 'zdy' && calc.result ? calc.result : calc.value);
+                }
+            }
+            const exponentList = await transaction.select(this.ctx.service.materialExponentHistory.tableName, {
+                where: { mid: this.ctx.material.id, order: this.ctx.material.order, is_summary: materialConst.is_summary.yes },
+            });
+            let expr = basicCalc + '*[';
+            let sumByChange = 0;
+            let constant = 0;
+            for (const ex of exponentList) {
+                if (ex.type === materialConst.ex_type[0].value) {
+                    constant = ex.weight_num || 0;
+                    expr += constant.toString();
+                } else if (ex.type === materialConst.ex_type[1].value) {
+                    const effectivePrice = ex.effective_price !== null && ex.effective_price !== undefined ? ex.effective_price : ex.m_price;
+                    const calcNum = ex.basic_price > 0 ? this.ctx.helper.mul(ex.weight_num, this.ctx.helper.div(effectivePrice, ex.basic_price)) : 0;
+                    const change = calcNum ? this.ctx.helper.round(calcNum, this.ctx.material.exponent_decimal.qty) : 0;
+                    expr += change !== 0 ? '+' + ex.weight_num.toString() + '*(' + effectivePrice.toString() + '/' + ex.basic_price.toString() + ')' : '';
+                    sumByChange = this.ctx.helper.add(sumByChange, change);
+                }
+            }
+            expr += '-1]';
+            expr = constant !== 0 ? expr : null;
+            const exTp = constant !== 0 ? this.ctx.helper.round(this.ctx.helper.mul(basicCalc, this.ctx.helper.sub(this.ctx.helper.add(constant, sumByChange), 1)), this.ctx.material.exponent_decimal.tp) : null;
+            const exTaxTp = exTp !== null ? this.ctx.helper.round(this.ctx.helper.mul(exTp, 1 + this.ctx.material.exponent_rate / 100), this.ctx.material.exponent_decimal.tp) : null;
+            await transaction.update(this.ctx.service.material.tableName, {
+                id: this.ctx.material.id,
+                ex_tp: exTp,
+                ex_tax_tp: exTaxTp,
+                ex_expr: expr,
+            });
+            return [exTp, exTaxTp, expr];
+        }
+
+        async _syncHistoricalTotals(transaction) {
+            const materialList = await transaction.select(this.ctx.service.material.tableName, {
+                where: { tid: this.ctx.tender.id },
+                orders: [['order', 'asc']],
+            });
+            let exponentPreTp = 0;
+            let exponentTaxPreTp = 0;
+            const updates = [];
+            for (const material of materialList) {
+                updates.push({
+                    id: material.id,
+                    ex_pre_tp: exponentPreTp,
+                    ex_tax_pre_tp: exponentTaxPreTp,
+                });
+                exponentPreTp = this.ctx.helper.add(exponentPreTp, material.ex_tp || 0);
+                exponentTaxPreTp = this.ctx.helper.add(exponentTaxPreTp, material.ex_tax_tp || 0);
+            }
+            if (updates.length > 0) await transaction.updateRows(this.ctx.service.material.tableName, updates);
+            const tpData = await this.ctx.service.materialAudit.getTpData(transaction, this.ctx.material.id, this.ctx.material.decimal, this.ctx.material.exponent_decimal);
+            await transaction.update(this.ctx.service.material.tableName, { id: this.ctx.material.id, tp_data: JSON.stringify(tpData) });
+        }
+
         async _updateMoreExponentData(transaction, datas, result, meList, ms_id = null, mn_id = null) {
             // 判断data值是否需要更新materialExponent和materialExponentShard
             const mesCondition = { me_id: this._.map(datas, 'id') };
@@ -331,8 +516,8 @@ module.exports = app => {
             if (mn_id !== null) mesCondition.mn_id = mn_id;
             const mesList = await transaction.select(this.ctx.service.materialExponentShard.tableName, { where: mesCondition });
             const materialExponentColArray = ['symbol', 'symbol_desc', 'code', 'basic_price', 'is_summary', 'basic_times'];
-            const materialExponentShardColArray = ['weight_num', 'm_price', 'remark'];
-            const updateCalcArray = ['weight_num', 'basic_price', 'm_price', 'is_summary'];
+            const materialExponentShardColArray = ['weight_num', 'm_up_risk', 'm_down_risk', 'm_price', 'remark'];
+            const updateCalcArray = ['weight_num', 'basic_price', 'm_up_risk', 'm_down_risk', 'm_price', 'is_summary'];
             const updateMeArray = [];
             const udpateMseArray = [];
             let needCalc = false;
@@ -377,9 +562,15 @@ module.exports = app => {
             }
             if (udpateMseArray.length > 0) {
                 await transaction.updateRows(this.ctx.service.materialExponentShard.tableName, udpateMseArray);
+                await this.ctx.service.materialExponentShard.resetEffectivePrice(transaction, this._.uniq(this._.map(udpateMseArray, 'id')), true);
                 // const condition = mn_id ? { mid: this.ctx.material.id, mn_id } : { mid: this.ctx.material.id, ms_id };
                 result.exponentShardData = await transaction.select(this.ctx.service.materialExponentShard.tableName, { where: { id: this._.map(udpateMseArray, 'id') } });
             }
+            const basicPriceMeIds = this._.map(this._.filter(updateMeArray, data => data.basic_price !== undefined), 'id');
+            if (basicPriceMeIds.length > 0) {
+                await this.ctx.service.materialExponentShard.resetEffectivePrice(transaction, basicPriceMeIds);
+                result.exponentShardData = await transaction.select(this.ctx.service.materialExponentShard.tableName, { where: { mid: this.ctx.material.id, me_id: basicPriceMeIds } });
+            }
             return needCalc;
         }
 
@@ -495,10 +686,11 @@ module.exports = app => {
                     constant = ex.weight_num ? ex.weight_num : 0;
                     expr += constant.toString();
                 } else if (ex.type === materialConst.ex_type[1].value) {
-                    const calc_num = ex.basic_price > 0 ? this.ctx.helper.mul(ex.weight_num, this.ctx.helper.div(ex.m_price, ex.basic_price)) : 0;
+                    const effectivePrice = ex.effective_price !== null && ex.effective_price !== undefined ? ex.effective_price : ex.m_price;
+                    const calc_num = ex.basic_price > 0 ? this.ctx.helper.mul(ex.weight_num, this.ctx.helper.div(effectivePrice, ex.basic_price)) : 0;
                     const change = calc_num ? this.ctx.helper.round(calc_num, decimal.qty) : 0;
                     // const change = ex.calc_num ? ex.calc_num : 0;
-                    expr = expr + (change !== 0 ? '+' + ex.weight_num.toString() + '*(' + ex.m_price.toString() + '/' + ex.basic_price.toString() + ')' : '');
+                    expr = expr + (change !== 0 ? '+' + ex.weight_num.toString() + '*(' + effectivePrice.toString() + '/' + ex.basic_price.toString() + ')' : '');
                     sumByChange = this.ctx.helper.add(sumByChange, change);
                 }
             }
@@ -596,45 +788,37 @@ module.exports = app => {
             const updateArray = [];
             const updateStageArray = [];
             const upColArray = ['basic_price', 'm_price'];
-            // const qtyColArray = ['calc_num'];
             for (const ex of exponentList) {
+                let updateEx = null;
                 for (const col of upColArray) {
-                    if (ex[col] && ex[col] !== this.ctx.helper.round(ex[col], decimal.up)) {
-                        const upex = this._.find(updateArray, { id: ex.id });
-                        if (upex) {
-                            upex[col] = this.ctx.helper.round(ex[col], decimal.up);
-                        } else {
-                            const insertEx = {
-                                id: ex.id,
-                            };
-                            insertEx[col] = this.ctx.helper.round(ex[col], decimal.up);
-                            updateArray.push(insertEx);
-                        }
+                    if (ex[col] !== null && ex[col] !== undefined && ex[col] !== this.ctx.helper.round(ex[col], decimal.up)) {
+                        updateEx = updateEx || { id: ex.id };
+                        updateEx[col] = this.ctx.helper.round(ex[col], decimal.up);
                     }
                 }
-                // for (const col of qtyColArray) {
-                //     if (ex[col] && ex[col] !== this.ctx.helper.round(ex[col], decimal.qty)) {
-                //         const upex = this._.find(updateArray, { id: ex.id });
-                //         if (upex) {
-                //             upex[col] = this.ctx.helper.round(ex[col], decimal.qty);
-                //         } else {
-                //             const insertEx = {
-                //                 id: ex.id,
-                //             };
-                //             insertEx[col] = this.ctx.helper.round(ex[col], decimal.qty);
-                //             updateArray.push(insertEx);
-                //         }
-                //     }
-                // }
+                const effectivePrice = this.getEffectivePrice(Object.assign({}, ex, updateEx || {}), decimal.up);
+                if (effectivePrice !== ex.effective_price) {
+                    updateEx = updateEx || { id: ex.id };
+                    updateEx.effective_price = effectivePrice;
+                }
+                if (ex.type === materialConst.ex_type[1].value) {
+                    const calcData = Object.assign({}, ex, updateEx || {});
+                    const calcNum = calcData.basic_price > 0 ? this.ctx.helper.mul(calcData.weight_num, this.ctx.helper.div(effectivePrice, calcData.basic_price)) : 0;
+                    const roundedCalcNum = calcNum ? this.ctx.helper.round(calcNum, decimal.qty) : calcNum;
+                    if (roundedCalcNum !== ex.calc_num) {
+                        updateEx = updateEx || { id: ex.id };
+                        updateEx.calc_num = roundedCalcNum;
+                    }
+                }
+                if (updateEx) updateArray.push(updateEx);
             }
             for (const ex of exponentShardList) {
-                if (ex.m_price && ex.m_price !== this.ctx.helper.round(ex.m_price, decimal.up)) {
-                    const insertEx = {
-                        id: ex.id,
-                    };
-                    insertEx.m_price = this.ctx.helper.round(ex.m_price, decimal.up);
-                    updateStageArray.push(insertEx);
-                }
+                const materialExponent = this._.find(exponentList, { id: ex.me_id });
+                if (!materialExponent) continue;
+                const updateEx = { id: ex.id };
+                if (ex.m_price !== null && ex.m_price !== undefined) updateEx.m_price = this.ctx.helper.round(ex.m_price, decimal.up);
+                updateEx.effective_price = this.getEffectivePrice(Object.assign({}, materialExponent, ex, updateEx), decimal.up);
+                if (updateEx.m_price !== ex.m_price || updateEx.effective_price !== ex.effective_price) updateStageArray.push(updateEx);
             }
             if (updateArray.length > 0) await transaction.updateRows(this.tableName, updateArray);
             if (updateStageArray.length > 0) await transaction.updateRows(this.ctx.service.materialExponentShard.tableName, updateStageArray);

+ 14 - 3
app/service/material_exponent_node.js

@@ -86,7 +86,8 @@ module.exports = app => {
                             for (const node of newNodeDatas) {
                                 const ms = this._.find(msList, { id: node.ms_id });
                                 for (const ex of exponentList) {
-                                    const exponentShardOlds = this._.filter(oldMsShardList, { ms_id: ms.id, me_id: ex.id });
+                                    const exponentShardOld = this._.find(oldMsShardList, { ms_id: ms.id, me_id: ex.id });
+                                    const sourceData = !this.ctx.material.exponent_node && exponentShardOld ? exponentShardOld : {};
                                     insertExponentShardDatas.push({
                                         tid: this.ctx.tender.id,
                                         mid: this.ctx.material.id,
@@ -94,7 +95,10 @@ module.exports = app => {
                                         mn_id: node.id,
                                         me_id: ex.id,
                                         type: ex.type,
-                                        m_price: !this.ctx.material.exponent_node ? exponentShardOlds.m_price : 0,
+                                        m_up_risk: sourceData.m_up_risk || 0,
+                                        m_down_risk: sourceData.m_down_risk || 0,
+                                        m_price: sourceData.m_price || 0,
+                                        effective_price: sourceData.effective_price !== null && sourceData.effective_price !== undefined ? sourceData.effective_price : (sourceData.m_price || 0),
                                         remark: ex.remark,
                                     });
                                 }
@@ -102,6 +106,7 @@ module.exports = app => {
                         } else {
                             for (const node of newNodeDatas) {
                                 for (const ex of exponentList) {
+                                    const sourceData = !this.ctx.material.exponent_node ? ex : {};
                                     insertExponentShardDatas.push({
                                         tid: this.ctx.tender.id,
                                         mid: this.ctx.material.id,
@@ -109,7 +114,10 @@ module.exports = app => {
                                         mn_id: node.id,
                                         me_id: ex.id,
                                         type: ex.type,
-                                        m_price: !this.ctx.material.exponent_node ? ex.m_price : 0,
+                                        m_up_risk: sourceData.m_up_risk || 0,
+                                        m_down_risk: sourceData.m_down_risk || 0,
+                                        m_price: sourceData.m_price || 0,
+                                        effective_price: sourceData.effective_price !== null && sourceData.effective_price !== undefined ? sourceData.effective_price : (sourceData.m_price || 0),
                                         remark: ex.remark,
                                     });
                                 }
@@ -149,7 +157,10 @@ module.exports = app => {
                                     mn_id: null,
                                     me_id: ex.id,
                                     type: ex.type,
+                                    m_up_risk: 0,
+                                    m_down_risk: 0,
                                     m_price: 0,
+                                    effective_price: 0,
                                     remark: ex.remark,
                                 });
                             }

+ 62 - 8
app/service/material_exponent_shard.js

@@ -12,7 +12,7 @@ const auditConst = require('../const/audit').material;
 const materialConst = require('../const/material');
 const MaterialCalculator = require('../lib/material_calc');
 
-const needUpdateArray = ['weight_num', 'm_price', 'remark'];
+const needUpdateArray = ['weight_num', 'm_up_risk', 'm_down_risk', 'm_price', 'remark'];
 
 module.exports = app => {
     class MaterialExponentShard extends app.BaseService {
@@ -70,15 +70,21 @@ module.exports = app => {
         // 单个修改工料时同步修改
         async update(transaction, data, ms_id = null, mn_id = null, needCalc = false) {
             const mesInfo = await transaction.get(this.tableName, { tid: this.ctx.tender.id, ms_id, mn_id, me_id: data.id });
+            if (!mesInfo) throw '指数分表数据不存在';
             const updateData = {
                 id: mesInfo.id,
             };
             for (const nu of needUpdateArray) {
                 if (data[nu] !== undefined && mesInfo[nu] !== data[nu]) updateData[nu] = data[nu];
             }
+            if (updateData.m_price !== undefined || updateData.m_up_risk !== undefined || updateData.m_down_risk !== undefined || needCalc) {
+                const exponentInfo = await this._getExponentInfo(transaction, data.id);
+                if (!exponentInfo) throw '指数工料数据不存在';
+                updateData.effective_price = this.ctx.service.materialExponent.getEffectivePrice(Object.assign({}, exponentInfo, mesInfo, updateData));
+            }
             // 如果只有id,则不更新
             if (Object.keys(updateData).length > 1) await transaction.update(this.tableName, updateData);
-            if (updateData.m_price !== undefined || updateData.weight_num !== undefined || needCalc) {
+            if (updateData.m_price !== undefined || updateData.m_up_risk !== undefined || updateData.m_down_risk !== undefined || updateData.weight_num !== undefined || needCalc) {
                 const mnInfo = mn_id ? await transaction.get(this.ctx.service.materialExponentNode.tableName, { mid: this.ctx.material.id, id: mn_id }) : null;
                 const msInfo = ms_id && !mn_id ? await transaction.get(this.ctx.service.materialStage.tableName, { mid: this.ctx.material.id, id: ms_id }) : null;
                 const ex_calc = mnInfo ? JSON.parse(mnInfo.ex_calc) : msInfo ? JSON.parse(msInfo.ex_calc) : materialConst.ex_calc;
@@ -87,9 +93,50 @@ module.exports = app => {
             return true;
         }
 
+        async _getExponentInfo(transaction, meId) {
+            if (this.ctx.material.order < this.ctx.material.highOrder) {
+                const historyInfo = await transaction.get(this.ctx.service.materialExponentHistory.tableName, {
+                    mid: this.ctx.material.id,
+                    order: this.ctx.material.order,
+                    me_id: meId,
+                });
+                if (historyInfo) return Object.assign({}, historyInfo, { id: historyInfo.me_id });
+            }
+            return await transaction.get(this.ctx.service.materialExponent.tableName, { id: meId, tid: this.ctx.tender.id });
+        }
+
+        async _getExponentList(transaction, mid, summaryOnly = true) {
+            if (mid === this.ctx.material.id && this.ctx.material.order < this.ctx.material.highOrder) {
+                const historyCondition = { mid, order: this.ctx.material.order };
+                if (summaryOnly) historyCondition.is_summary = materialConst.is_summary.yes;
+                const historyList = await transaction.select(this.ctx.service.materialExponentHistory.tableName, { where: historyCondition });
+                if (historyList.length > 0) return historyList.map(data => Object.assign({}, data, { id: data.me_id }));
+            }
+            const exponentCondition = { tid: this.ctx.tender.id };
+            if (summaryOnly) exponentCondition.is_summary = materialConst.is_summary.yes;
+            return await transaction.select(this.ctx.service.materialExponent.tableName, { where: exponentCondition });
+        }
+
+        async resetEffectivePrice(transaction, ids, byShardId = false) {
+            if (!ids || ids.length === 0) return [];
+            const condition = { mid: this.ctx.material.id };
+            condition[byShardId ? 'id' : 'me_id'] = ids;
+            const shardList = await transaction.select(this.tableName, { where: condition });
+            if (shardList.length === 0) return [];
+            const updates = [];
+            for (const shard of shardList) {
+                const exponentInfo = await this._getExponentInfo(transaction, shard.me_id);
+                if (!exponentInfo) continue;
+                const effectivePrice = this.ctx.service.materialExponent.getEffectivePrice(Object.assign({}, exponentInfo, shard));
+                if (effectivePrice !== shard.effective_price) updates.push({ id: shard.id, effective_price: effectivePrice });
+            }
+            if (updates.length > 0) await transaction.updateRows(this.tableName, updates);
+            return updates;
+        }
+
         async calcMaterialAllExTpByStage(transaction, mid, extra_ms_id = null, decimal = this.ctx.material.exponent_decimal ? this.ctx.material.exponent_decimal : materialConst.exponent_decimal, rate = this.ctx.material.exponent_rate) {
             const msList = await this.ctx.service.materialStage.getAllDataByCondition({ where: { mid } });
-            const exponentList = await transaction.select(this.ctx.service.materialExponent.tableName, { where: { tid: this.ctx.tender.id, is_summary: materialConst.is_summary.yes } });
+            const exponentList = await this._getExponentList(transaction, mid);
             const allStageExponentList = await transaction.select(this.tableName, { where: { mid } });
             const updateStages = [];
             for (const ms of msList) {
@@ -107,7 +154,7 @@ module.exports = app => {
 
         async calcMaterialAllExTpByNode(transaction, mid, extra_mn_id = null, needCalcStage = true, decimal = this.ctx.material.exponent_decimal ? this.ctx.material.exponent_decimal : materialConst.exponent_decimal, rate = this.ctx.material.exponent_rate) {
             const mnList = await transaction.select(this.ctx.service.materialExponentNode.tableName, { where: { mid } });
-            const exponentList = await transaction.select(this.ctx.service.materialExponent.tableName, { where: { tid: this.ctx.tender.id, is_summary: materialConst.is_summary.yes } });
+            const exponentList = await this._getExponentList(transaction, mid);
             const allExponentShardList = await transaction.select(this.tableName, { where: { mid } });
             const updateNodes = [];
             for (const mn of mnList) {
@@ -168,14 +215,15 @@ module.exports = app => {
             let sumByChange = 0;
             let constant = 0;
             for (const ex of exponentList) {
-                const stageEx = this._.find(stageExponentList, { me_id: ex.id });
+                const stageEx = this._.find(stageExponentList, { me_id: ex.id }) || ex;
                 if (ex.type === materialConst.ex_type[0].value) {
                     constant = stageEx.weight_num ? stageEx.weight_num : 0;
                     expr += constant.toString();
                 } else if (ex.type === materialConst.ex_type[1].value) {
-                    const calc_num = ex.basic_price > 0 ? this.ctx.helper.mul(stageEx.weight_num, this.ctx.helper.div(stageEx.m_price, ex.basic_price)) : 0;
+                    const effectivePrice = stageEx.effective_price !== null && stageEx.effective_price !== undefined ? stageEx.effective_price : stageEx.m_price;
+                    const calc_num = ex.basic_price > 0 ? this.ctx.helper.mul(stageEx.weight_num, this.ctx.helper.div(effectivePrice, ex.basic_price)) : 0;
                     const change = calc_num ? this.ctx.helper.round(calc_num, decimal.qty) : 0;
-                    expr = expr + (change !== 0 ? '+' + stageEx.weight_num.toString() + '*(' + stageEx.m_price.toString() + '/' + ex.basic_price.toString() + ')' : '');
+                    expr = expr + (change !== 0 ? '+' + stageEx.weight_num.toString() + '*(' + effectivePrice.toString() + '/' + ex.basic_price.toString() + ')' : '');
                     sumByChange = this.ctx.helper.add(sumByChange, change);
                 }
             }
@@ -189,7 +237,7 @@ module.exports = app => {
         // 更改计算总指数金额并返回值和公式
         async calcMaterialExTp(transaction, ex_calc, mid, msid = null, mnid = null, decimal = this.ctx.material.exponent_decimal, rate = this.ctx.material.exponent_rate, needCalcStage = true) {
             if (!msid && !mnid) throw '参数有误';
-            const exponentList = await transaction.select(this.ctx.service.materialExponent.tableName, { where: { tid: this.ctx.tender.id, is_summary: materialConst.is_summary.yes } });
+            const exponentList = await this._getExponentList(transaction, mid);
             const condition = {};
             if (msid !== null) condition.ms_id = msid;
             if (mnid !== null) condition.mn_id = mnid;
@@ -272,7 +320,10 @@ module.exports = app => {
                         me_id: me.id,
                         type: me.type,
                         weight_num: me.weight_num,
+                        m_up_risk: me.m_up_risk,
+                        m_down_risk: me.m_down_risk,
                         m_price: me.m_price,
+                        effective_price: me.effective_price !== null ? me.effective_price : me.m_price,
                         remark: me.remark,
                     };
                     newExponentList.push(newExponent);
@@ -301,7 +352,10 @@ module.exports = app => {
                         ms_id: ms.id,
                         type: e.type,
                         weight_num: e.weight_num,
+                        m_up_risk: e.m_up_risk,
+                        m_down_risk: e.m_down_risk,
                         m_price: e.m_price,
+                        effective_price: e.effective_price !== null ? e.effective_price : e.m_price,
                     };
                     insertStageExponentData.push(oneStageExponentData);
                 }

+ 2 - 0
app/view/material/exponent.ejs

@@ -197,9 +197,11 @@
 </div>
 <script>
     const readOnly = <%- material.readOnly %>;
+    const isAdmin = <%- ctx.session.sessionUser.is_admin %>;
     const materialID = <%- material.id %>;
     const materialTax = <%- material.material_tax %>;
     const materialOrder = <%- material.order %>;
+    const materialHighOrder = <%- material.highOrder %>;
     const isStageSelf = parseInt('<%- material.is_stage_self %>');
     const showStageExponent = <%- material.showStageExponent %>;
     const exponent_nodes = JSON.parse(unescape('<%- escape(JSON.stringify(ctx.material.exponent_node)) %>'));

+ 19 - 0
sql/update.sql

@@ -42,6 +42,25 @@ ADD COLUMN `kp_time` datetime NULL DEFAULT NULL COMMENT '开票时间' AFTER `kp
 ALTER TABLE `zh_stage_audit`
 ADD COLUMN `audit_locked_lid` varchar(5000) NOT NULL DEFAULT '' COMMENT '审批退回锁定台账id' AFTER `audit_locked`;
 
+ALTER TABLE `zh_material_exponent`
+ADD COLUMN `m_up_risk` tinyint(3) NOT NULL DEFAULT 0 COMMENT '上涨幅度(%)' AFTER `basic_times`,
+ADD COLUMN `m_down_risk` tinyint(3) NOT NULL DEFAULT 0 COMMENT '下跌幅度(%)' AFTER `m_up_risk`,
+ADD COLUMN `effective_price` decimal(30,8) DEFAULT NULL COMMENT '有效价格指数(Ft)' AFTER `m_price`;
+
+ALTER TABLE `zh_material_exponent_history`
+ADD COLUMN `m_up_risk` tinyint(3) NOT NULL DEFAULT 0 COMMENT '本期上涨幅度(%)' AFTER `basic_times`,
+ADD COLUMN `m_down_risk` tinyint(3) NOT NULL DEFAULT 0 COMMENT '本期下跌幅度(%)' AFTER `m_up_risk`,
+ADD COLUMN `effective_price` decimal(30,8) DEFAULT NULL COMMENT '本期有效价格指数(Ft)' AFTER `m_price`;
+
+ALTER TABLE `zh_material_exponent_shard`
+ADD COLUMN `m_up_risk` tinyint(3) NOT NULL DEFAULT 0 COMMENT '上涨幅度(%)' AFTER `weight_num`,
+ADD COLUMN `m_down_risk` tinyint(3) NOT NULL DEFAULT 0 COMMENT '下跌幅度(%)' AFTER `m_up_risk`,
+ADD COLUMN `effective_price` decimal(30,8) DEFAULT NULL COMMENT '有效价格指数(Ft)' AFTER `m_price`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------
+
+UPDATE `zh_material_exponent` SET `effective_price` = `m_price` WHERE `effective_price` IS NULL;
+UPDATE `zh_material_exponent_history` SET `effective_price` = `m_price` WHERE `effective_price` IS NULL;
+UPDATE `zh_material_exponent_shard` SET `effective_price` = `m_price` WHERE `effective_price` IS NULL;