Jelajahi Sumber

1. 填设计量
2. 甲供材料部分代码

MaiXinRong 5 tahun lalu
induk
melakukan
193f1ec875

+ 1 - 1
.gitignore

@@ -9,4 +9,4 @@ app/public/download/
 .DS_Store
 *.swp
 package-lock.json
-app/public/js/web
+app/public/js/web

+ 8 - 0
app/base/base_bills_service.js

@@ -507,6 +507,14 @@ class BaseBillsSerivce extends TreeService {
         node.is_leaf = false;
         return { create: datas, update: [node]};
     }
+
+    async deal2sgfh(tid) {
+        const sql = 'UPDATE ' + this.tableName + ' SET sgfh_qty = deal_qty, quantity = deal_qty + sjcl_qty + qtcl_qty, ' +
+            '   sgfh_tp = deal_tp, total_price = deal_tp + sjcl_tp + qtcl_tp' +
+            '  WHERE tender_id = ?';
+        const sqlParam = [tid];
+        await this.db.query(sql, sqlParam);
+    }
 }
 
 module.exports = BaseBillsSerivce;

+ 22 - 1
app/controller/ledger_controller.js

@@ -545,7 +545,28 @@ module.exports = app => {
         //         if (stream) await sendToWormhole(stream);
         //         ctx.body = {err: 1, msg: err.toString(), data: null};
         //     }
-        // }        
+        // }
+
+        /**
+         * 填设计量(Ajax)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async deal2sgfh(ctx) {
+            try {
+                if (!ctx.tender.data) throw '标段数据错误';
+                if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId || this._ledgerReadOnly()) throw '您无权进行该操作';
+                if (this.ctx.tender.measure_type === measureType.tz.value) throw '该功能仅工程量清单模式可用';
+
+                await this.ctx.service.ledger.deal2sgfh(ctx.tender.id);
+                const ledgerData = await ctx.service.ledger.getData(ctx.tender.id);
+                ctx.body = {err: 0, msg: '', data: {bills: ledgerData}};
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
 
         /**
          * 下载(清单Excel模板 or 导出项目台账Excel)

+ 20 - 0
app/controller/revise_controller.js

@@ -655,6 +655,26 @@ module.exports = app => {
         }
 
         /**
+         * 填设计量(Ajax)
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async deal2sgfh(ctx) {
+            try {
+                const revise = await this.checkRevise(ctx);
+                if (this.ctx.tender.measure_type === measureType.tz.value) throw '该功能仅工程量清单模式可用';
+
+                await this.ctx.service.revise.deal2sgfh(ctx.tender.id);
+                const ledgerData = await ctx.service.revise.getData(ctx.tender.id);
+                ctx.body = {err: 0, msg: '', data: {bills: ledgerData}};
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+        }
+
+        /**
          * 新增审批人(Ajax)
          *
          * @param ctx

+ 83 - 1
app/controller/stage_extra_controller.js

@@ -29,7 +29,7 @@ module.exports = app => {
             try {
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.stageExtra.jgcl)
-                }
+                };
                 await this.layout('stage_extra/jgcl.ejs', renderData);
             } catch (err) {
                 ctx.helper.log(err);
@@ -37,6 +37,52 @@ module.exports = app => {
         }
 
         /**
+         * 获取甲供材料页面数据 (Get)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadJgcl (ctx) {
+            try {
+                const data = await ctx.service.stageJgcl.getStageData(ctx.stage.id);
+                const preData = await ctx.service.stageJgcl.getPreStageData(ctx.stage.order);
+                for (const d of data) {
+                    const pd = this.ctx.helper._.find(preData, {uuid: d.uuid});
+                    if (pd) {
+                        p.pre_arrive_qty = pd.arrive_qty;
+                        p.pre_arrive_tp = pd.arrive_tp;
+                        p.pre_deduct_qty = pd.deduct_qty;
+                        p.pre_deduct_tp = pd.deduct_tp;
+
+                        p.end_arrive_qty = ctx.helper.add(pd.arrive_qty + p.arrive_qty);
+                        p.end_arrive_tp = ctx.helper.add(pd.arrive_tp + p.arrive_tp);
+                        p.end_deduct_qty = ctx.helper.add(pd.deduct_qty + p.deduct_qty);
+                        p.end_deduct_tp = ctx.helper.add(pd.deduct_tp + p.deduct_tp);
+                    }
+                }
+                ctx.body = {err: 0, msg: '', data: data};
+            } catch (error) {
+                ctx.helper.log(error);
+                ctx.body = this.ajaxErrorBody(error, '获取甲供材料数据失败,请刷新');
+            }
+        }
+
+        /**
+         * 提交甲供材料数据 (Ajaz)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateJgcl (ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.stageJgcl.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全局变量
@@ -53,6 +99,24 @@ module.exports = app => {
         }
 
         /**
+         * 获取奖罚金页面数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadBonus (ctx) {
+
+        }
+
+        /**
+         * 提交 奖罚金页面数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateBonus (ctx) {
+
+        }
+
+        /**
          * 其他(Get)
          * @param {Object} ctx - egg全局变量
          */
@@ -66,6 +130,24 @@ module.exports = app => {
                 ctx.helper.log(err);
             }
         }
