Kaynağa Gözat

调差清单设置 no.1 up

laiguoran 3 yıl önce
ebeveyn
işleme
ae512abaec

+ 1 - 0
app/const/page_show.js

@@ -38,6 +38,7 @@ const defaultSetting = {
     addDataCollect: 1,
     closeWapYfSf: 0,
     openManagement: 0,
+    openMaterialChecklist: 0,
 };
 
 

+ 98 - 0
app/controller/material_controller.js

@@ -445,6 +445,8 @@ module.exports = app => {
                 // 获取所选期数据并合并相加同类清单项
                 responseData.data.curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, ctx.material.stage_id);
                 responseData.data.curPosData = await ctx.service.stagePos.getStagesData(ctx.tender.id, ctx.material.stage_id, 'list');
+                // 获取清单设置已选清单
+                responseData.data.materialChecklistData = await ctx.service.materialChecklist.getAllDataByCondition({ where: { tid: ctx.tender.id }, orders: [['order', 'asc']] });
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
@@ -1222,6 +1224,102 @@ module.exports = app => {
                 ctx.body = responseData;
             }
         }
+
+        /**
+         * 清单设置页
+         * @param {Object} ctx - 全局上下文
+         */
+        async checklist(ctx) {
+            try {
+                await this._getMaterialAuditViewData(ctx);
+                const renderData = await this._getDefaultRenderData(ctx);
+                // 根据期判断需要获取的工料信息值表
+                const searchsql = { tid: ctx.tender.id };
+                let midList = [];
+                if (ctx.material.highOrder !== ctx.material.order) {
+                    midList = await ctx.service.material.getPreMidList(ctx.tender.id, ctx.material.order);
+                    searchsql.mid = midList;
+                }
+                searchsql.t_type = materialConst.t_type[0].value;
+                renderData.materialBillsData = await ctx.service.materialBills.getAllDataByCondition({ where: searchsql, orders: [['order', 'asc']] });
+                // 取对应期的截取上期的调差金额和应耗数量
+                if (ctx.material.highOrder !== ctx.material.order) {
+                    for (const [mindex, mb] of renderData.materialBillsData.entries()) {
+                        const result = await ctx.service.materialBillsHistory.getByMbId(ctx.material.id, ctx.material.order, mb.id);
+                        _.forEach(result, function(value, key) {
+                            if (key === 'mb_id') {
+                                renderData.materialBillsData[mindex].id = result ? result[key] : null;
+                            } else {
+                                renderData.materialBillsData[mindex][key] = result ? result[key] : null;
+                            }
+                        });
+                    }
+                }
+                // 取所有已被调用的工料清单表
+                // renderData.materialListData = await ctx.service.materialList.getMaterialData(ctx.tender.id, ctx.material.id);
+                // renderData.materialNotJoinListData = await ctx.service.materialListNotjoin.getAllDataByCondition({ where: { tid: ctx.tender.id, mid: ctx.material.id } });
+                renderData.materialType = JSON.stringify(materialConst);
+                renderData.jsFiles = this.app.jsFiles.common.concat(this.app.jsFiles.material.checklist);
+                // 获取清单数据
+                // renderData.ledger = await ctx.service.ledger.getData(ctx.tender.id);
+                // renderData.pos = await ctx.service.pos.getPosData({ tid: ctx.tender.id });
+                // 获取所选期数据并合并相加同类清单项
+                // renderData.curLedgerData = await ctx.service.stageBills.getStagesData(ctx.tender.id, ctx.material.stage_id);
+                // renderData.curPosData = await ctx.service.stagePos.getStagesData(ctx.tender.id, ctx.material.stage_id, 'list');
+                await this.layout('material/checklist.ejs', renderData, 'material/checklist_modal.ejs');
+            } catch (err) {
+                this.log(err);
+                ctx.redirect('/tender/' + ctx.tender.id + '/measure/material');
+            }
+        }
+
+        /**
+         * 指数调差 - 编辑指数清单项 (Ajax)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async saveChecklistData(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = {
+                    err: 0,
+                    msg: '',
+                    data: {},
+                };
+                console.log(data);
+                switch (data.type) {
+                    case 'adds':
+                        responseData.data = await ctx.service.materialList.adds(data.postData, data.checklist);
+                        break;
+                    case 'dels':
+                        responseData.data = await ctx.service.materialList.dels(data.postData, data.checklist);
+                        break;
+                    case 'updates':
+                        if (data.updateData.quantity === '' || data.updateData.quantity === null) {
+                            throw '请输入数量';
+                        }
+                        // 判断数量是否为数字
+                        if (isNaN(data.updateData.quantity)) {
+                            throw '不能输入其它非数字类型字符';
+                        }
+                        responseData.data = await ctx.service.materialList.saves(data.updateData);
+                        break;
+                    case 'pastes':
+                        responseData.data = await ctx.service.materialList.savePastes(data.updateData);
+                        // 取所有工料表
+                        break;
+                    case 'resetChecklist':
+                        responseData.data = await ctx.service.materialChecklist.resetData(data.pushData, data.removeData);
+                        break;
+                    default: throw '参数有误';
+                }
+
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
     }
 
     return MaterialController;

+ 1 - 0
app/controller/setting_controller.js

@@ -765,6 +765,7 @@ module.exports = app => {
                 if (!result) throw '保存数据失败';
                 // this.ctx.session.sessionProject.page_show.openChangeRevise = data.openChangeRevise ? 1 : 0;
                 this.ctx.session.sessionProject.page_show.openMaterialTax = data.openMaterialTax ? 1 : 0;
+                this.ctx.session.sessionProject.page_show.openMaterialChecklist = data.openMaterialChecklist ? 1 : 0;
                 const result2 = await ctx.service.project.updatePageshow(projectId);
                 if (!result2) throw '保存数据失败';
 

+ 876 - 0
app/public/js/material_checklist.js

@@ -0,0 +1,876 @@
+'use strict';
+
+/**
+ * 材料调差 - 调差清单设置
+ *
+ * @author EllisRan
+ * @date 2022/1/7
+ * @version
+ */
+
+function getStageId() {
+    return window.location.pathname.split('/')[5];
+}
+
+function findNotJoinLeafXmj(x, type = '') {
+    if (type === 'index') {
+        return notJoinList.findIndex(function (item) {
+            return item.gcl_id === x.gcl_id && item.xmj_id === x.id && (x.mx_id === undefined || (x.mx_id !== undefined && x.mx_id === item.mx_id));
+        });
+    }
+    return notJoinList.find(function (item) {
+        return item.gcl_id === x.gcl_id && item.xmj_id === x.id && (x.mx_id === undefined || (x.mx_id !== undefined && x.mx_id === item.mx_id));
+    });
+}
+
+function getPasteHint (str, row = '') {
+    let returnObj = str;
+    if (row) {
+        returnObj.msg = '清单第' + (row+1) + '行' + (str.msg ? str.msg : str);
+    }
+    return returnObj;
+}
+
+function makeChecklistData(lists, checklists) {
+    let html = '';
+    if (lists.length > 0) {
+        for(const [i,l] of lists.entries()) {
+            const checklistInfo = _.find(checklists, { b_code: l.b_code, name: l.name, unit: l.unit, unit_price: l.unit_price, quantity: l.quantity });
+            const isChecked = checklistInfo ? ' checked' : '';
+            const isDisabled = checklistInfo && checklistInfo.had_bills === 1 ? ' disabled' : '';
+            html += '<tr>\n' +
+                '                                    <td onselectstart="return false" style="{-moz-user-select:none}"><div class="text-center custom-control custom-checkbox mb-2">\n' +
+                '                                            <input type="checkbox" id="lists_'+ i +'" value="'+ i +'" name="customCheckbox" class="custom-control-input"'+ isChecked + isDisabled +'>\n' +
+                '                                            <label class="custom-control-label" for="lists_'+ i +'"></label>\n' +
+                '                                        </div></td>\n' +
+                '                                    <td class="text-center">'+ (i+1) +'</td>\n' +
+                '                                    <td>'+ l.b_code +'</td>\n' +
+                '                                    <td>'+ l.name +'</td>\n' +
+                '                                    <td class="text-center">'+ l.unit +'</td>\n' +
+                '                                    <td class="text-right">'+ l.unit_price +'</td>\n' +
+                '                                    <td class="text-right">'+ l.quantity +'</td>\n' +
+                '                                    <td class="text-right">'+ l.total_price +'</td>\n' +
+                '                                </tr>';
+        }
+    }
+    $('#lists_data').html(html);
+}
+
+// 清单搜索隐藏清单table部分值
+function remakeChecklistData(lists, showListData = lists) {
+    // 先加载台账数据
+    if (lists.length > 0) {
+        for (const [index,gcl] of lists.entries()) {
+            const isShow = _.find(showListData, gcl);
+            $('#lists_data tr').eq(index).css('display', (isShow ? 'table-row' : 'none'));
+        }
+    }
+}
+
+$(document).ready(() => {
+    function TipCellType()
+    {
+    }
+    TipCellType.prototype = new GC.Spread.Sheets.CellTypes.ColumnHeader();
+    TipCellType.prototype.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+        return { x: x, y: y, row: context.row, col: context.col, cellRect: cellRect, sheetArea: context.sheetArea, sheet: context.sheet };
+    };
+    TipCellType.prototype.processMouseEnter = function (hitInfo){
+        if (!this._toolTipElement) {
+            var div = document.createElement("div");
+            $(div).css("position", "absolute")
+                .css("border", "1px #C0C0C0 solid")
+                .css("box-shadow", "1px 2px 5px rgba(0,0,0,0.4)")
+                .css("font", "9pt Arial")
+                .css("background", "#fff")
+                // .css("color", "#fff")
+                .css("z-index", "1000")
+                .css("padding", 5);
+            this._toolTipElement = div;
+        }
+        $(this._toolTipElement).text("单位数量:每一单位清单下所需工料消耗量。")
+            .css("top", hitInfo.y + 15)
+            .css("left", hitInfo.x - 15);
+        $(this._toolTipElement).hide();
+        // document.body.insertBefore(this._toolTipElement, null);
+        $('#material-spread-div').append(this._toolTipElement, null);
+        $(this._toolTipElement).show("fast");
+    };
+    TipCellType.prototype.processMouseLeave = function (hitInfo) {
+        if (this._toolTipElement) {
+            this._toolTipElement.remove();
+            this._toolTipElement = null;
+        }
+    };
+    autoFlashHeight();
+    // 清单table
+    const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
+    const ledgerSpreadSetting = {
+        cols: [
+            {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 90, formatter: '@'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 220, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 80, formatter: '@'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 110, type: 'Number'},
+            {title: '工程量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 110, type: 'Number'},
+            {title: '台账金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 110, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    SpreadJsObj.initSheet(ledgerSpread.getActiveSheet(), ledgerSpreadSetting);
+
+    // 加载清单数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
+    postData(window.location.pathname + '/load', {}, function (result) {
+        ledger = result.ledger;
+        curLedgerData = result.curLedgerData;
+        pos = result.pos;
+        curPosData = result.curPosData;
+        materialListData = result.materialListData;
+        notJoinList = result.materialNotJoinListData;
+        materialChecklistData = result.materialChecklistData;
+        // 解析清单汇总数据
+        gclGatherModel.loadLedgerData(ledger, curLedgerData);
+        gclGatherModel.loadPosData(pos, curPosData);
+        gclGatherData = gclGatherModel.gatherGclData().filter(item => {
+            return item.qc_qty || item.contract_qty
+        });
+        console.log(gclGatherData);
+        const hadBillsidList = _.uniq(_.map(materialListData, 'gcl_id'));
+        const pushData = [];
+        // 对比清单设置和调差清单,还要和台账对比,显示已选清单列表 不同则更新到清单设置页中
+        for (const hb of hadBillsidList) {
+            const gcl = _.find(gclGatherData, function (item) {
+                return item.leafXmjs && item.leafXmjs.length > 0 && _.findIndex(item.leafXmjs, { gcl_id : hb }) !== -1;
+            });
+            if (gcl) {
+                const mc = _.find(materialChecklistData, { b_code: gcl.b_code, name: gcl.name, unit: gcl.unit, unit_price: gcl.unit_price, quantity: gcl.quantity });
+                if (!mc) {
+                    pushData.push({ order: _.indexOf(gclGatherData, gcl), b_code: gcl.b_code, name: gcl.name, unit: gcl.unit, unit_price: gcl.unit_price, quantity: gcl.quantity, total_price: gcl.total_price, had_bills: 1 })
+                }
+            }
+        }
+        const removeData = [];
+        for (const mc of materialChecklistData) {
+            const gcl = _.find(gclGatherData, { b_code: mc.b_code, name: mc.name, unit: mc.unit, unit_price: mc.unit_price, quantity: mc.quantity });
+            // 判断是否已不存在工料清单,台账修改过后删除之
+            if (!gcl) {
+                removeData.push(mc.id);
+            }
+        }
+        setChecklistData(pushData, removeData, true);
+    });
+    function setChecklistData(pushData, removeData, sendmsg = false) {
+        if (pushData.length > 0 || removeData.length > 0) {
+            postData(window.location.pathname + '/save', { type: 'resetChecklist', pushData, removeData }, function (result2) {
+                if (sendmsg && pushData.length > 0) {
+                    toastr.success('已同步并添加到调差清单数据至本页中');
+                }
+                if (sendmsg && removeData.length > 0) {
+                    toastr.warning('已删除部分与台账清单不匹配的清单数据');
+                }
+                materialChecklistData = result2;
+                showSjsData();
+            })
+        } else {
+            showSjsData();
+        }
+    }
+    function showSjsData() {
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialChecklistData);
+        SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+        if (materialChecklistData.length > 0) {
+            loadMaterialData(materialChecklistData[0].order, 0);
+        }
+        const sheet = materialSpread.getActiveSheet();
+        sheet.suspendPaint();
+        sheet.setCellType(1, 3, new TipCellType(), spreadNS.SheetArea.colHeader);
+        sheet.resumePaint();
+    }
+    // 调差清单工料table
+    const materialSpread = SpreadJsObj.createNewSpread($('#material-spread')[0]);
+    const materialSpreadSetting = {
+        cols: [
+            {title: '清单工料含量|编号', colSpan: '5|1', rowSpan: '1|1', field: 'code', hAlign: 0, width: 80, formatter: '@', readOnly: true},
+            {title: '|名称', colSpan: '|1', rowSpan: '|1', field: 'name', hAlign: 0, width: 100, formatter: '@', readOnly: true},
+            {title: '|单位', colSpan: '|1', rowSpan: '|1', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true},
+            {title: '|数量 �', colSpan: '|1', rowSpan: '|1', field: 'quantity', hAlign: 2, width: 80, type: 'Number', readOnly: 'readOnly.isEdit'},
+            {title: '|计算式', colSpan: '1', rowSpan: '|1', field: 'expr', hAlign: 2, width: 120, formatter: '@', readOnly: 'readOnly.isEdit'},
+        ],
+        emptyRows: 0,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+    };
+
+    const materialBase = {
+        isEdit: function (data) {
+            // 是否本期添加的工料
+            return data.order === stage_order;
+        }
+    };
+
+    const materialCol = {
+        readOnly: {
+            isEdit: function (data) {
+                return !(!readOnly && materialBase.isEdit(data));
+            },
+        },
+    };
+    SpreadJsObj.initSpreadSettingEvents(materialSpreadSetting, materialCol);
+    // 获取项目节数据
+    let materialList = [];
+    function loadMaterialData(iGclRow, iLXmjRow) {
+        const gcl = gclGatherData[iGclRow];
+        if (gcl && gcl.leafXmjs[iLXmjRow]) {
+            const xmj = gcl.leafXmjs[iLXmjRow];
+            materialList = [];
+            for (const m of materialListData) {
+                if (m.gcl_id === xmj.gcl_id && m.xmj_id === xmj.id && ((xmj.mx_id !==undefined && m.mx_id === xmj.mx_id) || xmj.mx_id === undefined)) {
+                    materialList.push(m);
+                }
+            }
+            // 对清单调差工料table的单位数量进行改变
+            materialSpreadSetting.cols[materialSpreadSetting.cols.length - 2].title = '|' + gcl.unit + '数量 �';
+            SpreadJsObj.initSheet(materialSpread.getActiveSheet(), materialSpreadSetting);
+            SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, materialList);
+        } else {
+            SpreadJsObj.loadSheetData(materialSpread.getActiveSheet(), SpreadJsObj.DataType.Data, []);
+        }
+        SpreadJsObj.resetTopAndSelect(materialSpread.getActiveSheet());
+    }
+
+    // 对添加工料表格赋值
+    function changeMaterialTable() {
+        $('#materialBills tr').removeClass('table-secondary');
+        $('#materialBills').find('input').removeAttr('disabled');
+        $('#materialBills').find('input').prop('checked', false);
+        for (const ml of materialList) {
+            const mbIndex = _.findIndex(materialBillsData, {id : ml.mb_id });
+            if (mbIndex !== -1) {
+                $('#materialBills tr').eq(mbIndex).addClass('table-secondary');
+                $('#materialBills').find('input').eq(mbIndex).attr('disabled', true);
+                $('#materialBills').find('input').eq(mbIndex).prop('checked', true);
+            }
+        }
+    }
+    // 选中清单并添加
+    $('#set_checklist_btn').click(function () {
+        const select_checklist = $('#lists_data').find('input:checked:not(:disabled)');
+        const pushData = [];
+        for (const sc of select_checklist) {
+            const order = parseInt($(sc).val());
+            const checklistInfo = _.find(materialChecklistData, { order: order });
+            if (!checklistInfo) {
+                pushData.push({
+                    order: order,
+                    b_code: gclGatherData[order].b_code,
+                    name: gclGatherData[order].name,
+                    unit: gclGatherData[order].unit,
+                    unit_price: gclGatherData[order].unit_price,
+                    quantity: gclGatherData[order].quantity,
+                    total_price: gclGatherData[order].total_price,
+                    had_bills: 0,
+                })
+            }
+        }
+        const notSelect_checklist = $('#lists_data').find('input:not(:checked):not(:disabled)');
+        const removeData = [];
+        for (const nsc of notSelect_checklist) {
+            const order = parseInt($(nsc).val());
+            const checklistInfo = _.find(materialChecklistData, { order: order });
+            if (checklistInfo) {
+               removeData.push(checklistInfo.id);
+            }
+        }
+        setChecklistData(pushData, removeData);
+        $('#addtclist').modal('hide');
+    });
+    // 筛选无调差工料清单
+    $('#notBills_checkList').click(function () {
+        const isCheck = $(this).is(':checked');
+        let newMaterialChecklistData = materialChecklistData;
+        if (isCheck) {
+            newMaterialChecklistData = _.filter(materialChecklistData, { had_bills: 0 });
+        }
+        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, newMaterialChecklistData);
+        SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+        if (newMaterialChecklistData.length > 0) {
+            loadMaterialData(newMaterialChecklistData[0].order, 0);
+        } else {
+            loadMaterialData(-1, 0);
+        }
+    });
+    // 添加调差工料
+    $('#add_material_bill').click(function () {
+        // 获取已选工料
+        $('#materialBills').find('input:disabled').prop('checked', false);
+        const selectList = $('#materialBills').find('input:checked');
+        if (selectList.length === 0) {
+            toastr.warning('请选择调差工料');
+            $('#materialBills').find('input:disabled').prop('checked', true);
+            return false;
+        }
+        const mb_id = [];
+        for (let s = 0; s < selectList.length; s++) {
+            mb_id.push($('#materialBills').find('input:checked').eq(s).val());
+        }
+        // 获取当前项目节或部位明细id
+        const sheet = ledgerSpread.getActiveSheet();
+        const select = SpreadJsObj.getSelectObject(sheet);
+        const gcl = gclGatherData[select.order].leafXmjs;
+        const index = materialChecklistData.indexOf(select);
+        const datas = [];
+        for (const xmj of gcl) {
+            const notx = findNotJoinLeafXmj(xmj);
+            const data = {
+                xmj_id: xmj.id,
+                gcl_id: xmj.gcl_id,
+                mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                gather_qty: xmj.gather_qty,
+                is_join: notx === undefined ? 1 : 0,
+            };
+            datas.push(data);
+        }
+        // 上传到数据库
+        console.log(datas, gcl);
+        postData(window.location.pathname + '/save', {type: 'adds', checklist: { id: select.id, had_bills: 1 }, postData: {xmjs: datas, mbIds: mb_id}}, function (result) {
+            materialListData = result;
+            materialChecklistData[index].had_bills = 1;
+            loadMaterialData(select.order, 0);
+            // SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
+            $('#addgl').modal('hide');
+        });
+        $('#materialBills').find('input:disabled').prop('checked', true);
+    });
+    if (!readOnly) {
+        // material-spread右键功能
+        const materialSpreadObj = {
+            del: function () {
+                const materialSheet = materialSpread.getActiveSheet();
+                const materialSelect = SpreadJsObj.getSelectObject(materialSheet);
+                const sheet = ledgerSpread.getActiveSheet();
+                const select = SpreadJsObj.getSelectObject(sheet);
+                const index = materialChecklistData.indexOf(select);
+                const gcl = gclGatherData[select.order].leafXmjs;
+                const datas = [];
+                for (const xmj of gcl) {
+                    const data = {
+                        xmj_id: xmj.id,
+                        gcl_id: xmj.gcl_id,
+                        mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                    };
+                    datas.push(data);
+                }
+                const xmj = gcl[0];
+                const materialCount = _.size(_.filter(materialListData, function (m) {
+                    return m.gcl_id === xmj.gcl_id && m.xmj_id === xmj.id && ((xmj.mx_id !==undefined && m.mx_id === xmj.mx_id) || xmj.mx_id === undefined);
+                }));
+                let checklist = false;
+                if (materialCount === 1) {
+                    checklist = {
+                        id: select.id,
+                        had_bills: 0,
+                    }
+                }
+                console.log(datas, materialSelect.mb_id, checklist);
+                postData(window.location.pathname + '/save', {type: 'dels', checklist, postData: { xmjs: datas, mb_id: materialSelect.mb_id }}, function (result) {
+                    materialListData = result;
+                    if (checklist) materialChecklistData[index].had_bills = checklist.had_bills;
+                    loadMaterialData(select.order, 0);
+                    // SpreadJsObj.reLoadRowData(ledgerSpread.getActiveSheet(), index);
+                });
+            },
+            deletePress: function (sheet) {
+                return;
+            },
+            editStarting: function (e, info) {
+                const col = info.sheet.zh_setting.cols[info.col];
+                const select = SpreadJsObj.getSelectObject(info.sheet);
+                if (col.field === 'quantity') {
+                    if (select.expr && select.expr !== '') {
+                        info.sheet.getCell(info.row, info.col).text(select.expr);
+                    }
+                }
+            },
+            editEnded: function (e, info) {
+                if (info.sheet.zh_setting) {
+                    const select = SpreadJsObj.getSelectObject(info.sheet);
+                    const col = info.sheet.zh_setting.cols[info.col];
+                    const validText = info.editingText ? info.editingText.replace('\n', '') : null;
+                    let orgValue;
+                    if (col.field === 'quantity') {
+                        orgValue = validText && validText !== ''
+                            ? _.toNumber(validText) ? select.quantity : select.expr
+                            : (select.expr && select.expr !== '') ? select.expr : select.quantity;
+                    } else {
+                        orgValue = select[col.field];
+                    }
+                    if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === '' || validText === null))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const exprQuantity = {
+                        expr: '',
+                        quantity: 0,
+                    };
+                    const [valid, msg] = materialSpreadObj._checkExpr(validText, exprQuantity);
+                    if (!valid) {
+                        toastr.error(msg);
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (isNaN(exprQuantity.quantity)) {
+                        toastr.error('不能输入其它非数字类型字符');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    const num = parseFloat(exprQuantity.quantity);
+                    if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
+                        toastr.warning('已保留6位小数');
+                        exprQuantity.quantity = ZhCalc.round(num, 6);
+                    }
+                    // 更新至服务器
+                    const ledgerSheet = ledgerSpread.getActiveSheet();
+                    const ledgerSelect = SpreadJsObj.getSelectObject(ledgerSheet);
+                    const gcl = gclGatherData[ledgerSelect.order].leafXmjs;
+                    const datas = [];
+                    for (const xmj of gcl) {
+                        const data = {
+                            xmj_id: xmj.id,
+                            gcl_id: xmj.gcl_id,
+                            mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                        };
+                        datas.push(data);
+                    }
+                    console.log(exprQuantity, datas, select.mb_id);
+                    postData(window.location.pathname + '/save', { type:'updates', updateData: { xmjs: datas, expr: exprQuantity.expr, quantity: exprQuantity.quantity, mb_id: select.mb_id } }, function (result) {
+                        materialListData = result;
+                        loadMaterialData(ledgerSelect.order, 0);
+                        materialSpread.getActiveSheet().setSelection(info.row + 1, info.col, 1, 1);
+                    }, function () {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    });
+                }
+            },
+            clipboardPasted(e, info) {
+                const hint = {
+                    cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                    numberExpr: {type: 'error', msg: '不能粘贴其它非数字类型字符'},
+                    numberCan: {type: 'warning', msg: '已保留6位小数'},
+                };
+                const range = info.cellRange;
+                const sortData = info.sheet.zh_data || [];
+                if (range.row + range.rowCount > sortData.length) {
+                    toastMessageUniq(hint.cellError);
+                    SpreadJsObj.reLoadSheetHeader(materialSpread.getActiveSheet());
+                    SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
+                    return;
+                }
+                if (sortData.length > 0 && range.col + range.colCount > 5) {
+                    toastMessageUniq(hint.cellError);
+                    SpreadJsObj.reLoadSheetHeader(materialSpread.getActiveSheet());
+                    SpreadJsObj.reLoadSheetData(materialSpread.getActiveSheet());
+                    return;
+                }
+                const data = [];
+                for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = range.row + iRow;
+                    const materialData = { id: sortData[curRow].id, mb_id: sortData[curRow].mb_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 validText = info.sheet.getText(curRow, curCol).replace('\n', '');
+                        const orgValue = sortData[curRow][colSetting.field];
+                        if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
+                            sameCol++;
+                            if (range.colCount === sameCol)  {
+                                bPaste = false;
+                            }
+                            continue;
+                        }
+                        const exprQuantity = {
+                            expr: '',
+                            quantity: 0,
+                        };
+                        const [valid, msg] = materialSpreadObj._checkExpr(validText, exprQuantity);
+                        if (!valid) {
+                            toastMessageUniq(getPasteHint(msg, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        if (isNaN(exprQuantity.quantity)) {
+                            toastMessageUniq(getPasteHint(hint.numberExpr, hintRow));
+                            bPaste = false;
+                            continue;
+                        }
+                        const num = parseFloat(exprQuantity.quantity);
+                        if (num < 0 || !/^\d+(\.\d{1,6})?$/.test(num)) {
+                            toastMessageUniq(getPasteHint(hint.numberCan, hintRow));
+                            exprQuantity.quantity = ZhCalc.round(num, 6);
+                        }
+                        materialData.expr = exprQuantity.expr;
+                        materialData.quantity = exprQuantity.quantity;
+                    }
+                    if (bPaste) {
+                        data.push(materialData);
+                    } else {
+                        SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                    }
+                }
+                if (data.length === 0) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.cellRange.row, info.cellRange.rowCount);
+                    return;
+                }
+                const ledgerSheet = ledgerSpread.getActiveSheet();
+                const ledgerSelect = SpreadJsObj.getSelectObject(ledgerSheet);
+                const gcl = gclGatherData[ledgerSelect.order].leafXmjs;
+                const datas = [];
+                for (const xmj of gcl) {
+                    const data2 = {
+                        xmj_id: xmj.id,
+                        gcl_id: xmj.gcl_id,
+                        mx_id: xmj.mx_id !== undefined ? xmj.mx_id : '',
+                    };
+                    datas.push(data2);
+                }
+                console.log(data, datas);
+                // 更新至服务器
+                postData(window.location.pathname + '/save', { type:'pastes', updateData: { xmjs: datas, pasteData: data } }, function (result) {
+                    materialListData = result;
+                    loadMaterialData(ledgerSelect.order, 0);
+                    materialSpread.getActiveSheet().setSelection(info.cellRange.row, info.cellRange.col, info.cellRange.rowCount, info.cellRange.colCount);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            _checkExprValid(expr) {
+                if (!expr) return [true, null];
+                const param = [];
+                let num = '', base = '';
+                for (let i = 0, iLen = expr.length; i < iLen; i++) {
+                    if (/^[\d\.%]+/.test(expr[i])) {
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        num = num + expr[i];
+                    } else if (expr[i] === '(') {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'left', value: '('});
+                    } else if (expr[i] === ')') {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'right', value: ')'});
+                    } else if (/^[\+\-*\/]/.test(expr[i])) {
+                        if (num !== '') {
+                            param.push({type: 'num', value: num});
+                            num = '';
+                        }
+                        if (base !== '') {
+                            param.push({type: 'base', value: base});
+                            base = '';
+                        }
+                        param.push({type: 'calc', value: expr[i]});
+                    } else {
+                        return [false, '输入的表达式含有非法字符: ' + expr[i]];
+                    }
+                }
+                if (num !== '') {
+                    param.push({type: 'num', value: num});
+                    num = '';
+                }
+                if (base !== '') {
+                    param.push({type: 'base', value: base});
+                    base = '';
+                }
+                if (param.length === 0) return true;
+                if (param.length > 1) {
+                    if (param[0].value === '-') {
+                        param[1].value = '-' + param[1];
+                    }
+                    param.unshift();
+                }
+                const iLen = param.length;
+                let iLeftCount = 0, iRightCount = 0;
+                for (const [i, p] of param.entries()) {
+                    if (p.type === 'calc') {
+                        if (i === 0 || i === iLen - 1)
+                            return [false, '输入的表达式非法:计算符号' + p.value + '前后应有数字'];
+                    }
+                    if (p.type === 'num') {
+                        num = p.value.replace('%', '');
+                        if (p.value.length - num.length > 1)
+                            return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                        num = _.toNumber(num);
+                        if (num === undefined || num === null || _.isNaN(num))
+                            return [false, '输入的表达式非法:' + p.value + '不是一个有效的数字'];
+                        if (i > 0) {
+                            if (param[i - 1].type !== 'calc' && param[i - 1].type !== 'left') {
+                                return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                            } else if (param[i - 1].value === '/' && num === 0) {
+                                return [false, '输入的表达式非法:请勿除0'];
+                            }
+                        }
+                    }
+                    if (p.type === 'base') {
+                        if (i > 0 && (param[i - 1].type === 'num' || param[i - 1].type === 'right'))
+                            return [false, '输入的表达式非法:' + p.value + '前应有运算符'];
+                    }
+                    if (p.type === 'left') {
+                        iLeftCount += 1;
+                        if (i !== 0 && param[i-1].type !== 'calc')
+                            return [false, '输入的表达式非法:(前应有运算符'];
+                    }
+                    if (p.type === 'right') {
+                        iRightCount += 1;
+                        if (i !== iLen - 1 && param[i+1].type !== 'calc')
+                            return [false, '输入的表达式非法:)后应有运算符'];
+                        if (iRightCount > iLeftCount)
+                            return [false, '输入的表达式非法:")"前无对应的"("'];
+                    }
+                }
+                if (iLeftCount > iRightCount)
+                    return [false, '输入的表达式非法:"("后无对应的")"'];
+                return [true, ''];
+            },
+            _checkExpr: function (text, data) {
+                if (text) {
+                    const num = _.toNumber(text);
+                    if (num) {
+                        data.quantity = num;
+                        data.expr = '';
+                    } else {
+                        const expr = $.trim(text).replace('\t', '').replace('=', '').toLowerCase();
+                        const [valid, msg] = this._checkExprValid(expr);
+                        if (!valid) return [valid, msg];
+                        data.expr = expr;
+                        data.quantity = ZhCalc.calcExpr.calcExprStrRpn(expr);
+                    }
+                } else {
+                    data.quantity = 0;
+                    data.expr = '';
+                }
+                return [true, ''];
+            },
+        };
+        materialSpread.bind(spreadNS.Events.EditStarting, materialSpreadObj.editStarting);
+        materialSpread.bind(spreadNS.Events.EditEnded, materialSpreadObj.editEnded);
+        materialSpread.bind(spreadNS.Events.ClipboardPasted, materialSpreadObj.clipboardPasted);
+        SpreadJsObj.addDeleteBind(materialSpread, materialSpreadObj.deletePress);
+        $.contextMenu({
+            selector: '#material-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, materialSpread);
+                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) {
+                        // 获取已选清单
+                        changeMaterialTable();
+                        $('#addgl').modal('show');
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = ledgerSpread.getActiveSheet();
+                        const select = SpreadJsObj.getSelectObject(sheet);
+                        if (!select) {
+                            return true;
+                        }
+                        return readOnly;
+                    }
+                },
+                'delete': {
+                    name: '删除工料',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        materialSpreadObj.del(materialSpread.getActiveSheet());
+                    },
+                    disabled: function (key, opt) {
+                        const sheet = materialSpread.getActiveSheet();
+                        const selection = sheet.getSelections();
+                        const sel = selection ? selection[0] : sheet.getSelections()[0];
+                        const select = SpreadJsObj.getSelectObject(sheet);
+                        if (!select) {
+                            return true;
+                        }
+                        if (!readOnly && sel.rowCount === 1 && select && materialBase.isEdit(select)) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+            }
+        });
+    }
+
+    // 切换清单行,读取所属项目节数据
+    ledgerSpread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
+        if (info.oldSelections !== undefined) {
+            const iOldRow = info.oldSelections[0].row, iNewRow = info.newSelections[0].row;
+            if (iNewRow !== iOldRow) {
+                loadMaterialData(materialChecklistData[iNewRow].order, 0);
+            }
+        }
+    });
+
+    $('#open_addtclist').click(function () {
+        $('#tclist_search').val('');
+        $('#tclist_search').siblings('a').hide();
+        makeChecklistData(gclGatherData, materialChecklistData);
+        $('#addtclist').modal('show');
+    });
+
+    // 回车提交
+    $('#tclist_search').on('keypress', function () {
+        if(window.event.keyCode === 13) {
+            $(this).blur();
+        }
+    });
+
+    $('#tclist_search').on('blur', function () {
+        const value = _.trim($(this).val());
+        let showListData = gclGatherData;
+        if (value !== '') {
+            $(this).siblings('a').show();
+            showListData = _.filter(gclGatherData, function (c) {
+                return (c.b_code && c.b_code.indexOf(value) !== -1) || (c.name && c.name.indexOf(value) !== -1);
+            })
+        } else {
+            $(this).siblings('a').hide();
+        }
+        remakeChecklistData(gclGatherData, showListData);
+    });
+
+    $('.remove-btn').on('click', function () {
+        $(this).hide();
+        $(this).siblings('input').val('');
+        remakeChecklistData(gclGatherData);
+    });
+
+    // 显示有调差工料清单
+    // $('#show_material_gcl').click(function () {
+    //     if ($(this).is(':checked')) {
+    //         const hadMaterialGclGatherData = [];
+    //         const hadGclIdList = [];
+    //         for (const ml of materialListData) {
+    //             if (hadGclIdList.indexOf(ml.gcl_id) === -1) {
+    //                 hadGclIdList.push(ml.gcl_id);
+    //             }
+    //         }
+    //         for (const gcl of gclGatherData) {
+    //             for (const index in gcl.leafXmjs) {
+    //                 const gcl_id = gcl.leafXmjs[index].gcl_id;
+    //                 if (hadGclIdList.indexOf(gcl_id) !== -1) {
+    //                     hadMaterialGclGatherData.push(gcl);
+    //                     break;
+    //                 }
+    //             }
+    //         }
+    //         gclGatherData = hadMaterialGclGatherData;
+    //     } else {
+    //         gclGatherModel.loadLedgerData(ledger, curLedgerData);
+    //         gclGatherModel.loadPosData(pos, curPosData);
+    //         gclGatherData = gclGatherModel.gatherGclData().filter(item => {
+    //             return item.qc_qty || item.contract_qty
+    //         });
+    //     }
+    //     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), SpreadJsObj.DataType.Data, gclGatherData);
+    //     loadLeafXmjData(0);
+    //     loadMaterialData(0, 0);
+    //     SpreadJsObj.resetTopAndSelect(ledgerSpread.getActiveSheet());
+    //     SpreadJsObj.resetTopAndSelect(leafXmjSpread.getActiveSheet());
+    //     SpreadJsObj.resetTopAndSelect(materialSpread.getActiveSheet());
+    // });
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            ledgerSpread.refresh();
+            materialSpread.refresh();
+        }
+    });
+
+    $.divResizer({
+        select: '#right-spr',
+        callback: function () {
+            ledgerSpread.refresh();
+            materialSpread.refresh();
+            const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+            setLocalCache('material_checklist_' + materialID, width);
+        }
+    });
+
+    // 展开收起工料并浏览器记住本期展开收起
+    $('a', '.right-nav').bind('click', function () {
+        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') === '#material-tab') {
+                const width = (($('#right-view').width()/$('#right-view').parent('div').width())*100).toFixed();
+                setLocalCache('material_checklist_' + materialID, width);
+            }
+        } else {
+            removeLocalCache('material_checklist_' + materialID);
+            tab.removeClass('active');
+            tabPanel.removeClass('active');
+            showSideTools(tab.hasClass('active'));
+        }
+        ledgerSpread.refresh();
+        materialSpread.refresh();
+    });
+    // 根据浏览器记录展开收起
+    if (getLocalCache('material_checklist_' + materialID)) {
+        const tab = $('.right-nav a[content="#material-tab"]'), tabPanel = $(tab.attr('content'));
+        $('a', '.side-menu').removeClass('active');
+        $('.tab-content .tab-select-show').removeClass('active');
+        tab.addClass('active');
+        tabPanel.addClass('active');
+        $('#right-view').width(getLocalCache('material_checklist_' + materialID) + '%');
+        showSideTools(tab.hasClass('active'));
+        ledgerSpread.refresh();
+        materialSpread.refresh();
+    }
+});

