Browse Source

指数调差功能第一版

laiguoran 4 years ago
parent
commit
64a8cbd665

+ 34 - 0
app/const/material.js

@@ -23,8 +23,42 @@ const m_type = [
     { text: '半成品', value: 6 },
     { text: '其他', value: 7 },
 ];
+// 指数调差类型
+const ex_type = [
+    { text: '定值', value: 1 },
+    { text: '变值', value: 2 },
+];
+
+const ex_calc = [
+    {
+        code: 'bqht',
+        text: '本期合同计量金额',
+        value: '',
+        select: false,
+    },
+    {
+        code: 'bqbg',
+        text: '本期变更计量金额',
+        value: '',
+        select: false,
+    },
+    {
+        code: 'bqwc',
+        text: '本期完成计量金额',
+        value: '',
+        select: false,
+    },
+];
+
+const is_summary = {
+    yes: 1,
+    no: 2,
+};
 
 module.exports = {
     t_type,
     m_type,
+    ex_type,
+    is_summary,
+    ex_calc,
 };

+ 144 - 0
app/controller/material_controller.js

@@ -269,6 +269,40 @@ module.exports = app => {
         }
 
         /**
+         * 检查旧数据是否存在指数法调差数据,没有自动添加定值
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _checkExponentExist(ctx) {
+            const material_exponent_constant = await ctx.service.materialExponent.getDataByCondition({
+                tid: ctx.tender.id,
+                type: materialConst.ex_type[0].value,
+            });
+            // 不存在则生成指数调差清单表定值和历史期定值(防止变更定值发生变化)
+            if (!material_exponent_constant) {
+                await ctx.service.materialExponent.addOldData();
+            }
+        }
+
+        /**
+         * 获取当前期指数列表信息
+         * @param ctx
+         * @return {Promise<void>}
+         * @private
+         */
+        async _getMaterialExponentData(ctx) {
+            // 根据期判断需要获取的工料信息值
+            const searchsql = { tid: ctx.tender.id };
+            if (ctx.material.highOrder !== ctx.material.order) {
+                const midList = await ctx.service.material.getPreMidList(ctx.tender.id, ctx.material.order);
+                searchsql.mid = midList;
+            }
+            // 取所有工料表
+            return await ctx.service.materialExponent.getAllDataByCondition({ where: searchsql });
+        }
+
+        /**
          * 调差工料页面 (Get)
          * @param {Object} ctx - egg全局变量
          * @return {Promise<void>}
@@ -297,6 +331,7 @@ module.exports = app => {
 
                 // 取当前期截止上期含税金额
                 renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order);
+                renderData.ex_pre_tp_hs = await ctx.service.material.getExPreTpHs(ctx.tender.id, ctx.material.order);
 
                 renderData.months = ctx.material.months ? ctx.material.months.split(',') : [];
                 renderData.monthsList = await this._getMaterialMonthsData(ctx, renderData.materialBillsData);
@@ -347,6 +382,53 @@ module.exports = app => {
         }
 
         /**
+         * 指数调差页面 (Get)
+         * @param {Object} ctx - egg全局变量
+         * @return {Promise<void>}
+         */
+        async exponent(ctx) {
+            try {
+                await this._checkExponentExist(ctx);
+                await this._getMaterialAuditViewData(ctx);
+                const renderData = await this._getDefaultRenderData(ctx);
+                const stage_list = await ctx.service.stage.getStageMsgByStageId(ctx.material.stage_id);
+                renderData.ex_calc = materialConst.ex_calc;
+                if (!ctx.material.ex_calc) {
+                    const calcBase = await ctx.service.stage.getMaterialCalcBase(stage_list, ctx.tender.info);
+                    for (const bq of renderData.ex_calc) {
+                        const calc = _.find(calcBase, function(item) {
+                            return bq.code === item.code;
+                        });
+                        bq.value = calc.value;
+                    }
+                } else {
+                    renderData.ex_calc = JSON.parse(ctx.material.ex_calc);
+                }
+
+                renderData.materialExponentData = await this._getMaterialExponentData(ctx);
+                // 取对应期的截取上期的调差金额和应耗数量
+                if (ctx.material.highOrder !== ctx.material.order) {
+                    for (const [mindex, me] of renderData.materialExponentData.entries()) {
+                        const result = await ctx.service.materialExponentHistory.getByMeId(ctx.material.id, ctx.material.order, me.id);
+                        _.forEach(result, function(value, key) {
+                            renderData.materialExponentData[mindex][key] = result ? result[key] : null;
+                        });
+                    }
+                }
+
+                // 取当前期截止上期含税金额
+                renderData.pre_tp_hs = await ctx.service.material.getPreTpHs(ctx.tender.id, ctx.material.order);
+                renderData.ex_pre_tp_hs = await ctx.service.material.getExPreTpHs(ctx.tender.id, ctx.material.order);
+                renderData.materialType = materialConst;
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.material.exponent);
+                await this.layout('material/exponent.ejs', renderData, 'material/exponent_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material');
+            }
+        }
+
+        /**
          * 附件页面 (Get)
          * @param {Object} ctx - egg全局变量
          * @return {Promise<void>}
@@ -562,6 +644,68 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 指数调差 - 编辑指数清单项 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveExponentData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                let ex_tp = ctx.material.ex_tp;
+                let ex_expr = ctx.material.ex_expr;
+                switch (data.type) {
+                    case 'add':
+                        responseData.data = await ctx.service.materialExponent.add();
+                        break;
+                    case 'del':
+                        [ex_tp, ex_expr] = await ctx.service.materialExponent.del(data.id);
+                        responseData.data.ex_tp = ex_tp;
+                        responseData.data.ex_expr = ex_expr;
+                        break;
+                    case 'update':
+                        [ex_tp, ex_expr] = await ctx.service.materialExponent.save(data.updateData);
+                        responseData.data.ex_tp = ex_tp;
+                        responseData.data.ex_expr = ex_expr;
+                        break;
+                    case 'rate':
+                        // 判断数量是否为数字
+                        if (isNaN(data.rate)) {
+                            throw '不能输入其它非数字类型字符';
+                        }
+                        if (ctx.material.readOnly) {
+                            throw '无权操作';
+                        }
+                        await ctx.service.material.changeRate(data.rate);
+                        break;
+                    case 'paste':
+                        [ex_tp, ex_expr] = await ctx.service.materialExponent.saveDatas(data.updateData);
+                        responseData.data.ex_tp = ex_tp;
+                        responseData.data.ex_expr = ex_expr;
+                        // 取所有指数清单
+                        responseData.data.info = await this._getMaterialExponentData(ctx);
+                        break;
+                    case 'ex_calc':
+                        // 判断数量是否为数字
+                        [ex_tp, ex_expr] = await ctx.service.material.changeExCalc(data.updateData);
+                        responseData.data.ex_tp = ex_tp;
+                        responseData.data.ex_expr = ex_expr;
+                        break;
+                    default: throw '参数有误';
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
         // 审批相关
         /**
          * 添加审批人

+ 4 - 0
app/public/js/material.js

@@ -841,9 +841,13 @@ $(document).ready(() => {
             const rate = parseInt($(this).val());
             postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
                 const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), 2);
+                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), 2);
                 const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), 2);
+                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), 2);
                 $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
                 $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+                $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
+                $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
             });
         });
 

+ 449 - 0
app/public/js/material_exponent.js

@@ -0,0 +1,449 @@
+const is_numeric = (value) => {
+    if (typeof(value) === 'object') {
+        return false;
+    } else {
+        return !Number.isNaN(Number(value)) && value.toString().trim() !== '';
+    }
+};
+function getPasteHint (str, row = '') {
+    let returnObj = str;
+    if (row) {
+        returnObj.msg = '指数清单第' + (row+1) + '行' + str.msg;
+    }
+    return returnObj;
+}
+function resetExTpTable() {
+    const rate = $('#changeRate').val();
+    const bqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), 2);
+    const jzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, bqhs), 2);
+    $('#tp_set').find('td').eq(3).text(ZhCalc.round(ex_tp, 2));
+    $('#tp_set').find('td').eq(4).text(ZhCalc.round(ZhCalc.add(ex_pre_tp, ex_tp), 2));
+    $('#rate_set').find('td').eq(3).text(bqhs !== 0 ? bqhs : '');
+    $('#rate_set').find('td').eq(4).text(jzbqhs !== 0 ? jzbqhs : '');
+    $('#ex_expr').html(ex_expr);
+}
+$(document).ready(() => {
+    autoFlashHeight();
+    const materialExponentSpread = SpreadJsObj.createNewSpread($('#material-exponent-spread')[0]);
+    const materialExponentSpreadSetting = {
+        cols: [
+            {title: '类型', colSpan: '1', rowSpan: '2', field: 'type', hAlign: 1, width: 60, formatter: '@', readOnly: true,cellType: 'customizeCombo', comboItems: materialType.ex_type, cellTypeKey: 1},
+            {title: '符号', colSpan: '1', rowSpan: '2', field: 'symbol', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '符号说明', colSpan: '1', rowSpan: '2', field: 'symbol_desc', hAlign: 0, width: 180, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 1, width: 60, formatter: '@', readOnly: 'readOnly.isEdit'},
+            {title: '加权系数', colSpan: '1', rowSpan: '2', field: 'weight_num', hAlign: 2, width: 120, formatter: '@', readOnly: 'readOnly.isConstant'},
+            {title: '基本价格指数', 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: '现行价格指数', colSpan: '1', rowSpan: '2', field: 'm_price', hAlign: 2, width: 120, type: 'Number', readOnly: 'readOnly.remark'},
+            {title: '计算值', colSpan: '1', rowSpan: '2', field: 'calc_num', hAlign: 2, width: 80, formatter: '@', readOnly: true},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'remark', hAlign: 0, width: 60, formatter: '@', readOnly: 'readOnly.remark'},
+            {title: '是否汇总', colSpan: '1', rowSpan: '2', field: 'is_summary', hAlign: 1, width: 60, cellType: 'checkbox', readOnly: 'readOnly.isEdit'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+    };
+    const materialExponentBase = {
+        isUsed: function (data) {
+            if (data.type === 2) {
+                return data.mid === materialID;
+            } else {
+                return false;
+            }
+        },
+        isEdit: function (data) {
+            return data.mid === materialID && data.type === 2;
+        },
+        isConstant: function (data) {
+            return (materialOrder === 1 && data.type === 1) || (data.mid === materialID && data.type === 2);
+        }
+    };
+    const materialExponentCol = {
+        getValue: {
+            calc_num : function (data) {
+                const calc_num = data.basic_price > 0 ? ZhCalc.mul(data.weight_num, ZhCalc.div(data.m_price, data.basic_price)) : 0;
+                return calc_num > 0 ? ZhCalc.round(calc_num, 3) : 0;
+            },
+        },
+        readOnly: {
+            isEdit: function (data) {
+                return !(!readOnly && materialExponentBase.isEdit(data));
+            },
+            isUsed: function (data) {
+                return !(!readOnly && materialExponentBase.isUsed(data));
+            },
+            remark: function (data) {
+                return !(!readOnly && data.type === 2);
+            },
+            isConstant: function (data) {
+                return !(!readOnly && materialExponentBase.isConstant(data));
+            }
+        },
+    };
+    SpreadJsObj.initSpreadSettingEvents(materialExponentSpreadSetting, materialExponentCol);
+    SpreadJsObj.initSheet(materialExponentSpread.getActiveSheet(), materialExponentSpreadSetting);
+    SpreadJsObj.loadSheetData(materialExponentSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialExponentData);
+
+    const materialExponentSpreadObj = {
+        refreshActn: function (rowCount = 1) {
+            const setObjEnable = function (obj, enable) {
+                if (enable) {
+                    obj.removeClass('disabled');
+                } else {
+                    obj.addClass('disabled');
+                }
+            };
+            const sheet = materialExponentSpread.getActiveSheet();
+            const select = SpreadJsObj.getSelectObject(sheet);
+            // 还需判断是否已被调差清单调用
+            setObjEnable($('#del'), !readOnly && select && materialExponentBase.isUsed(select) && rowCount === 1);
+        },
+        add: function () {
+            const sheet = materialExponentSpread.getActiveSheet();
+            postData(window.location.pathname + '/save', {type: 'add'}, function (result) {
+                if (result) {
+                    materialExponentData.push(result);
+                    sheet.addRows(materialExponentData.length - 1, 1);
+                    SpreadJsObj.reLoadRowData(sheet, materialExponentData.length - 1);
+                    sheet.setSelection(materialExponentData.length - 1, 0, 1, 1);
+                    materialExponentSpreadObj.refreshActn();
+                }
+            });
+        },
+        del: function () {
+            const sheet = materialExponentSpread.getActiveSheet();
+            const select = SpreadJsObj.getSelectObject(sheet);
+            postData(window.location.pathname + '/save', {type: 'del', id: select.id}, function (result) {
+                ex_tp = result.ex_tp;
+                ex_expr = result.ex_expr;
+                resetExTpTable();
+                const index = materialExponentData.indexOf(select);
+                materialExponentData.splice(index, 1);
+                sheet.deleteRows(index, 1);
+                const sel = sheet.getSelections();
+                sheet.setSelection(index > 0 ? index - 1 : 0, sel.length > 0 ? sel[0].col : 0, 1, 1);
+                materialExponentSpreadObj.refreshActn();
+            });
+        },
+        selectionChanged: function (e, info) {
+            const sel = info.sheet.getSelections()[0];
+            const col = info.sheet.zh_setting.cols[sel.col];
+            materialExponentSpreadObj.refreshActn(sel.rowCount);
+            const data = SpreadJsObj.getSelectObject(info.sheet);
+            materialExponentSpreadObj.setReadOnly(true);
+        },
+        editEnded: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field === 'is_summary') {
+                    return;
+                }
+                // 未改变值则不提交
+                const validText = is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
+                const orgValue = select[col.field];
+                if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                // 判断部分值是否输入的是数字判断和数据计算
+                if (col.field === 'basic_price' || col.field === 'm_price') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const num = parseFloat(validText);
+                    if (validText !== null && (num < 0 || num > 100 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                        toastr.error('请输入0~100范围内并且小于3位小数的浮点数');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
+                if (col.field === 'weight_num') {
+                    if (isNaN(validText)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const num = parseFloat(validText);
+                    if (validText !== null && (num < 0 || num >= 1 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                        toastr.error('请输入0~1范围内并且小于3位小数的浮点数');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const total_weight = ZhCalc.add(ZhCalc.sub(_.sumBy(materialExponentData, 'weight_num'), parseFloat(orgValue)), num);
+                    if (total_weight > 1) {
+                        toastr.error('加权系数总和不能大于1');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
+                select[col.field] = validText;
+                select.calc_num = materialExponentCol.getValue.calc_num(select);
+                // console.log(select);
+
+                // 更新至服务器
+                postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
+                    ex_tp = result.ex_tp;
+                    ex_expr = result.ex_expr;
+                    resetExTpTable();
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    materialExponentData.splice(info.row, 1, select);
+                }, function () {
+                    select[col.field] = orgValue;
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            }
+        },
+        buttonClicked: function (e, info) {
+            if (info.sheet.zh_setting) {
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (materialExponentCol.readOnly.isEdit(select)) {
+                    return;
+                }
+                if (col.field === 'is_summary') {
+                    if (info.sheet.isEditing()) {
+                        info.sheet.endEdit(true);
+                    }
+                    select.is_summary = info.sheet.getValue(info.row, info.col) ? 1 : 0;
+                    // 更新至服务器
+                    postData(window.location.pathname + '/save', { type:'update', updateData: select }, function (result) {
+                        ex_tp = result.ex_tp;
+                        ex_expr = result.ex_expr;
+                        resetExTpTable();
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    }, function () {
+                        select.is_summary = info.sheet.getValue(info.row, info.col) ? 0 : 1;
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    });
+                }
+            }
+        },
+        deletePress: function (sheet) {
+            return;
+        },
+        clipboardPasted(e, info) {
+            const hint = {
+                cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+                numberCan: {type: 'error', msg: '请粘贴0~100范围内并且小于3位小数的浮点数'},
+                numberCan2: {type: 'error', msg: '请粘贴0~1范围内并且小于3位小数的浮点数'},
+                weightNumberCan: {type: 'error', msg: '粘贴的加权系数总和不能大于1'},
+            };
+            const range = info.cellRange;
+            const sortData = info.sheet.zh_data || [];
+            if (info.cellRange.row + info.cellRange.rowCount > sortData.length) {
+                toastMessageUniq(hint.cellError);
+                // SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialBillsData);
+                SpreadJsObj.reLoadSheetHeader(materialExponentSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(materialExponentSpread.getActiveSheet());
+                return;
+            }
+            if (sortData.length > 0 && range.col + range.colCount > 10) {
+                toastMessageUniq(hint.cellError);
+                SpreadJsObj.reLoadSheetHeader(materialExponentSpread.getActiveSheet());
+                SpreadJsObj.reLoadSheetData(materialExponentSpread.getActiveSheet());
+                return;
+            }
+            const data = [];
+            // const rowData = [];
+            for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                let bPaste = true;
+                const curRow = range.row + iRow;
+                // const materialData = JSON.parse(JSON.stringify(sortData[curRow]));
+                const materialExData = { id: sortData[curRow].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;
+
+                    let validText = info.sheet.getText(curRow, curCol);
+                    validText = is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    const orgValue = sortData[curRow][colSetting.field];
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                        sameCol++;
+                        if (range.colCount === sameCol)  {
+                            bPaste = false;
+                        }
+                        continue;
+                    }
+                    if (colSetting.field === 'basic_price' || colSetting.field === 'm_price') {
+                        if (isNaN(validText)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        const num = parseFloat(validText);
+                        if (validText !== null && (num < 0 || num > 100 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                            toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                    }
+                    if (colSetting.field === 'weight_num') {
+                        if (isNaN(validText)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        const num = parseFloat(validText);
+                        if (validText !== null && (num < 0 || num >= 1 || !/^\d+(\.\d{1,3})?$/.test(num))) {
+                            toastMessageUniq(getPasteHint(hint.numberCan2, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        const total_weight = ZhCalc.add(ZhCalc.sub(_.sumBy(materialExponentData, 'weight_num'), parseFloat(orgValue)), num);
+                        console.log(total_weight, _.sumBy(materialExponentData, 'weight_num'), orgValue, num);
+                        if (total_weight > 1) {
+                            toastMessageUniq(getPasteHint(hint.weightNumberCan, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                    }
+                    materialExData[colSetting.field] = validText;
+                    sortData[curRow][colSetting.field] = validText;
+                }
+                if (bPaste) {
+                    materialExData.calc_num = materialExponentCol.getValue.calc_num(sortData[curRow]);
+                    data.push(materialExData);
+                    // rowData.push(curRow);
+                } else {
+                    SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                }
+            }
+            if (data.length === 0) {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            }
+            // console.log(data);
+            // 更新至服务器
+            postData(window.location.pathname + '/save', { type:'paste', updateData: data }, function (result) {
+                materialExponentData = result.info;
+                SpreadJsObj.loadSheetData(materialExponentSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialExponentData);
+                ex_tp = result.ex_tp;
+                ex_expr = result.ex_expr;
+                resetExTpTable();
+            }, function () {
+                SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                return;
+            });
+        },
+        setReadOnly: function(readOnly) {
+            // SpreadJsObj.resetFieldReadOnly(materialSpread.getActiveSheet(), 'msg_spread', 'm_spread', 'm_tp', 'pre_tp', readOnly);
+        }
+    };
+    materialExponentSpreadObj.refreshActn();
+    materialExponentSpread.bind(spreadNS.Events.SelectionChanged, materialExponentSpreadObj.selectionChanged);
+    materialExponentSpread.bind(spreadNS.Events.ClipboardPasted, materialExponentSpreadObj.clipboardPasted);
+    SpreadJsObj.addDeleteBind(materialExponentSpread, materialExponentSpreadObj.deletePress);
+
+    if (!readOnly) {
+        $('#add').click(materialExponentSpreadObj.add);
+        $('#del').click(materialExponentSpreadObj.del);
+        materialExponentSpread.bind(spreadNS.Events.EditEnded, materialExponentSpreadObj.editEnded);
+        materialExponentSpread.bind(spreadNS.Events.ButtonClicked, materialExponentSpreadObj.buttonClicked);
+        // 右键菜单
+        $.contextMenu({
+            selector: '#material-exponent-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, materialExponentSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                'create': {
+                    name: '新增',
+                    icon: 'fa-sign-in',
+                    callback: function (key, opt) {
+                        materialExponentSpreadObj.add(materialExponentSpread.getActiveSheet());
+                    },
+                },
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        materialExponentSpreadObj.del(materialExponentSpread.getActiveSheet());
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = materialExponentSpread.getActiveSheet();
+                        const select = SpreadJsObj.getSelectObject(sheet);
+                        const sel = sheet.getSelections()[0];
+                        materialExponentSpreadObj.refreshActn(sel.rowCount);
+                        if (!readOnly && select && materialExponentBase.isUsed(select) && sel.rowCount === 1) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+            }
+        });
+
+        // 调差基数选中
+        $('.calc_select').on('click', function () {
+            for (const calc of ex_calc) {
+                calc.select = $('.calc_select[value="'+ calc.code +'"]').is(':checked');
+            }
+            postData(window.location.pathname + '/save', { type:'ex_calc', updateData: ex_calc }, function (result) {
+                ex_tp = result.ex_tp;
+                ex_expr = result.ex_expr;
+                resetExTpTable();
+            });
+        });
+
+        $('#changeRate').change(function () {
+            const rate = parseInt($(this).val());
+            postData(window.location.pathname + '/save', { type:'rate', rate: rate }, function (result) {
+                const bqhs = ZhCalc.round(ZhCalc.mul(m_tp, 1+rate/100), 2);
+                const exbqhs = ZhCalc.round(ZhCalc.mul(ex_tp, 1+rate/100), 2);
+                const jzbqhs = ZhCalc.round(ZhCalc.add(pre_tp_hs, bqhs), 2);
+                const exjzbqhs = ZhCalc.round(ZhCalc.add(ex_pre_tp_hs, exbqhs), 2);
+                $('#rate_set').find('td').eq(1).text(bqhs !== 0 ? bqhs : '');
+                $('#rate_set').find('td').eq(2).text(jzbqhs !== 0 ? jzbqhs : '');
+                $('#rate_set').find('td').eq(3).text(exbqhs !== 0 ? exbqhs : '');
+                $('#rate_set').find('td').eq(4).text(exjzbqhs !== 0 ? exjzbqhs : '');
+            });
+        });
+    }
+
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            materialExponentSpread.refresh();
+            const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+            // setLocalCache('material_month_' + materialID, width);
+        }
+    });
+
+    // 展开收起月信息价并浏览器记住本期展开收起
+    $('a', '.right-nav').bind('click', function () {
+        //const main = $('#main-view'), tool = $('#tools-view');
+        const tab = $(this), tabPanel = $(tab.attr('content'));
+        if (!tab.hasClass('active')) {
+            $('a', '.side-menu').removeClass('active');
+            $('.tab-content .tab-select-show').removeClass('active');
+            tab.addClass('active');
+            tabPanel.addClass('active');
+            showSideTools(tab.hasClass('active'));
+            if (tab.attr('content') === '#base-tab') {
+                const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+                // setLocalCache('material_month_' + materialID, width);
+                // materialMonthSpread.refresh();
+            }
+        } else {
+            // removeLocalCache('material_month_' + materialID);
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        // materialSpread.refresh();
+        materialExponentSpread.refresh();
+    });
+});

+ 3 - 0
app/router.js

@@ -332,6 +332,9 @@ module.exports = app => {
     app.post('/tender/:id/measure/material/:order/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveBillsData');
     // 月信息价
     app.post('/tender/:id/measure/material/:order/month/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveMonth');
+    // 指数调差
+    app.get('/tender/:id/measure/material/:order/exponent', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.exponent');
+    app.post('/tender/:id/measure/material/:order/exponent/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveExponentData');
     // 调差清单
     app.get('/tender/:id/measure/material/:order/list', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.list');
     app.post('/tender/:id/measure/material/:order/list/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveListsData');

+ 37 - 0
app/service/material.js

@@ -127,6 +127,7 @@ module.exports = app => {
                 if (preMaterial) {
                     newMaterial.rate = preMaterial.rate;
                     newMaterial.pre_tp = this.ctx.helper.add(preMaterial.m_tp, preMaterial.pre_tp);
+                    newMaterial.ex_pre_tp = this.ctx.helper.add(preMaterial.ex_tp, preMaterial.ex_pre_tp);
                 }
                 // 新增期记录
                 const result = await transaction.insert(this.tableName, newMaterial);
@@ -148,10 +149,13 @@ module.exports = app => {
                     await this.ctx.service.materialList.copyPreMaterialList(transaction, preMaterial, newMaterial);
                     // 修改本期应耗数量值和有效价差,需要剔除不参与调差的清单数据,并返回总金额
                     const m_tp = await this.ctx.service.materialBills.updateNewMaterial(transaction, this.ctx.tender.id, newMaterial.id, this.ctx, newMaterial.stage_id);
+                    // 修改现行价格指数,并返回调差基数json
+                    const ex_calc = await this.ctx.service.materialExponent.updateNewMaterial(transaction, newMaterial.id, this.ctx, newMaterial.stage_id, preMaterial.ex_calc);
                     // 计算得出本期总金额
                     const updateMaterialData = {
                         id: newMaterial.id,
                         m_tp,
+                        ex_calc: JSON.stringify(ex_calc),
                     };
                     await transaction.update(this.tableName, updateMaterialData);
                 }
@@ -182,6 +186,8 @@ module.exports = app => {
                 await transaction.delete(this.ctx.service.materialListNotjoin.tableName, { mid: id });
                 await transaction.delete(this.ctx.service.materialBillsHistory.tableName, { mid: id });
                 await transaction.delete(this.ctx.service.materialFile.tableName, { mid: id });
+                await transaction.delete(this.ctx.service.materialExponent.tableName, { mid: id });
+                await transaction.delete(this.ctx.service.materialExponentHistory.tableName, { mid: id });
                 // 如果存在上一期,把上一期的quantity,expr,msg_tp,msg_times,msg_spread,m_up_risk,m_down_risk,m_spread,m_tp,pre_tp,is_summary添加到bill中
                 const materialInfo = await this.getDataById(id);
                 if (materialInfo.order > 1) {
@@ -196,6 +202,15 @@ module.exports = app => {
                         'WHERE mbh.`tid` = ? AND mbh.`order` = ? AND mbh.`mb_id` = mb.`id`';
                     const sqlParam = [this.ctx.tender.id, materialInfo.order - 1];
                     await transaction.query(sql, sqlParam);
+
+                    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.`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];
+                    await transaction.query(sql2, sqlParam2);
                 }
                 await transaction.delete(this.tableName, { id });
                 await transaction.commit();
@@ -240,6 +255,28 @@ module.exports = app => {
         }
 
         /**
+         * 修改调差基数
+         * @param {int} rate 税率
+         * @return {Promise<*>}
+         */
+        async changeExCalc(ex_calc) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const updateData = {
+                    id: this.ctx.material.id,
+                    ex_calc: JSON.stringify(ex_calc),
+                };
+                await transaction.update(this.tableName, updateData);
+                const [ex_tp, ex_expr] = await this.ctx.service.materialExponent.calcMaterialExTp(transaction, ex_calc);
+                await transaction.commit();
+                return [ex_tp, ex_expr];
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
          * 取当前期截止上期含税金额
          * @param {int} tid 标段id
          * @param {int} order 调差期数