+
+        /**
+         * 获取 其他 数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async loadOther (ctx) {
+
+        }
+
+        /**
+         * 提交 其他 数据 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateOther (ctx) {
+
+        }
     }
 
     return StageExtraController;

+ 0 - 1
app/middleware/stage_check.js

@@ -97,7 +97,6 @@ module.exports = options => {
             } else { // 其他不可见
                 throw '您无权查看该数据';
             }
-            console.log(stage.readOnly);
 
             const lastRevise = yield this.service.ledgerRevise.getLastestRevise(this.tender.id);
             stage.revising = (lastRevise && lastRevise.status !== reviseStatus.checked) || false;

+ 13 - 1
app/public/js/ledger.js

@@ -857,7 +857,7 @@ $(document).ready(function() {
                     treeOperationObj.refreshTree(sheet, refreshNode);
                 })
             }
-        }
+        },
     };
     sjsSettingObj.setFxTreeStyle(ledgerSpreadSetting, sjsSettingObj.FxTreeStyle.jz);
     ledgerTreeCol.initSpreadSetting(ledgerSpreadSetting);
@@ -1146,6 +1146,18 @@ $(document).ready(function() {
                 return !readOnly;
             }
         };
+        if (!checkTzMeasureType()) {
+            billsContextMenuOptions.items.applyDeal2Sgfh = {
+                name: '填设计量',
+                callback: function (key, opt) {
+                    postData(window.location.pathname + '/deal2sgfh', null, function (result) {
+                        ledgerTree.loadDatas(result.bills);
+                        treeCalc.calculateAll(ledgerTree);
+                        SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+                    });
+                }
+            };
+        }
         billsContextMenuOptions.items.sprSort = '-----------';
     }
     if (!readOnly) {

+ 326 - 4
app/public/js/se_jgcl.js

@@ -13,10 +13,10 @@ const spreadSetting = {
         {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@'},
         {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, 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: 'get_qty', hAlign: 2, width: 60, type: 'Number'},
-        {title: '|金额',  colSpan: '|1', rowSpan: '|1', field: 'get_tp', hAlign: 2, width: 60, type: 'Number'},
-        {title: '截止本期到场|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_get_qty', hAlign: 2, width: 60, type: 'Number'},
-        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_get_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
+        {title: '本期到场|数量',  colSpan: '2|1', rowSpan: '1|1', field: 'arrive_qty', hAlign: 2, width: 60, type: 'Number'},
+        {title: '|金额',  colSpan: '|1', rowSpan: '|1', field: 'arrive_tp', hAlign: 2, width: 60, type: 'Number'},
+        {title: '截止本期到场|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_arrive_qty', hAlign: 2, width: 60, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'end_arrive_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
         {title: '本期扣回|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deduct_qty', hAlign: 2, width: 60, type: 'Number'},
         {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deduct_tp', hAlign: 2, width: 60, type: 'Number', readOnly: true},
         {title: '截止本期扣回|数量', colSpan: '2|1', rowSpan: '1|1', field: 'end_deduct_qty', hAlign: 2, width: 60, type: 'Number'},
@@ -55,4 +55,326 @@ $(document).ready(() => {
             jgclSpread.refresh();
         }
     });
+
+    class Jgcl {
+        constructor (setting) {
+            this.data = [];
+        }
+        resortData() {
+            this.data.sort(function (a, b) {
+                return a.order - b.order;
+            });
+        }
+        loadDatas(datas) {
+            this.data = datas;
+            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.resortData();
+        }
+    }
+    const jgclObj = new Jgcl();
+
+    postData(window.location.pathname + '/load', null, function (result) {
+        jgclObj.loadDatas(result);
+        SpreadJsObj.loadSheetData(jgclSheet, SpreadJsObj.DataType.Data, jgclObj.data);
+    });
+
+    if (!readOnly) {
+        const jgclOprObj = {
+            /**
+             * 删除按钮响应事件
+             * @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', {updateType: 'update', updateData: datas}, function (result) {
+                        jgclObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(jgclSheet);
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(jgclSheet);
+                    });
+                }
+            },
+            deleteJgcl: 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.add_sid === stageId) {
+                        toastMessageUniq(hint.isOld);
+                        continue;
+                    } else {
+                        if (node.add_uid !== userID || stageUserId !== userID) {
+                            toastMessageUniq(hint.invalidDel);
+                            continue;
+                        }
+                        datas.push(node.id);
+                    }
+                }
+                if (datas.length > 0) {
+                    postData(window.location.pathname + '/update', {updateType: 'del', updateData: datas}, function (result) {
+                        jgclObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(jgclSheet);
+                    }, function () {
+                        SpreadJsObj.reLoadSheetData(jgclSheet);
+                    });
+                }
+            },
+            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) {
+                    jgclObj.loadUpdateData(result);
+                    //SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }, 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.add_sid !== stageId;
+                        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: '甲供材料名称不可为空,已过滤'},
+                    unit_price: {type: 'warning', msg: '输入的 单价 非法,已过滤'},
+                    arrive_qty: {type: 'warning', msg: '输入的 本期到场-数量 非法,已过滤'},
+                    reduce_qty: {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 (col.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 {
+                            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) {
+                        jgclObj.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(info.sheet);
+                    });
+                } else {
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }
+            },
+            upMove: function () {
+
+            },
+            downMove: function () {
+
+            }
+        };
+        jgclSheet.bind(spreadNS.Events.EditEnded, jgclOprObj.editEnded);
+        jgclSheet.bind(spreadNS.Events.EditStarting, jgclOprObj.editStarting);
+        jgclSheet.bind(spreadNS.Events.ClipboardPasting, jgclOprObj.clipboardPasting);
+        SpreadJsObj.addDeleteBind(jgclSpread, jgclOprObj.deletePress);
+        $.contextMenu({
+            selector: '#jgcl-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, jgclSpread);
+                return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+            },
+            items: {
+                del: {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        jgclOprObj.deleteJgcl(jgclSheet);
+                    },
+                    disabled: function (key, opt) {
+                        const sels = jgclSheet.getSelections();
+                        if (!sels || !sels[0]) return true;
+
+                        const row = sels[0].row;
+                        const node = jgclObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                upMove: {
+                    name: '上移',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        jgclOprObj.upMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = jgclSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row === 0) return true;
+
+                        const row = sels[0].row;
+                        const node = jgclObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                },
+                downMove: {
+                    name: '下移',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        jgclOprObj.downMove();
+                    },
+                    disabled: function (key, opt) {
+                        const sels = jgclSheet.getSelections();
+                        if (!sels || !sels[0] || sels[0].row >= jgclObj.data.length - 1) return true;
+
+                        const row = sels[0].row;
+                        const node = jgclObj.data[row];
+                        return node === undefined || node === null;
+                    },
+                    visible: function (key, opt) {
+                        return !readOnly;
+                    }
+                }
+            },
+        })
+    }
 });

+ 4 - 0
app/router.js

@@ -99,6 +99,7 @@ module.exports = app => {
     app.get('/tender/:id/ledger/download/:file', sessionAuth, tenderCheck, 'ledgerController.download');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, 'ledgerController.posUpdate');
     app.post('/tender/:id/pos/paste', sessionAuth, tenderCheck, 'ledgerController.posPaste');
+    app.post('/tender/:id/ledger/deal2sgfh', sessionAuth, tenderCheck, 'ledgerController.deal2sgfh');
     // 台账审批相关
     app.get('/tender/:id/ledger/audit', sessionAuth, tenderCheck, 'ledgerAuditController.index');
     app.post('/tender/:id/ledger/audit/add', sessionAuth, tenderCheck, 'ledgerAuditController.add');
@@ -115,6 +116,7 @@ module.exports = app => {
     app.post('/tender/:id/revise/add', sessionAuth, tenderCheck, 'reviseController.add');
     app.post('/tender/:id/revise/cancel', sessionAuth, tenderCheck, 'reviseController.cancel');
     app.post('/tender/:id/revise/save', sessionAuth, tenderCheck, 'reviseController.save');
+    //app.post('/tender/:id/revise/deal2sgfh', sessionAuth, tenderCheck, 'reviseController.deal2sgfh');
     // 台账修订页面
     app.get('/tender/:id/revise/info', sessionAuth, tenderCheck, 'reviseController.info');
     app.post('/tender/:id/revise/info/load', sessionAuth, tenderCheck, 'reviseController.loadInfoData');
@@ -195,6 +197,8 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/compare/load', sessionAuth, tenderCheck, stageCheck, 'stageController.compareAuditor');
     // 附加功能
     app.get('/tender/:id/measure/stage/:order/extra/jgcl', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.jgcl');
+    app.post('/tender/:id/measure/stage/:order/extra/jgcl/load', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.loadJgcl');
+    app.post('/tender/:id/measure/stage/:order/extra/jgcl/update', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.updateJgcl');
     app.get('/tender/:id/measure/stage/:order/extra/bonus', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.bonus');
     app.get('/tender/:id/measure/stage/:order/extra/other', sessionAuth, tenderCheck, stageCheck, 'stageExtraController.other');
 

+ 6 - 1
app/view/stage_extra/jgcl.ejs

@@ -16,4 +16,9 @@
             </div>
         </div>
     </div>
-</div>
+</div>
+<script>
+    const stageId = <%- ctx.stage.id %>;
+    const stageUserId = <%- ctx.stage.user_id %>;
+    const readOnly = <%- ctx.stage.readOnly %>;
+</script>