Browse Source

安全生产、临时用地

MaiXinRong 3 years ago
parent
commit
ef77a0b0f1

+ 111 - 0
app/controller/stage_extra_controller.js

@@ -199,6 +199,117 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 安全生产(Get)
+         * @param {Object} ctx - egg全局变量
+         */
+        async safeProd(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.safeProd)
+                };
+                await this.layout('stage_extra/safe_prod.ejs', renderData);
+            } catch (err) {
+                ctx.helper.log(err);
+            }
+        }
+
+        /**
+         * 获取 安全生产 数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadSafeProd (ctx) {
+            try {
+                const data = await ctx.service.stageSafeProd.getStageData(ctx.stage);
+                const preData = await ctx.service.stageSafeProd.getPreStageData(ctx.stage.order);
+                for (const d of data) {
+                    const pd = this.ctx.helper._.find(preData, {uuid: d.uuid});
+                    if (pd) {
+                        d.pre_qty = pd.qty;
+                        d.pre_tp = pd.tp;
+                    }
+                }
+                ctx.body = {err: 0, msg: '', data: data};
+            } catch (error) {
+                ctx.helper.log(error);
+                ctx.body = this.ajaxErrorBody(error, '获取数据失败,请刷新');
+            }
+
+        }
+
+        /**
+         * 提交 安全生产 数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateSafeProd (ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.stageSafeProd.updateDatas(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                ctx.helper.log(error);
+                ctx.body = this.ajaxErrorBody(error, '提交数据失败,请重试');
+            }
+        }
+
+
+        /**
+         * 临时用地(Get)
+         * @param {Object} ctx - egg全局变量
+         */
+        async tempLand(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.tempLand)
+                };
+                await this.layout('stage_extra/temp_land.ejs', renderData);
+            } catch (err) {
+                ctx.helper.log(err);
+            }
+        }
+
+        /**
+         * 获取 临时用地 数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadTempLand (ctx) {
+            try {
+                const data = await ctx.service.stageTempLand.getStageData(ctx.stage);
+                const preData = await ctx.service.stageTempLand.getPreStageData(ctx.stage.order);
+                for (const d of data) {
+                    const pd = this.ctx.helper._.find(preData, {uuid: d.uuid});
+                    if (pd) {
+                        d.pre_qty = pd.qty;
+                        d.pre_tp = pd.tp;
+                    }
+                }
+                ctx.body = {err: 0, msg: '', data: data};
+            } catch (error) {
+                ctx.helper.log(error);
+                ctx.body = this.ajaxErrorBody(error, '获取数据失败,请刷新');
+            }
+
+        }
+
+        /**
+         * 提交 临时用地 数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateTempLand (ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.stageTempLand.updateDatas(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                ctx.helper.log(error);
+                ctx.body = this.ajaxErrorBody(error, '提交数据失败,请重试');
+            }
+        }
+
         async uploadFile(ctx) {
             let stream;
             try {

+ 453 - 0
app/public/js/se_safe_prod.js

@@ -0,0 +1,453 @@
+'use strict';
+
+/**
+ * 安全生产
+ *
+ * @author Mai
+ * @date 2021/10/21
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+
+    const safeSpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 350, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '计划|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '本期|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '截止本期|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 180, formatter: '@', cellType: 'ellipsisAutoTip'}
+        ],
+        emptyRows: readOnly ? 0 : 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+        localCache: {
+            key: 'stage-extra-temp',
+            colWidth: true,
+        },
+        getColor: function (sheet, data, row, col, defaultColor) {
+            if (!data || !data.quantity) return defaultColor;
+
+            return data.quantity >= 0
+                ? data.end_qty > data.quantity ? '#f8d7da' : defaultColor
+                : data.end_qty < data.quantity ? '#f8d7da' : defaultColor;
+        }
+    };
+
+    const safeSpread = SpreadJsObj.createNewSpread($('#safe-prod-spread')[0]);
+    const safeSheet = safeSpread.getActiveSheet();
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(safeSpreadSetting);
+    SpreadJsObj.initSheet(safeSheet, safeSpreadSetting);
+
+    $.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();
+            safeSpread.refresh();
+        }
+    });
+
+    class SeSafe {
+        constructor () {
+            this.data = [];
+        }
+        resortData() {
+            this.data.sort(function (a, b) {
+                return a.order - b.order;
+            });
+        }
+        calculateAll() {
+            for (const d of this.data) {
+                d.end_qty = ZhCalc.add(d.pre_qty, d.qty);
+                d.end_tp = ZhCalc.add(d.pre_tp, d.tp);
+            }
+        }
+        loadDatas(datas) {
+            this.data = datas;
+            this.calculateAll();
+            this.resortData();
+        }
+        loadUpdateData(updateData) {
+            if (updateData.add) {
+                for (const a of updateData.add) {
+                    this.data.push(a);
+                }
+            }
+            if (updateData.update) {
+                for (const u of updateData.update) {
+                    const d = this.data.find(function (x) {
+                        return u.id === x.id;
+                    });
+                    if (d) {
+                        _.assign(d, u);
+                    } else {
+                        this.data.push(d);
+                    }
+                }
+            }
+            if (updateData.del) {
+                _.remove(this.data, function (d) {
+                    return updateData.del.indexOf(d.id) >= 0;
+                });
+            }
+            this.calculateAll();
+            this.resortData();
+        }
+        sum () {
+            const result = {
+                total_price: 0,
+                tp: 0,
+                end_tp: 0,
+            };
+            for (const d of this.data) {
+                result.total_price = ZhCalc.add(result.total_price, d.total_price);
+                result.tp = ZhCalc.add(result.tp, d.tp);
+                result.end_tp = ZhCalc.add(result.end_tp, d.end_tp);
+            }
+            return result;
+        }
+    }
+    const seSafeObj = new SeSafe();
+    const refreshSum = function () {
+        const sum = seSafeObj.sum();
+        const html = [];
+        const getTrHtml = function (name, value) {
+            return '<tr><td>' + name + '</td><td class="text-right">' + (!checkZero(value) ? value : '') + ' </td></tr>';
+        };
+        html.push(getTrHtml('金额', sum.total_price));
+        html.push(getTrHtml('本期金额', sum.tp));
+        html.push(getTrHtml('截止本期金额', sum.end_tp));
+        $('#sum').html(html.join(' '));
+    };
+
+    postData(window.location.pathname + '/load', null, function (result) {
+        seSafeObj.loadDatas(result);
+        SpreadJsObj.loadSheetData(safeSheet, SpreadJsObj.DataType.Data, seSafeObj.data);
+        refreshSum();
+    });
+
+    if (!readOnly) {
+        const seSafeOprObj = {
+            /**
+             * 删除按钮响应事件
+             * @param sheet
+             */
+            deletePress: function (sheet) {
+                if (!sheet.zh_setting || readOnly) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+
+                for (let iRow = sels[0].row; iRow < sels[0].row + sels[0].rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sortData[iRow];
+                    if (node) {
+                        const data = {id: node.id};
+                        for (let iCol = sels[0].col; iCol < sels[0].col + sels[0].colCount; iCol++) {
+                            const colSetting = sheet.zh_setting.cols[iCol];
+                            if (colSetting.field === 'name') {
+                                toastr.error('名称不能为空,如需删除请使用右键删除');
+                                return;
+                            }
+                            const style = sheet.getStyle(iRow, iCol);
+                            if (!style.locked) {
+                                const colSetting = sheet.zh_setting.cols[iCol];
+                                data[colSetting.field] = null;
+                                bDel = true;
+                            }
+                        }
+                        if (bDel) {
+                            datas.push(data);
+                        }
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {update: datas}, function (result) {
+                        seSafeObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(safeSheet);
+                        refreshSum();
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(safeSheet);
+                    });
+                }
+            },
+            delete: function (sheet) {
+                if (!sheet.zh_setting || readOnly) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+                const hint = {
+                    isOld: {type: 'warning', msg: '该数据已计量,不可删除'},
+                    invalidDel: {type: 'warning', msg: '该数据为往期新增,只有原报可删除'},
+                };
+
+                for (let iRow = sels[0].row, iLen = sels[0].row + sels[0].rowCount; iRow < iLen; iRow++) {
+                    const node = sortData[iRow];
+                    if (node.pre_used || !checkZero(node.end_tp)) {
+                        toastMessageUniq(hint.isOld);
+                        continue;
+                    } else {
+                        if (node.add_sid !== stageId && stageUserId !== userID) {
+                            toastMessageUniq(hint.invalidDel);
+                            continue;
+                        }
+                        datas.push(node.id);
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {del: datas}, function (result) {
+                        seSafeObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(safeSheet);
+                        refreshSum();
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(safeSheet);
+                    });
+                }
+            },
+            editEnded: function (e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) return;
+
+                const node = info.sheet.zh_data[info.row];
+                const col = info.sheet.zh_setting.cols[info.col];
+                const data = {};
+
+                if (node) {
+                    data.update = {};
+                    data.update.id = node.id;
+
+                    const oldValue = node ? node[col.field] : null;
+                    const newValue = trimInvalidChar(info.editingText);
+                    if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    data.update[col.field] = newValue;
+                } else {
+                    if (col.field !== 'name') {
+                        toastr.warning('请先输入名称');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    data.add = {};
+                    data.add.order = info.row + 1;
+                    data.add.name = trimInvalidChar(info.editingText);
+                }
+
+                postData(window.location.pathname + '/update', data, function (result) {
+                    seSafeObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                    refreshSum();
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            editStarting(e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) {
+                    info.cancel = true;
+                    return;
+                }
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                const node = info.sheet.zh_data[info.row];
+                if (!node) return;
+
+                switch (col.field) {
+                    case 'name':
+                    case 'unit':
+                    case 'unit_price':
+                    case 'quantity':
+                        info.cancel = readOnly || node.pre_used;
+                        break;
+                }
+            },
+            clipboardPasting(e, info) {
+                const setting = info.sheet.zh_setting, sortData = info.sheet.zh_data;
+                info.cancel = true;
+
+                if (!setting || !sortData) return;
+                const pasteData = info.pasteData.html
+                    ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                    : (info.pasteData.text === ''
+                        ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                        : SpreadJsObj.analysisPasteText(info.pasteData.text));
+                const hint = {
+                    name: {type: 'warning', msg: '名称不可为空,已过滤'},
+                };
+
+                const uDatas = [], iDatas = [];
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const node = sortData[curRow];
+
+                    let bPaste = false;
+                    const data = {};
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = setting.cols[curCol];
+                        const value = trimInvalidChar(pasteData[iRow][iCol]);
+
+                        if (colSetting.field === 'name' && (!value || value === '')) {
+                            toastMessageUniq(hint.name);
+                            break;
+                        }
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (num) {
+                                data[colSetting.field] = num;
+                                bPaste = true;
+                            }
+                        } else {
+                            data[colSetting.field] = value;
+                            bPaste = true;
+                        }
+                    }
+                    if (bPaste) {
+                        if (node) {
+                            data.id = node.id;
+                            uDatas.push(data);
+                        } else {
+                            data.order = curRow + 1;
+                            iDatas.push(data);
+                        }
+                    }
+                }
+                const updateData = {};
+                if (uDatas.length > 0) updateData.update = uDatas;
+                if (iDatas.length > 0) updateData.add = iDatas;
+                if (uDatas.length > 0 || iDatas.length > 0) {
+                    postData(window.location.pathname + '/update', updateData, function (result) {
+                        seSafeObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(info.sheet);
+                        refreshSum();
+                    });
+                } else {
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }
+            },
+            upMove: function () {
+                const sels = safeSheet.getSelections(), sortData = safeSheet.zh_data;
+                const node = sortData[sels[0].row];
+                const preNode = sortData[sels[0].row - 1];
+                const data = [
+                    {id: node.id, order: preNode.order},
+                    {id: preNode.id, order: node.order}
+                ];
+                postData(window.location.pathname + '/update', {update: data}, function (result) {
+                    seSafeObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadRowsData(safeSheet, [sels[0].row, sels[0].row - 1]);
+                    safeSheet.setSelection(sels[0].row - 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+                });
+            },
+            downMove: function () {
+                const sels = safeSheet.getSelections(), sortData = safeSheet.zh_data;
+                const node = sortData[sels[0].row];
+                const nextNode = sortData[sels[0].row + 1];
+                const data = [
+                    {id: node.id, order: nextNode.order},
+                    {id: nextNode.id, order: node.order}
+                ];
+                postData(window.location.pathname + '/update', {update: data}, function (result) {
+                    seSafeObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadRowsData(safeSheet, [sels[0].row, sels[0].row + 1]);
+                    safeSheet.setSelection(sels[0].row + 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+                });
+            }
+        };
+        safeSheet.bind(spreadNS.Events.EditEnded, seSafeOprObj.editEnded);
+        safeSheet.bind(spreadNS.Events.EditStarting, seSafeOprObj.editStarting);
+        safeSheet.bind(spreadNS.Events.ClipboardPasting, seSafeOprObj.clipboardPasting);
+        SpreadJsObj.addDeleteBind(safeSpread, seSafeOprObj.deletePress);
+        $.contextMenu({
+            selector: '#safe-prod-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, safeSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                del: {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        seSafeOprObj.delete(safeSheet);
+                    },
+                    disabled: function (key, opt) {
+                        const sels = safeSheet.getSelections();
+                        if (!sels || !sels[0]) return true;
+
+                        const row = sels[0].row;
+                        const node = seSafeObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                sprDel: '------------',
+                upMove: {
+                    name: '上移',
+                    icon: 'fa-arrow-up',
+                    callback: function (key, opt) {
+                        seSafeOprObj.upMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = safeSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row === 0) return true;
+
+                        const row = sels[0].row;
+                        const node = seSafeObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                downMove: {
+                    name: '下移',
+                    icon: 'fa-arrow-down',
+                    callback: function (key, opt) {
+                        seSafeOprObj.downMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = safeSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row >= seSafeObj.data.length - 1) return true;
+
+                        const row = sels[0].row;
+                        const node = seSafeObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                }
+            },
+        })
+    }
+
+    $('#exportExcel').click(function () {
+        SpreadExcelObj.exportSimpleXlsxSheet(safeSpreadSetting, seSafeObj.data, $('.sidebar-title').attr('data-original-title') + "-安全生产.xlsx");
+    });
+});