+ 26 - 0
app/service/material_audit.js

@@ -225,6 +225,27 @@ module.exports = app => {
                 }
                 await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
 
+                const materialExponentData = await this.ctx.service.materialExponent.getAllDataByCondition({ where: { tid: this.ctx.tender.id } });
+                const mehList = [];
+                for (const me of materialExponentData) {
+                    const newMeh = {
+                        tid: this.ctx.tender.id,
+                        mid: this.ctx.material.id,
+                        order: this.ctx.material.order,
+                        me_id: me.id,
+                        type: me.type,
+                        weight_num: me.weight_num,
+                        basic_price: me.basic_price,
+                        basic_times: me.basic_times,
+                        m_price: me.m_price,
+                        calc_num: me.calc_num,
+                        is_summary: me.is_summary,
+                    };
+                    mehList.push(newMeh);
+                }
+                console.log(mehList);
+                await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
+
                 // 微信模板通知
                 const materialInfo = await this.ctx.service.material.getDataById(materialId);
                 const wechatData = {
@@ -431,6 +452,11 @@ module.exports = app => {
                     tid: this.ctx.tender.id,
                     order: this.ctx.material.order,
                 });
+                // 删除material_exponent_history信息
+                await transaction.delete(this.ctx.service.materialExponentHistory.tableName, {
+                    tid: this.ctx.tender.id,
+                    order: this.ctx.material.order,
+                });
 
                 // 微信模板通知
                 const begin_audit = await this.getDataByCondition({

+ 249 - 0
app/service/material_exponent.js

@@ -0,0 +1,249 @@
+'use strict';
+
+/**
+ * 期计量 数据模型
+ *
+ * @author Mai
+ * @date 2018/8/13
+ * @version
+ */
+
+const auditConst = require('../const/audit').material;
+const materialConst = require('../const/material');
+const MaterialCalculator = require('../lib/material_calc');
+
+module.exports = app => {
+    class MaterialExponent extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_exponent';
+        }
+
+        /**
+         * 添加指数清单
+         * @return {void}
+         */
+        async add() {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                const newExponent = {
+                    tid: this.ctx.tender.id,
+                    mid: this.ctx.material.id,
+                    in_time: new Date(),
+                };
+                // 新增工料
+                const result = await transaction.insert(this.tableName, newExponent);
+                if (result.affectedRows !== 1) {
+                    throw '新增指数数据失败';
+                }
+                await transaction.commit();
+                return await this.getDataById(result.insertId);
+            } catch (error) {
+                console.log(error);
+                await transaction.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 删除指数清单
+         * @param {int} id 工料id
+         * @return {void}
+         */
+        async del(id) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            // 判断t_type是否为费用,且存在quantity,m_spread值
+            const transaction = await this.db.beginTransaction();
+            try {
+                const meInfo = await this.getDataById(id);
+                await transaction.delete(this.tableName, { id });
+                let ex_tp = this.ctx.material.ex_tp;
+                let ex_expr = this.ctx.material.ex_expr;
+                if (meInfo.is_summary === materialConst.is_summary.yes) {
+                    // 金额发生变化,则重新计算本期金额
+                    [ex_tp, ex_expr] = await this.ctx.service.materialExponent.calcMaterialExTp(transaction);
+                }
+                await transaction.commit();
+                return [ex_tp, ex_expr];
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 修改指数清单信息
+         * @param {Object} data 工料内容
+         * @return {void}
+         */
+        async save(data) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            delete data.in_time;
+            // delete data.m_tp;
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, data);
+                const [ex_tp, ex_expr] = await this.calcMaterialExTp(transaction);
+                await transaction.commit();
+                return [ex_tp, ex_expr];
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 复制粘贴指数清单
+         * @param {Object} data 工料内容
+         * @return {void}
+         */
+        async saveDatas(datas) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            // 判断是否可修改
+            // 判断t_type是否为费用
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.updateRows(this.tableName, datas);
+                const [ex_tp, ex_expr] = await this.calcMaterialExTp(transaction);
+                await transaction.commit();
+                return [ex_tp, ex_expr];
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 旧数据补充定值和历史数据
+         * @returns {Promise<void>}
+         */
+        async addOldData() {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 先获取第一期的mid
+                const first_material = await this.ctx.service.material.getDataByCondition({
+                    tid: this.ctx.tender.id,
+                    order: 1,
+                });
+                const insert_data = {
+                    tid: this.ctx.tender.id,
+                    mid: first_material.id,
+                    type: materialConst.ex_type[0].value,
+                    symbol: 'X',
+                    symbol_desc: '非可调因子',
+                    code: 'A',
+                    weight_num: 0,
+                    is_summary: materialConst.is_summary.yes,
+                    in_time: new Date(),
+                };
+                const result = await transaction.insert(this.tableName, insert_data);
+                // 获取最新期数据
+                const high_material = await this.ctx.service.material.getDataByCondition({
+                    tid: this.ctx.tender.id,
+                    order: this.ctx.material.highOrder,
+                });
+                const qi_order = high_material.status === auditConst.status.uncheck || high_material.status === auditConst.status.checkNo ? high_material.order - 1 : high_material;
+                const insert_history_data = [];
+                for (let i = 1; i <= qi_order; i++) {
+                    const one_insert = {
+                        tid: this.ctx.tender.id,
+                        mid: first_material.id,
+                        order: i,
+                        me_id: result.insertId,
+                        type: materialConst.ex_type[0].value,
+                        weight_num: 0,
+                        is_summary: materialConst.is_summary.yes,
+                    };
+                    insert_history_data.push(one_insert);
+                }
+                if (insert_history_data.length > 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, insert_history_data);
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        // 更改计算总指数金额并返回值和公式
+        async calcMaterialExTp(transaction, ex_calc = (this.ctx.material.ex_calc ? JSON.parse(this.ctx.material.ex_calc) : null), mid = null) {
+            let basic_calc = 0;
+            if (ex_calc) {
+                for (const calc of ex_calc) {
+                    if (calc.select) {
+                        basic_calc = this.ctx.helper.add(basic_calc, calc.value);
+                    }
+                }
+            }
+            let expr = basic_calc + '*[';
+            const exponentList = await transaction.select(this.tableName, { where: { tid: this.ctx.tender.id, is_summary: materialConst.is_summary.yes } });
+            let sumByChange = 0;
+            let constant = 0;
+            for (const ex of exponentList) {
+                if (ex.type === materialConst.ex_type[0].value) {
+                    constant = ex.weight_num ? ex.weight_num : 0;
+                    expr += constant.toString();
+                } else if (ex.type === materialConst.ex_type[1].value) {
+                    const change = ex.calc_num ? ex.calc_num : 0;
+                    expr = expr + (change !== 0 ? '+' + change.toString() : '');
+                    sumByChange = this.ctx.helper.add(sumByChange, change);
+                }
+            }
+            expr += '-1]';
+            const ex_tp = this.ctx.helper.mul(basic_calc, this.ctx.helper.sub(this.ctx.helper.add(constant, sumByChange), 1));
+            await transaction.update(this.ctx.service.material.tableName, {
+                id: mid ? mid : this.ctx.material.id,
+                ex_tp,
+                ex_expr: expr,
+            });
+            console.log(ex_tp, expr);
+            return [ex_tp, expr];
+        }
+
+        /**
+         * 更新新一期的现行价格指数,并返回指数总金额和公式
+         * @param transaction
+         * @param tid
+         * @param mid
+         * @returns {Promise<number>}
+         */
+        async updateNewMaterial(transaction, mid, ctx, stage_id, ex_calc) {
+            const calcBase = await ctx.service.stage.getMaterialCalcBase(stage_id, ctx.tender.info);
+            const old_ex_calc = ex_calc ? JSON.parse(ex_calc) : null;
+            const new_ex_calc = materialConst.ex_calc;
+            for (const bq of new_ex_calc) {
+                const calc = this._.find(calcBase, function(item) {
+                    return bq.code === item.code;
+                });
+                const oldcalc = old_ex_calc ? this._.find(JSON.parse(old_ex_calc), function(item) {
+                    return bq.code === item.code;
+                }) : null;
+                bq.value = calc.value;
+                bq.select = oldcalc ? oldcalc.select : false;
+            }
+            await this.calcMaterialExTp(transaction, new_ex_calc, mid);
+            return new_ex_calc;
+        }
+    }
+
+    return MaterialExponent;
+};

+ 37 - 0
app/service/material_exponent_history.js

@@ -0,0 +1,37 @@
+'use strict';
+
+/**
+ * 指数历史期数据表 数据模型
+ *
+ * @author Mai
+ * @date 2018/8/13
+ * @version
+ */
+
+
+module.exports = app => {
+    class MaterialExponentHistory extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_exponent_history';
+        }
+
+        /**
+         * 获取历史本期应耗数量和上期调差金额
+         * @param mid
+         * @param order
+         * @param mb_id
+         * @returns {Promise<*[]>}
+         */
+        async getByMeId(mid, order, me_id) {
+            return await this.getDataByCondition({ mid, order, me_id });
+        }
+    }
+    return MaterialExponentHistory;
+};

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

