浏览代码

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

Tony Kang 1 年之前
父节点
当前提交
6c5785fb57

+ 132 - 0
app/const/audit.js

@@ -259,6 +259,137 @@ const stage = (function() {
     };
 })();
 
+// 结算
+const settle = (function() {
+    // 流程状态
+    const status = {
+        uncheck: 1, // 待上报
+        checking: 2, // 待审批|审批中
+        checked: 3, // 审批通过
+        checkNo: 4, // 审批退回原报
+        checkNoPre: 5, // 审批退回上一人
+        checkAgain: 6, // 重新审批 // 该状态仅可用于,终审退回时,修改原终审的审批状态,并同时新增一条新的终审审批中记录
+        checkCancel: 7, // 撤回 // 该状态为上一审批人可发起,回到它到审批阶段,并同时新增一条新的审批中记录
+        checkSkip: 8, // 跳过
+    };
+
+    // 流程状态提示
+    const statusString = [];
+    statusString[status.uncheck] = '待上报';
+    statusString[status.checking] = '审批中';
+    statusString[status.checked] = '审批通过';
+    statusString[status.checkNo] = '审批退回';
+    statusString[status.checkNoPre] = '审批退回';
+    statusString[status.checkAgain] = '重新审批';
+    statusString[status.checkCancel] = '撤回';
+
+    // 流程状态样式
+    const statusClass = [];
+    statusClass[status.uncheck] = '';
+    statusClass[status.checking] = '';
+    statusClass[status.checked] = 'text-success';
+    statusClass[status.checkNo] = 'text-warning';
+    statusClass[status.checkNoPre] = 'text-warning';
+    statusClass[status.checkAgain] = 'text-warning';
+    statusClass[status.checkCancel] = 'text-warning';
+
+    /**
+     * 期列表,审批状态一列
+     */
+        // 按钮
+    const statusButton = [];
+    statusButton[status.uncheck] = '待上报';
+    statusButton[status.checking] = '审批';
+    statusButton[status.checked] = '';
+    statusButton[status.checkNo] = '重新上报';
+    statusButton[status.checkNoPre] = '重新审批';
+    statusButton[status.checkAgain] = '重新审批';
+    statusButton[status.checkCancel] = '撤回';
+    // 按钮样式
+    const statusButtonClass = [];
+    statusButtonClass[status.uncheck] = 'btn-primary';
+    statusButtonClass[status.checking] = 'btn-success';
+    statusButtonClass[status.checked] = '';
+    statusButtonClass[status.checkNo] = 'btn-warning';
+    statusButtonClass[status.checkNoPre] = 'btn-warning';
+    statusButtonClass[status.checkAgain] = 'btn-warning';
+    statusButtonClass[status.checkCancel] = 'btn-warning';
+    // 描述文本
+    const auditString = [];
+    auditString[status.uncheck] = '';
+    auditString[status.checking] = '审批中';
+    auditString[status.checked] = '审批通过';
+    auditString[status.checkNo] = '审批退回';
+    auditString[status.checkNoPre] = '审批退回';
+    auditString[status.checkAgain] = '重新审批';
+    auditString[status.checkCancel] = '撤回';
+    auditString[status.checkSkip] = '审批通过';
+    // 文字样式
+    const auditStringClass = [];
+    auditStringClass[status.uncheck] = '';
+    auditStringClass[status.checking] = 'text-warning';
+    auditStringClass[status.checked] = 'text-success';
+    auditStringClass[status.checkNo] = 'text-warning';
+    auditStringClass[status.checkNoPre] = 'text-warning';
+    auditStringClass[status.checkAgain] = 'text-warning';
+    auditStringClass[status.checkCancel] = 'text-warning';
+    auditStringClass[status.checkSkip] = 'text-success';
+    /* ------------------------------------------------------- */
+
+    /**
+     * 期列表,审批进度一列
+     */
+        // 描述文本
+    const auditProgress = [];
+    auditProgress[status.uncheck] = '待上报';
+    auditProgress[status.checking] = '审批中';
+    auditProgress[status.checked] = '审批通过';
+    auditProgress[status.checkNo] = '审批退回';
+    auditProgress[status.checkNoPre] = '审批退回';
+    auditProgress[status.checkAgain] = '重新审批';
+    auditProgress[status.checkCancel] = '撤回';
+    // 样式
+    const auditProgressClass = [];
+    auditProgressClass[status.uncheck] = '';
+    auditProgressClass[status.checking] = 'text-warning';
+    auditProgressClass[status.checked] = 'text-success';
+    auditProgressClass[status.checkNo] = 'text-warning';
+    auditProgressClass[status.checkNoPre] = 'text-warning';
+    auditProgressClass[status.checkAgain] = 'text-warning';
+    auditProgressClass[status.checkCancel] = 'text-warning';
+    /* ------------------------------------------------------- */
+
+    const tiStatusString = [];
+    tiStatusString[status.uncheck] = '待上报';
+    tiStatusString[status.checking] = '审批中';
+    tiStatusString[status.checked] = '审批通过';
+    tiStatusString[status.checkNo] = '审批退回';
+    tiStatusString[status.checkNoPre] = '审批中';
+    tiStatusString[status.checkAgain] = '审批中';
+    tiStatusString[status.checkCancel] = '撤回';
+    const tiStatusStringClass = [];
+    tiStatusStringClass[status.uncheck] = '';
+    tiStatusStringClass[status.checking] = 'text-warning';
+    tiStatusStringClass[status.checked] = 'text-success';
+    tiStatusStringClass[status.checkNo] = 'text-warning';
+    tiStatusStringClass[status.checkNoPre] = 'text-warning';
+    tiStatusStringClass[status.checkAgain] = 'text-warning';
+    tiStatusStringClass[status.checkCancel] = 'text-warning';
+    const backType = {
+        org: 1,
+        pre: 2,
+    };
+    return {
+        status, statusString, statusClass,
+        statusButton, statusButtonClass,
+        auditString, auditStringClass,
+        auditProgress, auditProgressClass,
+        backType,
+        timesLen: 100,
+        tiStatusString, tiStatusStringClass,
+    };
+})();
+
 // 变更令状态
 const status = {
     uncheck: 1, // 待上报
@@ -722,6 +853,7 @@ module.exports = {
     auditType,
     ledger,
     stage,
+    settle,
     revise,
     material,
     flow: {

+ 2 - 0
app/const/project_log.js

@@ -17,6 +17,7 @@ const type = {
     changeProject: 5,
     changeApply: 6,
     changePlan: 7,
+    settle: 8,
 };
 
 const type_list = [
@@ -28,6 +29,7 @@ const type_list = [
     { code: 'changeProject', type: type.changeProject, name: '变更立项' },
     { code: 'changeApply', type: type.changeApply, name: '变更申请' },
     { code: 'changePlan', type: type.changePlan, name: '变更方案' },
+    { code: 'settle', type: type.settle, name: '计量结算' },
 ];
 // 操作状态
 const status = {

+ 2 - 1
app/controller/change_controller.js

@@ -1005,7 +1005,8 @@ module.exports = app => {
                     // 判断条件修订可上报条件
                     // 1.有原报,不管有无其他人   可以添加原报作为审核人,但是不能只原报上报
                     // 2.无原报,有其他人   不可以添加原报
-                    const noYBUidList = ctx.change.status === audit.flow.status.revise ? ctx.helper._.initial(ctx.helper._.tail(lastUidList)) : ctx.helper._.tail(lastUidList);
+                    // const noYBUidList = ctx.change.status === audit.flow.status.revise ? ctx.helper._.initial(ctx.helper._.tail(lastUidList)) : ctx.helper._.tail(lastUidList);
+                    const noYBUidList = ctx.helper._.tail(lastUidList);
                     // if (!ctx.helper._.isEqual(lastUidList, nowUidList) && ctx.helper._.includes(noYBUidList, lastUidList[0]) && nowUidList.length === 2 && nowUidList[0] === nowUidList[1]) {
                     if (nowUidList.length === 2 && nowUidList[0] === nowUidList[1]) {
                         throw '该变更令不能指定原报人为单独审批人';

+ 4 - 4
app/controller/material_controller.js

@@ -912,19 +912,19 @@ module.exports = app => {
                         break;
                     case 'change':
                     case 'join':
-                        await ctx.service.materialListNotjoin.del(data.select.id, data.gather_qty, data.ms_id);
+                        await ctx.service.materialListNotjoin.del(data.select.id, data.ms_id);
                         break;
                     case 'notjoin':
-                        responseData.data = await ctx.service.materialListNotjoin.add(data.select, data.gather_qty, data.ms_id);
+                        responseData.data = await ctx.service.materialListNotjoin.add(data.select, data.ms_id);
                         break;
                     case 'notchange':
-                        responseData.data = await ctx.service.materialListNotjoin.addChange(data.select, data.gather_qty, data.ms_id);
+                        responseData.data = await ctx.service.materialListNotjoin.addChange(data.select, data.ms_id);
                         break;
                     case 'self':
                         responseData.data = await ctx.service.materialListSelf.add(data.select);
                         break;
                     case 'noself':
-                        responseData.data = await ctx.service.materialListSelf.del(data.select.id, data.ms_id, data.select.gather_qty);
+                        responseData.data = await ctx.service.materialListSelf.del(data.select.id, data.ms_id, data.select);
                         break;
                     case 'paste':
                         await ctx.service.materialList.saveDatas(data.updateData, data.ms_id);

+ 5 - 0
app/controller/setting_controller.js

@@ -42,6 +42,7 @@ module.exports = app => {
             const ctx = this.ctx;
             await this.ctx.service.s2bProj.refreshSessionS2b(pid);
             ctx.subMenu.s2b.display = !!ctx.session.sessionProject.gxby || !!ctx.session.sessionProject.dagl;
+            ctx.subMenu.datacollect.display = ctx.session.sessionProject.page_show.openDataCollect;
             // this.ctx.subMenu.s2b.display = false;
         }
 
@@ -1095,6 +1096,9 @@ module.exports = app => {
 
         async dataCollect(ctx) {
             try {
+                if (!ctx.session.sessionProject.page_show.openDataCollect) {
+                    throw '该功能已关闭或无法查看';
+                }
                 const projectId = ctx.session.sessionProject.id;
                 await this._checkMenu(projectId);
                 const projectData = await ctx.service.project.getDataById(projectId);
@@ -1131,6 +1135,7 @@ module.exports = app => {
                 }, 'setting/datacollect_modal.ejs');
             } catch (error) {
                 ctx.helper.log(error);
+                ctx.session.postError = error.toString();
                 ctx.redirect('/dashboard');
             }
         }

+ 90 - 2
app/controller/settle_controller.js

@@ -1,13 +1,14 @@
 'use strict';
 
 /**
- * 过程结算相关控制器
+ * 过程结算 控制器
  *
  * @author Mai
  * @date 2023/10/27
  * @version
  */
 
+const auditConst = require('../const/audit');
 
 module.exports = app => {
 
@@ -36,14 +37,101 @@ module.exports = app => {
                 const renderData = {
                     tender: ctx.tender.data,
                     preUrl: `/tender/${ctx.tender.id}/measure/stage`,
+                    auditConst: auditConst.settle,
+                    auditType: auditConst.auditType,
                 };
-                renderData.settles = await ctx.service.settle.getValidSettle(ctx.tender.id);
+                renderData.settles = await ctx.service.settle.getValidSettles(ctx.tender.id);
+                renderData.checkedStageCount = await ctx.service.stage.count({ tid: ctx.tender.id, status: auditConst.stage.status.checked });
                 await this.layout('settle/list.ejs', renderData, 'settle/list_modal.ejs');
             } catch (err) {
                 this.log(err);
                 ctx.redirect(this.menu.menu.dashboard.url);
             }
         }
+
+        /**
+         * 期审批流程(Get)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async loadAuditors(ctx) {
+            try {
+                const order = JSON.parse(ctx.request.body.data).order;
+                const tenderId = ctx.params.id;
+                const settle = await ctx.service.settle.getDataByCondition({ tid: tenderId, settle_order: order });
+                await ctx.service.settle.loadRelaUser(settle);
+                await ctx.service.settle.loadAuditViewData(settle);
+                ctx.body = { err: 0, msg: '', data: settle };
+            } catch (error) {
+                this.log(error);
+                ctx.body = { err: 1, msg: error.toString(), data: null };
+            }
+        }
+
+        async add(ctx) {
+            try {
+                if (ctx.session.sessionUser.accountId !== ctx.tender.data.user_id) throw '您无权创建计量期';
+
+                const date = ctx.request.body.date;
+                const period = ctx.request.body.period;
+                if (!date || !period) throw '请选择结算年月和结算周期';
+
+                const newSettle = await ctx.service.settle.addSettle(ctx.tender.id, date, period);
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle/' + newSettle.settle_order);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '新增结算期失败,请重试');
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle');
+            }
+        }
+
+        async save(ctx) {
+            try {
+                const data = {
+                    order: ctx.request.body.order,
+                    date: ctx.request.body.date,
+                    period: ctx.request.body.period,
+                };
+                const settle = await ctx.service.settle.getDataByCondition({ tid: ctx.tender.id, settle_order: data.order });
+                if (!settle) throw '修改的结算期不存在';
+
+                if (ctx.session.sessionUser.accountId !== stage.user_id) throw '您无权修改该数据';
+
+                await this.ctx.service.stage.saveSettle(ctx.tender.id, data.order, data.date, data.period);
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '保存结算期数据失败,请重试');
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle');
+            }
+        }
+
+        async delete(ctx) {
+            try {
+                if (ctx.request.body.confirm !== undefined && ctx.request.body.confirm !== '确认删除本期') {
+                    throw '请输入正确的文本信息';
+                }
+
+                const sid = ctx.request.body.settle_id;
+                const settle = await ctx.service.settle.getDataById(sid);
+                const settleCount = await ctx.service.settle.count({ tid: ctx.tender.id });
+                if (!settle || settle.tid !== ctx.tender.id) throw '选择的结算期已不存在';
+                if (settleCount === settle.settle_order) throw '选择的结算期无法删除';
+                if (ctx.session.sessionUser.accountId !== settle.user_id && !ctx.session.sessionUser.is_admin) throw '您无权删除结算期';
+
+                await ctx.service.deleteSettle(sid);
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle/');
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '删除结算期数据失败,请重试');
+                ctx.redirect('/tender/' + ctx.tender.id + '/settle/');
+            }
+        }
+
+        async index(ctx) {
+            await ctx.service.stage.loadStageAuditViewData(ctx.stage);
+
+        }
     }
 
     return SettleController;

+ 12 - 0
app/controller/sub_proj_controller.js

@@ -133,6 +133,18 @@ module.exports = app => {
             }
         }
 
+        async refresh(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '参数有误';
+                const result = await ctx.service.subProject.refreshManagementPermission({ id: data.id });
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
         async rela(ctx) {
             try {
                 const id = ctx.query.id;

+ 4 - 1
app/extend/helper.js

@@ -1656,7 +1656,7 @@ module.exports = {
             console.log(err);
         }
     },
-    getQtySource(qty_source) {
+    getQtySource(qty_source, no_qc_qty = 0) {
         let qty = '';
         switch (qty_source) {
             case qtySourceValueConst.gather_qty: qty = '`gather_qty`'; break;
@@ -1664,6 +1664,9 @@ module.exports = {
             case qtySourceValueConst.gather_minus_qty: qty = '(`gather_qty`+`qc_minus_qty`)'; break;
             default: throw '未配置计量来源出错';
         }
+        if (qty_source !== qtySourceValueConst.contract_qty && no_qc_qty) {
+            qty = '(' + qty + '-`qc_qty`)';
+        }
         return qty;
     },
     resetQtys(qtys) {

+ 38 - 0
app/middleware/settle_check.js

@@ -0,0 +1,38 @@
+'use strict';
+
+/**
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 结算期 校验 中间件
+     * 1. 读取 结算期 数据
+     * 2. 检验用户是否参与(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* stageCheck(next) {
+        try {
+            // 读取标段数据
+            const settleOrder = parseInt(this.params.order);
+            if (settleOrder <= 0) throw '您访问的期不存在';
+            const settle = yield this.service.settle.getDataByCondition({ tid: this.tender.id, settle_order: settleOrder });
+            if (!settle) throw '您访问的期不存在';
+
+            yield this.service.settle.doCheckSettle(settle);
+            // 获取最新的期数
+            settle.highOrder = yield this.service.settle.count({ tid: this.tender.id });
+
+            this.settle = settle;
+            yield next;
+        } catch (err) {
+            this.log(err);
+            this.redirect(`/tender/${this.params.id}/settle`);
+        }
+    };
+};

+ 3 - 86
app/public/js/budget_list.js

@@ -179,92 +179,9 @@ $(document).ready(() => {
         table: '#budgetList',
     });
 
-    class srObject {
-        constructor() {
-            const self = this;
-            this.selectTree = null;
-            const srSpreadSetting = {
-                cols: [
-                    {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
-                    {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
-                    {title: '期数', field: 'lastStageOrder', hAlign: 1, width: 60, formatter: '@'},
-                    {title: '审批状态', field: 'lastStageStatus', hAlign: 1, width: 60, formatter: '@'},
-                ],
-                emptyRows: 0,
-                headRows: 1,
-                headRowHeight: [32],
-                defaultRowHeight: 21,
-                headerFont: '12px 微软雅黑',
-                font: '12px 微软雅黑',
-                headColWidth: [30],
-                selectedBackColor: '#fffacd',
-                readOnly: true,
-            };
-            this.spread = SpreadJsObj.createNewSpread($('#sr-spread')[0]);
-            this.sheet = this.spread.getActiveSheet();
-            SpreadJsObj.initSheet(this.sheet, srSpreadSetting);
-
-            this.spread.bind(spreadNS.Events.ButtonClicked, function (e, info) {
-                if (!info.sheet.zh_setting) return;
-
-                const col = info.sheet.zh_setting.cols[info.col];
-                if (col.field !== 'selected') return;
-
-                const node = SpreadJsObj.getSelectObject(info.sheet);
-                self.selectNode(node, !node[col.field]);
-                SpreadJsObj.reloadColData(info.sheet, 0);
-            });
-
-
-
-            $('#sr-select-all').click(function () {
-                if (!self.selectTree) return;
-                for (const n of self.selectTree.nodes) {
-                    n.selected = this.checked;
-                }
-                SpreadJsObj.reloadColData(self.sheet, 0);
-            });
-            $('#select-rela-ok').click(() => {
-                const rela = self.getSelects();
-                postData('/subproj/rela/save', { id: curBudget.id, rela_tender: rela.join(',') }, function () {
-                    $(`[bid=${curBudget.bid}]`)[0].setAttribute('rela-tender', rela.join(','));
-                    $('#select-rela').modal('hide');
-                });
-            });
-        }
-        selectNode(node, select) {
-            const posterity = this.selectTree.getPosterity(node);
-            posterity.unshift(node);
-            for (const p of posterity) {
-                p.selected = select;
-            }
-        }
-        getSelects() {
-            const select = [];
-            for (const n of this.selectTree.nodes) {
-                if ((!n.children || n.children.length === 0) && n.selected) select.push(n.tid);
-            }
-            return select;
-        }
-        init() {
-            $('#sr-select-all')[0].checked = false;
-            const self = this;
-            postData(`/subproj/rela?id=${curBudget.id}`, {}, tenders => {
-                const rela = curBudget.rela_tender ? curBudget.rela_tender.split(',') : [];
-                self.selectTree = Tender2Tree.convert(category, tenders, null, null, function (node, source) {
-                    node.lastStageOrder = `第${source.lastStageOrder}期`;
-                    node.lastStageStatus = source.lastStageStatus;
-                });
-                for (const node of self.selectTree.nodes) {
-                    node.selected = rela.indexOf(node.tid + '') >= 0;
-                }
-                SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.selectTree);
-            });
+    let srSelect = new srObject({
+        getRelaProject: function() {
+            return curBudget;
         }
-    }
-    let srSelect;
-    $('#select-rela').on('shown.bs.modal', () => {
-        if (!srSelect) srSelect = new srObject();
-        srSelect.init();
     });
 });

+ 3 - 87
app/public/js/file_list.js

@@ -166,93 +166,9 @@ $(document).ready(() => {
         source: projectList,
         table: '#projectList',
     });
-
-    class srObject {
-        constructor() {
-            const self = this;
-            this.selectTree = null;
-            const srSpreadSetting = {
-                cols: [
-                    {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
-                    {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
-                    {title: '期数', field: 'lastStageOrder', hAlign: 1, width: 60, formatter: '@'},
-                    {title: '审批状态', field: 'lastStageStatus', hAlign: 1, width: 60, formatter: '@'},
-                ],
-                emptyRows: 0,
-                headRows: 1,
-                headRowHeight: [32],
-                defaultRowHeight: 21,
-                headerFont: '12px 微软雅黑',
-                font: '12px 微软雅黑',
-                headColWidth: [30],
-                selectedBackColor: '#fffacd',
-                readOnly: true,
-            };
-            this.spread = SpreadJsObj.createNewSpread($('#sr-spread')[0]);
-            this.sheet = this.spread.getActiveSheet();
-            SpreadJsObj.initSheet(this.sheet, srSpreadSetting);
-
-            this.spread.bind(spreadNS.Events.ButtonClicked, function (e, info) {
-                if (!info.sheet.zh_setting) return;
-
-                const col = info.sheet.zh_setting.cols[info.col];
-                if (col.field !== 'selected') return;
-
-                const node = SpreadJsObj.getSelectObject(info.sheet);
-                self.selectNode(node, !node[col.field]);
-                SpreadJsObj.reloadColData(info.sheet, 0);
-            });
-
-
-
-            $('#sr-select-all').click(function () {
-                if (!self.selectTree) return;
-                for (const n of self.selectTree.nodes) {
-                    n.selected = this.checked;
-                }
-                SpreadJsObj.reloadColData(self.sheet, 0);
-            });
-            $('#select-rela-ok').click(() => {
-                const rela = self.getSelects();
-                postData('/subproj/rela/save', { id: curProject.id, rela_tender: rela.join(',') }, function () {
-                    $(`[tree_id=${curProject.id}]`)[0].setAttribute('rela-tender', rela.join(','));
-                    $('#select-rela').modal('hide');
-                });
-            });
-        }
-        selectNode(node, select) {
-            const posterity = this.selectTree.getPosterity(node);
-            posterity.unshift(node);
-            for (const p of posterity) {
-                p.selected = select;
-            }
-        }
-        getSelects() {
-            const select = [];
-            for (const n of this.selectTree.nodes) {
-                if ((!n.children || n.children.length === 0) && n.selected) select.push(n.tid);
-            }
-            return select;
-        }
-        init() {
-            $('#sr-select-all')[0].checked = false;
-            const self = this;
-            postData(`/subproj/rela?id=${curProject.id}`, {}, tenders => {
-                const rela = curProject.rela_tender ? curProject.rela_tender.split(',') : [];
-                self.selectTree = Tender2Tree.convert(category, tenders, null, null, function (node, source) {
-                    node.lastStageOrder = `第${source.lastStageOrder}期`;
-                    node.lastStageStatus = source.lastStageStatus;
-                });
-                for (const node of self.selectTree.nodes) {
-                    node.selected = rela.indexOf(node.tid + '') >= 0;
-                }
-                SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.selectTree);
-            });
+    let srSelect = new srObject({
+        getRelaProject: function() {
+            return curProject;
         }
-    }
-    let srSelect;
-    $('#select-rela').on('shown.bs.modal', () => {
-        if (!srSelect) srSelect = new srObject();
-        srSelect.init();
     });
 });

+ 10 - 5
app/public/js/material.js

@@ -574,7 +574,7 @@ $(document).ready(() => {
             const data = SpreadJsObj.getSelectObject(info.sheet);
             materialSpreadObj.setReadOnly(true);
             // console.log(data);
-            setCurBillSourceList(data.mb_id || data.id, $('#myTab').find('.active').data('msid') || null);
+            setCurBillSourceList(data ? (data.mb_id || data.id) : null, $('#myTab').find('.active').data('msid') || null);
         },
         editEnded: function (e, info) {
             if (info.sheet.zh_setting) {
@@ -584,7 +584,7 @@ $(document).ready(() => {
                     return;
                 }
                 // 未改变值则不提交
-                let validText = col.type === 'Number' && is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
+                let validText = (col.type === 'Number' || col.field === 'm_type' || col.field === 't_type') && is_numeric(info.editingText) ? parseFloat(info.editingText) : (info.editingText ? trimInvalidChar(info.editingText) : null);
                 const orgValue = select[col.field];
                 if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
@@ -744,7 +744,7 @@ $(document).ready(() => {
                     if (!colSetting) continue;
 
                     let validText = info.sheet.getText(curRow, curCol);
-                    validText = colSetting.type === 'Number' && is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
+                    validText = (colSetting.type === 'Number' || colSetting.field === 'm_type' || colSetting.field === 't_type') && is_numeric(validText) ? parseFloat(validText) : (validText ? trimInvalidChar(validText) : null);
                     const orgValue = sortData[curRow][colSetting.field];
                     if (orgValue == validText || ((!orgValue || orgValue === '') && (validText === ''))) {
                         sameCol++;
@@ -1381,7 +1381,7 @@ $(document).ready(() => {
             const msInfo = _.find(materialStageData, { id: ms_id });
             setListsData(msInfo.sid, ms_id);
             const data = SpreadJsObj.getSelectObject(materialSpread.getActiveSheet());
-            setCurBillSourceList(data.mb_id || data.id, ms_id);
+            setCurBillSourceList(data ? (data.mb_id || data.id) : null, ms_id);
             closeWaitingView();
         }, 1000);
     });
@@ -1389,7 +1389,9 @@ $(document).ready(() => {
     function setCurBillSourceList(mb_id, ms_id = null) {
         const showSourceList = [];
         if (mb_id) {
-            const list = _.filter(materialListData2, { mb_id, ms_id, is_join: 1 });
+            const list = _.filter(materialListData2, function (item) {
+                return item.mb_id === mb_id && item.ms_id === ms_id && (item.is_join === 1 || item.is_join === 2);
+            });
             console.log(list);
             if (list.length > 0) {
                 for (const l of list) {
@@ -1423,6 +1425,9 @@ $(document).ready(() => {
             case qtySourceValueConst.gather_minus_qty: qty = ZhCalc.add(info.gather_qty, info.qc_minus_qty); break;
             default: throw '未配置计量来源出错';
         }
+        if (qtySource !== qtySourceValueConst.contract_qty && info.is_join === 2) {
+            qty = ZhCalc.sub(qty, info.qc_qty);
+        }
         return qty;
     }
 

+ 4 - 4
app/public/js/material_checklist.js

@@ -448,7 +448,7 @@ $(document).ready(() => {
                 qc_qty: xmj.qc_qty,
                 qc_minus_qty: xmj.qc_minus_qty,
                 gather_qty: xmj.gather_qty,
-                is_join: notx === undefined ? 1 : 0,
+                is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
             };
             if (ms_id) data.ms_id = ms_id;
             datas.push(data);
@@ -474,7 +474,7 @@ $(document).ready(() => {
                                 qc_qty: xmj.qc_qty,
                                 qc_minus_qty: xmj.qc_minus_qty,
                                 gather_qty: xmj.gather_qty,
-                                is_join: notx === undefined ? 1 : 0,
+                                is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
                                 ms_id: ms.id,
                             };
                             datas.push(data);
@@ -1388,7 +1388,7 @@ $(document).ready(() => {
                         qc_qty: xmj.qc_qty,
                         qc_minus_qty: xmj.qc_minus_qty,
                         gather_qty: xmj.gather_qty,
-                        is_join: notx === undefined ? 1 : 0,
+                        is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
                     };
                     if (ms_id) data.ms_id = ms_id;
                     datas.push(data);
@@ -1417,7 +1417,7 @@ $(document).ready(() => {
                                         qc_qty: xmj.qc_qty,
                                         qc_minus_qty: xmj.qc_minus_qty,
                                         gather_qty: xmj.gather_qty,
-                                        is_join: notx === undefined ? 1 : 0,
+                                        is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
                                         ms_id: ms.id,
                                     };
                                     datas.push(data);

+ 70 - 27
app/public/js/material_list.js

@@ -68,20 +68,34 @@ function calcOneBQJC(xmj) {
     const notx = findNotJoinLeafXmj(xmj);
     if (notx === undefined) {
         const list = xmj.mx_id !== undefined ? getMaterialListByLeafXmj(xmj.gcl_id, xmj.id, xmj.mx_id) : getMaterialListByLeafXmj(xmj.gcl_id, xmj.id);
-        const notx2 = findNotChangeLeafXmj(xmj);
-        if (notx2 !== undefined) {
-            for (const l of list) {
-                jiacha = ZhCalc.add(jiacha, ZhCalc.mul(ZhCalc.mul(xmj.contract_qty, l.quantity), getMpSpreadByMBData(l.mb_id)));
-            }
-        } else {
-            for (const l of list) {
-                jiacha = ZhCalc.add(jiacha, ZhCalc.mul(ZhCalc.mul(xmj.gather_qty, l.quantity), getMpSpreadByMBData(l.mb_id)));
-            }
+        // const notx2 = findNotChangeLeafXmj(xmj);
+        // if (notx2 !== undefined) {
+        //     for (const l of list) {
+        //         jiacha = ZhCalc.add(jiacha, ZhCalc.mul(ZhCalc.mul(xmj.contract_qty, l.quantity), getMpSpreadByMBData(l.mb_id)));
+        //     }
+        // } else {
+        for (const l of list) {
+            jiacha = ZhCalc.add(jiacha, ZhCalc.mul(ZhCalc.mul(calcQty(xmj, l.is_join), l.quantity), getMpSpreadByMBData(l.mb_id)));
         }
+        // }
     }
     return ZhCalc.round(jiacha, materialDecimal.tp);
 }
 
+function calcQty(info, is_join) {
+    let qty = '';
+    switch (qtySource) {
+        case qtySourceValueConst.gather_qty: qty = info.gather_qty; break;
+        case qtySourceValueConst.contract_qty: qty = info.contract_qty; break;
+        case qtySourceValueConst.gather_minus_qty: qty = ZhCalc.add(info.gather_qty, info.qc_minus_qty); break;
+        default: throw '未配置计量来源出错';
+    }
+    if (qtySource !== qtySourceValueConst.contract_qty && is_join === 2) {
+        qty = ZhCalc.sub(qty, info.qc_qty);
+    }
+    return qty;
+}
+
 function getPasteHint (str, row = '') {
     let returnObj = str;
     if (row) {
@@ -173,19 +187,19 @@ $(document).ready(() => {
         {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 80, formatter: '@'},
         {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 110, type: 'Number'},
     ];
-    if (materialQtySource === 1) {
+    if (qtySource === qtySourceValueConst.gather_qty) {
         ledgerCols.push({title: '本期计量数量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 110, type: 'Number'});
         ledgerCols.push({title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 110, type: 'Number'});
         ledgerCols.push({title: '|小计', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 110, type: 'Number'});
-    } else if (materialQtySource === 2) {
+    } else if (qtySource === qtySourceValueConst.contract_qty) {
         ledgerCols.push({title: '本期计量数量|合同', colSpan: '1|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 110, type: 'Number'});
-    } else if (materialQtySource === 3) {
+    } else if (qtySource === qtySourceValueConst.gather_minus_qty) {
         ledgerCols.push({title: '本期计量数量|合同', colSpan: '4|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 110, type: 'Number'});
         ledgerCols.push({title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 110, type: 'Number'});
         ledgerCols.push({title: '|不计价', colSpan: '|1', rowSpan: '|1', field: 'qc_minus_qty', hAlign: 2, width: 110, type: 'Number'});
         ledgerCols.push({title: '|小计', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 110, type: 'Number', getValue: 'getValue.gather_qty'});
     }
-    ledgerCols.push({title: '本期完成金额', colSpan: '1', rowSpan: '2', field: 'gather_tp', hAlign: 2, width: 110, type: 'Number'});
+    ledgerCols.push({title: '本期完成金额', colSpan: '1', rowSpan: '2', field: 'gather_tp', hAlign: 2, width: 110, type: 'Number', getValue: 'getValue.gather_tp'});
     ledgerCols.push({title: '本期价差', colSpan: '1', rowSpan: '2', field: 'total_jiacha', hAlign:3, width: 110, type: 'Number'});
     const ledgerSpreadSetting = {
         // cols: [
@@ -211,10 +225,17 @@ $(document).ready(() => {
     const ledgerCol = {
         getValue: {
             gather_qty: function (data) {
-                if (materialQtySource === 3) {
+                if (qtySource === qtySourceValueConst.gather_minus_qty) {
                     return ZhCalc.add(data.gather_qty, data.qc_minus_qty);
                 }
             },
+            gather_tp: function (data) {
+                if (qtySource === qtySourceValueConst.contract_qty) {
+                    return data.contract_tp;
+                } else {
+                    return data.gather_tp;
+                }
+            },
         }
     }
     ledgerSpreadSetting.cols = ledgerCols;
@@ -228,7 +249,7 @@ $(document).ready(() => {
                 gcl.leafXmjs[index].jiacha = jiacha !== 0 ? ZhCalc.round(jiacha, materialDecimal.tp) : null;
             }
             const leafXmjs = gcl.leafXmjs.filter(item => {
-                return item.qc_qty || item.contract_qty
+                return item.qc_qty || item.contract_qty || item.qc_minus_qty;
             });
             console.log(leafXmjs);
             SpreadJsObj.loadSheetData(leafXmjSpread.getActiveSheet(), SpreadJsObj.DataType.Data, leafXmjs);
@@ -255,13 +276,13 @@ $(document).ready(() => {
         {title: '|分部工程', colSpan: '|1', rowSpan: '|1', field: 'fbgc', hAlign: 0, width: 100, formatter: '@'},
         {title: '|分项工程', colSpan: '|1', rowSpan: '|1', field: 'fxgc', hAlign: 0, width: 180, formatter: '@'},
     ];
-    if (materialQtySource === 1) {
+    if (qtySource === qtySourceValueConst.gather_qty) {
         leafXmjCols.push({title: '本期计量数量|合同', colSpan: '3|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'});
         leafXmjCols.push({title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'});
         leafXmjCols.push({title: '|小计', colSpan: '|1', rowSpan: '|1', field: 'gather_qty', hAlign: 2, width: 60, type: 'Number'});
-    } else if (materialQtySource === 2) {
+    } else if (qtySource === qtySourceValueConst.contract_qty) {
         leafXmjCols.push({title: '本期计量数量|合同', colSpan: '1|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 100, type: 'Number'});
-    } else if (materialQtySource === 3) {
+    } else if (qtySource === qtySourceValueConst.gather_minus_qty) {
         leafXmjCols.push({title: '本期计量数量|合同', colSpan: '4|1', rowSpan: '1|1', field: 'contract_qty', hAlign: 2, width: 60, type: 'Number'});
         leafXmjCols.push({title: '|数量变更', colSpan: '|1', rowSpan: '|1', field: 'qc_qty', hAlign: 2, width: 60, type: 'Number'});
         leafXmjCols.push({title: '|不计价', colSpan: '|1', rowSpan: '|1', field: 'qc_minus_qty', hAlign: 2, width: 60, type: 'Number'});
@@ -310,7 +331,7 @@ $(document).ready(() => {
     const leafXmjCol = {
         getValue: {
             gather_qty: function (data) {
-                if (materialQtySource === 3) {
+                if (qtySource === qtySourceValueConst.gather_minus_qty) {
                     return ZhCalc.add(data.gather_qty, data.qc_minus_qty);
                 }
             },
@@ -354,11 +375,13 @@ $(document).ready(() => {
                     updateBillsData(ms_id);
                     const newGclGatherListData = [];
                     for (const [index, s] of result.ledgerListData.entries()) {
+                        // gclGatherModel.clearGatherData();
                         gclGatherModel.loadLedgerData(_.cloneDeep(ledger), s);
                         gclGatherModel.loadPosData(_.cloneDeep(pos), result.posListData[index]);
                         const oneGclGatherData = gclGatherModel.gatherGclData().filter(item => {
                             return item.qc_qty || item.contract_qty || item.qc_minus_qty
                         });
+                        console.log(oneGclGatherData);
                         newGclGatherListData.push(oneGclGatherData);
                     }
                     gclGatherListData = newGclGatherListData;
@@ -835,7 +858,7 @@ $(document).ready(() => {
                 const color = notx === undefined ? '' : '#d6d8db';
                 // leafXmjSheet.getRange(iRow, -1, 1, -1).backColor(color);
                 const notx2 = findNotChangeLeafXmj(x);
-                const color2 = notx2 === undefined ? color : '#FFE699';
+                const color2 = notx2 === undefined || qtySource === qtySourceValueConst.contract_qty ? color : '#FFE699';
                 leafXmjSheet.getRange(iRow, -1, 1, -1).backColor(color2);
             }
         }
@@ -893,7 +916,7 @@ $(document).ready(() => {
                     qc_qty: xmj.qc_qty,
                     qc_minus_qty: xmj.qc_minus_qty,
                     gather_qty: xmj.gather_qty,
-                    is_join: notx === undefined ? 1 : 0,
+                    is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
                 };
                 if (ms_id) data.ms_id = ms_id;
                 datas.push(data);
@@ -920,7 +943,7 @@ $(document).ready(() => {
                                     qc_qty: xmj.qc_qty,
                                     qc_minus_qty: xmj.qc_minus_qty,
                                     gather_qty: xmj.gather_qty,
-                                    is_join: notx === undefined ? 1 : 0,
+                                    is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
                                     ms_id: ms.id,
                                 };
                                 datas.push(data);
@@ -967,7 +990,7 @@ $(document).ready(() => {
                 qc_qty: xmj.qc_qty,
                 qc_minus_qty: xmj.qc_minus_qty,
                 gather_qty: xmj.gather_qty,
-                is_join: notx === undefined ? 1 : 0,
+                is_join: notx !== undefined ? 0 : notx2 !== undefined ? 2 : 1,
             };
             console.log(data);
             postData(window.location.pathname + '/save', {type: 'add', postData: data, ms_id: $('#myTab').find('.active').data('msid') || null }, function (result) {
@@ -1000,7 +1023,7 @@ $(document).ready(() => {
             const data = {
                 type: type,
                 select: type === 'join' ? findNotJoinLeafXmj(select) : select,
-                gather_qty: select.gather_qty,
+                // gather_qty: select.gather_qty,
                 ms_id: $('#myTab').find('.active').data('msid') || null,
             };
             // 添加到
@@ -1027,7 +1050,7 @@ $(document).ready(() => {
             const data = {
                 type: type,
                 select: type === 'change' ? findNotChangeLeafXmj(select) : select,
-                gather_qty: type === 'change' ? select.gather_qty : select.contract_qty,
+                // gather_qty: type === 'change' ? select.gather_qty : select.contract_qty,
                 ms_id: $('#myTab').find('.active').data('msid') || null,
             };
             // 添加到
@@ -1065,6 +1088,9 @@ $(document).ready(() => {
                     for (const [index, ms] of materialStageData.entries()) {
                         const gclOther = _.find(gclGatherListData[index], { b_code: gclData.b_code, name: gclData.name, unit: gclData.unit, unit_price: gclData.unit_price });
                         let gather_qty = null;
+                        let contract_qty = null;
+                        let qc_qty = null;
+                        let qc_minus_qty = null;
                         if (gclOther) {
                             const leafXmjs = gclOther.leafXmjs.filter(item => {
                                 return item.qc_qty || item.contract_qty || item.qc_minus_qty
@@ -1072,12 +1098,23 @@ $(document).ready(() => {
                             const oneXmj = _.find(leafXmjs, function (item) {
                                 return item.gcl_id === select.gcl_id && item.id === select.id && (select.mx_id === undefined || item.mx_id === select.mx_id);
                             });
-                            if (oneXmj) gather_qty = oneXmj.gather_qty;
+                            if (oneXmj) {
+                                gather_qty = oneXmj.gather_qty;
+                                contract_qty = oneXmj.contract_qty;
+                                qc_qty = oneXmj.qc_qty;
+                                qc_minus_qty = oneXmj.qc_minus_qty ? oneXmj.qc_minus_qty : null;
+                            }
                         }
                         data.select.gather_qty['ms_id_' + ms.id] = gather_qty;
+                        data.select.contract_qty['ms_id_' + ms.id] = contract_qty;
+                        data.select.qc_qty['ms_id_' + ms.id] = qc_qty;
+                        data.select.qc_minus_qty['ms_id_' + ms.id] = qc_minus_qty;
                     }
                 } else {
                     data.select.gather_qty = select.gather_qty ? select.gather_qty : null;
+                    data.select.contract_qty = select.contract_qty ? select.contract_qty : null;
+                    data.select.qc_qty = select.qc_qty ? select.qc_qty : null;
+                    data.select.qc_minus_qty = select.qc_minus_qty ? select.qc_minus_qty : null;
                 }
             }
             console.log(data);
@@ -1166,12 +1203,15 @@ $(document).ready(() => {
                             if (!select || sel.rowCount !== 1) {
                                 return false;
                             }
+                            if (qtySource === qtySourceValueConst.contract_qty) {
+                                return false;
+                            }
                             const notx = findNotJoinLeafXmj(select);
                             if (!(!readOnly && select && notx === undefined)) {
                                 return false;
                             }
                             const notx2 = findNotChangeLeafXmj(select);
-                            if (!readOnly && select && notx2 === undefined && materialQtySource !== qtySourceValueConst.contract_qty) {
+                            if (!readOnly && select && notx2 === undefined) {
                                 return true;
                             } else {
                                 return false;
@@ -1191,6 +1231,9 @@ $(document).ready(() => {
                             if (!select || sel.rowCount !== 1) {
                                 return false;
                             }
+                            if (qtySource === qtySourceValueConst.contract_qty) {
+                                return false;
+                            }
                             const notx = findNotJoinLeafXmj(select);
                             if (!(!readOnly && select && notx === undefined)) {
                                 return false;

+ 121 - 0
app/public/js/shares/select_rela_tender.js

@@ -0,0 +1,121 @@
+class srObject {
+    constructor(setting) {
+        this.firstInit = true;
+        this.setting = setting;
+        const self = this;
+        this.selectTree = null;
+
+        const srSpreadSetting = {
+            cols: [
+                {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox'},
+                {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', cellType: 'tree'},
+                {title: '期数', field: 'lastStageOrder', hAlign: 1, width: 60, formatter: '@'},
+                {title: '审批状态', field: 'lastStageStatus', hAlign: 1, width: 60, formatter: '@'},
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            selectedBackColor: '#fffacd',
+            readOnly: true,
+        };
+        this.spread = SpreadJsObj.createNewSpread($('#sr-source-spread')[0]);
+        this.sheet = this.spread.getActiveSheet();
+        SpreadJsObj.initSheet(this.sheet, srSpreadSetting);
+
+        const srResultSpreadSetting = {
+            cols: [
+                {title: '名称', field: 'name', hAlign: 0, width: 180, formatter: '@', },
+            ],
+            emptyRows: 0,
+            headRows: 1,
+            headRowHeight: [32],
+            defaultRowHeight: 21,
+            headerFont: '12px 微软雅黑',
+            font: '12px 微软雅黑',
+            headColWidth: [30],
+            readOnly: true,
+        };
+        this.resultSpread = SpreadJsObj.createNewSpread($('#sr-result-spread')[0]);
+        this.resultSheet = this.resultSpread.getActiveSheet();
+        SpreadJsObj.initSheet(this.resultSheet, srResultSpreadSetting);
+
+        this.spread.bind(spreadNS.Events.ButtonClicked, function (e, info) {
+            if (!info.sheet.zh_setting) return;
+
+            const col = info.sheet.zh_setting.cols[info.col];
+            if (col.field !== 'selected') return;
+
+            const node = SpreadJsObj.getSelectObject(info.sheet);
+            self.selectNode(node, !node[col.field]);
+            SpreadJsObj.reloadColData(info.sheet, 0);
+            self.refreshResult();
+        });
+
+        $('#sr-select-all').click(function () {
+            if (!self.selectTree) return;
+            for (const n of self.selectTree.nodes) {
+                n.selected = this.checked;
+            }
+            SpreadJsObj.reloadColData(self.sheet, 0);
+            self.refreshResult();
+        });
+        $('#select-rela-ok').click(() => {
+            const rela = self.getSelects();
+            postData('/subproj/rela/save', { id: self.relaProject.id, rela_tender: rela.join(',') }, function () {
+                $(`[tree_id=${self.relaProject.id}]`)[0].setAttribute('rela-tender', rela.join(','));
+                $('#select-rela').modal('hide');
+            });
+        });
+
+        $('#select-rela').on('shown.bs.modal', () => {
+            self.init(self.setting.getRelaProject());
+        });
+    }
+    selectNode(node, select) {
+        const posterity = this.selectTree.getPosterity(node);
+        posterity.unshift(node);
+        for (const p of posterity) {
+            p.selected = select;
+        }
+    }
+    getSelects() {
+        const select = [];
+        for (const n of this.selectTree.nodes) {
+            if ((!n.children || n.children.length === 0) && n.selected) select.push(n.tid);
+        }
+        return select;
+    }
+    refreshResult() {
+        const result = [];
+        for (const n of this.selectTree.nodes) {
+            if ((!n.children || n.children.length === 0) && n.selected) result.push(n);
+        }
+        SpreadJsObj.loadSheetData(this.resultSheet, SpreadJsObj.DataType.Data, result);
+    }
+    init(relaProject) {
+        $('#sr-select-all')[0].checked = false;
+        const self = this;
+        if (this.firstInit) {
+            this.spread.refresh();
+            this.resultSpread.refresh();
+            this.firstInit = false;
+        }
+        self.relaProject = relaProject;
+        postData(`/subproj/rela?id=${relaProject.id}`, {}, tenders => {
+            const rela = relaProject.rela_tender ? relaProject.rela_tender.split(',') : [];
+            self.selectTree = Tender2Tree.convert(category, tenders, null, null, function (node, source) {
+                node.lastStageOrder = `第${source.lastStageOrder}期`;
+                node.lastStageStatus = source.lastStageStatus;
+            });
+            for (const node of self.selectTree.nodes) {
+                node.selected = rela.indexOf(node.tid + '') >= 0;
+            }
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.selectTree);
+            self.refreshResult();
+        });
+    }
+}

+ 3 - 3
app/public/js/stage_pay.js

@@ -417,10 +417,10 @@ $(document).ready(() => {
             }
             if (param.length === 0) return true;
             if (param.length > 1) {
-                if (param[0].value === '-') {
-                    param[1].value = '-' + param[1];
+                if (param[0].value === '-' && param[1].type === 'num') {
+                    param[1].value = '-' + param[1].value;
+                    param.shift();
                 }
-                param.unshift();
             }
             const iLen = param.length;
             let iLeftCount = 0, iRightCount = 0;

+ 9 - 1
app/public/js/sub_project.js

@@ -46,7 +46,7 @@ $(document).ready(function() {
                     html.push(`<td class="text-center"></td>`);
                 } else {
                     html.push(`<td class="text-center">${moment(node.create_time).format('YYYY-MM-DD')}</td>`);
-                    html.push(`<td class="text-center">${node.management || ''}<a class="ml-2" href="javascript: void(0)" name="set-management"><i class="fa fa-pencil-square-o "></i></a></td>`);
+                    html.push(`<td class="text-center">${node.management || ''}<a class="ml-2" href="javascript: void(0)" name="set-management"><i class="fa fa-pencil-square-o "></i></a>${node.management ? '<a class="ml-2" href="javascript: void(0)" name="refresh-management"><i class="fa fa-refresh "></i></a>': ''}</td>`);
                 }
                 // 操作
                 html.push(`<td>`);
@@ -235,6 +235,14 @@ $(document).ready(function() {
             $('#sm-management').attr('tree_id', treeId);
             $('#set-management').modal('show');
         });
+        $('body').on('click', 'a[name=refresh-management]', function(e) {
+            const treeId = $(this).parent().parent().attr('tree_id');
+            const node = ProjectTree.getItems(treeId);
+            if (node.is_folder) return;
+            postData('/subproj/refresh', { id: treeId }, function(result) {
+                toastr.success(`更新${result.um + result.dm + result.im}条用户权限`);
+            });
+        });
         $('body').on('click', '.fold-switch', function() {
             const id = this.getAttribute('id');
             const node = ProjectTree.getItems(id);

+ 8 - 0
app/router.js

@@ -16,6 +16,7 @@ module.exports = app => {
     const uncheckTenderCheck = app.middlewares.uncheckTenderCheck();
     // 期读取中间件
     const stageCheck = app.middlewares.stageCheck();
+    const settleCheck = app.middlewares.settleCheck();
     // 材料调差读取中间件
     const materialCheck = app.middlewares.materialCheck();
     // 第三方接口认证判断中间件
@@ -422,6 +423,12 @@ module.exports = app => {
 
     // 过程结算
     app.get('/tender/:id/settle', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.list');
+    app.post('/tender/:id/settle/auditors', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.loadAuditors');
+    app.post('/tender/:id/settle/add', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'settleController.add');
+    app.post('/tender/:id/settle/save', sessionAuth, tenderCheck, uncheckTenderCheck, 'settleController.save');
+    app.post('/tender/:id/settle/delete', sessionAuth, tenderCheck, uncheckTenderCheck, tenderBuildCheck, 'settleController.delete');
+    // 结算期
+    app.get('/tender/:id/settle/:sorder',sessionAuth, tenderCheck, uncheckTenderCheck, settleCheck, 'settleController.index');
 
     // 报表
     app.get('/tender/:id/report', sessionAuth, tenderCheck, uncheckTenderCheck, 'reportController.index');
@@ -726,6 +733,7 @@ module.exports = app => {
     app.post('/subproj/move', sessionAuth, projectManagerCheck, 'subProjController.move');
     app.post('/subproj/del', sessionAuth, projectManagerCheck, 'subProjController.del');
     app.post('/subproj/save', sessionAuth, projectManagerCheck, 'subProjController.save');
+    app.post('/subproj/refresh', sessionAuth, projectManagerCheck, 'subProjController.refresh');
     app.post('/subproj/rela/save', sessionAuth, 'subProjController.saveRela');
     app.post('/subproj/rela', sessionAuth, 'subProjController.rela');
     app.post('/subproj/member', sessionAuth, projectManagerCheck, 'subProjController.member');

+ 5 - 22
app/service/material_bills.js

@@ -605,12 +605,7 @@ module.exports = app => {
         async calcQuantityByMB(transaction, mid, mb, materialCalculator, decimal, pre_is_stage_self, qty_source) {
             const [newmsg_spread, newm_spread] = await this.getSpread(mb, null, decimal.up);
             if (mb.t_type === materialConst.t_type[0].value) {
-                const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(qty_source) + '*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
-                const sqlParam = [mid, mb.id];
-                const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                console.log(mb_quantity);
-                // 取历史期记录获取截止上期调差金额,并清空本期单价和时间,来源地,重新计算价差和有效价差
-                const newQuantity = this.ctx.helper.round(mb_quantity.quantity, decimal.qty);
+                const newQuantity = await this.ctx.service.materialList.getMbQuantity(transaction, mid, qty_source, decimal.qty, mb.id);
                 const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, newm_spread), decimal.tp);
                 const updateData = {
                     id: mb.id,
@@ -815,10 +810,7 @@ module.exports = app => {
                             // 通过管理重新算出quantity并保留小数位
                             if (mb.t_type === materialConst.t_type[0].value) {
                                 // 通过管理重新算出quantity并保留小数位
-                                const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(this.ctx.material.qty_source) + '*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
-                                const sqlParam = [this.ctx.material.id, mb.id, ms.id];
-                                const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                                const newQuantity = this.ctx.helper.round(mb_quantity.quantity, newDecimalQty);
+                                const newQuantity = await this.ctx.service.materialList.getMbQuantity(transaction, this.ctx.material.id, this.ctx.material.qty_source, newDecimalQty, mb.id, ms.id);
                                 if (newQuantity !== msb.quantity) {
                                     updateStageBillData.quantity = newQuantity;
                                     msb.quantity = newQuantity;
@@ -893,10 +885,7 @@ module.exports = app => {
                     if (newDecimalQty !== this.ctx.material.decimal.qty) {
                         // 通过管理重新算出quantity并保留小数位
                         if (mb.t_type === materialConst.t_type[0].value) {
-                            const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(this.ctx.material.qty_source) + '*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
-                            const sqlParam = [this.ctx.material.id, mb.id];
-                            const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, newDecimalQty);
+                            const newQuantity = await this.ctx.service.materialList.getMbQuantity(transaction, this.ctx.material.id, this.ctx.material.qty_source, newDecimalQty, mb.id);
                             mb.quantity = newQuantity;
                             updateData.quantity = newQuantity;
                         } else if (mb.t_type === materialConst.t_type[1].value) {
@@ -940,10 +929,7 @@ module.exports = app => {
                                 ms_id: ms.id,
                             });
                             // 通过管理重新算出quantity并保留小数位
-                            const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(newQtySource) + '*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
-                            const sqlParam = [this.ctx.material.id, mb.id, ms.id];
-                            const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+                            const newQuantity = await this.ctx.service.materialList.getMbQuantity(transaction, this.ctx.material.id, newQtySource, this.ctx.material.decimal.qty, mb.id, ms.id);
                             if (newQuantity !== msb.quantity) {
                                 const updateStageBillData = {
                                     id: msb.id,
@@ -964,10 +950,7 @@ module.exports = app => {
                         updateData.m_tax_tp = this.ctx.helper.round(tp.tax_total_price, this.ctx.material.decimal.tp);
                         updateList.push(updateData);
                     } else {
-                        const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(newQtySource) + '*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
-                        const sqlParam = [this.ctx.material.id, mb.id];
-                        const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                        const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+                        const newQuantity = await this.ctx.service.materialList.getMbQuantity(transaction, this.ctx.material.id, newQtySource, this.ctx.material.decimal.qty, mb.id);
                         if (newQuantity !== mb.quantity) {
                             mb.quantity = newQuantity;
                             updateData.quantity = newQuantity;

+ 18 - 21
app/service/material_list.js

@@ -223,12 +223,7 @@ module.exports = app => {
                 m_spread = msbInfo.m_spread;
                 updateId = msbInfo.id;
             }
-            const msSql = ms_id ? ' AND `ms_id` = ' + ms_id : '';
-            const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(this.ctx.material.qty_source) + '*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=?' + msSql + ' AND `is_join`=1';
-            const sqlParam = [this.ctx.material.id, mb_id];
-            const mb_quantity = await transaction.queryOne(sql, sqlParam);
-            console.log(mb_quantity);
-            const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+            const newQuantity = await this.getMbQuantity(transaction, this.ctx.material.id, this.ctx.material.qty_source, this.ctx.material.decimal.qty, mb_id, ms_id);
             const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, m_spread), this.ctx.material.decimal.tp);
             const updateData = {
                 id: updateId,
@@ -245,10 +240,7 @@ module.exports = app => {
                     const msbList = await transaction.select(this.ctx.service.materialStageBills.tableName, { where: { mid: this.ctx.material.id, mb_id } });
                     for (const msb of msbList) {
                         if (msb.ms_id !== parseInt(ms_id)) {
-                            const sql4 = 'SELECT SUM(' + this.ctx.helper.getQtySource(this.ctx.material.qty_source) + '*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
-                            const sqlParam4 = [this.ctx.material.id, mb_id, msb.ms_id];
-                            const mb_quantity4 = await transaction.queryOne(sql4, sqlParam4);
-                            const newQuantity4 = this.ctx.helper.round(mb_quantity4.quantity, this.ctx.material.decimal.qty);
+                            const newQuantity4 = await this.getMbQuantity(transaction, this.ctx.material.id, this.ctx.material.qty_source, this.ctx.material.decimal.qty, mb_id, msb.ms_id);
                             const newTp4 = this.ctx.helper.round(this.ctx.helper.mul(newQuantity4, msb.m_spread), this.ctx.material.decimal.tp);
                             const updateData4 = {
                                 id: msb.id,
@@ -325,10 +317,7 @@ module.exports = app => {
                     const updateMsIds = [];
                     const msbList = await transaction.select(this.ctx.service.materialStageBills.tableName, { where: { mid: this.ctx.material.id, mb_id: mbInfo.id } });
                     for (const msb of msbList) {
-                        const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(this.ctx.material.qty_source) + '*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `ms_id`=? AND `is_join`=1';
-                        const sqlParam = [this.ctx.material.id, mbInfo.id, msb.ms_id];
-                        const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                        const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+                        const newQuantity = await this.getMbQuantity(transaction, this.ctx.material.id, this.ctx.material.qty_source, this.ctx.material.decimal.qty, mbInfo.id, msb.ms_id);
                         const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, msb.m_spread), this.ctx.material.decimal.tp);
                         const updateData = {
                             id: msb.id,
@@ -356,11 +345,7 @@ module.exports = app => {
                     };
                     await transaction.update(this.ctx.service.materialBills.tableName, updateBillsData);
                 } else {
-                    const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(this.ctx.material.qty_source) + '*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=? AND `is_join`=1';
-                    const sqlParam = [this.ctx.material.id, mbInfo.id];
-                    const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                    console.log(mb_quantity);
-                    const newQuantity = this.ctx.helper.round(mb_quantity.quantity, this.ctx.material.decimal.qty);
+                    const newQuantity = await this.getMbQuantity(transaction, this.ctx.material.id, this.ctx.material.qty_source, this.ctx.material.decimal.qty, mbInfo.id);
                     const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, mbInfo.m_spread), this.ctx.material.decimal.tp);
                     const updateData = {
                         id: mbInfo.id,
@@ -499,7 +484,7 @@ module.exports = app => {
                         gather_qty: ml.gather_qty,
                         quantity: ml.quantity ? ml.quantity : 0,
                         expr: ml.expr ? ml.expr : '',
-                        is_join: is_join ? 0 : 1,
+                        is_join: is_join ? 0 : is_change ? 2 : 1,
                         in_time: new Date(),
                     };
                     if (ml.sid) {
@@ -529,7 +514,7 @@ module.exports = app => {
                         gather_qty: ml.gather_qty,
                         quantity: ml.quantity ? ml.quantity : 0,
                         expr: ml.expr ? ml.expr : '',
-                        is_join: is_join ? 0 : 1,
+                        is_join: is_join ? 0 : is_change ? 2 : 1,
                         is_self: 1,
                         in_time: new Date(),
                     };
@@ -847,6 +832,18 @@ module.exports = app => {
                 throw err;
             }
         }
+
+        async getMbQuantity(transaction, mid, qty_source, qty_decimal, mb_id, ms_id = null, needRound = 1) {
+            const msSql = ms_id ? ' AND `ms_id` = ' + ms_id : '';
+            const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(qty_source) + '*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=?' + msSql + ' AND `is_join`=1';
+            const sqlParam = [mid, mb_id];
+            const mb_quantity = await transaction.queryOne(sql, sqlParam);
+            const sql2 = 'SELECT SUM(' + this.ctx.helper.getQtySource(qty_source, 1) + '*`quantity`) as quantity FROM ' + this.tableName + ' WHERE `mid`=? AND `mb_id`=?' + msSql + ' AND `is_join`=2';
+            const sqlParam2 = [mid, mb_id];
+            const mb_quantity2 = await transaction.queryOne(sql2, sqlParam2);
+            const newQuantity = this.ctx.helper.add(mb_quantity.quantity, mb_quantity2.quantity);
+            return needRound ? this.ctx.helper.round(newQuantity, qty_decimal) : newQuantity;
+        }
     }
     return MaterialList;
 };

+ 9 - 11
app/service/material_list_notjoin.js

@@ -26,7 +26,7 @@ module.exports = app => {
          * 添加不参与调差的清单
          * @return {void}
          */
-        async add(data, gather_qty, ms_id = null) {
+        async add(data, ms_id = null) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
@@ -52,7 +52,7 @@ module.exports = app => {
                 });
                 data.xmj_id = data.id;
                 data.mx_id = data.mx_id ? data.mx_id : '';
-                await this.updateAllMaterials(transaction, data, 'add', gather_qty, ms_id);
+                await this.updateAllMaterials(transaction, data, 'add', ms_id);
                 // 新增不参与调差清单
                 const result = await transaction.insert(this.tableName, newListNotJoin);
                 if (result.affectedRows === 0) {
@@ -70,7 +70,7 @@ module.exports = app => {
          * 添加数量变更不计量的清单
          * @return {void}
          */
-        async addChange(data, gather_qty, ms_id = null) {
+        async addChange(data, ms_id = null) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
@@ -87,7 +87,7 @@ module.exports = app => {
                 };
                 data.xmj_id = data.id;
                 data.mx_id = data.mx_id ? data.mx_id : '';
-                await this.updateAllMaterials(transaction, data, 'change', gather_qty, ms_id);
+                await this.updateAllMaterials(transaction, data, 'change', ms_id);
                 // 新增不参与调差清单
                 const result = await transaction.insert(this.tableName, newListNotChange);
                 if (result.affectedRows === 0) {
@@ -106,14 +106,14 @@ module.exports = app => {
          * @param {int} id 工料id
          * @return {void}
          */
-        async del(id, gather_qty, ms_id = null) {
+        async del(id, ms_id = null) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
             const transaction = await this.db.beginTransaction();
             try {
                 const notJoinInfo = await this.getDataById(id);
-                await this.updateAllMaterials(transaction, notJoinInfo, 'del', gather_qty, ms_id);
+                await this.updateAllMaterials(transaction, notJoinInfo, 'del', ms_id);
                 // 判断是否可删
                 const result = await transaction.delete(this.tableName, { id });
                 await transaction.commit();
@@ -129,7 +129,7 @@ module.exports = app => {
          * @param transaction
          * @returns {Promise<void>}
          */
-        async updateAllMaterials(transaction, data, type, gather_qty = null, ms_id = null) {
+        async updateAllMaterials(transaction, data, type, ms_id = null) {
             // 先判断material_list是否存在值并quantity不为null
             const searchSql = {
                 mid: this.ctx.material.id,
@@ -152,8 +152,7 @@ module.exports = app => {
                 for (const ml of materialListData) {
                     const updateData = {
                         id: ml.id,
-                        gather_qty,
-                        is_join: type === 'add' ? 0 : 1,
+                        is_join: type === 'add' ? 0 : type === 'change' ? 2 : 1,
                     };
                     if (mbIdList.indexOf(ml.mb_id) === -1) {
                         mbIdList.push(ml.mb_id);
@@ -203,11 +202,10 @@ module.exports = app => {
                 gcl_id: datas.gcl_id,
                 xmj_id: datas.xmj_id,
                 mx_id: datas.mx_id,
-                type: 1,
             };
             const info = await transaction.get(this.tableName, searchData);
             if (info) {
-                return 0;
+                return info.type === 1 ? 0 : info.type === 2 ? 2 : 0;
             }
             return 1;
         }

+ 12 - 3
app/service/material_list_self.js

@@ -71,14 +71,17 @@ module.exports = app => {
          * @param {int} id 工料id
          * @return {void}
          */
-        async del(id, ms_id = null, gather_qty = null) {
+        async del(id, ms_id = null, qtys = null) {
             if (!this.ctx.tender || !this.ctx.material) {
                 throw '数据错误';
             }
             const transaction = await this.db.beginTransaction();
             try {
                 const selfInfo = await this.getDataById(id);
-                selfInfo.gather_qty = gather_qty;
+                selfInfo.contract_qty = qtys.contract_qty ? qtys.contract_qty : null;
+                selfInfo.qc_qty = qtys.gather_qty ? qtys.qc_qty : null;
+                selfInfo.qc_minus_qty = qtys.qc_minus_qty ? qtys.qc_minus_qty : null;
+                selfInfo.gather_qty = qtys.gather_qty ? qtys.gather_qty : null;
                 await this.updateAllMaterials(transaction, selfInfo, ms_id);
                 // 判断是否可删
                 const result = await transaction.delete(this.tableName, { id });
@@ -135,7 +138,7 @@ module.exports = app => {
                     // const oneML = this._.filter(materialListData, { ms_id: ms.id });
                     // const gather_qty = oneML[0].gather_qty;
                     // const is_join = oneML[0].is_join;
-                    if (data.gather_qty['ms_id_' + ms.id] !== null) {
+                    if (data.contract_qty['ms_id_' + ms.id] !== null || data.qc_qty['ms_id_' + ms.id] !== null || data.qc_minus_qty['ms_id_' + ms.id] !== null || data.gather_qty['ms_id_' + ms.id] !== null) {
                         for (const m of materialListGclData) {
                             insertList.push({
                                 tid: this.ctx.tender.id,
@@ -146,6 +149,9 @@ module.exports = app => {
                                 xmj_id: data.xmj_id,
                                 mx_id: data.mx_id,
                                 ms_id: ms.id,
+                                contract_qty: data.contract_qty['ms_id_' + ms.id],
+                                qc_qty: data.qc_qty['ms_id_' + ms.id],
+                                qc_minus_qty: data.qc_minus_qty['ms_id_' + ms.id],
                                 gather_qty: data.gather_qty['ms_id_' + ms.id],
                                 quantity: m.quantity,
                                 expr: m.expr,
@@ -168,6 +174,9 @@ module.exports = app => {
                         gcl_id: data.gcl_id,
                         xmj_id: data.xmj_id,
                         mx_id: data.mx_id,
+                        contract_qty: data.contract_qty,
+                        qc_qty: data.qc_qty,
+                        qc_minus_qty: data.qc_minus_qty,
                         gather_qty: data.gather_qty,
                         quantity: m.quantity,
                         expr: m.expr,

+ 3 - 8
app/service/material_stage_bills.js

@@ -223,13 +223,8 @@ module.exports = app => {
         async calcQuantityByMB(transaction, mid, mb, msb, materialCalculator, decimal, pre_is_stage_self, qty_source) {
             const [newmsg_spread, newm_spread] = await this.getSpread(mb, null, decimal.up);
             if (mb.t_type === materialConst.t_type[0].value) {
-                const sql = 'SELECT SUM(' + this.ctx.helper.getQtySource(qty_source) + '*`quantity`) as quantity FROM ' + this.ctx.service.materialList.tableName + ' WHERE `mid`=? AND `ms_id`=? AND `mb_id`=? AND `is_join`=1';
-                const sqlParam = [mid, msb.ms_id, mb.id];
-                const mb_quantity = await transaction.queryOne(sql, sqlParam);
-                console.log(mb_quantity);
-                // 取历史期记录获取截止上期调差金额,并清空本期单价和时间,来源地,重新计算价差和有效价差
-                const newQuantity = this.ctx.helper.round(mb_quantity.quantity, decimal.qty);
-                const newTp = this.ctx.helper.round(this.ctx.helper.mul(newQuantity, newm_spread), decimal.tp);
+                const newQuantity = await this.ctx.service.materialList.getMbQuantity(transaction, mid, qty_source, decimal.qty, mb.id, null, 0);
+                const newTp = this.ctx.helper.round(this.ctx.helper.mul(this.ctx.helper.round(newQuantity, decimal.qty), newm_spread), decimal.tp);
                 msb.quantity = newQuantity;
                 msb.msg_spread = newmsg_spread;
                 msb.m_spread = newm_spread;
@@ -249,7 +244,7 @@ module.exports = app => {
                 //     tax_pre_tp: mb.m_tax_tp !== null ? this.ctx.helper.add(mb.tax_pre_tp, mb.m_tax_tp) : mb.tax_pre_tp,
                 // };
                 // await transaction.update(this.tableName, updateData);
-                const m_tp = mb.is_summary === 1 ? await this.ctx.helper.round(this.ctx.helper.mul(mb_quantity.quantity, newm_spread), decimal.tp) : 0;
+                const m_tp = mb.is_summary === 1 ? await this.ctx.helper.round(this.ctx.helper.mul(newQuantity, newm_spread), decimal.tp) : 0;
                 const m_tax_tp = this.ctx.helper.round(this.ctx.helper.mul(m_tp, (1 + this.ctx.helper.div(mb.m_tax, 100))), decimal.tp);
                 return [m_tp, m_tax_tp];
             } else if (mb.t_type === materialConst.t_type[1].value) {

+ 317 - 0
app/service/settle.js

@@ -0,0 +1,317 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const auditConst = require('../const/audit');
+const projectLogConst = require('../const/project_log');
+
+module.exports = app => {
+    class Settle extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'settle';
+        }
+
+        /**
+         * 获取标段下的全部结算期,按倒序
+         * @param tenderId
+         * @return {Promise<void>}
+         */
+        async getValidSettles(tenderId) {
+            const settles = await this.db.select(this.tableName, {
+                where: { tid: tenderId },
+                orders: [['settle_order', 'desc']],
+            });
+            if (settles.length !== 0 && !this.ctx.session.sessionUser.is_admin) {
+                const last = settles[0];
+                if (last.status === auditConst.settle.status.uncheck && !this.ctx.tender.isTourist) {
+                    if (last.user_id !== this.ctx.session.sessionUser.accountId) {
+                        settles.splice(0, 1);
+                    }
+                }
+            }
+            return settles;
+        }
+
+        /**
+         * 新增结算期
+         * @param tenderId - 标段id
+         * @param date - 结算年月
+         * @param period - 结算周期
+         * @return {Promise<void>}
+         */
+        async addSettle(tenderId, date, period) {
+            const settles = await this.getAllDataByCondition({
+                where: { tid: tenderId },
+                order: ['order'],
+            });
+            const pre = settles[settles.length - 1];
+            if (settles.length > 0 && pre.status !== auditConst.settle.status.checked) {
+                throw '上一期未审批通过,请等待上一期审批通过后,再新增数据';
+            }
+            const checkedStage = await this.ctx.service.stage.getLastestCompleteStage(tenderId);
+            if (!checkedStage) throw '不存在审批通过的计量期,请先进行期计量,再结算';
+
+            const newSettle = {
+                tid: tenderId,
+                add_sid: checkedStage.order, add_sorder: checkedStage.order,
+                user_id: this.ctx.session.sessionUser.accountId,
+                settle_order: settles.length + 1, settle_time: date, settle_period: period,
+                audit_times: 1, audit_status: auditConst.settle.status.uncheck,
+            };
+            if (pre) {
+                newSettle.pre_contract_tp = this.ctx.helper.add(pre.pre_contract_tp, pre.contract_tp);
+                newSettle.pre_positive_qc_tp = this.ctx.helper.add(pre.pre_positive_qc_tp, pre.positive_qc_tp);
+                newSettle.pre_negative_qc_tp = this.ctx.helper.add(pre.pre_negative_qc_tp, pre.negative_qc_tp);
+                newSettle.pre_qc_tp = this.ctx.helper.add(pre.pre_qc_tp, pre.qc_tp);
+                newSettle.pre_tp = this.ctx.helper.add(pre.tp, pre.pre_tp);
+            }
+            const transaction = await this.db.beginTransaction();
+            try {
+                // 新增期记录
+                const result = await transaction.insert(this.tableName, newSettle);
+                if (result.affectedRows === 1) {
+                    newSettle.id = result.insertId;
+                } else {
+                    throw '新增期数据失败';
+                }
+                await this.ctx.service.settleAudit.copyPreAuditors(transaction, pre, newSettle);
+                // 存在上一期时
+                if (pre) {
+                    // todo 复制上一期其他数据
+                }
+                await transaction.commit();
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+
+            return newSettle;
+        }
+
+        /**
+         * 编辑结算期
+         *
+         * @param {Number} tenderId - 标段Id
+         * @param {Number} order - 第N期
+         * @param {String} date - 结算年月
+         * @param {String} period - 结算周期
+         * @return {Promise<void>}
+         */
+        async saveSettle(tenderId, order, date, period) {
+            await this.db.update(this.tableName, {
+                settle_time: date,
+                settle_period: period,
+            }, { where: { tid: tenderId, settle_order: order } });
+        }
+
+        /**
+         * 删除结算期
+         * @param id
+         * @returns {Promise<boolean>}
+         */
+        async deleteSettle(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                const settleInfo = await this.getDataById(id);
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.settleAudit.tableName, { sid: id });
+                // await transaction.delete(this.ctx.service.settleAuditAss.tableName, { sid: id });
+                // 记录删除日志
+                await this.ctx.service.projectLog.addProjectLog(transaction, projectLogConst.type.settle, projectLogConst.status.delete, `第${settleInfo.order}期`);
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                await transaction.rollback();
+                throw err;
+            }
+        }
+
+        async loadRelaUser(settle) {
+            const status = auditConst.settle.status;
+            const accountId = this.ctx.session.sessionUser.accountId;
+
+            settle.user = await this.ctx.service.projectAccount.getAccountInfoById(settle.user_id);
+            settle.auditors = await this.ctx.service.settleAudit.getAuditors(settle.id, settle.audit_times); // 全部参与的审批人
+            settle.auditorIds = this._.map(settle.auditors, 'aid');
+            settle.curAuditors = settle.auditors.filter(x => { return x.status === status.checking; }); // 当前流程中审批中的审批人
+            settle.curAuditorIds = this._.map(settle.curAuditors, 'aid');
+            settle.flowAuditors = settle.curAuditors.length > 0 ? settle.auditors.filter(x => { return x.order === settle.curAuditors[0].order; }) : []; // 当前流程中参与的审批人(包含会签时,审批通过的人)
+            settle.flowAuditorIds = this._.map(settle.flowAuditors, 'aid');
+            settle.auditorGroups = this.ctx.helper.groupAuditors(settle.auditors);
+            settle.userGroups = this.ctx.helper.groupAuditorsUniq(settle.auditorGroups);
+            settle.finalAuditorIds = settle.userGroups[settle.userGroups.length - 1].map(x => { return x.aid; });
+
+            // 协作相关
+            settle.assists = await this.service.settleAuditAss.getData(settle); // 全部协同人
+            settle.assists = settle.assists.filter(x => {
+                return x.user_id === settle.user_id || settle.auditorIds.indexOf(x.user_id) >= 0;
+            }); // 过滤无效协同人
+            settle.userAssists = settle.assists.filter(x => { return x.user_id === settle.user_id; }); // 原报协同人
+            settle.userAssistIds = this._.map(settle.userAssists, 'ass_user_id');
+            settle.auditAssists = settle.assists.filter(x => { return x.user_id !== settle.user_id; }); // 审批协同人
+            settle.auditAssistIds = this._.map(settle.auditAssists, 'ass_user_id');
+            settle.curAssists = settle.assists.filter(x => { return settle.curAuditorIds.indexOf(x.user_id) >= 0; }); // 当前审批人的协同人
+            settle.curAssistsIds = this._.map(settle.curAssists, 'ass_user_id');
+            settle.relaAssists = settle.assists.filter(x => { return x.user_id === accountId }); // 登录人的协同人
+            // 当前参与人Id
+            settle.userIds = settle.status === settle.uncheck // 当前流程下全部参与人id
+                ? [settle.user_id, ...settle.userAssistIds]
+                : [settle.user_id, ...settle.userAssistIds, ...settle.auditorIds, ...settle.auditAssistIds];
+        }
+
+        async loadAuditViewData(settle) {
+            const times = settle.audit_status === auditConst.settle.status.checkNo ? settle.audit_times - 1 : settle.audit_times;
+
+            if (!settle.user) settle.user = await this.ctx.service.projectAccount.getAccountInfoById(settle.user_id);
+            settle.auditHistory = await this.ctx.service.settleAudit.getAuditorHistory(settle.id, times);
+            // 获取审批流程中左边列表
+            if (settle.status === auditConst.settle.status.checkNo && settle.user_id !== this.ctx.session.sessionUser.accountId) {
+                const auditors = await this.ctx.service.settleAudit.getAuditors(settle.id, settle.audit_times - 1); // 全部参与的审批人
+                const auditorGroups = this.ctx.helper.groupAuditors(auditors);
+                settle.hisUserGroup = this.ctx.helper.groupAuditorsUniq(auditorGroups);
+            } else {
+                settle.hisUserGroup = settle.userGroups;
+            }
+        }
+
+        /**
+         * cancancel = 0 不可撤回
+         * cancancel = 1 原报撤回
+         * cancancel = 2 审批人撤回 审批通过
+         * cancancel = 3 审批人撤回 审批退回上一人
+         * cancancel = 4 审批人撤回 退回原报
+         * cancancel = 5 会签未全部审批通过时,审批人撤回 审批通过
+         *
+         * @param settle
+         * @returns {Promise<void>}
+         */
+        async _doCheckSettleCanCancel(settle) {
+            // 默认不可撤回
+            settle.cancancel = 0;
+            // 获取当前审批人的上一个审批人,判断是否是当前登录人,并赋予撤回功能,(当审批人存在有审批过时,上一人不允许再撤回)
+            const status = auditConst.settle.status;
+            if (settle.status === status.checked || settle.status === status.uncheck) return;
+
+            const accountId = this.ctx.session.sessionUser.accountId;
+            if (settle.status !== status.checkNo) {
+                // 找出当前操作人上一个审批人,包括审批完成的和退回上一个审批人的,同时当前操作人为第一人时,就是则为原报
+                if (settle.flowAuditors.find(x => { return x.status !== status.checking}) && settle.flowAuditorIds.indexOf(accountId) < 0) return; // 当前流程存在审批人审批通过时,不可撤回
+                const flowAssists = settle.auditAssists.filter(x => { return settle.flowAuditorIds.indexOf(x.user_id) >= 0; });
+                if (flowAssists.find(x => { return x.confirm; })) return; //当前流程存在协审人确认时,不可撤回
+                if (settle.curAuditorIds.indexOf(accountId) < 0 && settle.flowAuditorIds.indexOf(accountId) >= 0) {
+                    settle.cancancel = 5; // 会签未全部审批通过时,审批人撤回审批通过
+                    return;
+                }
+
+                const preAuditors = settle.curAuditors[0].order !== 1 ? settle.auditors.filter(x => { return x.order === settle.curAuditors[0].order - 1; }) : [];
+                const preAuditorCheckAgain = preAuditors.find(pa => { return pa.status === status.checkAgain; });
+                const preAuditorCheckCancel = preAuditors.find(pa => { return pa.status === status.checkCancel; });
+                const preAuditorHasOld = preAuditors.find(pa => { return pa.is_old === 1; });
+                const preAuditorIds = (preAuditorCheckAgain ? [] : preAuditors.map(x => { return x.aid })); // 重审不可撤回
+                if ((this._.isEqual(settle.flowAuditorIds, preAuditorIds) && preAuditorCheckCancel) || preAuditorHasOld) {
+                    return; // 不可以多次撤回
+                }
+
+                const preAuditChecked = preAuditors.find(pa => { return pa.status === status.checked && pa.aid === accountId; });
+                const preAuditCheckNoPre = preAuditors.find(pa => { return pa.status === status.checkNoPre && pa.aid === accountId; });
+                if (preAuditorIds.indexOf(accountId) >= 0) {
+                    if (preAuditChecked) {
+                        settle.cancancel = 2;// 审批人撤回审批通过
+                    } else if (preAuditCheckNoPre) {
+                        settle.cancancel = 3;// 审批人撤回审批退回上一人
+                    }
+                    settle.preAuditors = preAuditors;
+                } else if (preAuditors.length === 0 && accountId === settle.user_id) {
+                    settle.cancancel = 1;// 原报撤回
+                }
+            } else {
+                const lastAuditors = await this.service.settleAudit.getAuditors(settle.id, settle.times - 1);
+                const onAuditor = _.findLast(lastAuditors, { status: status.checkNo });
+                if (onAuditor.aid === accountId) {
+                    settle.cancancel = 4;// 审批人撤回退回原报
+                    settle.preAuditors = lastAuditors.filter(x => { return x.order === onAuditor.order });
+                }
+            }
+        }
+        async _doCheckSettleReadOnly(settle) {
+            const status = auditConst.settle.status;
+            // 校验权限(参与人、分享、游客)
+            const accountId = this.session.sessionUser.accountId;
+            const shareIds = [];
+            // 是否只读
+            if (settle.status === status.uncheck || settle.status === status.checkNo) {
+                settle.readOnly = accountId !== settle.user_id && settle.userAssistIds.indexOf(accountId) < 0;
+            } else {
+                settle.readOnly = true;
+            }
+            // 读取数据相关
+            settle.curTimes = settle.status === status.checkNo && settle.readOnly ? settle.times - 1 : settle.times;
+            // 协作人相关
+
+            if (settle.status === status.uncheck) {
+                if (!settle.readOnly) {
+                    settle.assist = settle.userAssists.find(x => { return x.ass_user_id === accountId; });
+                }
+                settle.curTimes = settle.times;
+            } else if (settle.status === status.checkNo) {
+                if (!settle.readOnly) {
+                    settle.assist = settle.userAssists.find(x => { return x.ass_user_id === accountId; });
+                    settle.curTimes = settle.times;
+                }
+            } else {
+                const ass = settle.auditAssists.find(x => { return settle.flowAuditorIds.indexOf(x.user_id) >= 0 && x.ass_user_id === accountId; });
+                if (!settle.readOnly) {
+                    settle.assist = ass;
+                }
+                if (!settle.readOnly) {
+                    settle.readOnly = !_.isEqual(settle.flowAuditorIds, settle.curAuditorIds);
+                    settle.canCheck = true;
+                }
+            }
+            if (settle.readOnly) {
+                settle.assist = accountId === settle.user_id || settle.auditorIds.indexOf(accountId) >= 0 ? null : settle.auditAssists.find(x => { return x.ass_user_id === accountId});
+            }
+
+            // 上传文件权限
+            const permission = this.session.sessionUser.permission;
+            if (settle.userIds.indexOf(accountId) >= 0 || this.session.sessionUser.is_admin) {
+                settle.filePermission = true;
+            } else {
+                if (shareIds.indexOf(accountId) !== -1 || (permission !== null && permission.tender !== undefined && permission.tender.indexOf('2') !== -1)) {// 分享人
+                    if (settle.status === status.uncheck) throw '您无权查看该数据';
+                    settle.filePermission = false;
+                } else if (this.tender.isTourist || this.session.sessionUser.is_admin) {
+                    settle.filePermission = this.tender.touristPermission.file || settle.auditorIds.indexOf(accountId) !== -1;
+                } else {
+                    throw '您无权查看该数据';
+                }
+            }
+        }
+        async doCheckSettle(settle) {
+            // 读取原报、审核人等参与人数据
+            await this.loadRelaUser(settle);
+            // 是否台账修订中
+            const lastRevise = await this.service.ledgerRevise.getLastestRevise(this.tender.id);
+            settle.revising = (lastRevise && lastRevise.status !== auditConst.revise.status.checked) || false;
+            // 是否只读等权限
+            await this._doCheckSettleReadOnly(settle);
+            // 可否撤回,是哪一种撤回
+            await this._doCheckSettleCanCancel(settle);
+        }
+    }
+
+    return Settle;
+};

+ 128 - 0
app/service/settle_audit.js

@@ -0,0 +1,128 @@
+'use strict';
+
+/**
+ * 与期不同,含原报
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const auditConst = require('../const/audit');
+
+module.exports = app => {
+    class SettleAudit extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'settle_audit';
+        }
+
+        async getAuditors(settleId, times) {
+            return await this.getAllDataByCondition({ where: { settle_id: settleId, audit_times: times } }); // 全部参与的审批人
+        }
+
+        async getAuditorGroup(settleId, times) {
+            const auditors = await this.getAuditors(settleId, times); // 全部参与的审批人
+            return this.ctx.helper.groupAuditors(auditors, 'active_order');
+        }
+
+        async getUniqAuditors(settle) {
+            const auditors = await this.getAuditors(settle.id, settle.audit_times); // 全部参与的审批人
+            const result = [];
+            auditors.forEach(x => {
+                if (result.findIndex(r => { return x.aid === r.aid && x.audit_order === x.audit_order; }) < 0) {
+                    result.push(x);
+                }
+            });
+            return result;
+        }
+
+        async getUniqAuditorsGroup(settleId, times) {
+            const group = await this.getAuditorGroup(settleId, times);
+            return this.ctx.helper.groupAuditorsUniq(group);
+        }
+
+        async getAuditorHistory(settleId, times, reverse = false) {
+            const history = [];
+            if (times >= 1) {
+                for (let i = 1; i <= times; i++) {
+                    const auditors = await this.getAuditors(settleId, i);
+                    const group = this.ctx.helper.groupAuditors(auditors);
+                    const historyGroup = [];
+                    const max_order = group.length > 0 && group[group.length - 1].length > 0 ? group[group.length - 1][0].audit_order : -1;
+                    for (const g of group) {
+                        const his = {
+                            auditYear: '', auditDate: '', auditTime: '', audit_time: null,
+                            audit_type: g[0].audit_type, audit_order: g[0].audit_order,
+                            auditors: g
+                        };
+                        if (his.audit_type === auditType.key.common) {
+                            his.name = g[0].name;
+                        } else {
+                            his.name = this.ctx.helper.transFormToChinese(his.audit_order) + '审';
+                        }
+                        his.is_final = his.audit_order === max_order;
+                        let audit_time;
+                        g.forEach(x => {
+                            if (x.status === auditConst.settle.status.checkSkip) return;
+                            if (!his.status || x.status === auditConst.settle.status.checking) his.audit_status = x.audit_status;
+                            if (x.audit_time && (!audit_time || x.audit_time > audit_time)) {
+                                audit_time = x.audit_time;
+                                if (his.status !== auditConst.settle.status.checking) his.audit_status = x.audit_status;
+                            }
+                        });
+                        if (audit_time) {
+                            his.audit_time = audit_time;
+                            const auditTime = this.ctx.moment(audit_time);
+                            his.auditYear = auditTime.format('YYYY');
+                            his.auditDate = auditTime.format('MM-DD');
+                            his.auditTime = auditTime.format('HH:mm:ss');
+                        }
+                        historyGroup.push(his);
+                    }
+                    if (reverse) {
+                        history.push(historyGroup.reverse());
+                    } else {
+                        history.push(historyGroup);
+                    }
+                }
+            }
+            return history;
+        }
+
+        async copyPreAuditors(transaction, preSettle, newSettle) {
+            const auditors = preSettle ? await this.getUniqAuditors(preSettle) : [];
+            const newAuditors = [];
+            // 添加原报
+            const user = await this.ctx.service.projectAccount.getDataById(this.ctx.session.sessionUser.accountId);
+            newAuditors.push({
+                tid: newSettle.tid, settle_id: newSettle.id,
+                audit_id: this.ctx.session.sessionUser.accountId,
+                audit_times: 1, audit_order: 0, audit_type: auditConst.auditType.key.common,
+                active_order: 0, audit_status: auditConst.settle.status.uncheck,
+                name: user.name, company: user.company, role: user.role, mobile: user.mobile,
+            });
+            // 添加其他参与人
+            for (const a of auditors) {
+                newAuditors.push({
+                    tid: newSettle.tid, settle_id: newSettle.id,
+                    audit_id: a.id,
+                    audit_times: 1, audit_order: a.audit_order, audit_type: a.audit_type,
+                    active_order: a.audit_order, audit_status: auditConst.auditConst.settle.status.uncheck,
+                    name: a.name, company: a.company, role: a.role, mobile: a.mobile,
+                });
+            }
+            const result = await transaction.insert(this.tableName, newAuditors);
+            if (result.affectedRows !== newAuditors.length) throw '初始化审批流程错误';
+        }
+
+    }
+
+    return SettleAudit;
+};

+ 29 - 0
app/service/sub_project.js

@@ -393,6 +393,35 @@ module.exports = app => {
                 throw error;
             }
         }
+
+        async refreshManagementPermission(data) {
+            const subProject = await this.getDataById(data.id);
+
+            const users = await this.ctx.service.projectAccount.getAllDataByCondition({ where: { project_id: subProject.project_id, company: subProject.management }});
+            const orgMember = await this.ctx.service.subProjPermission.getAllDataByCondition({ where: { spid: subProject.id } });
+            const dm = [], um = [], im = [];
+            const filing_type = this.ctx.service.filing.allFilingType.join(','), file_permission = '1,2';
+            for (const u of users) {
+                const nm = orgMember.find(x => { return u.id === x.uid; });
+                if (nm) {
+                    if (!nm.file_permission) um.push({ id: nm.id, file_permission, filing_type });
+                } else {
+                    im.push({ id: this.uuid.v4(), spid: subProject.id, pid: subProject.project_id, uid: u.id, file_permission, filing_type });
+                }
+            }
+            const conn = await this.db.beginTransaction();
+            try {
+                if (dm.length > 0) await conn.delete(this.ctx.service.subProjPermission.tableName, { id: dm });
+                if (um.length > 0) await conn.updateRows(this.ctx.service.subProjPermission.tableName, um);
+                if (im.length > 0) await conn.insert(this.ctx.service.subProjPermission.tableName, im);
+
+                await conn.commit();
+                return { dm: dm.length, um: um.length, im: im.length };
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
     }
 
     return SubProject;

+ 1 - 22
app/view/budget/list_modal.ejs

@@ -20,25 +20,4 @@
         </div>
     </div>
 </div>
-<!--弹出关联标段-->
-<div class="modal fade" id="select-rela" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择关联标段</h5>
-            </div>
-            <div class="modal-body">
-                <h5>可选标段</h5>
-                <div id="sr-spread" style="height: 300px"></div>
-            </div>
-            <div class="modal-footer">
-                <div class="form-check form-check-inline">
-                    <input class="form-check-input" type="checkbox" id="sr-select-all">
-                    <label class="form-check-label" for="sr-select-all">全选</label>
-                </div>
-                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                <button type="button" class="btn btn-sm btn-primary" id="select-rela-ok">确定</button>
-            </div>
-        </div>
-    </div>
-</div>
+<% include ../shares/select_rela_tender_modal.ejs %>

+ 1 - 22
app/view/file/modal.ejs

@@ -1,22 +1 @@
-<!--弹出关联标段-->
-<div class="modal fade" id="select-rela" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择关联标段</h5>
-            </div>
-            <div class="modal-body">
-                <h5>可选标段</h5>
-                <div id="sr-spread" style="height: 300px"></div>
-            </div>
-            <div class="modal-footer">
-                <div class="form-check form-check-inline">
-                    <input class="form-check-input" type="checkbox" id="sr-select-all">
-                    <label class="form-check-label" for="sr-select-all">全选</label>
-                </div>
-                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
-                <button type="button" class="btn btn-sm btn-primary" id="select-rela-ok">确定</button>
-            </div>
-        </div>
-    </div>
-</div>
+<% include ../shares/select_rela_tender_modal.ejs %>

+ 1 - 1
app/view/material/list.ejs

@@ -109,7 +109,7 @@
     const stage_order = <%- material.order %>;
     const materialID = <%- material.id %>;
     const tenderID = <%- tender.id %>;
-    const materialQtySource = <%- material.qty_source %>;
+    const qtySource = <%- material.qty_source %>;
     const qtySourceValueConst = JSON.parse(unescape('<%- escape(JSON.stringify(qtySourceValueConst)) %>'));
     const materialIsNewQty = parseInt('<%- material.is_new_qty %>');
     const materialDecimal = JSON.parse(unescape('<%- escape(JSON.stringify(material.decimal)) %>'));

+ 112 - 0
app/view/settle/list.ejs

@@ -0,0 +1,112 @@
+<% include ../tender/tender_sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
+            <h2>
+                结算期列表
+            </h2>
+            <div class="ml-auto">
+                <% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && (settles.length === 0 || settles[0].status === auditConst.status.checked) && checkedStageCount > 0) { %>
+                <a href="#add-qi" data-toggle="modal" data-target="#add-qi" class="btn btn-primary btn-sm">开始新一期</a>
+                <% } %>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <table class="table table-bordered table-hover">
+                    <thead>
+                    <tr>
+                        <th class="text-center" width="70px">结算期数</th>
+                        <th class="text-center" width="70px">结算月份</th>
+                        <th class="text-center" width="70px">结算周期</th>
+                        <th class="text-center" width="100px" name="contract_tp">本期合同结算</th>
+                        <th class="text-center" width="100px" name="qc_tp">本期变更结算</th>
+                        <th class="text-center" width="100px" name="tp">本期完成结算</th>
+                        <th class="text-center" width="100px" name="pre_tp">截止上期完成结算</th>
+                        <th class="text-center" width="100px" name="end_tp">截止本期完成结算</th>
+                        <th class="text-center" width="200px">审批进度</th>
+                        <th class="text-center" width="90px">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    <% for (const [i,s] of settles.entries()) { %>
+                    <tr>
+                        <td>
+                            <a href="<%- '/tender/' + ctx.tender.id + '/settle/' + s.order %>" target="_blank">第 <%- s.order %> 期</a>
+                            <% if ((i === 0 || (settles[i-1] && settles[i-1].status !== auditConst.status.checked)) && s.user_id === ctx.session.sessionUser.accountId) { %>
+                            <a href="#edit" class="edit-stage" data-index="<%- i %>" data-toggle="modal" data-target="#edit"><i class="fa fa-pencil-square-o "></i></a>
+                            <% } %>
+                        </td>
+                        <td class="text-center"><%- s.s_time %></td>
+                        <td class="text-center">
+                            <span data-toggle="tooltip" data-placement="bottom" data-original-title="<%- (s.period ? s.period : '') %>">
+                                <%- (s.period ? (s.period.split('~')[1] ? s.period.split('~')[1] : s.period)  : '') %>
+                            </span>
+                        </td>
+                        <% if (ctx.tender.info.display.thousandth) { %>
+                        <td class="text-right"><%- (s.contract_tp ? ctx.helper.formatNum(s.contract_tp, '#,##0.######') : '')%></td>
+                        <td class="text-right"><%- (s.qc_tp ? ctx.helper.formatNum(s.qc_tp, '#,##0.######') : '')%></td>
+                        <td class="text-right"><%- (s.tp ? ctx.helper.formatNum(s.tp, '#,##0.######') : '')%></td>
+                        <td class="text-right"><%- (s.pre_tp ? ctx.helper.formatNum(s.pre_tp, '#,##0.######') : '')%></td>
+                        <td class="text-right"><%- (s.end_tp ? ctx.helper.formatNum(s.end_tp, '#,##0.######') : '')%></td>
+                        <% } else { %>
+                        <td class="text-right"><%- (s.contract_tp ? s.contract_tp : '')%></td>
+                        <td class="text-right"><%- (s.qc_tp ? s.qc_tp : '')%></td>
+                        <td class="text-right"><%- (s.tp ? s.tp : '')%></td>
+                        <td class="text-right"><%- (s.pre_tp ? s.pre_tp : '')%></td>
+                        <td class="text-right"><%- (s.end_tp ? s.end_tp : '')%></td>
+                        <% } %>
+                        <td class="<%- auditConst.auditProgressClass[s.status] %>">
+                            <% if (s.curAuditors.length > 0) { %>
+                            <% if (s.curAuditors[0].audit_type === auditType.key.common) { %>
+                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- s.curAuditors[0].name %><%if (s.curAuditors[0].role !== '' && s.curAuditors[0].role !== null) { %>-<%- s.curAuditors[0].role %><% } %></a>
+                            <% } else { %>
+                            <a href="#sp-list" data-toggle="modal" data-target="#sp-list" s-order="<%- s.order %>"><%- ctx.helper.transFormToChinese(s.curAuditors[0].audit_order) + '审' %></a>
+                            <% } %>
+                            <% } %>
+                            <%- auditConst.auditProgress[s.status] %>
+                        </td>
+                        <td class="text-center">
+                            <% if (s.status === auditConst.status.uncheck && s.user_id === ctx.session.sessionUser.accountId) { %>
+                            <a href="<%- '/tender/' + ctx.tender.id + '/settle/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <% } else if (s.status === auditConst.status.checkNo && s.curAuditors && s.user_id === ctx.session.sessionUser.accountId) { %>
+                            <a href="<%- '/tender/' + ctx.tender.id + '/settle/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <% } else if (s.status === auditConst.status.checking && s.curAuditors && s.curAuditors.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
+                            <a href="<%- '/tender/' + ctx.tender.id + '/settle/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <% } else if (s.status === auditConst.status.checkNoPre && s.curAuditors && s.curAuditor2.findIndex(x => { return x.aid === ctx.session.sessionUser.accountId; }) >= 0) { %>
+                            <a href="<%- '/tender/' + ctx.tender.id + '/settle/' + s.order %>" target="_blank" class="btn <%- auditConst.statusButtonClass[s.status] %> btn-sm"><%- auditConst.statusButton[s.status] %></a>
+                            <% } else { %>
+                            <span class="<%- auditConst.auditStringClass[s.status] %>"><%- auditConst.auditString[s.status] %></span>
+                            <% } %>
+                        </td>
+                    </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/sub_menu.js"></script>
+<script>
+    const settles = JSON.parse('<%- JSON.stringify(settles) %>');
+    $.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();
+        }
+    });
+</script>

+ 301 - 0
app/view/settle/list_modal.ejs

@@ -0,0 +1,301 @@
+<% if (ctx.session.sessionUser.accountId === ctx.tender.data.user_id && ctx.tender.data.ledger_status === auditConst.status.checked &&
+        (settles.length === 0 || settles[settles.length- 1].status === auditConst.status.checked)) { %>
+<!--弹出添加期-->
+<div class="modal fade" id="add-qi" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="/tender/<%- ctx.tender.id %>/settle/add" method="post" onsubmit="return checkValidSettle(this);">
+            <div class="modal-header">
+                <h5 class="modal-title">添加新一期</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>期</label>
+                    <input class="form-control form-control-sm" value="第 <%- settles.length + 1 %> 期" type="text" readonly="">
+                </div>
+                <div class="form-group">
+                    <label>结算年月<b class="text-danger">*</b></label>
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" readonly placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" name="date" autocomplete="off">
+                </div>
+                <div class="form-group">
+                    <label>结算周期<b class="text-danger">*</b></label>
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" readonly placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text" name="period" autocomplete="off">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-primary btn-sm" id="add-stage-btn">确定添加</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
+<!--审批流程/结果-->
+<div class="modal fade" id="sp-list" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">审批流程</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-4 modal-height-500" style="overflow: auto">
+                        <div class="card mt-3">
+                            <ul class="list-group list-group-flush" id="auditor-list">
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="col-8 modal-height-500" style="overflow: auto" id="audit-list">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% if (settles.length > 0 && settles[0].user_id === ctx.session.sessionUser.accountId) { %>
+<!--设置-->
+<div class="modal fade" id="edit" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <form class="modal-content" action="/tender/<%- ctx.tender.id %>/settle/save" method="post" onsubmit="return checkValidSettle(this);">
+            <div class="modal-header">
+                <h5 class="modal-title">期编辑</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>期</label>
+                    <input class="form-control form-control-sm" id="edit-name" value="第 <%- stages[0].order %> 期" type="text" readonly="" name="name">
+                </div>
+                <div class="form-group">
+                    <label>结算年月<b class="text-danger">*</b></label>
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" readonly name="date" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text">
+                </div>
+                <div class="form-group">
+                    <label>结算周期<b class="text-danger">*</b></label>
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" readonly name="period" placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                <input type="hidden" name="order" id="edit-order" value="<%- stages[0].order %>">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="submit" class="btn btn-primary btn-sm" id="edit-ok" >确定修改</button>
+            </div>
+        </form>
+    </div>
+</div>
+<% } %>
+<script src="/public/js/datepicker/datepicker.min.js"></script>
+<script src="/public/js/datepicker/datepicker.zh.js"></script>
+<script src="/public/js/moment/moment.min.js"></script>
+<script>
+    const tenderId = '<%- ctx.tender.id %>';
+    const auditConst = JSON.parse('<%- JSON.stringify(auditConst) %>');
+    const auditType = JSON.parse('<%- JSON.stringify(auditType) %>');
+    $('.datepicker-here').datepicker({
+        autoClose: true,
+    });
+
+    $('.edit-stage').on('click', function () {
+        const index = parseInt($(this).data('index'));
+        const editDate = $('#edit-date').datepicker().data('datepicker');
+        $('#edit-name').val('第 ' + stages[index].order + ' 期');
+        $('#edit-order').val(stages[index].order);
+        if (stages[index].s_time && stages[index].s_time !== '') {
+            editDate.selectDate(new Date(stages[index].s_time));
+        }
+        const period = [];
+        for (const p of stages[index].period.split('~')) {
+            if (p && p !== '') {
+                period.push(new Date(p));
+            }
+        }
+        const editPeriod = $('#edit-period').datepicker().data('datepicker');
+        if (period.length > 0) {
+            editPeriod.selectDate(period);
+        }
+    });
+
+    function checkValidSettle(obj) {
+        const date = $('input[name=date]', obj).val();
+        if (date === '') {
+            toastr.error('请选择结算年月');
+            return false;
+        }
+        const period = $('input[name=period]', obj).val();
+        if (period === '') {
+            toastr.error('请选择结算周期');
+            return false;
+        }
+
+        const startDate = period.split('~')[0];
+        const endDate = period.split('~')[1] ? period.split('~')[1] : null;
+
+        if ((startDate.indexOf(date) === -1 && !endDate) || (startDate.indexOf(date) === -1 && endDate && endDate.indexOf(date) === -1)) {
+            toastr.error('所选日期与当前月份不匹配,请重新选择');
+            $('input[name="period"]', obj).parents('.form-group').find('.text-danger').remove();
+            $('input[name="period"]', obj).parents('.form-group').append('<small class="text-danger">所选日期与当前月份不匹配,请重新选择</small>');
+            return false;
+        }
+        return true;
+    }
+
+    $('#audit-list').on('click', 'a', function() {
+        const type = $(this).data('target')
+        const auditCard = $(this).parent().parent()
+        if (type === 'show') {
+            $(this).data('target', 'hide')
+            auditCard.find('.fold-card').slideDown('swing', () => {
+                auditCard.find('#end-target').text($(this).data('idx') + '#')
+                auditCard.find('#fold-btn').text('收起历史审核记录')
+            })
+        } else {
+            $(this).data('target', 'show')
+            auditCard.find('.fold-card').slideUp('swing', () => {
+                auditCard.find('#end-target').text('1#')
+                auditCard.find('#fold-btn').text('展开历史审核记录')
+            })
+        }
+    });
+
+    const getGroupAuditHtml = function (group) {
+        return group.map(u => { return `<small class="d-inline-block text-dark mx-1" title="${u.role}" data-auditorId="${u.aid}">${u.name}</small>`; }).join('');
+    };
+    const getAuditTypeHtml = function (type) {
+        if (type === auditType.key.common) return '';
+        return `<div class="li-subscript"><span class="badge badge-pill badge-${auditType.info[type].class} p-1 badge-bg-small"><small>${auditType.info[type].short}</small></span></div>`;
+    };
+    const getAuditTypeText = function (type) {
+        if (type === auditType.key.common) return '';
+        return `<span class="text-${auditType.info[type].class}">${auditType.info[type].long}</span>`;
+    };
+    const getAuditorsHtml = function (auditors) {
+        const auditorsHTML = [];
+        auditors.forEach((group, idx) => {
+            if (idx === 0) {
+                auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                    <span class="mr-1"><i class="fa fa fa-play-circle fa-rotate-90"></i></span>
+                <span class="text-muted">${getGroupAuditHtml(group)}</span>
+                <span class="badge badge-light badge-pill ml-auto"><small>原报</small></span>
+                </li>`);
+            } else if(idx === auditors.length -1 && idx !== 0) {
+                auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                    <span class="mr-1"><i class="fa fa fa-stop-circle fa-rotate-90"></i></span>
+                <span class="text-muted">${getGroupAuditHtml(group)}</span>
+                <div class="d-flex ml-auto">
+                ${getAuditTypeHtml(group[0].audit_type)}
+                <span class="badge badge-light badge-pill ml-auto"><small>终审</small></span>
+                </div>
+                </li>`);
+            } else {
+                auditorsHTML.push(`<li class="list-group-item d-flex justify-content-between align-items-center">
+                    <span class="mr-1"><i class="fa fa fa-chevron-circle-down"></i></span>
+                <span class="text-muted">${getGroupAuditHtml(group)}</span>
+                <div class="d-flex ml-auto">
+                ${getAuditTypeHtml(group[0].audit_type)}
+                <span class="badge badge-light badge-pill"><small>${transFormToChinese(idx)}审</small></span>
+                </div>
+                </li>`);
+            }
+        });
+        return auditorsHTML;
+    }
+    const getAuditHistroyHtml = function (auditHistory) {
+        const historyHTML = [];
+        auditHistory.forEach((his, idx) => {
+            if (idx === auditHistory.length - 1 && auditHistory.length !== 1) {
+                historyHTML.push(`<div class="text-right"><a href="javascript: void(0);" id="fold-btn" data-target="show">展开历史审批流程</a></div>`);
+            }
+            historyHTML.push(`<div class="${idx < auditHistory.length - 1 ? 'fold-card' : ''}">`);
+            historyHTML.push(`<div class="text-center text-muted">${idx+1}#</div>`);
+            historyHTML.push(`<ul class="timeline-list list-unstyled mt-2 ${ idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'last-auditor-list' : '' }">`);
+            his.forEach((group) => {
+                historyHTML.push(`<li class="timeline-list-item pb-2 ${ group.status === auditConst.status.uncheck && idx === auditHistory.length - 1 && auditHistory.length !== 1 ? 'is_uncheck' : ''}">`);
+                if (group.auditYear) {
+                    historyHTML.push(`<div class="timeline-item-date">${group.auditYear}<span>${group.auditDate}</span><span>${group.auditTime}</span></div>`);
+                }
+                if (group.audit_order < his.length - 1) {
+                    historyHTML.push('<div class="timeline-item-tail"></div>');
+                }
+                if (group.status === auditConst.status.checked) {
+                    historyHTML.push('<div class="timeline-item-icon bg-success text-light"><i class="fa fa-check"></i></div>');
+                } else if (group.status === auditConst.status.checkNo || group.status === auditConst.status.checkNoPre || group.status === auditConst.status.checkCancel) {
+                    historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-level-up"></i></div>');
+                } else if (group.status === auditConst.status.checking) {
+                    historyHTML.push('<div class="timeline-item-icon bg-warning text-light"><i class="fa fa-ellipsis-h"></i></div>');
+                } else {
+                    historyHTML.push('<div class="timeline-item-icon bg-secondary text-light"></div>');
+                }
+
+                historyHTML.push('<div class="timeline-item-content">');
+                if (group.audit_order > 0) {
+                    const statuStr = group.status !== auditConst.status.uncheck ?
+                        `<span class="pull-right ${auditConst.statusClass[group.status]}">${auditConst.statusString[group.status]}</span>` : '';
+                    historyHTML.push(`<div class="py-1">
+                        <span class="text-black-50">
+                        ${ !group.is_final ? group.audit_order + '' : '终' }审 ${getAuditTypeText(group.audit_type)}
+                        </span>
+                        ${statuStr}
+                    </div>`);
+                } else {
+                    historyHTML.push(` <div class="py-1">
+                        <span class="text-black-50">原报</span>
+                        <span class="pull-right text-success">${idx !== 0 ? '重新' : '' }上报审批</span>
+                    </div>`);
+                }
+                historyHTML.push('<div class="card"><div class="card-body px-3 py-0">');
+                for (const [i, auditor] of group.auditors.entries()) {
+                    historyHTML.push(`<div class="card-text p-2 py-3 row ${ ( i > 0 ? 'border-top' : '') }">`);
+                    historyHTML.push(`<div class="col"><span class="h6">${auditor.name}</span><span class="text-muted ml-1">${auditor.role}</span></div>`);
+                    historyHTML.push('<div class="col">');
+                    if (auditor.status === auditConst.status.checked) {
+                        historyHTML.push('<span class="pull-right text-success"><i class="fa fa-check-circle"></i></span>');
+                    } if (auditor.status === auditConst.status.checkNo || auditor.status === auditConst.status.checkNoPre || auditor.status === auditConst.status.checkCancel) {
+                        historyHTML.push('<span class="pull-right text-warning"><i class="fa fa-share-square fa-rotate-270"></i></span>');
+                    }
+                    historyHTML.push('</div>');
+                    if (group.audit_order > 0 && auditor.opinion) {
+                        historyHTML.push(`<div class="col-12 py-1 bg-light"><i class="fa fa-commenting-o mr-1"></i>${auditor.opinion}</div>`);
+                    }
+                    historyHTML.push('</div>');
+                }
+                historyHTML.push('</div></div>');
+                historyHTML.push('</div>');
+                historyHTML.push('</li>');
+            });
+            historyHTML.push('</div>');
+            historyHTML.push('</ul>');
+        });
+        return historyHTML.join('');
+    }
+    // 获取审批流程
+    $('a[data-target="#sp-list" ]').on('click', function () {
+        postData('/tender/' + tenderId + '/settle/auditors', { order: $(this).attr('s-order') }, function (result) {
+            $('#auditor-list').html(getAuditorsHtml(result.hisUserGroup));
+            $('#audit-list').html(getAuditHistroyHtml(result.auditHistory));
+        });
+    });
+
+    $(window).resize(checkTableWidth);
+    function checkTableWidth() {
+        if($('table th[name=contract_tp]').outerWidth() < 107) {
+            $('table th[name=contract_tp]').html('本期<br>合同计量');
+            $('table th[name=qc_tp]').html('本期数量<br>变更计量');
+            $('table th[name=tp]').html('本期<br>完成计量');
+            $('table th[name=pre_tp]').html('截止上期<br>完成计量');
+            $('table th[name=end_tp]').html('截止本期<br>完成计量');
+        } else {
+            $('table th[name=contract_tp]').html('本期合同计量');
+            $('table th[name=qc_tp]').html('本期数量变更计量');
+            $('table th[name=tp]').html('本期完成计量');
+            $('table th[name=pre_tp]').html('截止上期完成计量');
+            $('table th[name=end_tp]').html('截止本期完成计量');
+        }
+    }
+
+    $(function () {
+        checkTableWidth();
+    });
+</script>

+ 30 - 0
app/view/shares/select_rela_tender_modal.ejs

@@ -0,0 +1,30 @@
+<!--弹出关联标段-->
+<div class="modal fade" id="select-rela" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择关联标段</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-7">
+                        <h5>可选标段 </h5>
+                        <div class="modal-height-300" id="sr-source-spread"></div>
+                    </div>
+                    <div class="col-5">
+                        <h5>已选标段 </h5>
+                        <div class="modal-height-300" id="sr-result-spread"></div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <div class="form-check form-check-inline">
+                    <input class="form-check-input" type="checkbox" id="sr-select-all">
+                    <label class="form-check-label" for="sr-select-all">全选</label>
+                </div>
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="select-rela-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 2 - 0
config/web.js

@@ -1088,6 +1088,7 @@ const JsFiles = {
                     '/public/js/shares/tenders2tree.js',
                     '/public/js/shares/show_level.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/select_rela_tender.js',
                     '/public/js/file_list.js',
                 ],
                 mergeFile: 'file_list',
@@ -1120,6 +1121,7 @@ const JsFiles = {
                     '/public/js/shares/tenders2tree.js',
                     '/public/js/shares/show_level.js',
                     '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/shares/select_rela_tender.js',
                     '/public/js/budget_list.js',
                 ],
                 mergeFile: 'budget_list',