+ 442 - 0
app/public/js/se_temp_land.js

@@ -0,0 +1,442 @@
+'use strict';
+
+/**
+ * 临时占地
+ *
+ * @author Mai
+ * @date 2021/10/21
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+
+    const tempSpreadSetting = {
+        cols: [
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 350, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', cellType: 'unit'},
+            {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+            {title: '本期|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qty', hAlign: 2, width: 60, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '截止本期|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_qty', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+            {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 180, formatter: '@', cellType: 'ellipsisAutoTip'}
+        ],
+        emptyRows: readOnly ? 0 : 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: readOnly,
+        localCache: {
+            key: 'stage-extra-temp',
+            colWidth: true,
+        }
+    };
+
+    const TempSpread = SpreadJsObj.createNewSpread($('#temp-land-spread')[0]);
+    const tempSheet = TempSpread.getActiveSheet();
+    if (thousandth) sjsSettingObj.setTpThousandthFormat(tempSpreadSetting);
+    SpreadJsObj.initSheet(tempSheet, tempSpreadSetting);
+
+    $.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();
+            TempSpread.refresh();
+        }
+    });
+
+    class SeTemp {
+        constructor () {
+            this.data = [];
+        }
+        resortData() {
+            this.data.sort(function (a, b) {
+                return a.order - b.order;
+            });
+        }
+        calculateAll() {
+            for (const d of this.data) {
+                d.end_qty = ZhCalc.add(d.pre_qty, d.qty);
+                d.end_tp = ZhCalc.add(d.pre_tp, d.tp);
+            }
+        }
+        loadDatas(datas) {
+            this.data = datas;
+            this.calculateAll();
+            this.resortData();
+        }
+        loadUpdateData(updateData) {
+            if (updateData.add) {
+                for (const a of updateData.add) {
+                    this.data.push(a);
+                }
+            }
+            if (updateData.update) {
+                for (const u of updateData.update) {
+                    const d = this.data.find(function (x) {
+                        return u.id === x.id;
+                    });
+                    if (d) {
+                        _.assign(d, u);
+                    } else {
+                        this.data.push(d);
+                    }
+                }
+            }
+            if (updateData.del) {
+                _.remove(this.data, function (d) {
+                    return updateData.del.indexOf(d.id) >= 0;
+                });
+            }
+            this.calculateAll();
+            this.resortData();
+        }
+        sum () {
+            const result = {
+                total_price: 0,
+                tp: 0,
+                end_tp: 0,
+            };
+            for (const d of this.data) {
+                result.total_price = ZhCalc.add(result.total_price, d.total_price);
+                result.tp = ZhCalc.add(result.tp, d.tp);
+                result.end_tp = ZhCalc.add(result.end_tp, d.end_tp);
+            }
+            return result;
+        }
+    }
+    const seTempObj = new SeTemp();
+    const refreshSum = function () {
+        const sum = seTempObj.sum();
+        const html = [];
+        const getTrHtml = function (name, value) {
+            return '<tr><td>' + name + '</td><td class="text-right">' + (!checkZero(value) ? value : '') + ' </td></tr>';
+        };
+        html.push(getTrHtml('本期金额', sum.tp));
+        html.push(getTrHtml('截止本期金额', sum.end_tp));
+        $('#sum').html(html.join(' '));
+    };
+
+    postData(window.location.pathname + '/load', null, function (result) {
+        seTempObj.loadDatas(result);
+        SpreadJsObj.loadSheetData(tempSheet, SpreadJsObj.DataType.Data, seTempObj.data);
+        refreshSum();
+    });
+
+    if (!readOnly) {
+        const seTempOprObj = {
+            /**
+             * 删除按钮响应事件
+             * @param sheet
+             */
+            deletePress: function (sheet) {
+                if (!sheet.zh_setting || readOnly) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+
+                for (let iRow = sels[0].row; iRow < sels[0].row + sels[0].rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sortData[iRow];
+                    if (node) {
+                        const data = {id: node.id};
+                        for (let iCol = sels[0].col; iCol < sels[0].col + sels[0].colCount; iCol++) {
+                            const colSetting = sheet.zh_setting.cols[iCol];
+                            if (colSetting.field === 'name') {
+                                toastr.error('名称不能为空,如需删除请使用右键删除');
+                                return;
+                            }
+                            const style = sheet.getStyle(iRow, iCol);
+                            if (!style.locked) {
+                                const colSetting = sheet.zh_setting.cols[iCol];
+                                data[colSetting.field] = null;
+                                bDel = true;
+                            }
+                        }
+                        if (bDel) {
+                            datas.push(data);
+                        }
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {update: datas}, function (result) {
+                        seTempObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(tempSheet);
+                        refreshSum();
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(tempSheet);
+                    });
+                }
+            },
+            delete: function (sheet) {
+                if (!sheet.zh_setting || readOnly) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+                const hint = {
+                    isOld: {type: 'warning', msg: '该数据已计量,不可删除'},
+                    invalidDel: {type: 'warning', msg: '该数据为往期新增,只有原报可删除'},
+                };
+
+                for (let iRow = sels[0].row, iLen = sels[0].row + sels[0].rowCount; iRow < iLen; iRow++) {
+                    const node = sortData[iRow];
+                    if (node.pre_used || !checkZero(node.end_tp)) {
+                        toastMessageUniq(hint.isOld);
+                        continue;
+                    } else {
+                        if (node.add_sid !== stageId && stageUserId !== userID) {
+                            toastMessageUniq(hint.invalidDel);
+                            continue;
+                        }
+                        datas.push(node.id);
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {del: datas}, function (result) {
+                        seTempObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(tempSheet);
+                        refreshSum();
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(tempSheet);
+                    });
+                }
+            },
+            editEnded: function (e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) return;
+
+                const node = info.sheet.zh_data[info.row];
+                const col = info.sheet.zh_setting.cols[info.col];
+                const data = {};
+
+                if (node) {
+                    data.update = {};
+                    data.update.id = node.id;
+
+                    const oldValue = node ? node[col.field] : null;
+                    const newValue = trimInvalidChar(info.editingText);
+                    if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    data.update[col.field] = newValue;
+                } else {
+                    if (col.field !== 'name') {
+                        toastr.warning('请先输入名称');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    data.add = {};
+                    data.add.order = info.row + 1;
+                    data.add.name = trimInvalidChar(info.editingText);
+                }
+
+                postData(window.location.pathname + '/update', data, function (result) {
+                    seTempObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                    refreshSum();
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            },
+            editStarting(e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) {
+                    info.cancel = true;
+                    return;
+                }
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                const node = info.sheet.zh_data[info.row];
+                if (!node) return;
+
+                switch (col.field) {
+                    case 'name':
+                    case 'unit':
+                    case 'unit_price':
+                        info.cancel = readOnly || node.pre_used;
+                        break;
+                }
+            },
+            clipboardPasting(e, info) {
+                const setting = info.sheet.zh_setting, sortData = info.sheet.zh_data;
+                info.cancel = true;
+
+                if (!setting || !sortData) return;
+                const pasteData = info.pasteData.html
+                    ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                    : (info.pasteData.text === ''
+                        ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                        : SpreadJsObj.analysisPasteText(info.pasteData.text));
+                const hint = {
+                    name: {type: 'warning', msg: '名称不可为空,已过滤'},
+                };
+
+                const uDatas = [], iDatas = [];
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const node = sortData[curRow];
+
+                    let bPaste = false;
+                    const data = {};
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = setting.cols[curCol];
+                        const value = trimInvalidChar(pasteData[iRow][iCol]);
+
+                        if (colSetting.field === 'name' && (!value || value === '')) {
+                            toastMessageUniq(hint.name);
+                            break;
+                        }
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (num) {
+                                data[colSetting.field] = num;
+                                bPaste = true;
+                            }
+                        } else {
+                            data[colSetting.field] = value;
+                            bPaste = true;
+                        }
+                    }
+                    if (bPaste) {
+                        if (node) {
+                            data.id = node.id;
+                            uDatas.push(data);
+                        } else {
+                            data.order = curRow + 1;
+                            iDatas.push(data);
+                        }
+                    }
+                }
+                const updateData = {};
+                if (uDatas.length > 0) updateData.update = uDatas;
+                if (iDatas.length > 0) updateData.add = iDatas;
+                if (uDatas.length > 0 || iDatas.length > 0) {
+                    postData(window.location.pathname + '/update', updateData, function (result) {
+                        seTempObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(info.sheet);
+                        refreshSum();
+                    });
+                } else {
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }
+            },
+            upMove: function () {
+                const sels = tempSheet.getSelections(), sortData = tempSheet.zh_data;
+                const node = sortData[sels[0].row];
+                const preNode = sortData[sels[0].row - 1];
+                const data = [
+                    {id: node.id, order: preNode.order},
+                    {id: preNode.id, order: node.order}
+                ];
+                postData(window.location.pathname + '/update', {update: data}, function (result) {
+                    seTempObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadRowsData(tempSheet, [sels[0].row, sels[0].row - 1]);
+                    tempSheet.setSelection(sels[0].row - 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+                });
+            },
+            downMove: function () {
+                const sels = tempSheet.getSelections(), sortData = tempSheet.zh_data;
+                const node = sortData[sels[0].row];
+                const nextNode = sortData[sels[0].row + 1];
+                const data = [
+                    {id: node.id, order: nextNode.order},
+                    {id: nextNode.id, order: node.order}
+                ];
+                postData(window.location.pathname + '/update', {update: data}, function (result) {
+                    seTempObj.loadUpdateData(result);
+                    SpreadJsObj.reLoadRowsData(tempSheet, [sels[0].row, sels[0].row + 1]);
+                    tempSheet.setSelection(sels[0].row + 1, sels[0].col, sels[0].rowCount, sels[0].colCount);
+                });
+            }
+        };
+        tempSheet.bind(spreadNS.Events.EditEnded, seTempOprObj.editEnded);
+        tempSheet.bind(spreadNS.Events.EditStarting, seTempOprObj.editStarting);
+        tempSheet.bind(spreadNS.Events.ClipboardPasting, seTempOprObj.clipboardPasting);
+        SpreadJsObj.addDeleteBind(TempSpread, seTempOprObj.deletePress);
+        $.contextMenu({
+            selector: '#temp-land-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, TempSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                del: {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        seTempOprObj.delete(tempSheet);
+                    },
+                    disabled: function (key, opt) {
+                        const sels = tempSheet.getSelections();
+                        if (!sels || !sels[0]) return true;
+
+                        const row = sels[0].row;
+                        const node = seTempObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                sprDel: '------------',
+                upMove: {
+                    name: '上移',
+                    icon: 'fa-arrow-up',
+                    callback: function (key, opt) {
+                        seTempOprObj.upMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = tempSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row === 0) return true;
+
+                        const row = sels[0].row;
+                        const node = seTempObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                downMove: {
+                    name: '下移',
+                    icon: 'fa-arrow-down',
+                    callback: function (key, opt) {
+                        seTempOprObj.downMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = tempSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row >= seTempObj.data.length - 1) return true;
+
+                        const row = sels[0].row;
+                        const node = seTempObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                }
+            },
+        })
+    }
+
+    $('#exportExcel').click(function () {
+        SpreadExcelObj.exportSimpleXlsxSheet(tempSpreadSetting, seTempObj.data, $('.sidebar-title').attr('data-original-title') + "-临时占地.xlsx");
+    });
+});