@@ -0,0 +1,122 @@
+<% include ./material_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex justify-content-between">
+            <% include ./material_sub_mini_menu.ejs %>
+            <div>
+                <% if ((material.status === auditConst.status.uncheck || material.status === auditConst.status.checkNo) && ctx.session.sessionUser.accountId === material.user_id) { %>
+                    <div class="d-inline-block">
+                        <a href="javascript: void(0);" id="add" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                        <a href="javascript: void(0);" id="del" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    </div>
+                <% } %>
+                <div class="d-inline-block ml-3">
+                    本期调差计量期:第<span class="mx-2"><%= material.s_order.split(',').join(',') %></span>期
+                </div>
+                <div class="d-inline-block ml-3">
+                    本期价差:<b id="ex_expr"><%- material.ex_expr %></b>
+                </div>
+            </div>
+            <div class="ml-auto">
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="left-view" class="c-body" style="width: 100%">
+                <!--上部分-->
+                <div class="sjs-height-1" id="material-exponent-spread">
+                </div>
+                <!--下部分-->
+                <div class="bcontent-wrap">
+                    <div class="bc-bar mb-1">
+                        <div class="input-group input-group-sm ">
+                            <div class="input-group-prepend">
+                                <span class="input-group-text" id="basic-addon1">增值税税率</span>
+                            </div>
+                            <select class="form-control form-control-sm col-1" id="changeRate">
+                                <% if (!material.readOnly) { %>
+                                    <option value="9" <% if(material.rate === 9) { %>selected<% } %>>9%</option>
+                                    <option value="10" <% if(material.rate === 10) { %>selected<% } %>>10%</option>
+                                    <option value="11" <% if(material.rate === 11) { %>selected<% } %>>11%</option>
+                                <% } else { %>
+                                    <option value="<%= material.rate %>" selected><%= material.rate %>%</option>
+                                <% } %>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="sp-wrap">
+                        <div class="col-4 p-0">
+                            <table class="table table-sm table-bordered">
+                                <tr><th rowspan="2"></th><th colspan="2">信息价</th><th colspan="2">价格指数</th></tr>
+                                <tr class="text-center"><th>本期金额</th><th>截止本期金额</th><th>本期金额</th><th>截止本期金额</th></tr>
+                                <tr id="tp_set"><td>材料价差费用</td>
+                                    <td><%= material.m_tp !== null ? ctx.helper.round(material.m_tp, 2) : null %></td>
+                                    <td><%= material.m_tp !== null || material.pre_tp !== null ? ctx.helper.round(ctx.helper.add(material.pre_tp, material.m_tp), 2) : null %></td>
+                                    <td><%= material.ex_tp !== null ? ctx.helper.round(material.ex_tp, 2) : null %></td>
+                                    <td><%= material.ex_tp !== null || material.ex_pre_tp !== null ? ctx.helper.round(ctx.helper.add(material.ex_pre_tp, material.ex_tp), 2) : null %></td>
+                                </tr>
+                                <tr id="rate_set"><td>材料价差费用(含税)</td>
+                                    <td><%= material.m_tp !== null ? ctx.helper.round(ctx.helper.mul(material.m_tp, 1+material.rate/100), 2) : null %></td>
+                                    <td><%= material.m_tp !== null || pre_tp_hs !== null ? ctx.helper.round(ctx.helper.add(pre_tp_hs, ctx.helper.round(ctx.helper.mul(material.m_tp, 1+material.rate/100), 2)), 2) : null %></td>
+                                    <td><%= material.ex_tp !== null ? ctx.helper.round(ctx.helper.mul(material.ex_tp, 1+material.rate/100), 2) : null %></td>
+                                    <td><%= material.ex_tp !== null || ex_pre_tp_hs !== null ? ctx.helper.round(ctx.helper.add(ex_pre_tp_hs, ctx.helper.round(ctx.helper.mul(material.ex_tp, 1+material.rate/100), 2)), 2) : null %></td>
+                                </tr>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div id="right-view" class="c-body" style="display:none;width: 33%">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
+                <div class="tab-content" style="width: 100%">
+                    <div id="tiaochaje" class="tab-pane active">
+                        <div class="sjs-sh-1">
+                            <table class="table table-bordered">
+                                <thead>
+                                <tr><th>需调整的价差</th><th>金额</th><th>选择</th></tr>
+                                </thead>
+                                <tbody id="calc_basic_select">
+                                <% for (const bq of ex_calc) { %>
+                                    <tr>
+                                        <td><%- bq.text %></td><td><%- bq.value %></td>
+                                        <td><input type="checkbox" value="<%- bq.code %>" <% if (material.readOnly) { %>disabled<% } %> class="calc_select" <% if (bq.select) { %>checked<% } %>></td>
+                                    </tr>
+                                <% } %>
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <!--右侧菜单-->
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" content="#base-tab" href="javascript: void(0);">调差基数</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<div style="display: none">
+    <img src="/public/images/ellipsis_horizontal.png" id="ellipsis-icon" />
+    <img src="/public/images/icon-ok.png" id="icon-ok" />
+</div>
+<script>
+    const readOnly = <%- material.readOnly %>;
+    const materialID = <%- material.id %>;
+    const materialOrder = <%- material.order %>;
+    let m_tp = <%= material.m_tp !== null ? material.m_tp : 0 %>;
+    let ex_tp = <%= material.ex_tp !== null ? material.ex_tp : 0 %>;
+    const pre_tp = <%= material.pre_tp !== null ? material.pre_tp : 0 %>;
+    const ex_pre_tp = <%= material.ex_pre_tp !== null ? material.ex_pre_tp : 0 %>;
+    const pre_tp_hs = <%= pre_tp_hs !== null ? pre_tp_hs : 0 %>;
+    const ex_pre_tp_hs = <%= ex_pre_tp_hs !== null ? ex_pre_tp_hs : 0 %>;
+    const materialType = JSON.parse('<%- JSON.stringify(materialType) %>');
+    const ex_calc = JSON.parse('<%- JSON.stringify(ex_calc) %>');
+    let materialExponentData = JSON.parse('<%- JSON.stringify(materialExponentData) %>');
+</script>

