浏览代码

1. 更新管理单元账号权限
2. 结算部分代码

MaiXinRong 1 年之前
父节点
当前提交
8aec0df615

+ 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 = {

+ 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;

+ 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`);
+        }
+    };
+};

+ 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');

+ 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;