+ 11 - 0
app/router.js

@@ -312,18 +312,29 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/compare/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.compareAuditor');
 
     // 附加功能
+    // 甲供材料
     app.get('/tender/:id/measure/stage/:order/extra/jgcl', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.jgcl');
     app.post('/tender/:id/measure/stage/:order/extra/jgcl/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.loadJgcl');
     app.post('/tender/:id/measure/stage/:order/extra/jgcl/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.updateJgcl');
+    // 奖罚金
     app.get('/tender/:id/measure/stage/:order/extra/bonus', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.bonus');
     app.post('/tender/:id/measure/stage/:order/extra/bonus/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.loadBonus');
     app.post('/tender/:id/measure/stage/:order/extra/bonus/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.updateBonus');
+    // 其他
     app.get('/tender/:id/measure/stage/:order/extra/other', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.other');
     app.post('/tender/:id/measure/stage/:order/extra/other/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.loadOther');
     app.post('/tender/:id/measure/stage/:order/extra/other/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.updateOther');
     app.post('/tender/:id/measure/stage/:order/extra/upload/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.uploadFile');
     app.get('/tender/:id/measure/stage/:order/extra/download/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.downloadFile');
     app.post('/tender/:id/measure/stage/:order/extra/delete/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.deleteFile');
+    // 安全生产
+    app.get('/tender/:id/measure/stage/:order/extra/safeProd', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.safeProd');
+    app.post('/tender/:id/measure/stage/:order/extra/safeProd/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.loadSafeProd');
+    app.post('/tender/:id/measure/stage/:order/extra/safeProd/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.updateSafeProd');
+    // 临时用地
+    app.get('/tender/:id/measure/stage/:order/extra/tempLand', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.tempLand');
+    app.post('/tender/:id/measure/stage/:order/extra/tempLand/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.loadTempLand');
+    app.post('/tender/:id/measure/stage/:order/extra/tempLand/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageExtraController.updateTempLand');
 
     // 关联台账
     app.get('/tender/:id/measure/stage/:order/rela', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.index');

+ 291 - 0
app/service/stage_safe_prod.js

@@ -0,0 +1,291 @@
+'use strict';
+
+/**
+ * 安全生产
+ *
+ * @author Mai
+ * @date 2021/10/21
+ * @version
+ */
+
+
+const auditConst = require('../const/audit').stage;
+module.exports = app => {
+    class StageSafeProd extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_safe_prod';
+        }
+
+        async getStageData(stage) {
+            const data = await this.getAllDataByCondition({where: { sid: stage.id }});
+            if (stage && stage.readOnly && !this.ctx.tender.isTourist && stage.status !== auditConst.status.checked) {
+                for (const d of data) {
+                    const his = d.shistory ? JSON.parse(d.shistory) : [];
+                    const h = this.ctx.helper._.find(his, {
+                        stimes: stage.curTimes, sorder: stage.curOrder
+                    });
+                    delete d.shistory;
+                    d.qty = h ? h.qty : null;
+                    d.tp = h ? h.tp : null;
+                }
+            }
+            return data;
+        }
+
+        async getPreStageData(sorder) {
+            const sql = 'SELECT o.uuid, Sum(o.qty) as qty, Sum(o.tp) as tp ' +
+                '  From ' + this.tableName + ' o ' +
+                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s ON s.id = o.sid' +
+                '  WHERE s.order < ? And o.tid = ?' +
+                '  GROUP By uuid';
+            const sqlParam = [sorder, this.ctx.tender.id];
+            const data = await this.db.query(sql, sqlParam);
+            return data;
+        }
+
+        async getEndStageData(sorder) {
+            const sql = 'SELECT o.uuid, Sum(o.qty) as qty, Sum(o.tp) as tp ' +
+                '  From ' + this.tableName + ' o ' +
+                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s ON s.id = o.sid' +
+                '  WHERE s.order <= ? And o.tid = ?' +
+                '  GROUP By uuid';
+            const sqlParam = [sorder, this.ctx.tender.id];
+            const data = await this.db.query(sql, sqlParam);
+            return data;
+        }
+
+        async _addDatas(data) {
+            const info = this.ctx.tender.info;
+            const tpDecimal = info.decimal.extra ? info.decimal.extraTp : info.decimal.tp;
+
+            const datas = data instanceof Array ? data : [data];
+            const insertData = [];
+            for (const d of datas) {
+                if (!d.name || !d.order) throw '新增其他数据,提交的数据错误';
+                const nd = {
+                    tid: this.ctx.tender.id,
+                    sid: this.ctx.stage.id,
+                    sorder: this.ctx.stage.order,
+                    uuid: this.uuid.v4(),
+                    add_sid: this.ctx.stage.id,
+                    add_uid: this.ctx.session.sessionUser.accountId,
+                    add_time: new Date(),
+                };
+                nd.name = d.name;
+                nd.order = d.order;
+                if (d.unit) nd.unit = d.unit;
+                if (d.unit_price) nd.unit_price = d.unit_price;
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, d.unit);
+                if (d.quantity) {
+                    nd.quantity = this.ctx.helper.round(d.quantity, precision.value);
+                    nd.total_price = this.ctx.helper.mul(nd.unit_price, nd.quantity, tpDecimal);
+                }
+                if (d.qty !== undefined) {
+                    nd.qty = this.ctx.helper.round(d.qty, precision.value);
+                    nd.tp = this.ctx.helper.mul(nd.unit_price, nd.qty, tpDecimal);
+                }
+                if (d.memo) nd.memo = d.memo;
+                insertData.push(nd);
+            }
+            await this.db.insert(this.tableName, insertData);
+            return await this.getAllDataByCondition({
+                where: { sid: this.ctx.stage.id, uuid: this.ctx.helper._.map(insertData, 'uuid') }
+            });
+        }
+
+        async _delDatas (data) {
+            const datas = data instanceof Array ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')} });
+            for (const od of orgDatas) {
+                if (od.pre_used) throw '往期已经计量,不可删除';
+            }
+            await this.db.delete(this.tableName, {id: datas});
+            return datas;
+        }
+
+        async _updateDatas (data) {
+            const info = this.ctx.tender.info;
+            const tpDecimal = info.decimal.extra ? info.decimal.extraTp : info.decimal.tp;
+
+            const datas = data instanceof Array ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({
+                where: { sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id') }
+            });
+
+            const uDatas = [];
+            for (const d of datas) {
+                const od = this.ctx.helper._.find(orgDatas, {id: d.id});
+                if (!od) continue;
+
+                const nd = {id: od.id};
+                if (d.name !== undefined) {
+                    if (od.pre_used) throw '往期已使用,不可修改名称';
+                    nd.name = d.name;
+                }
+                if (d.unit !== undefined) nd.unit = d.unit;
+                nd.unit_price = d.unit_price !== undefined ? this.ctx.helper.round(d.unit_price, info.decimal.up) : od.unit_price;
+                const precision = this.ctx.helper.findPrecision(info.precision, d.unit);
+                if (d.order !== undefined) nd.order = d.order;
+                if (d.quantity !== undefined) {
+                    if (od.pre_used) throw '往期已使用,不可修改数量';
+                    nd.quantity = this.ctx.helper.round(d.quantity, precision.value);
+                    nd.total_price = this.ctx.helper.mul(nd.unit_price, nd.quantity, tpDecimal);
+                }
+                if (d.qty !== undefined) {
+                    nd.qty = this.ctx.helper.round(d.qty, precision.value);
+                    nd.tp = this.ctx.helper.mul(nd.unit_price, nd.qty, tpDecimal);
+                }
+                if (d.memo !== undefined) nd.memo = d.memo;
+                uDatas.push(nd);
+            }
+            if (uDatas.length > 0) {
+                await this.db.updateRows(this.tableName, uDatas);
+                return uDatas;
+            } else {
+                return [];
+            }
+        }
+
+        async updateDatas(data) {
+            const result = {add: [], del: [], update: []};
+            try {
+                if (data.add) {
+                    result.add = await this._addDatas(data.add);
+                }
+                if (data.update) {
+                    result.update = await this._updateDatas(data.update);
+                }
+                if (data.del) {
+                    result.del = await this._delDatas(data.del);
+                }
+                return result;
+            } catch (err) {
+                if (err.stack) {
+                    throw err;
+                } else {
+                    result.err = err.toString();
+                    return result;
+                }
+            }
+        }
+
+        async updateHistory(stage, transaction) {
+            const datas = await this.getStageData(stage);
+            if (datas.length === 0) return;
+
+            const updateDatas = [];
+            const times = this.ctx.stage.curTimes, order = this.ctx.stage.curOrder;
+            for (const d of datas) {
+                const history = d.shistory && d.shistory !== '' ? JSON.parse(d.shistory) : [];
+                const his = history.find(function (x) {
+                    return x.stimes && x.stimes === times
+                        && x.sorder && x.sorder === order;
+                });
+                if (his) {
+                    his.qty = d.qty;
+                    his.tp = d.tp;
+                    if (d.sid === d.add_sid) {
+                        his.quantity = d.quantity;
+                        his.total_price = d.total_price;
+                    }
+                } else {
+                    const nHis = { stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder, qty: d.qty, tp: d.tp };
+                    if (d.sid === d.add_sid) {
+                        nHis.quantity = d.quantity;
+                        nHis.total_price = d.total_price;
+                    }
+                    history.push(nHis);
+                }
+                updateDatas.push({ id: d.id, shistory: JSON.stringify(history) });
+            }
+            await transaction.updateRows(this.tableName, updateDatas);
+        }
+
+        async updateHistory4CheckAgain(stage, transaction) {
+            const datas = await this.getStageData(stage);
+            if (datas.length === 0) return;
+
+            const updateDatas = [];
+            const times = this.ctx.stage.curTimes, order = this.ctx.stage.curOrder + 1;
+            for (const d of datas) {
+                const history = d.shistory && d.shistory !== '' ? JSON.parse(d.shistory) : [];
+                const his = history.find(function (x) {
+                    return x.stimes && x.stimes === times
+                        && x.sorder && x.sorder === order;
+                });
+                if (his) {
+                    his.qty = d.qty;
+                    his.tp = d.tp;
+                    if (d.sid === d.add_sid) {
+                        his.quantity = d.quantity;
+                        his.total_price = d.total_price;
+                    }
+                } else {
+                    const nHis = { stimes: times, sorder: order, qty: d.qty, tp: d.tp };
+                    if (d.sid === d.add_sid) {
+                        nHis.quantity = d.quantity;
+                        nHis.total_price = d.total_price;
+                    }
+                    history.push(nHis);
+                }
+                updateDatas.push({ id: d.id, shistory: JSON.stringify(history) });
+            }
+            await transaction.updateRows(this.tableName, updateDatas);
+        }
+
+        async addInitialStageData(stage, preStage, transaction) {
+            if (!stage || !preStage) {
+                throw '标段数据有误';
+            }
+            const preDatas = await this.getAllDataByCondition({
+                columns: ['tid', 'uuid', 'add_uid', 'add_sid', 'add_time', 'name', 'quantity', 'total_price', 'qty', 'order', 'memo', 'pre_used'],
+                where: { sid: preStage.id }
+            });
+            if (preDatas.length > 0) {
+                for (const pd of preDatas) {
+                    pd.pre_used = pd.pre_used || !this.ctx.helper.checkZero(pd.qty);
+                    delete pd.qty;
+                    pd.sid = stage.id;
+                    pd.sorder = stage.order;
+                }
+                const result = await transaction.insert(this.tableName, preDatas);
+                return result.affectedRows === preDatas.length;
+            } else {
+                return true;
+            }
+
+        }
+
+        async deleteStageTimesData(sid, times, transaction) {
+            const datas = await this.getAllDataByCondition({where: { sid: sid }});
+            if (datas.length === 0) return;
+
+            const updateDatas = [];
+            for (const d of datas) {
+                const history = d.shistory && d.shistory !== '' ? JSON.parse(d.shistory) : [];
+                const his = history.filter(function (x) {
+                    return x.stimes && x.stimes < times;
+                });
+                his.sort(function (x, y) {
+                    return (x.stimes * 1000 + x.sorder) - (y.stimes * 1000 + y.sorder);
+                });
+                updateDatas.push({
+                    id: d.id,
+                    shistory: JSON.stringify(his),
+                    qty: his.length > 0 ? his[his.length - 1].qty : null,
+                    tp: his.length > 0 ? his[his.length - 1].tp : null,
+                });
+            }
+            await transaction.updateRows(this.tableName, updateDatas);
+        }
+    }
+
+    return StageSafeProd;
+};

+ 266 - 0
app/service/stage_temp_land.js

@@ -0,0 +1,266 @@
+'use strict';
+
+/**
+ * 临时占地
+ *
+ * @author Mai
+ * @date 2021/10/21
+ * @version
+ */
+
+
+const auditConst = require('../const/audit').stage;
+module.exports = app => {
+    class StageTempLand extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_temp_land';
+        }
+
+        async getStageData(stage) {
+            const data = await this.getAllDataByCondition({where: { sid: stage.id }});
+            if (stage && stage.readOnly && !this.ctx.tender.isTourist && stage.status !== auditConst.status.checked) {
+                for (const d of data) {
+                    const his = d.shistory ? JSON.parse(d.shistory) : [];
+                    const h = this.ctx.helper._.find(his, {
+                        stimes: stage.curTimes, sorder: stage.curOrder
+                    });
+                    delete d.shistory;
+                    d.qty = h ? h.qty : null;
+                    d.tp = h ? h.tp : null;
+                }
+            }
+            return data;
+        }
+
+        async getPreStageData(sorder) {
+            const sql = 'SELECT o.uuid, Sum(o.qty) as qty, Sum(o.tp) as tp ' +
+                '  From ' + this.tableName + ' o ' +
+                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s ON s.id = o.sid' +
+                '  WHERE s.order < ? And o.tid = ?' +
+                '  GROUP By uuid';
+            const sqlParam = [sorder, this.ctx.tender.id];
+            const data = await this.db.query(sql, sqlParam);
+            return data;
+        }
+
+        async getEndStageData(sorder) {
+            const sql = 'SELECT o.uuid, Sum(o.qty) as qty, Sum(o.tp) as tp ' +
+                '  From ' + this.tableName + ' o ' +
+                '  LEFT JOIN ' + this.ctx.service.stage.tableName + ' s ON s.id = o.sid' +
+                '  WHERE s.order <= ? And o.tid = ?' +
+                '  GROUP By uuid';
+            const sqlParam = [sorder, this.ctx.tender.id];
+            const data = await this.db.query(sql, sqlParam);
+            return data;
+        }
+
+        async _addDatas(data) {
+            const info = this.ctx.tender.info;
+            const tpDecimal = info.decimal.extra ? info.decimal.extraTp : info.decimal.tp;
+
+            const datas = data instanceof Array ? data : [data];
+            const insertData = [];
+            for (const d of datas) {
+                if (!d.name || !d.order) throw '新增其他数据,提交的数据错误';
+                const nd = {
+                    tid: this.ctx.tender.id,
+                    sid: this.ctx.stage.id,
+                    sorder: this.ctx.stage.order,
+                    uuid: this.uuid.v4(),
+                    add_sid: this.ctx.stage.id,
+                    add_uid: this.ctx.session.sessionUser.accountId,
+                    add_time: new Date(),
+                };
+                nd.name = d.name;
+                nd.order = d.order;
+                if (d.unit) nd.unit = d.unit;
+                if (d.unit_price) nd.unit_price = d.unit_price;
+                const precision = this.ctx.helper.findPrecision(this.ctx.tender.info.precision, d.unit);
+                if (d.qty !== undefined) {
+                    nd.qty = this.ctx.helper.round(d.qty, precision.value);
+                    nd.tp = this.ctx.helper.mul(nd.unit_price, nd.qty, tpDecimal);
+                }
+                if (d.memo) nd.memo = d.memo;
+                insertData.push(nd);
+            }
+            await this.db.insert(this.tableName, insertData);
+            return await this.getAllDataByCondition({
+                where: { sid: this.ctx.stage.id, uuid: this.ctx.helper._.map(insertData, 'uuid') }
+            });
+        }
+
+        async _delDatas (data) {
+            const datas = data instanceof Array ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({where: {sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id')} });
+            for (const od of orgDatas) {
+                if (od.pre_used) throw '往期已经计量,不可删除';
+            }
+            await this.db.delete(this.tableName, {id: datas});
+            return datas;
+        }
+
+        async _updateDatas (data) {
+            const info = this.ctx.tender.info;
+            const tpDecimal = info.decimal.extra ? info.decimal.extraTp : info.decimal.tp;
+
+            const datas = data instanceof Array ? data : [data];
+            const orgDatas = await this.getAllDataByCondition({
+                where: { sid: this.ctx.stage.id, id: this.ctx.helper._.map(datas, 'id') }
+            });
+
+            const uDatas = [];
+            for (const d of datas) {
+                const od = this.ctx.helper._.find(orgDatas, {id: d.id});
+                if (!od) continue;
+
+                const nd = {id: od.id};
+                if (d.name !== undefined) {
+                    if (od.pre_used) throw '往期已使用,不可修改名称';
+                    nd.name = d.name;
+                }
+                if (d.unit !== undefined) nd.unit = d.unit;
+                nd.unit_price = d.unit_price !== undefined ? this.ctx.helper.round(d.unit_price, info.decimal.up) : od.unit_price;
+                const precision = this.ctx.helper.findPrecision(info.precision, d.unit);
+                if (d.order !== undefined) nd.order = d.order;
+                if (d.qty !== undefined) {
+                    nd.qty = this.ctx.helper.round(d.qty, precision.value);
+                    nd.tp = this.ctx.helper.mul(nd.unit_price, nd.qty, tpDecimal);
+                }
+                if (d.memo !== undefined) nd.memo = d.memo;
+                uDatas.push(nd);
+            }
+            if (uDatas.length > 0) {
+                await this.db.updateRows(this.tableName, uDatas);
+                return uDatas;
+            } else {
+                return [];
+            }
+        }
+
+        async updateDatas(data) {
+            const result = {add: [], del: [], update: []};
+            try {
+                if (data.add) {
+                    result.add = await this._addDatas(data.add);
+                }
+                if (data.update) {
+                    result.update = await this._updateDatas(data.update);
+                }
+                if (data.del) {
+                    result.del = await this._delDatas(data.del);
+                }
+                return result;
+            } catch (err) {
+                if (err.stack) {
+                    throw err;
+                } else {
+                    result.err = err.toString();
+                    return result;
+                }
+            }
+        }
+
+        async updateHistory(stage, transaction) {
+            const datas = await this.getStageData(stage);
+            if (datas.length === 0) return;
+
+            const updateDatas = [];
+            const times = this.ctx.stage.curTimes, order = this.ctx.stage.curOrder;
+            for (const d of datas) {
+                const history = d.shistory && d.shistory !== '' ? JSON.parse(d.shistory) : [];
+                const his = history.find(function (x) {
+                    return x.stimes && x.stimes === times
+                        && x.sorder && x.sorder === order;
+                });
+                if (his) {
+                    his.qty = d.qty;
+                    his.tp = d.tp;
+                } else {
+                    const nHis = { stimes: this.ctx.stage.curTimes, sorder: this.ctx.stage.curOrder, qty: d.qty, tp: d.tp };
+                    history.push(nHis);
+                }
+                updateDatas.push({ id: d.id, shistory: JSON.stringify(history) });
+            }
+            await transaction.updateRows(this.tableName, updateDatas);
+        }
+
+        async updateHistory4CheckAgain(stage, transaction) {
+            const datas = await this.getStageData(stage);
+            if (datas.length === 0) return;
+
+            const updateDatas = [];
+            const times = this.ctx.stage.curTimes, order = this.ctx.stage.curOrder + 1;
+            for (const d of datas) {
+                const history = d.shistory && d.shistory !== '' ? JSON.parse(d.shistory) : [];
+                const his = history.find(function (x) {
+                    return x.stimes && x.stimes === times
+                        && x.sorder && x.sorder === order;
+                });
+                if (his) {
+                    his.qty = d.qty;
+                    his.tp = d.tp;
+                } else {
+                    const nHis = { stimes: times, sorder: order, qty: d.qty, tp: d.tp };
+                    history.push(nHis);
+                }
+                updateDatas.push({ id: d.id, shistory: JSON.stringify(history) });
+            }
+            await transaction.updateRows(this.tableName, updateDatas);
+        }
+
+        async addInitialStageData(stage, preStage, transaction) {
+            if (!stage || !preStage) {
+                throw '标段数据有误';
+            }
+            const preDatas = await this.getAllDataByCondition({
+                columns: ['tid', 'uuid', 'add_uid', 'add_sid', 'add_time', 'name', 'qty', 'order', 'memo', 'pre_used'],
+                where: { sid: preStage.id }
+            });
+            if (preDatas.length > 0) {
+                for (const pd of preDatas) {
+                    pd.pre_used = pd.pre_used || !this.ctx.helper.checkZero(pd.qty);
+                    delete pd.qty;
+                    pd.sid = stage.id;
+                    pd.sorder = stage.order;
+                }
+                const result = await transaction.insert(this.tableName, preDatas);
+                return result.affectedRows === preDatas.length;
+            } else {
+                return true;
+            }
+
+        }
+
+        async deleteStageTimesData(sid, times, transaction) {
+            const datas = await this.getAllDataByCondition({where: { sid: sid }});
+            if (datas.length === 0) return;
+
+            const updateDatas = [];
+            for (const d of datas) {
+                const history = d.shistory && d.shistory !== '' ? JSON.parse(d.shistory) : [];
+                const his = history.filter(function (x) {
+                    return x.stimes && x.stimes < times;
+                });
+                his.sort(function (x, y) {
+                    return (x.stimes * 1000 + x.sorder) - (y.stimes * 1000 + y.sorder);
+                });
+                updateDatas.push({
+                    id: d.id,
+                    shistory: JSON.stringify(his),
+                    qty: his.length > 0 ? his[his.length - 1].qty : null,
+                    tp: his.length > 0 ? his[his.length - 1].tp : null,
+                });
+            }
+            await transaction.updateRows(this.tableName, updateDatas);
+        }
+    }
+
+    return StageTempLand;
+};

+ 42 - 0
app/view/stage_extra/safe_prod.ejs

@@ -0,0 +1,42 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                其他
+            </div>
+            <% if (ctx.app.config.is_debug) { %>
+            <div class="d-inline-block ml-3">
+                <a id="exportExcel" class="btn btn-primary btn-sm" href="javascript: void(0)">导出安全生产Excel</a>
+            </div>
+            <% } %>
+            <div class="ml-auto"></div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="w-100 sub-content row">
+            <div class="c-body col-9">
+                <div class="sjs-height-1" id="safe-prod-spread" z-index="1">
+                </div>
+                <div z-index="0" style="display: none;">
+                    <input class="datepicker-here form-control form-control-sm" data-date-format="yyyy-MM-DD" data-language="zh" type="text" autocomplete="off" id="dp-input">
+                </div>
+            </div>
+            <div class="c-body col-3">
+                <table class="table table-bordered" style="width: 99%">
+                    <tr><th class="text-center" width="66.6%">名称</th><th class="text-center">金额</th></tr>
+                    <tbody id="sum">
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const stageId = <%- ctx.stage.id %>;
+    const stageUserId = <%- ctx.stage.user_id %>;
+    const readOnly = <%- ctx.stage.readOnly %>;
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+</script>

+ 2 - 0
app/view/stage_extra/sub_menu_list.ejs

@@ -1,4 +1,6 @@
 <nav-menu title="返回" url="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order %>" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
 <nav-menu title="甲供材料" url="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order + '/extra/jgcl'%>" ml="3" active="<%= ctx.url.indexOf('extra/jgcl') %>"></nav-menu>
 <nav-menu title="奖罚金" url="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order + '/extra/bonus'%>" ml="3" active="<%= ctx.url.indexOf('extra/bonus') %>"></nav-menu>
+<nav-menu title="安全生产" url="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order + '/extra/safeProd'%>" ml="3" active="<%= ctx.url.indexOf('extra/safeProd') %>"></nav-menu>
+<nav-menu title="临时占地" url="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order + '/extra/tempLand'%>" ml="3" active="<%= ctx.url.indexOf('extra/tempLand') %>"></nav-menu>
 <nav-menu title="其他" url="/tender/<%= ctx.tender.id %>/measure/stage/<%= ctx.stage.order + '/extra/other'%>" ml="3" active="<%= ctx.url.indexOf('extra/other') %>"></nav-menu>

+ 42 - 0
app/view/stage_extra/temp_land.ejs

@@ -0,0 +1,42 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                其他
+            </div>
+            <% if (ctx.app.config.is_debug) { %>
+            <div class="d-inline-block ml-3">
+                <a id="exportExcel" class="btn btn-primary btn-sm" href="javascript: void(0)">导出临时占地Excel</a>
+            </div>
+            <% } %>
+            <div class="ml-auto"></div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0"></div>
+        <div class="w-100 sub-content row">
+            <div class="c-body col-9">
+                <div class="sjs-height-1" id="temp-land-spread" z-index="1">
+                </div>
+                <div z-index="0" style="display: none;">
+                    <input class="datepicker-here form-control form-control-sm" data-date-format="yyyy-MM-DD" data-language="zh" type="text" autocomplete="off" id="dp-input">
+                </div>
+            </div>
+            <div class="c-body col-3">
+                <table class="table table-bordered" style="width: 99%">
+                    <tr><th class="text-center" width="66.6%">名称</th><th class="text-center">金额</th></tr>
+                    <tbody id="sum">
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const stageId = <%- ctx.stage.id %>;
+    const stageUserId = <%- ctx.stage.user_id %>;
+    const readOnly = <%- ctx.stage.readOnly %>;
+    const thousandth = <%- ctx.tender.info.display.thousandth %>;
+</script>

+ 40 - 0
config/web.js

@@ -503,6 +503,46 @@ const JsFiles = {
                 ],
                 mergeFile: 'se_other',
             },
+            safeProd: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/file-saver/FileSaver.js',
+                    '/public/js/shares/export_excel.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/se_safe_prod.js',
+                ],
+                mergeFile: 'se_safe_prod',
+            },
+            tempLand: {
+                files: [
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/spreadjs/sheets/v11/interop/gc.spread.excelio.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/file-saver/FileSaver.js',
+                    '/public/js/shares/export_excel.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/sjs_setting.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/se_temp_land.js',
+                ],
+                mergeFile: 'se_temp_land',
+            },
         },
         stageRela: {
             info: {

+ 46 - 0
sql/update.sql

@@ -1,3 +1,49 @@
 
 ALTER TABLE `zh_stage`
 ADD COLUMN `im_start_num`  int(11) NOT NULL DEFAULT 1 AFTER `im_gather_node`;
+
+-- 安全生产
+CREATE TABLE `zh_stage_safe_prod` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `tid` int(11) unsigned NOT NULL COMMENT '标段id',
+  `sid` int(11) unsigned NOT NULL COMMENT '期id',
+  `sorder` tinyint(4) unsigned NOT NULL COMMENT '期序号',
+  `uuid` varchar(36) CHARACTER SET ascii NOT NULL COMMENT '安全生产标识id',
+  `add_sid` int(11) unsigned NOT NULL COMMENT '新增期id',
+  `add_uid` int(11) NOT NULL COMMENT '新增人id',
+  `add_time` datetime NOT NULL COMMENT '新增时间',
+  `order` int(11) unsigned NOT NULL COMMENT '排序',
+  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '名称',
+  `unit` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '单位',
+  `unit_price` decimal(24,8) DEFAULT NULL COMMENT '单价',
+  `quantity` decimal(24,8) DEFAULT NULL COMMENT '计划-数量',
+  `total_price` decimal(24,8) DEFAULT NULL COMMENT '计划-金额',
+  `qty` decimal(24,8) DEFAULT NULL COMMENT '本期-数量',
+  `tp` decimal(24,8) DEFAULT NULL COMMENT '本期-金额',
+  `shistory` text CHARACTER SET utf8 COMMENT '本期历史数据',
+  `pre_used` tinyint(4) DEFAULT '0' COMMENT '往期是否使用',
+  `memo` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+
+-- 临时占地
+CREATE TABLE `zh_stage_temp_land` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `tid` int(11) unsigned NOT NULL COMMENT '标段id',
+  `sid` int(11) unsigned NOT NULL COMMENT '期id',
+  `sorder` tinyint(4) unsigned NOT NULL COMMENT '期序号',
+  `uuid` varchar(36) CHARACTER SET ascii NOT NULL COMMENT '安全生产标识id',
+  `add_sid` int(11) unsigned NOT NULL COMMENT '新增期id',
+  `add_uid` int(11) NOT NULL COMMENT '新增人id',
+  `add_time` datetime NOT NULL COMMENT '新增时间',
+  `order` int(11) unsigned NOT NULL COMMENT '排序',
+  `name` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '名称',
+  `unit` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '单位',
+  `unit_price` decimal(24,8) DEFAULT NULL COMMENT '单价',
+  `qty` decimal(24,8) DEFAULT NULL COMMENT '本期-数量',
+  `tp` decimal(24,8) DEFAULT NULL COMMENT '本期-金额',
+  `shistory` text CHARACTER SET utf8 COMMENT '本期历史数据',
+  `pre_used` tinyint(4) DEFAULT '0' COMMENT '往期是否使用',
+  `memo` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;