Forráskód Böngészése

1. 暂时去掉返回json数据的gzip压缩
2. 修订页面,显示至功能
3. 计量审批,隔离审批通过,退回原报,退回上一个人方法,修改终审缓存数据方法
4. 计量台账,前端添加测试用代码

MaiXinRong 5 éve
szülő
commit
7ea0d0628d

+ 1 - 1
app/controller/ledger_controller.js

@@ -750,7 +750,7 @@ module.exports = app => {
             const [ledgerSpread, posSpread] = this._getSpreadSetting();
             return {
                 revise: revise, tender: ctx.tender.data,
-                reviseBills, revisePos, ledgerSpread, posSpread, tenderMenu,
+                reviseBills, revisePos, ledgerSpread, posSpread, tenderMenu, measureType,
                 preUrl: '/tender/' + ctx.tender.id,
                 audit: audit.revise,
                 jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.ledger.revise),

+ 2 - 0
app/controller/stage_controller.js

@@ -149,6 +149,7 @@ module.exports = app => {
                 // 查询截止上期数据
                 if (ctx.stage.order > 1) {
                     renderData.preStageData = await ctx.service.stageBillsFinal.getFinalData(ctx.tender.data, ctx.stage);
+                    //renderData.preStageData = await ctx.service.stageBills.getEndStageData(ctx.tender.id, ctx.stage.order - 1);
                 } else {
                     renderData.preStageData = [];
                 }
@@ -188,6 +189,7 @@ module.exports = app => {
                 // 查询截止上期数据
                 if (ctx.stage.order > 1) {
                     responseData.data.preStageData = await ctx.service.stagePosFinal.getFinalData(ctx.tender.data, ctx.stage);
+                    //responseData.data.preStageData = await ctx.service.stagePos.getEndStageData(ctx.tender.id, ctx.stage.order - 1);
                 } else {
                     responseData.data.preStageData = [];
                 }

+ 2 - 1
app/middleware/gzip.js

@@ -18,11 +18,12 @@ module.exports = options => {
         // 后续中间件执行完成后将响应体转换成 gzip
         let body = ctx.body;
         if (!body) return;
+        if (isJSON(body)) return;
 
         // 支持 options.threshold
         if (options.threshold && ctx.length < options.threshold) return;
 
-        if (isJSON(body)) body = JSON.stringify(body);
+        //if (isJSON(body)) body = JSON.stringify(body);
 
         // 设置 gzip body,修正响应头
         const stream = zlib.createGzip();

+ 53 - 0
app/public/js/revise.js

@@ -10,12 +10,38 @@
 
 $(document).ready(() => {
     autoFlashHeight();
+    // 初始化spread
     const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
     const billsSheet = billsSpread.getActiveSheet();
     SpreadJsObj.initSheet(billsSheet, billsSpreadSetting);
     const posSpread = SpreadJsObj.createNewSpread($('#pos-spread')[0]);
     const posSheet = posSpread.getActiveSheet();
     SpreadJsObj.initSheet(posSheet, posSpreadSetting);
+    // 初始化 清单树结构
+    const treeSetting = {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+        calcFields: ['sgfh_tp', 'sjcl_tp', 'qtcl_tp', 'total_price'],
+    };
+    if (!isTz) {
+        treeSetting.calcFields.push('deal_tp');
+    }
+    treeSetting.calcFun = function (node) {
+        node.dgn_price = ZhCalc.round(ZhCalc.div(node.total_price, node.dgn_qty1), 2);
+    };
+    const billsTree = createNewPathTree('ledger', treeSetting);
+    billsTree.loadDatas(billsData);
+    treeCalc.calculateAll(billsTree);
+    // 加载至spread
+    SpreadJsObj.loadSheetData(billsSheet, SpreadJsObj.DataType.Tree, billsTree);
+    // 初始化 部位明细
+    const pos = new PosData({ id: 'id', ledgerId: 'lid' });
+    pos.loadDatas(posData);
+
     $.divResizer({
         select: '#revise-resize',
         callback: function () {
@@ -178,4 +204,31 @@ $(document).ready(() => {
         const content = $('textarea').val();
         postData('save', { content: content });
     });
+
+    // 显示层次
+    (function (select, sheet) {
+        if (!sheet.zh_tree) return;
+        $(select).click(function () {
+            const tag = $(this).attr('tag');
+            const tree = sheet.zh_tree;
+            switch (tag) {
+                case "1":
+                case "2":
+                case "3":
+                case "4":
+                case "5":
+                    tree.expandByLevel(parseInt(tag));
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "last":
+                    tree.expandByCustom(() => { return true; });
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+                case "leafXmj":
+                    tree.expandToLeafXmj();
+                    SpreadJsObj.refreshTreeRowVisible(sheet);
+                    break;
+            }
+        });
+    })('a[name=showLevel]', billsSheet);
 });

+ 179 - 2
app/public/js/stage.js

@@ -117,6 +117,23 @@ function getNodeList(node) {
 }
 
 $(document).ready(() => {
+    toastr.options = {
+        "closeButton": false,
+        "debug": false,
+        "newestOnTop": false,
+        "progressBar": false,
+        "positionClass": "toast-top-right",
+        "preventDuplicates": false,
+        "onclick": null,
+        "showDuration": "300",
+        "hideDuration": "1000",
+        "timeOut": "5000",
+        "extendedTimeOut": "1000",
+        "showEasing": "swing",
+        "hideEasing": "linear",
+        "showMethod": "fadeIn",
+        "hideMethod": "fadeOut"
+    };
     // 界面布局
     autoFlashHeight();
     // 初始化 台账树结构 数据结构
@@ -612,13 +629,168 @@ $(document).ready(() => {
                     stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), filterNodes);
                 }
             }
-        }
+        },
+        measureAllPosInNode(node, ratio = 1) {
+            const posterity = stageTree.getPosterity(node);
+            const data = {updateType: 'update', updateData: []};
+            for (const p of posterity) {
+                if (p.children && p.children.length > 0) continue;
+                const posRange = stagePos.getLedgerPos(p.id);
+                if (posRange && posRange.length > 0) {
+                    for (const pr of posRange) {
+                        if (pr.contract_qty && !checkZero(pr.contract_qty)) continue;
+                        const validValue = ZhCalc.sub(pr.quantity, pr.end_contract_qty);
+                        if (validValue <= 0) continue;
+                        const value = ratio !== 1 ? ZhCalc.mul(pr.quantity, ratio) : pr.quantity;
+                        const pData = {
+                            pid: pr.id,
+                            lid: pr.lid,
+                            contract_qty: validValue > 0 ? (value > validValue ? validValue : value) : (value < validValue ? validValue : value),
+                        };
+                        data.updateData.push(pData);
+                    }
+                }
+                if (data.updateData.length > 1000 || _.map(data.updateData, 'lid').length > 200) {
+                    toastr.warning('选中的数据过多,仅计量' + data.updateData.length + '条,请稍后');
+                    break;
+                }
+            }
+            console.log(data);
+            if (data.updateData.length === 0) {
+                toastr.info('其下全部部位明细均已计量');
+                return;
+            }
+            postData(window.location.pathname + '/update', {pos: data}, function (result) {
+                if (result.pos) {
+                    stagePos.updateDatas(result.pos.pos);
+                    stagePos.loadCurStageData(result.pos.curStageData);
+                }
+                const nodes = stageTree.loadPostStageData(result.ledger);
+                stageTreeSpreadObj.refreshTreeNodes(slSpread.getActiveSheet(), nodes);
+                stagePosSpreadObj.loadCurPosData();
+                needCheckDetail();
+                toastr.success('已计量' + data.updateData.length + '条');
+            }, function () {
+                stagePosSpreadObj.loadCurPosData();
+            });
+        },
     };
     slSpread.bind(spreadNS.Events.EditEnded, stageTreeSpreadObj.editEnded);
     slSpread.bind(spreadNS.Events.SelectionChanged, stageTreeSpreadObj.selectionChanged);
     slSpread.bind(spreadNS.Events.ClipboardPasting, stageTreeSpreadObj.clipboardPasting);
     slSpread.bind(spreadNS.Events.ClipboardPasted, stageTreeSpreadObj.clipboardPasted);
     SpreadJsObj.addDeleteBind(slSpread, stageTreeSpreadObj.deletePress);
+    $.contextMenu({
+        selector: '#stage-ledger',
+        build: function ($trigger, e) {
+            const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
+            return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+        },
+        items: {
+            'measureAll': {
+                name: '计量其下所有部位明细',
+                icon: 'fa-rocket',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    stageTreeSpreadObj.measureAllPosInNode(node);
+                },
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                }
+            },
+            'measureAllFiveTenth': {
+                name: '计量其下所有部位明细(计量50%)',
+                icon: 'fa-plane',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    stageTreeSpreadObj.measureAllPosInNode(node, 0.5);
+                },
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                }
+            },
+            'measureAllOneTenth': {
+                name: '计量其下所有部位明细(计量10%)',
+                icon: 'fa-car',
+                callback: function (key, opt) {
+                    const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    stageTreeSpreadObj.measureAllPosInNode(node, 0.1);
+                },
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                }
+            },
+            'hint1': {
+                name: '最多计量200条清单下1000条部位明细',
+                className: 'text-danger',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                },
+                disabled: function (key, opt) {
+                    return true;
+                }
+            },
+            'hint1_1': {
+                name: '(计数以清单为准,会计完清单下全部部位)',
+                className: 'text-danger',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                },
+                disabled: function (key, opt) {
+                    return true;
+                }
+            },
+            'hint2': {
+                name: '可再次使用该功能计量剩下的节点',
+                className: 'text-danger',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                },
+                disabled: function (key, opt) {
+                    return true;
+                }
+            },
+            'hint2_2': {
+                name: '(凡是计量的部位,不论计量多少,均不再计量)',
+                className: 'text-danger',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                },
+                disabled: function (key, opt) {
+                    return true;
+                }
+            },
+            'hint3': {
+                name: '如提示数据过多后,未成功,请缩小范围再试',
+                className: 'text-danger',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                },
+                disabled: function (key, opt) {
+                    return true;
+                }
+            },
+            'hint4': {
+                name: '该功能仅供测试用,请勿滥用,可能导致服务挂掉',
+                className: 'text-danger',
+                visible: function (key, opt) {
+                    const select = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                    return select;
+                },
+                disabled: function (key, opt) {
+                    return true;
+                }
+            },
+        }
+    });
 
     const stagePosSpreadObj = {
         /**
@@ -629,10 +801,10 @@ $(document).ready(() => {
             if (node) {
                 const posData = stagePos.ledgerPos[itemsPre + node.id] || [];
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', posData);
+                getNodeList(node.id);
             } else {
                 SpreadJsObj.loadSheetData(spSpread.getActiveSheet(), 'data', []);
             }
-            getNodeList(node.id);
         },
         editEnded: function (e, info) {
             if (info.sheet.zh_setting) {
@@ -860,7 +1032,12 @@ $(document).ready(() => {
     });
 
     // 加载部位明细数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
+    console.time('loadPosFromServer');
     postData(window.location.pathname + '/pos', null, function (result) {
+        console.timeEnd('loadPosFromServer');
+        console.log('pos: ' + result.pos.length);
+        console.log('cur: ' + result.curStageData.length);
+        console.log('pre: ' + result.preStageData.length);
         stagePos.loadDatas(result.pos);
         if (result.curStageData) {
             stagePos.loadCurStageData(result.curStageData);

+ 16 - 8
app/service/pos.js

@@ -30,14 +30,22 @@ module.exports = app => {
         }
 
         async getPosDataByIds(ids) {
-            this.initSqlBuilder();
-            this.sqlBuilder.setAndWhere('id', {
-                operate: 'in',
-                value: ids
-            });
-            this.sqlBuilder.columns = ['id', 'tid', 'lid', 'name', 'quantity', 'drawing_code', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty'];
-            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName)
-            return await this.db.query(sql, sqlParam);
+            if (ids instanceof Array && ids.length > 0) {
+                const sql = 'SELECT id, tid, lid, name, quantity, drawing_code, sgfh_qty, sjcl_qty, qtcl_qty' +
+                    '  FROM ' + this.tableName +
+                    '  WHERE id in (' + this.ctx.helper.getInArrStrSqlFilter(ids) + ')';
+                return await this.db.query(sql, []);
+            } else {
+                return [];
+            }
+            // this.initSqlBuilder();
+            // this.sqlBuilder.setAndWhere('id', {
+            //     operate: 'in',
+            //     value: ids
+            // });
+            // this.sqlBuilder.columns = ['id', 'tid', 'lid', 'name', 'quantity', 'drawing_code', 'sgfh_qty', 'sjcl_qty', 'qtcl_qty'];
+            // const [sql, sqlParam] = this.sqlBuilder.build(this.tableName)
+            // return await this.db.query(sql, sqlParam);
         }
 
         async _insertPosData(transaction, data, tid) {

+ 252 - 108
app/service/stage_audit.js

@@ -202,132 +202,87 @@ module.exports = app => {
             return true;
         }
 
-        /**
-         * 审批
-         * @param {Number} stageId - 标段id
-         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
-         * @param {Number} times - 第几次审批
-         * @returns {Promise<void>}
-         */
-        async check(stageId, checkData, times = 1) {
-            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
-                throw '提交数据错误';
+        async _checked(stageId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            if (!audit) {
+                throw '审核数据错误';
             }
+            const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
+            const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
 
             const transaction = await this.db.beginTransaction();
             try {
-                // 整理当前流程审核人状态更新
-                const time = new Date();
-                const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
-                if (!audit) {
-                    throw '审核数据错误';
-                }
-                // 更新当前审核流程
                 await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
-                if (checkData.checkType === auditConst.status.checked) { // 审批通过
-                    const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
-                    // 无下一审核人表示,审核结束
-                    if (nextAudit) {
-                        // 计算该审批人最终数据
-                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                        // 复制一份下一审核人数据
-                        await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
-                        // 流程至下一审批人
-                        await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
-                        // 同步 期信息
-                        const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-                        await transaction.update(this.ctx.service.stage.tableName, {
-                            id: stageId, status: auditConst.status.checking,
-                            contract_tp: tpData.contract_tp,
-                            qc_tp: tpData.qc_tp,
-                        });
-                    } else {
-                        // 本期结束
-                        // 生成截止本期数据 final数据
-                        await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
-                        await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
-                        // 计算并合同支付最终数据
-                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                        // 同步 期信息
-                        const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
-                        await transaction.update(this.ctx.service.stage.tableName, {
-                            id: stageId, status: checkData.checkType,
-                            contract_tp: tpData.contract_tp,
-                            qc_tp: tpData.qc_tp,
-                        });
-                    }
-                } else if (checkData.checkType === auditConst.status.checkNo) { // 审批退回 原报, times+1
+                // 计算并合同支付最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 无下一审核人表示,审核结束
+                if (nextAudit) {
+                    // 复制一份下一审核人数据
+                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
+                    // 流程至下一审批人
+                    await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
                     // 同步 期信息
-                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                     await transaction.update(this.ctx.service.stage.tableName, {
-                        id: stageId, status: checkData.checkType,
+                        id: stageId, status: auditConst.status.checking,
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
-                        times: times + 1,
                     });
-                    // 拷贝新一次审核流程列表
-                    // const auditors = await this.getAllDataByCondition({
-                    //     where: {sid: stageId, times: times},
-                    //     columns: ['tid', 'sid', 'aid', 'order']
-                    // });
-                    const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
-                    const sqlParam = [this.tableName, stageId, times];
-                    const auditors = await this.db.query(sql, sqlParam);
-                    let order = 1;
-                    for (const a of auditors) {
-                        a.times = times + 1;
-                        a.order = order;
-                        a.status = auditConst.status.uncheck;
-                        order++;
-                    }
-                    await transaction.insert(this.tableName, auditors);
-                    // 计算该审批人最终数据
-                    await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                    // 复制一份最新数据给原报
-                    await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
-                } else if (checkData.checkType === auditConst.status.checkNoPre) { // 审批退回 上一审批人
+                } else {
+                    // 本期结束
+                    // 生成截止本期数据 final数据
+                    await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+                    await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
                     // 同步 期信息
-                    const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
                     await transaction.update(this.ctx.service.stage.tableName, {
                         id: stageId, status: checkData.checkType,
                         contract_tp: tpData.contract_tp,
                         qc_tp: tpData.qc_tp,
                     });
-                    // 将当前审批人 与 上一审批人再次添加至流程,顺移其后审批人流程顺序
-                    if (audit.order > 1) {
-                        // 顺移气候审核人流程顺序
-                        this.initSqlBuilder();
-                        this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
-                        this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
-                        this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
-                        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
-                        const data = await transaction.query(sql, sqlParam);
-
-                        // 上一审批人,当前审批人 再次添加至流程
-                        const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
-                        const newAuditors = [];
-                        newAuditors.push({
-                            tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
-                            times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
-                            begin_time: time,
-                        });
-                        newAuditors.push({
-                            tid: audit.tid, sid: audit.sid, aid: audit.aid,
-                            times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
-                        });
-                        await transaction.insert(this.tableName, newAuditors);
-
-                        // 计算该审批人最终数据
-                        await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
-                        // 复制一份最新数据给上一人
-                        await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
-                    } else {
-                        throw '审核数据错误';
-                    }
-                } else {
-                    throw '无效审批操作';
                 }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async _checkNo(stageId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            if (!audit) {
+                throw '审核数据错误';
+            }
+            const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
+            const sqlParam = [this.tableName, stageId, times];
+            const auditors = await this.db.query(sql, sqlParam);
+            let order = 1;
+            for (const a of auditors) {
+                a.times = times + 1;
+                a.order = order;
+                a.status = auditConst.status.uncheck;
+                order++;
+            }
 
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
+                // 同步 期信息
+                await transaction.update(this.ctx.service.stage.tableName, {
+                    id: stageId, status: checkData.checkType,
+                    contract_tp: tpData.contract_tp,
+                    qc_tp: tpData.qc_tp,
+                    times: times + 1,
+                });
+                // 拷贝新一次审核流程列表
+                await transaction.insert(this.tableName, auditors);
+                // 计算该审批人最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 复制一份最新数据给原报
+                await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
                 await transaction.commit();
             } catch (err) {
                 await transaction.rollback();
@@ -335,6 +290,195 @@ module.exports = app => {
             }
         }
 
+        async _checkNoPre(stageId, checkData, times) {
+            const time = new Date();
+            // 整理当前流程审核人状态更新
+            const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            if (!audit || audit.order <= 1) {
+                throw '审核数据错误';
+            }
+            const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
+
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
+                // 顺移气候审核人流程顺序
+                this.initSqlBuilder();
+                this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
+                this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
+                this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
+                const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+                const data = await transaction.query(sql, sqlParam);
+                // 上一审批人,当前审批人 再次添加至流程
+                const newAuditors = [];
+                newAuditors.push({
+                    tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
+                    times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
+                    begin_time: time,
+                });
+                newAuditors.push({
+                    tid: audit.tid, sid: audit.sid, aid: audit.aid,
+                    times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
+                });
+                await transaction.insert(this.tableName, newAuditors);
+                // 计算该审批人最终数据
+                await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+                // 复制一份最新数据给下一人
+                await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
+                await transaction.commit();
+            } catch(err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        /**
+         * 审批
+         * @param {Number} stageId - 标段id
+         * @param {auditConst.status.checked|auditConst.status.checkNo} checkType - 审批结果
+         * @param {Number} times - 第几次审批
+         * @returns {Promise<void>}
+         */
+        async check(stageId, checkData, times = 1) {
+            if (checkData.checkType !== auditConst.status.checked && checkData.checkType !== auditConst.status.checkNo && checkData.checkType !== auditConst.status.checkNoPre) {
+                throw '提交数据错误';
+            }
+            // // 整理当前流程审核人状态更新
+            // const audit = await this.getDataByCondition({sid: stageId, times: times, status: auditConst.status.checking});
+            // if (!audit) {
+            //     throw '审核数据错误';
+            // }
+            //const time = new Date();
+
+            switch (checkData.checkType) {
+                case auditConst.status.checked:
+                    await this._checked(stageId, checkData, times);
+                    break;
+                case auditConst.status.checkNo:
+                    await this._checkNo(stageId, checkData, times);
+                    break;
+                case auditConst.status.checkNoPre:
+                    await this._checkNoPre(stageId, checkData, times);
+                    break;
+                default:
+                    throw '无效审批操作';
+            }
+
+            // const transaction = await this.db.beginTransaction();
+            // try {
+            //     // 更新当前审核流程
+            //     await transaction.update(this.tableName, {id: audit.id, status: checkData.checkType, opinion: checkData.opinion, end_time: time});
+            //     if (checkData.checkType === auditConst.status.checked) { // 审批通过
+            //         const nextAudit = await this.getDataByCondition({sid: stageId, times: times, order: audit.order + 1});
+            //         // 无下一审核人表示,审核结束
+            //         if (nextAudit) {
+            //             // 计算该审批人最终数据
+            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //             // 复制一份下一审核人数据
+            //             await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, nextAudit.order, transaction);
+            //             // 流程至下一审批人
+            //             await transaction.update(this.tableName, {id: nextAudit.id, status: auditConst.status.checking, begin_time: time});
+            //             // 同步 期信息
+            //             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //             await transaction.update(this.ctx.service.stage.tableName, {
+            //                 id: stageId, status: auditConst.status.checking,
+            //                 contract_tp: tpData.contract_tp,
+            //                 qc_tp: tpData.qc_tp,
+            //             });
+            //         } else {
+            //             // 本期结束
+            //             // 生成截止本期数据 final数据
+            //             await this.ctx.service.stageBillsFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+            //             await this.ctx.service.stagePosFinal.generateFinalData(transaction, this.ctx.tender, this.ctx.stage);
+            //             // 计算并合同支付最终数据
+            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //             // 同步 期信息
+            //             const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //             await transaction.update(this.ctx.service.stage.tableName, {
+            //                 id: stageId, status: checkData.checkType,
+            //                 contract_tp: tpData.contract_tp,
+            //                 qc_tp: tpData.qc_tp,
+            //             });
+            //         }
+            //     } else if (checkData.checkType === auditConst.status.checkNo) { // 审批退回 原报, times+1
+            //         // 同步 期信息
+            //         const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //         await transaction.update(this.ctx.service.stage.tableName, {
+            //             id: stageId, status: checkData.checkType,
+            //             contract_tp: tpData.contract_tp,
+            //             qc_tp: tpData.qc_tp,
+            //             times: times + 1,
+            //         });
+            //         // 拷贝新一次审核流程列表
+            //         // const auditors = await this.getAllDataByCondition({
+            //         //     where: {sid: stageId, times: times},
+            //         //     columns: ['tid', 'sid', 'aid', 'order']
+            //         // });
+            //         const sql = 'SELECT `tid`, `sid`, `aid`, `order` FROM ?? WHERE `sid` = ? and `times` = ? GROUP BY `aid`';
+            //         const sqlParam = [this.tableName, stageId, times];
+            //         const auditors = await this.db.query(sql, sqlParam);
+            //         let order = 1;
+            //         for (const a of auditors) {
+            //             a.times = times + 1;
+            //             a.order = order;
+            //             a.status = auditConst.status.uncheck;
+            //             order++;
+            //         }
+            //         await transaction.insert(this.tableName, auditors);
+            //         // 计算该审批人最终数据
+            //         await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //         // 复制一份最新数据给原报
+            //         await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times + 1, 0, transaction);
+            //     } else if (checkData.checkType === auditConst.status.checkNoPre) { // 审批退回 上一审批人
+            //         // 同步 期信息
+            //         const tpData = await this.ctx.service.stageBills.getSumTotalPrice(this.ctx.stage);
+            //         await transaction.update(this.ctx.service.stage.tableName, {
+            //             id: stageId, status: checkData.checkType,
+            //             contract_tp: tpData.contract_tp,
+            //             qc_tp: tpData.qc_tp,
+            //         });
+            //         // 将当前审批人 与 上一审批人再次添加至流程,顺移其后审批人流程顺序
+            //         if (audit.order > 1) {
+            //             // 顺移气候审核人流程顺序
+            //             this.initSqlBuilder();
+            //             this.sqlBuilder.setAndWhere('sid', { value: this.ctx.stage.id, operate: '=', });
+            //             this.sqlBuilder.setAndWhere('order', { value: audit.order, operate: '>', });
+            //             this.sqlBuilder.setUpdateData('order', { value: 2, selfOperate: '+', });
+            //             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
+            //             const data = await transaction.query(sql, sqlParam);
+            //
+            //             // 上一审批人,当前审批人 再次添加至流程
+            //             const preAuditor = await this.getDataByCondition({sid: stageId, times: times, order: audit.order - 1});
+            //             const newAuditors = [];
+            //             newAuditors.push({
+            //                 tid: preAuditor.tid, sid: preAuditor.sid, aid: preAuditor.aid,
+            //                 times: preAuditor.times, order: preAuditor.order + 2, status: auditConst.status.checking,
+            //                 begin_time: time,
+            //             });
+            //             newAuditors.push({
+            //                 tid: audit.tid, sid: audit.sid, aid: audit.aid,
+            //                 times: audit.times, order: audit.order + 2, status: auditConst.status.uncheck
+            //             });
+            //             await transaction.insert(this.tableName, newAuditors);
+            //
+            //             // 计算该审批人最终数据
+            //             await this.ctx.service.stagePay.calcAllStagePays(this.ctx.stage, transaction);
+            //             // 复制一份最新数据给上一人
+            //             await this.ctx.service.stagePay.copyAuditStagePays(this.ctx.stage, this.ctx.stage.times, audit.order + 1, transaction);
+            //         } else {
+            //             throw '审核数据错误';
+            //         }
+            //     } else {
+            //         throw '无效审批操作';
+            //     }
+            //
+            //     await transaction.commit();
+            // } catch (err) {
+            //     await transaction.rollback();
+            //     throw err;
+            // }
+        }
+
         /**
          * 获取审核人需要审核的期列表
          *

+ 42 - 2
app/service/stage_bills.js

@@ -40,7 +40,7 @@ module.exports = app => {
                     lidSql = ' And lid in (' + this.db.escape(lid) + ')';
                 }
             }
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
                         '  INNER JOIN ( ' +
                         '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.tableName +
                         '      WHERE tid = ? And sid = ?' + lidSql +
@@ -68,7 +68,7 @@ module.exports = app => {
          */
         async getAuditorStageData(tid, sid, times, order, lid) {
             const lidSql = lid ? ' And Bills.lid in (?)' : '';
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+            const sql = 'SELECT Bills.* FROM ' + this.tableName + ' As Bills ' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `tid`, `sid` From ' + this.tableName +
                 '      WHERE `times` < ? OR (`times` = ? AND `order` <= ?) And tid = ? And sid = ?' + lidSql +
@@ -88,6 +88,46 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 获取截止本期数据
+         * @param {Number} tid - 标段id
+         * @param {Number} sorder - 截止期序号
+         * @param {String|Array[String]} lid - 台账id
+         * @returns {Promise<*>}
+         */
+        async getEndStageData(tid, sorder, lid) {
+            let lidSql = '';
+            if (lid) {
+                if (lid instanceof Array) {
+                    lidSql = lid.length > 0 ? ' And lid in (' + this.ctx.helper.getInArrStrSqlFilter(lid) + ')' : '';
+                } else {
+                    lidSql = ' And lid in (' + this.db.escape(lid) + ')';
+                }
+            }
+
+            const sql = 'SELECT Bills.tid, Bills.lid,' +
+                '  Sum(Bills.contract_qty) As contract_qty, Sum(Bills.contract_tp) As contract_tp,' +
+                '  Sum(Bills.qc_qty) As qc_qty, Sum(Bills.qc_tp) As qc_tp FROM ' + this.tableName + ' As Bills ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `progress`, `lid`, `sid` From ' + this.tableName +
+                '      WHERE tid = ? ' + lidSql +
+                '      GROUP BY `lid`, `sid`' +
+                '  ) As MaxFilter ' +
+                '  ON (Bills.times * ' + timesLen + ' + `order`) = MaxFilter.progress And Bills.lid = MaxFilter.lid And Bills.`sid` = MaxFilter.`sid`' +
+                '  INNER JOIN ' + this.ctx.service.stage.tableName + ' As Stage' +
+                '  ON Bills.sid = Stage.id' +
+                '  WHERE Stage.order <= ?' +
+                '  GROUP BY `lid`';
+            const sqlParam = [tid, sorder];
+            if (!lid) {
+                return await this.db.query(sql, sqlParam);
+            } else if (lid instanceof Array) {
+                return await this.db.query(sql, sqlParam);
+            } else {
+                return await this.db.queryOne(sql, sqlParam);
+            }
+        }
+
         async getStageBills(tid, sid, lid) {
             const sql = 'SELECT Stage.*, Ledger.unit_price FROM ?? As Stage, ?? As Ledger ' +
                         '  Where Stage.tid = ?, Stage.sid = ?, Stage.lid = ?, Stage.lid = Ledger.id ' +

+ 16 - 16
app/service/stage_bills_final.js

@@ -54,22 +54,22 @@ module.exports = app => {
                 throw '数据错误';
             }
             if (stage.order > 1) {
-                const sql = 'Insert Into ??(tid, sid, lid, sorder, contract_qty, contract_tp, qc_qty, qc_tp)' +
-                    '  SELECT cur.tid, cur.sid, cur.lid, ? As `sorder`, ' +
-                    '      IFNULL(cur.contract_qty, 0) + IFNULL(pre.contract_qty, 0), IFNULL(cur.contract_tp, 0) + IFNULL(pre.contract_tp, 0),' +
-                    '      IFNULL(cur.qc_qty, 0) + IFNULL(pre.qc_qty, 0), IFNULL(cur.qc_tp, 0) + IFNULL(pre.qc_tp, 0)' +
-                    '  FROM (SELECT b.tid, b.sid, b.lid, b.contract_qty, b.contract_tp, b.qc_qty, b.qc_tp' +
-                    '    FROM ' + this.ctx.service.stageBills.tableName + ' AS b' +
-                    '    INNER JOIN(' +
-                    '      SELECT Max(`times` * ' + timesLen + ' + `order`) As `flow`, `lid` FROM ' + this.ctx.service.stageBills.tableName +
-                    '      WHERE `sid` = ?' +
-                    '      GROUP By `lid`) As MF' +
-                    '    ON (b.times * ' + timesLen + ' + b.order) = MF.flow AND b.lid = MF.lid' +
-                    '    WHERE b.sid = ?) As cur' +
-                    '  LEFT JOIN ?? As pre' +
-                    '  ON cur.lid = pre.lid And pre.sorder = ?';
-                const sqlParam = [this.tableName, stage.order, stage.id, stage.id, this.tableName, stage.order - 1];
-                await transaction.query(sql, sqlParam);
+                const cur = await this.ctx.service.stageBills.getLastestStageData(tender.id, stage.id);
+                const pre = await this.getFinalData(tender, stage);
+                for (const c of cur) {
+                    delete c.id;
+                    delete c.said;
+                    delete c.times;
+                    delete c.order;
+                    delete c.postil;
+                    const p = this.ctx.helper._.find(pre, {lid: cur.lid});
+                    if (!p) continue;
+                    c.contract_qty = this.ctx.helper.add(c.contract_qty, p.contract_qty);
+                    c.contract_tp = this.ctx.helper.add(c.contract_tp, p.contract_tp);
+                    c.qc_qty = this.ctx.helper.add(c.qc_qty, p.qc_qty);
+                    c.qc_tp = this.ctx.helper.add(c.qc_tp, p.qc_tp);
+                }
+                await transaction.insert(this.tableName, cur);
             } else {
                 const sql = 'Insert Into ??(tid, sid, lid, sorder, contract_qty, contract_tp, qc_qty, qc_tp)' +
                     '  SELECT b.tid, b.sid, b.lid, ? As `sorder`, b.contract_qty, b.contract_tp, b.qc_qty, b.qc_tp' +

+ 43 - 4
app/service/stage_pos.js

@@ -36,12 +36,12 @@ module.exports = app => {
             let pidSql = '';
             if (pid) {
                 if (pid instanceof Array) {
-                    pidSql = pid.length > 0 ? (' And pid in (' + pid.join(', ') + ')') : '';
+                    pidSql = pid.length > 0 ? (' And pid in ('  + this.ctx.helper.getInArrStrSqlFilter(pid) + ')') : '';
                 } else {
                     pidSql = (pid instanceof String || pid instanceof Number) ? ' And pid = ' + pid : '';
                 }
             }
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos ' +
+            const sql = 'SELECT Pos.* FROM ' + this.tableName + ' As Pos ' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `tid`, `sid`, `pid` From ' + this.tableName +
                 '      WHERE `tid` = ? And sid = ?' + pidSql +
@@ -70,11 +70,11 @@ module.exports = app => {
         async getAuditorStageData(tid, sid, times, order, pid) {
             let pidSql;
             if (pid instanceof Array) {
-                pidSql = pid.length > 0 ? ' And Pos.pid in (' + pid.join(', ') + ')' : '';
+                pidSql = pid.length > 0 ? ' And Pos.pid in (' + this.ctx.helper.getInArrStrSqlFilter(pid) + ')' : '';
             } else {
                 pidSql = pid ? 'And Pos.pid = ' + pid.toString() : '';
             }
-            const sql = 'SELECT * FROM ' + this.tableName + ' As Pos ' +
+            const sql = 'SELECT Pos.* FROM ' + this.tableName + ' As Pos ' +
                 '  INNER JOIN ( ' +
                 '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `pid` From ' + this.tableName +
                 '      WHERE `times` < ? OR (`times` = ? AND `order` <= ?)' +
@@ -93,6 +93,45 @@ module.exports = app => {
         }
 
         /**
+         * 获取截止本期数据
+         * @param {Number} tid - 标段
+         * @param {Number} sorder - 截止期序号
+         * @param {String|Array[String]}lid - 台账id
+         * @returns {Promise<*>}
+         */
+        async getEndStageData(tid, sorder, lid) {
+            let lidSql = '';
+            if (lid) {
+                if (lid instanceof Array) {
+                    lidSql = lid.length > 0 ? this.ctx.helper.getInArrStrSqlFilter(lid) : '';
+                } else {
+                    lidSql = (lid instanceof String || lid instanceof Number) ? ' And pid = ' + lid : '';
+                }
+            }
+
+            const sql = 'SELECT Pos.tid, Pos.lid, Pos.pid, SUM(Pos.contract_qty) As contract_qty, SUM(Pos.qc_qty) As qc_qty, Pos.postil FROM ' + this.tableName + ' As Pos ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times` * ' + timesLen + ' + `order`) As `flow`, `tid`, `sid`, `pid` From ' + this.tableName +
+                '      WHERE `tid` = ? ' + lidSql +
+                '      GROUP BY `pid`, `sid`' +
+                '  ) As MaxFilter ' +
+                '  ON (Pos.times * ' + timesLen + ' + Pos.order) = MaxFilter.flow And Pos.pid = MaxFilter.pid' +
+                '    And Pos.`tid` = MaxFilter.`tid` And Pos.`sid` = MaxFilter.`sid`' +
+                '  INNER JOIN ' + this.ctx.service.stage.tableName + ' As Stage' +
+                '  ON Pos.sid = Stage.id' +
+                '  WHERE Stage.order <= ?' +
+                '  GROUP BY `pid`';
+            const sqlParam = [tid, sorder];
+            if (!lid) {
+                return await this.db.query(sql, sqlParam);
+            } else if (lid instanceof Array) {
+                return await this.db.query(sql, sqlParam);
+            } else {
+                return await this.db.queryOne(sql, sqlParam);
+            }
+        }
+
+        /**
          * 新增部位明细数据(仅供updateStageData调用)
          *
          * @param transaction - 事务

+ 15 - 15
app/service/stage_pos_final.js

@@ -55,21 +55,21 @@ module.exports = app => {
                 throw '数据错误';
             }
             if (stage.order > 1) {
-                const sql = 'Insert Into ??(tid, sid, lid, pid, sorder, contract_qty, qc_qty)' +
-                            '  SELECT cur.tid, cur.sid, cur.lid, cur.pid, ? As `sorder`,' +
-                            '      IFNULL(cur.contract_qty, 0) + IFNULL(pre.contract_qty, 0), IFNULL(cur.qc_qty, 0) + IFNULL(pre.qc_qty, 0)' +
-                            '    FROM (SELECT p.tid, p.sid, p.lid, p.pid, p.contract_qty, p.qc_qty' +
-                            '      FROM ' + this.ctx.service.stagePos.tableName + ' AS p' +
-                            '      INNER JOIN(' +
-                            '        SELECT Max(`times` * ' + timesLen + ' + `order`) As `flow`, `pid` FROM ' + this.ctx.service.stagePos.tableName +
-                            '        WHERE `sid` = ?' +
-                            '        GROUP By `pid`) As MF' +
-                            '      ON (p.times * ' + timesLen + ' + p.order) = MF.flow AND p.pid = MF.pid' +
-                            '      WHERE p.sid = ?) As cur' +
-                            '    LEFT JOIN ?? As pre' +
-                            '    ON cur.pid = pre.pid AND pre.sorder = ?';
-                const sqlParam = [this.tableName, stage.order, stage.id, stage.id, this.tableName, stage.order - 1];
-                await transaction.query(sql, sqlParam);
+                const cur = await this.ctx.service.stagePos.getLastestStageData(tender.id, stage.id);
+                const pre = await this.getFinalData(tender, stage);
+                for (const c of cur) {
+                    delete c.id;
+                    delete c.said;
+                    delete c.times;
+                    delete c.order;
+                    delete c.postil;
+                    c.sorder = stage.order;
+                    const p = this.ctx.helper._.find(pre, {pid: cur.pid});
+                    if (!p) continue;
+                    c.contract_qty = this.ctx.helper.add(c.contract_qty, p.contract_qty);
+                    c.qc_qty = this.ctx.helper.add(c.qc_qty, p.qc_qty);
+                }
+                await transaction.insert(this.tableName, cur);
             } else {
                 const sql = 'Insert Into ??(tid, sid, lid, pid, sorder, contract_qty, qc_qty)' +
                             '  SELECT p.tid, p.sid, p.lid, p.pid, ? As `sorder`, p.contract_qty, p.qc_qty' +

+ 1 - 0
app/view/layout/layout.ejs

@@ -15,6 +15,7 @@
     <link rel="stylesheet" href="/public/css/jquery-contextmenu/jquery.contextMenu.min.css">
     <link rel="stylesheet" href="/public/css/ztree/zTreeStyle.css" type="text/css">
     <link rel="stylesheet" href="/public/css/datepicker/datepicker.min.css" rel="stylesheet" type="text/css">
+    <link href="/public/css/toastr.css" rel="stylesheet">
     <!-- JS. -->
     <% for (const file of jsFiles) { %>
     <script type="text/javascript" src="<%- file %>"></script>

+ 1 - 1
config/config.default.js

@@ -118,7 +118,7 @@ module.exports = appInfo => {
 
     // 压缩设置
     config.gzip = {
-        threshold: 1024,
+        threshold: 2048,
     };
 
     config.customLogger = {

+ 1 - 0
config/web.js

@@ -145,6 +145,7 @@ const JsFiles = {
                 files: [
                     "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
                     "/public/js/decimal.min.js",
+                    "/public/js/toastr.min.js",
                 ],
                 mergeFiles: [
                     "/public/js/div_resizer.js",