+ 1 - 0
app/view/material/exponent_modal.ejs

@@ -0,0 +1 @@
+<% include ./audit_modal.ejs %>

+ 18 - 5
app/view/material/index.ejs

@@ -20,13 +20,21 @@
                 <table class="table table-bordered">
                     <thead>
                     <tr>
-                        <th>期数</th>
-                        <th class="text-center">添加时间</th>
-                        <th class="text-center">计量期</th>
+                        <th rowspan="2">期数</th>
+                        <th class="text-center" rowspan="2">添加时间</th>
+                        <th class="text-center" rowspan="2">计量期</th>
+                        <th class="text-center" colspan="2">信息价调差</th>
+                        <th class="text-center" colspan="2">指数法调差</th>
+                        <th class="text-center" rowspan="2">合计(含税)</th>
+                        <th class="text-center" rowspan="2">合计</th>
+                        <th class="text-center" rowspan="2">审批进度</th>
+                        <th class="text-center" rowspan="2" width="140">操作</th>
+                    </tr>
+                    <tr>
+                        <th class="text-center">价差费用(含税)</th>
+                        <th class="text-center">价差费用</th>
                         <th class="text-center">价差费用(含税)</th>
                         <th class="text-center">价差费用</th>
-                        <th class="text-center">审批进度</th>
-                        <th class="text-center">操作</th>
                     </tr>
                     </thead>
                     <tbody>