+ 0 - 1
app/public/js/zh_calc.js

@@ -272,7 +272,6 @@ const ZhCalc = (function () {
                     return Number(expr);
                 } else {
                     const rpnArr = this.parse2Rpn(expr);
-                    console.log(rpnArr);
                     const result = this.evalRpn(rpnArr);
                     return result === Infinity ? null : result;
                 }

+ 4 - 1
app/router.js

@@ -472,7 +472,10 @@ module.exports = app => {
     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');
     app.post('/tender/:id/measure/material/:order/list/load', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.loadListsData');
-
+    // 调差清单设置页
+    app.get('/tender/:id/measure/material/:order/checklist', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.checklist');
+    app.post('/tender/:id/measure/material/:order/checklist/load', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.loadListsData');
+    app.post('/tender/:id/measure/material/:order/checklist/save', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.saveChecklistData');
     // 附件
     app.get('/tender/:id/measure/material/:order/file', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.file');
     app.post('/tender/:id/measure/material/:order/file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, materialCheck, 'materialController.upload');

+ 63 - 59
app/service/material_audit.js

@@ -231,55 +231,6 @@ module.exports = app => {
                 if (materialBillsData.length === 0 && this.ctx.material.ex_expr === null) {
                     throw '请至少使用一种调差方式';
                 }
-                const mbhList = [];
-                for (const mb of materialBillsData) {
-                    if (mb.code === '') {
-                        throw '调差工料编号不能为空';
-                    }
-                    const newMbh = {
-                        tid: this.ctx.tender.id,
-                        mid: this.ctx.material.id,
-                        order: this.ctx.material.order,
-                        mb_id: mb.id,
-                        quantity: mb.quantity,
-                        expr: mb.expr,
-                        msg_tp: mb.msg_tp,
-                        msg_times: mb.msg_times,
-                        msg_spread: mb.msg_spread,
-                        m_up_risk: mb.m_up_risk,
-                        m_down_risk: mb.m_down_risk,
-                        m_spread: mb.m_spread,
-                        m_tp: mb.m_tp,
-                        pre_tp: mb.pre_tp,
-                        m_tax_tp: mb.m_tax_tp,
-                        tax_pre_tp: mb.tax_pre_tp,
-                        origin: mb.origin,
-                        is_summary: mb.is_summary,
-                        m_tax: mb.m_tax,
-                    };
-                    mbhList.push(newMbh);
-                }
-                if(mbhList.length !== 0) 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);
-                }
-                if(mehList.length !== 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
 
                 // 微信模板通知
                 const materialInfo = await this.ctx.service.material.getDataById(materialId);
@@ -401,6 +352,59 @@ module.exports = app => {
                         id: materialId, status: checkData.checkType,
                     });
 
+                    // 处理旧数据,防止重复插入到历史表
+                    await transaction.delete(this.ctx.service.materialBillsHistory.tableName, { tid: this.ctx.tender.id, order: this.ctx.material.order});
+                    const mbhList = [];
+                    for (const mb of materialBillsData) {
+                        if (mb.code === '') {
+                            throw '调差工料编号不能为空';
+                        }
+                        const newMbh = {
+                            tid: this.ctx.tender.id,
+                            mid: this.ctx.material.id,
+                            order: this.ctx.material.order,
+                            mb_id: mb.id,
+                            quantity: mb.quantity,
+                            expr: mb.expr,
+                            msg_tp: mb.msg_tp,
+                            msg_times: mb.msg_times,
+                            msg_spread: mb.msg_spread,
+                            m_up_risk: mb.m_up_risk,
+                            m_down_risk: mb.m_down_risk,
+                            m_spread: mb.m_spread,
+                            m_tp: mb.m_tp,
+                            pre_tp: mb.pre_tp,
+                            m_tax_tp: mb.m_tax_tp,
+                            tax_pre_tp: mb.tax_pre_tp,
+                            origin: mb.origin,
+                            is_summary: mb.is_summary,
+                            m_tax: mb.m_tax,
+                        };
+                        mbhList.push(newMbh);
+                    }
+                    if(mbhList.length !== 0) await transaction.insert(this.ctx.service.materialBillsHistory.tableName, mbhList);
+                    // 处理旧数据,防止重复插入到历史表
+                    await transaction.delete(this.ctx.service.materialExponentHistory.tableName, { tid: this.ctx.tender.id, order: this.ctx.material.order});
+                    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);
+                    }
+                    if(mehList.length !== 0) await transaction.insert(this.ctx.service.materialExponentHistory.tableName, mehList);
+
                     // 微信模板通知
                     const users = this._.uniq(this._.concat(this._.map(auditors, 'aid'), materialInfo.user_id));
                     const wechatData = {
@@ -482,16 +486,16 @@ module.exports = app => {
                 });
                 // 拷贝新一次审核流程列表
                 await transaction.insert(this.tableName, auditors);
-                // 删除material_bills_history信息
-                await transaction.delete(this.ctx.service.materialBillsHistory.tableName, {
-                    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,
-                });
+                // // 删除material_bills_history信息
+                // await transaction.delete(this.ctx.service.materialBillsHistory.tableName, {
+                //     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({

+ 59 - 0
app/service/material_checklist.js

@@ -0,0 +1,59 @@
+'use strict';
+/**
+ * 清单设置 数据模型
+ * @author LanJianRong
+ * @date 2020/6/30
+ * @version
+ */
+
+module.exports = app => {
+    class MaterialChecklist extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'material_checklist';
+        }
+
+        async resetData(pushData, removeData) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                if (pushData.length > 0) {
+                    const insertDatas = [];
+                    for (const p of pushData) {
+                        p.mid = this.ctx.material.id;
+                        p.tid = this.ctx.tender.id;
+                        insertDatas.push(p);
+                    }
+                    await transaction.insert(this.tableName, insertDatas);
+                }
+                if (removeData.length > 0) {
+                    for (const r of removeData) {
+                        await transaction.delete(this.tableName, { id: r });
+                    }
+                }
+                await transaction.commit();
+                return await this.getAllDataByCondition({ where: { tid: this.ctx.tender.id }, orders: [['order', 'asc']] });
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async updateHadBills(transaction, id, had_bills) {
+            if (!this.ctx.tender || !this.ctx.material) {
+                throw '数据错误';
+            }
+            return await transaction.update(this.tableName, { id, had_bills });
+        }
+    }
+    return MaterialChecklist;
+};
+

+ 8 - 2
app/service/material_list.js

@@ -296,7 +296,7 @@ module.exports = app => {
          * 添加工料清单关联(多清单对应)
          * @return {void}
          */
-        async adds(datas) {
+        async adds(datas, checklist = false) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
@@ -344,6 +344,9 @@ module.exports = app => {
                         throw '新增工料数据失败';
                     }
                 }
+                if (checklist) {
+                    await this.ctx.service.materialChecklist.updateHadBills(transaction, checklist.id, checklist.had_bills);
+                }
                 // 重算工料和总金额
                 // const calcMBIdList = this._.uniq(mb_idList);
                 // if (calcMBIdList.length > 0) {
@@ -364,7 +367,7 @@ module.exports = app => {
          * @param {int} id 工料id
          * @return {void}
          */
-        async dels(datas) {
+        async dels(datas, checklist = false) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
@@ -376,6 +379,9 @@ module.exports = app => {
                 }
                 // await transaction.delete(this.tableName, { id });
                 await this.calcQuantityByML(transaction, datas.mb_id);
+                if (checklist) {
+                    await this.ctx.service.materialChecklist.updateHadBills(transaction, checklist.id, checklist.had_bills);
+                }
                 await transaction.commit();
                 // console.log(datas);
                 return await this.getMaterialData(this.ctx.tender.id, this.ctx.material.id);

+ 62 - 0
app/view/material/checklist.ejs

@@ -0,0 +1,62 @@
+<% 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>
+                <div class="d-inline-block ml-1">
+                    <a href="javascript:void(0);" id="open_addtclist" class="btn btn-sm btn-light text-primary" data-original-title="添加调差清单"><i class="fa fa-plus" aria-hidden="true"></i> 选择调差清单</a>
+                </div>
+                <div class="d-inline-block">
+                    <a class="btn btn-sm btn-light">
+                        <div class="custom-control custom-checkbox">
+                            <input type="checkbox" class="custom-control-input" id="notBills_checkList">
+                            <label class="custom-control-label text-primary" for="notBills_checkList">筛选无调差工料清单</label>
+                        </div>
+                    </a>
+                </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 class="col-12 c-body">
+                <!--上部分-->
+                <div class="sjs-height-1 row w-100 sub-content">
+                    <div class="c-body" id="left-view" style="width: 60%">
+                        <div class="sjs-height-1" id="ledger-spread"></div>
+                    </div>
+                    <div class="c-body" id="right-view" style="width: 40%">
+                        <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">
+                            <div class="tab-pane active" id="material-spread-div" style="position: relative">
+                                <div class="sjs-height-1" id="material-spread"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <!--右侧菜单-->
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link active" content="#material-tab" href="javascript: void(0);">清单工料含量</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const materialType = JSON.parse('<%- materialType %>');
+    const materialBillsData = JSON.parse(unescape('<%- escape(JSON.stringify(materialBillsData)) %>'));
+    const readOnly = <%- material.readOnly %>;
+    const stage_order = <%- material.order %>;
+    const materialID = <%- material.id %>;
+    const materialDecimal = JSON.parse(unescape('<%- escape(JSON.stringify(material.decimal)) %>'));
+    let materialChecklistData, materialListData, notJoinList, ledger, curLedgerData, pos, curPosData, gclGatherData;
+</script>

+ 84 - 0
app/view/material/checklist_modal.ejs

@@ -0,0 +1,84 @@
+<% if (!material.readOnly) { %>
+<!--添加调差工料-->
+<div class="modal fade" id="addgl" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择调差工料</h5>
+            </div>
+            <div class="modal-body">
+                <!--<div class="input-group input-group-sm mb-2">-->
+                    <!--<input type="text" class="form-control form-control-sm" name="search" id="search_input" placeholder="输入 工料编号 / 名称 检索" aria-describedby="button-addon2">-->
+                    <!--<div class="input-group-append">-->
+                        <!--<button class="btn btn-outline-secondary btn-sm" type="button" id="search_btn"><i class="fa fa-search"></i></button>-->
+                    <!--</div>-->
+                <!--</div>-->
+                <div class="modal-height-300">
+                    <table class="table table-sm table-bordered">
+                        <thead>
+                        <tr class="text-center"><th>选择</th><th>编号</th><th>名称</th><th>单位</th><th>规格</th></tr>
+                        </thead>
+                        <tbody id="materialBills">
+                        <% for (const [index,m] of materialBillsData.entries()) { %>
+                        <% if (m.code !== null && m.code !== '') { %>
+                        <tr class="table-secondary">
+                            <td><input type="checkbox" checked disabled value="<%= m.id %>"></td>
+                            <td><%= m.code %></td>
+                            <td><%= m.name %></td>
+                            <td><%= m.unit %></td>
+                            <td><%= m.spec %></td>
+                        </tr>
+                        <% } else { %>
+                        <tr class="table-secondary" style="display: none">
+                            <td><input type="checkbox" checked disabled value="<%= m.id %>"></td>
+                            <td><%= m.code %></td>
+                            <td><%= m.name %></td>
+                            <td><%= m.unit %></td>
+                            <td><%= m.spec %></td>
+                        </tr>
+                        <% } %>
+                        <% } %>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="add_material_bill">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
+<!--添加调差清单-->
+<div class="modal fade " data-backdrop="static" id="addtclist">
+    <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="mySmallModalLabel">从清单中选择</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-12">
+                        <div class="mb-2 col-6 p-0 search-group"><input id="tclist_search" class="form-control form-control-sm" placeholder="输入 清单编号、名称 检索" value=""><a href="javascript:void(0);" style="display: none" class="text-danger remove-btn" title="移除关键词"><i class="fa fa-times-circle "></i></a></div>
+                        <div style="overflow-y:auto" class="sjs-biangeng-height">
+                            <table class="table table-striped table-bordered table-hover table-sm">
+                                <thead class="text-center"><tr><th width="40">选择</th><th width="40">序号</th><th>清单编号</th><th>名称</th><th width="50">单位</th><th width="100">单价</th><th width="100">数量</th><th width="100">金额</th></tr></thead>
+                                <tbody id="lists_data">
+                                </tbody>
+                            </table>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" id="set_checklist_btn" class="btn btn-sm  btn-primary">添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% include ./audit_modal.ejs %>

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

@@ -22,6 +22,9 @@
                 </div>
             </div>
             <div class="ml-auto">
+                <% if (material.order === material.highOrder) { %>
+                <a href="/tender/<%- ctx.tender.id %>/measure/material/<%- material.highOrder %>/checklist" class="btn btn-sm btn-outline-primary">清单设置</a>
+                <% } %>
                 <a href="#cc-digits" class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#cc-digits" data-placement="bottom" title="" >小数位数</a>
             </div>
         </div>

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

@@ -3,11 +3,13 @@
         第<%- ctx.material.order %>期 - <%- (tender.name.length > 11 ? tender.name.substring(0,11) + '...' : tender.name) %>
     </div>
     <div class="scrollbar-auto">
+        <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/checklist') { %>
         <div class="nav-box">
                 <ul class="nav-list list-unstyled">
                     <li class=""><a class="text-primary" href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-chevron-left "></i> <span>返回</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) { %>active<% } %>">

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

@@ -4,11 +4,13 @@
         <i class="fa fa-bars"></i>
     </div>
     <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% if (ctx.url !== '/tender/' + ctx.tender.id + '/measure/material/' + ctx.material.order + '/checklist') { %>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li class=""><a class="text-primary" href="/tender/<%- ctx.tender.id %>/measure/material"><i class="fa fa-chevron-left "></i> <span>返回</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) { %>active<% } %>">

+ 7 - 0
app/view/setting/fun.ejs

@@ -69,6 +69,12 @@
                                                 <label class="form-check-label" for="openMaterialTax">使用材料税</label>
                                             </div>
                                         </div>
+                                        <div class="form-group mb-1">
+                                            <div class="form-check form-check-inline">
+                                                <input class="form-check-input" type="checkbox" id="openMaterialChecklist" <% if(ctx.session.sessionProject.page_show.openMaterialChecklist) { %>checked<% } %> onchange="updateSetting();">
+                                                <label class="form-check-label" for="openMaterialChecklist">开启「清单设置」添加调差工料功能</label>
+                                            </div>
+                                        </div>
                                     </div>
                                 </div>
                             </div>
@@ -108,6 +114,7 @@
             needGcl: $('#need_gcl')[0].checked,
             // openChangeRevise: $('#openChangeRevise')[0].checked,
             openMaterialTax: $('#openMaterialTax')[0].checked,
+            openMaterialChecklist: $('#openMaterialChecklist')[0].checked,
         });
     }
 </script>

+ 16 - 0
config/web.js

@@ -682,6 +682,22 @@ const JsFiles = {
                 mergeFiles: ['/public/js/sub_menu.js', '/public/js/zip_oss.js', '/public/js/material_file.js', '/public/js/material_audit.js'],
                 mergeFile: 'material_file',
             },
+            checklist: {
+                files: ['/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js', '/public/js/decimal.min.js',
+                    // '/public/js/calc/calcEval.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/gcl_gather.js',
+                    '/public/js/material_checklist.js',
+                    '/public/js/shares/cs_tools.js',
+                ],
+                mergeFile: 'material_checklist',
+            },
         },
         compare: {
             tz: {