@@ -35,10 +43,15 @@
                             <td>
                                 <a href="<%- '/tender/' + ctx.tender.id + '/measure/material/' + m.order %>">第 <%- m.order %> 期</a>
                             </td>
+                            <% console.log(ctx.helper.add(ctx.helper.round(ctx.helper.mul(m.ex_tp, 1+m.rate/100), 2), ctx.helper.round(ctx.helper.mul(m.m_tp, 1+m.rate/100), 2))) %>
                             <td class="text-center"><%= moment(m.in_time).format('YYYY-MM-DD') %></td>
                             <td class="text-center">第 <%= m.s_order %> 期</td>
                             <td class="text-right"><%= m.m_tp !== null ? ctx.helper.round(ctx.helper.mul(m.m_tp, 1+m.rate/100), 2) : null %></td>
                             <td class="text-right"><%= m.m_tp !== null ? ctx.helper.round(m.m_tp, 2) : null %></td>
+                            <td class="text-right"><%= m.ex_tp !== null ? ctx.helper.round(ctx.helper.mul(m.ex_tp, 1+m.rate/100), 2) : null %></td>
+                            <td class="text-right"><%= m.ex_tp !== null ? ctx.helper.round(m.ex_tp, 2) : null %></td>
+                            <td class="text-right"><%= ctx.helper.add(ctx.helper.round(ctx.helper.mul(m.ex_tp, 1+m.rate/100), 2), ctx.helper.round(ctx.helper.mul(m.m_tp, 1+m.rate/100), 2)) %></td>
+                            <td class="text-right"><%= ctx.helper.add(ctx.helper.round(m.ex_tp, 2), ctx.helper.round(m.m_tp, 2)) %></td>
                             <td class="<%- auditConst.auditProgressClass[m.status] %>">
                                 <% if (m.curAuditor) { %>
                                     <a href="#sp-list" data-toggle="modal" data-target="#sp-list" m-order="<%- m.order %>"><%- m.curAuditor.name %><%if (m.curAuditor.role !== '' && m.curAuditor.role !== null) { %>-<%- m.curAuditor.role %><% } %></a>

+ 17 - 3
app/view/material/info.ejs

@@ -52,9 +52,20 @@
                     <div class="sp-wrap">
                         <div class="col-4 p-0">
                             <table class="table table-sm table-bordered">
-                                <tr><th></th><th>本期金额</th><th>截止本期金额</th></tr>
-                                <tr id="tp_set"><td>材料价差费用</td><td><%= material.m_tp !== null ? ctx.helper.round(material.m_tp, 2) : null %></td><td><%= material.m_tp !== null || material.pre_tp !== null ? ctx.helper.round(ctx.helper.add(material.pre_tp, material.m_tp), 2) : null %></td></tr>
-                                <tr id="rate_set"><td>材料价差费用(含税)</td><td><%= material.m_tp !== null ? ctx.helper.round(ctx.helper.mul(material.m_tp, 1+material.rate/100), 2) : null %></td><td><%= material.m_tp !== null || pre_tp_hs !== null ? ctx.helper.round(ctx.helper.add(pre_tp_hs, ctx.helper.round(ctx.helper.mul(material.m_tp, 1+material.rate/100), 2)), 2) : null %></td></tr>
+                                <tr><th rowspan="2"></th><th colspan="2">信息价</th><th colspan="2">价格指数</th></tr>
+                                <tr class="text-center"><th>本期金额</th><th>截止本期金额</th><th>本期金额</th><th>截止本期金额</th></tr>
+                                <tr id="tp_set"><td>材料价差费用</td>
+                                    <td><%= material.m_tp !== null ? ctx.helper.round(material.m_tp, 2) : null %></td>
+                                    <td><%= material.m_tp !== null || material.pre_tp !== null ? ctx.helper.round(ctx.helper.add(material.pre_tp, material.m_tp), 2) : null %></td>
+                                    <td><%= material.ex_tp !== null ? ctx.helper.round(material.ex_tp, 2) : null %></td>
+                                    <td><%= material.ex_tp !== null || material.ex_pre_tp !== null ? ctx.helper.round(ctx.helper.add(material.ex_pre_tp, material.ex_tp), 2) : null %></td>
+                                </tr>
+                                <tr id="rate_set"><td>材料价差费用(含税)</td>
+                                    <td><%= material.m_tp !== null ? ctx.helper.round(ctx.helper.mul(material.m_tp, 1+material.rate/100), 2) : null %></td>
+                                    <td><%= material.m_tp !== null || pre_tp_hs !== null ? ctx.helper.round(ctx.helper.add(pre_tp_hs, ctx.helper.round(ctx.helper.mul(material.m_tp, 1+material.rate/100), 2)), 2) : null %></td>
+                                    <td><%= material.ex_tp !== null ? ctx.helper.round(ctx.helper.mul(material.ex_tp, 1+material.rate/100), 2) : null %></td>
+                                    <td><%= material.ex_tp !== null || ex_pre_tp_hs !== null ? ctx.helper.round(ctx.helper.add(ex_pre_tp_hs, ctx.helper.round(ctx.helper.mul(material.ex_tp, 1+material.rate/100), 2)), 2) : null %></td>
+                                </tr>
                             </table>
                         </div>
                     </div>
@@ -102,8 +113,11 @@
     const readOnly = <%- material.readOnly %>;
     const materialID = <%- material.id %>;
     let m_tp = <%= material.m_tp !== null ? material.m_tp : 0 %>;
+    let ex_tp = <%= material.ex_tp !== null ? material.ex_tp : 0 %>;
     const pre_tp = <%= material.pre_tp !== null ? material.pre_tp : 0 %>;
+    const ex_pre_tp = <%= material.ex_pre_tp !== null ? material.ex_pre_tp : 0 %>;
     const pre_tp_hs = <%= pre_tp_hs !== null ? pre_tp_hs : 0 %>;
+    const ex_pre_tp_hs = <%= ex_pre_tp_hs !== null ? ex_pre_tp_hs : 0 %>;
     const calcBase = JSON.parse('<%- JSON.stringify(calcBase) %>');
     const months = JSON.parse('<%- JSON.stringify(months) %>');
     let monthsList = JSON.parse('<%- JSON.stringify(monthsList) %>');

+ 8 - 1
app/view/material/material_sub_menu.ejs

@@ -11,11 +11,18 @@
         <div class="nav-box">
                 <ul class="nav-list list-unstyled">
                     <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order) { %>active<% } %>">
-                        <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>"><span class="ml-3">调差工料</span></a>
+                        <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>"><span class="ml-3">信息价调差</span></a>
                     </li>
                 </ul>
             </div>
         <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/exponent') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>/exponent"><span class="ml-3">指数法调差</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
                 <ul class="nav-list list-unstyled">
                     <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/list') { %>active<% } %>">
                         <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- material.order %>/list"><span class="ml-3">调差清单</span></a>

+ 8 - 1
app/view/material/material_sub_mini_menu.ejs

@@ -12,7 +12,14 @@
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order) { %>active<% } %>">
-                    <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>"><span class="ml-3">调差工料</span></a>
+                    <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>"><span class="ml-3">信息价调差</span></a>
+                </li>
+            </ul>
+        </div>
+        <div class="nav-box">
+            <ul class="nav-list list-unstyled">
+                <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/exponent') { %>active<% } %>">
+                    <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- ctx.material.order %>/exponent"><span class="ml-3">指数法调差</span></a>
                 </li>
             </ul>
         </div>

+ 1 - 0
app/view/profile/sms.ejs

@@ -23,6 +23,7 @@
                     </div>
                     <% } %>
                     <!--绑定手机-->
+                    <% if (accountData.auth_mobile === '') { %><div class="alert alert-warning">认证手机用户找回密码操作,请优先设置。</div><% } %>
                     <form id="mobile-form" <% if (accountData.auth_mobile !== '') { %>style="display: none" <% } %>>
                         <div class="form-group">
                             <label>认证手机</label>

+ 16 - 0
config/web.js

@@ -485,6 +485,22 @@ const JsFiles = {
                 ],
                 mergeFile: 'material',
             },
+            exponent: {
+                files: ['/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js', '/public/js/decimal.min.js', '/public/js/toastr.min.js'],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/material_exponent.js',
+                    '/public/js/shares/cs_tools.js',
+                    '/public/js/material_audit.js',
+                    '/public/js/datepicker/datepicker.min.js',
+                    '/public/js/datepicker/datepicker.zh.js',
+                ],
+                mergeFile: 'material_exponent',
+            },
             list: {
                 files: ['/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js', '/public/js/decimal.min.js', '/public/js/toastr.min.js'],
                 mergeFiles: [

+ 37 - 0
sql/update.sql

@@ -30,3 +30,40 @@ ADD COLUMN `tid`  int(11) NULL COMMENT '标段id' AFTER `add_sid`;
 
 Update zh_stage_jgcl LEFT JOIN zh_stage ON zh_stage_jgcl.sid = zh_stage.id Set zh_stage_jgcl.tid = zh_stage.tid
 
+-- 指数调差
+ALTER TABLE `zh_material` ADD `ex_tp` DECIMAL(30,8) NULL DEFAULT NULL COMMENT '指数本期金额' AFTER `pre_tp`, ADD `ex_pre_tp` DECIMAL(30,8) NULL DEFAULT NULL COMMENT '指数截止上期金额' AFTER `ex_tp`, ADD `ex_expr` VARCHAR(3000) NULL DEFAULT NULL COMMENT '指数调差结果公式' AFTER `ex_pre_tp`, ADD `ex_base_selested` VARCHAR(255) NULL DEFAULT NULL COMMENT '已选调差基数值' AFTER `ex_expr`;
+
+CREATE TABLE `zh_material_exponent` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '标段id',
+  `mid` int(11) NOT NULL COMMENT '调差期id',
+  `type` tinyint(1) NOT NULL DEFAULT '2' COMMENT '类型(1定值或2变值)',
+  `symbol` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '符号',
+  `symbol_desc` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '符号说明',
+  `code` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '编号',
+  `weight_num` decimal(30,8) DEFAULT NULL COMMENT '加权系数',
+  `basic_price` decimal(30,8) DEFAULT NULL COMMENT '基本价格',
+  `basic_times` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '基准时间',
+  `m_price` decimal(30,8) DEFAULT NULL COMMENT '现行价格指数',
+  `calc_num` decimal(30,8) DEFAULT NULL COMMENT '计算值',
+  `remark` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
+  `is_summary` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否汇总,默认为0否',
+  `in_time` datetime NOT NULL COMMENT '添加时间',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='指数调差表';
+
+CREATE TABLE `zh_material_exponent_history` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `tid` int(11) NOT NULL COMMENT '所属标段id',
+  `mid` int(11) NOT NULL COMMENT '调差期id',
+  `order` tinyint(4) NOT NULL COMMENT '调差期期数',
+  `me_id` int(11) NOT NULL COMMENT '指数id',
+  `type` tinyint(1) NOT NULL DEFAULT '2' COMMENT '类型(1定值或2变值)',
+  `weight_num` decimal(30,8) DEFAULT NULL COMMENT '本期加权系数',
+  `basic_price` decimal(30,8) DEFAULT NULL COMMENT '本期基本价格指数',
+  `basic_times` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '本期基准时间',
+  `m_price` decimal(30,8) DEFAULT NULL COMMENT '本期现行价格指数',
+  `calc_num` decimal(30,8) DEFAULT NULL COMMENT '计算值',
+  `is_summary` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否汇总',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='指数调差清单历史表';