Prechádzať zdrojové kódy

feature: 添加了对项目组成员、项目审核组成员增删改的权限控制

eyes4 6 mesiacov pred
rodič
commit
5048dacc20

+ 6 - 1
pmr-biz-manager/src/bpmn/flow_engine.ts

@@ -43,7 +43,7 @@ export class FlowEngine extends EventEmitter {
     private readonly _model_id: string;
     private readonly _prj_id: string;
     private readonly _task_id: string | undefined;
-    private readonly _owner: string;
+    private _owner: string;
     private readonly _args: IFlowArgs;
     private execution;
     private readonly _source: string;
@@ -115,6 +115,11 @@ export class FlowEngine extends EventEmitter {
         return this._model_id;
     }
 
+    /// 项目负责人变动后,流程的负责人也相应变动
+    set owner(value: string) {
+        this._owner = value;
+    }
+
     private new_engine(source: string, args: any): Engine {
         let self = this;
         // @ts-ignore

+ 0 - 1
pmr-biz-manager/src/routes/api/prj/info/add.ts

@@ -91,7 +91,6 @@ function add(json: IRequest, params: IMethodParams, cached_data: ICachedData): P
             if (!bpmn) throw Resp.gen_err(Resp.InternalServerError, '项目流程模型数据缺失!');
             let flow = new FlowEngine(bpmn.id, bpmn.content, {prj_id: id, owner: cached_data.user_id}, bpmn_flow_on_end);
             await flow.run();
-            // await PrjInfo.update({flow_case_id: flow.id}, {where: {id: id}, transaction: t});
             await BpmnCase.create({
                 id: flow.id,
                 model_id: bpmn.id,

+ 55 - 6
pmr-biz-manager/src/routes/api/prj/info/modify.ts

@@ -13,7 +13,12 @@ import {start_plan_alt_flow, update_parent_task_info} from "@src/utils/plan_task
 import {PrjFile} from "@core-models/PrjFile";
 import {BizContractInfo} from "@core-models/BizContractInfo";
 import {IdGen} from "@util/IdGen";
-import {is_project_modifiable} from "@src/utils/prj_premission_helper";
+import {is_project_modifiable, is_project_privileged_account} from "@src/utils/prj_premission_helper";
+import {AcsUserInfo} from "@core-models/AcsUserInfo";
+import {BpmnCase} from "@core-models/BpmnCase";
+import {BpmnWork} from "@core-models/BpmnWork";
+import {FlowEngine} from "@src/bpmn/flow_engine";
+import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 
 interface IData {
     /**
@@ -61,12 +66,30 @@ interface IData {
 function permission_guard(content: IRequest, cached_data: ICachedData): Promise<void> {
     return new Promise<void>(async (resolve, reject) => {
         let data = <IData>content.data;
-        let prj = await PrjInfo.findOne({where: {id: data.id},  raw: true});
+        let prj = await PrjInfo.findOne({where: {id: data.id}, raw: true});
         if (!prj)
             return reject(Resp.gen_err(Resp.ResourceNotFound, '项目不存在。'));
+        let phase = await PrjPhaseDefine.findOne({where: {id: prj.phase_id}, raw: true});
+        if (!phase) return reject(Resp.gen_err(Resp.ResourceNotFound, '项目状态信息出错,id: ' + prj.id));
+        if (phase.order_index >= 20 ) {
+            if (data.type_id !== undefined) {
+                return reject(Resp.gen_err(Resp.Forbidden, '项目立项后不允许修改项目类型。'))
+            }
+            if (!await is_project_privileged_account(cached_data.user_id)) {
+                if (data.deliver_at)
+                    return reject(Resp.gen_err(Resp.Forbidden, '项目已立项,您没有权限修改项目交付时间,请确认您是特权人员。'));
+            }
+
+        }
 
+        /// 只有项目特权人员才可以修改项目负责人
+        if (data.leader_id && data.leader_id !== prj.leader_id) {
+            if (!await is_project_privileged_account(cached_data.user_id)) {
+                return  reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改项目负责人,请确认您是特权人员。'));
+            }
+        }
         if (!await is_project_modifiable(cached_data.user_id, prj.id))
-            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改该项目,请确认您是项目负责人或特权人员。'));
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改该项目信息,请确认您是项目负责人或特权人员。'));
         if (data.deliver_at) {
             data.deliver_at += ' 23:59:59';
         }
@@ -74,7 +97,7 @@ function permission_guard(content: IRequest, cached_data: ICachedData): Promise<
         if (task) {
             return reject(Resp.gen_err(Resp.Forbidden, '项目交付时间不能早于项目计划中任务的结束时间。'));
         }
-        task =  await PrjPlanTaskDraft.findOne({where: {prj_id: prj.id, end_at: {[Op.gt]: data.deliver_at}}, raw: true});
+        task = await PrjPlanTaskDraft.findOne({where: {prj_id: prj.id, end_at: {[Op.gt]: data.deliver_at}}, raw: true});
         if (task) {
             return reject(Resp.gen_err(Resp.Forbidden, '项目交付时间不能早于项目计划草稿中任务的结束时间。'));
         }
@@ -115,6 +138,28 @@ function modify(content: IRequest, params: IMethodParams, cached_data: ICachedDa
             if (!prj) {
                 throw Resp.gen_err(Resp.ResourceNotFound, '项目不存在.');
             }
+
+            // 修改了项目负责人,这个问题就严重了,要改很多东西
+            // 要将该项目的项目流程全部移交给新负责人,
+            // 涉及bpmn case和bpmn work中与项目相关部分, 以及flow实例中的owner
+            // task相关的work不变
+            if (data.leader_id && data.leader_id !== prj.leader_id) {
+                let new_leader = await AcsUserInfo.findOne({where: {id: data.leader_id}, raw: true, transaction: t});
+                if (!new_leader) {
+                    throw Resp.gen_err(Resp.ResourceNotFound, '新选中的项目负责人不存在.');
+                }
+                let [count, flow_cases] = await BpmnCase.update({creator_id: data.leader_id},
+                    {where: {prj_id: prj.id, task_id: {[Op.is]: null}, completed_at: {[Op.is]: null}}, transaction: t, returning: true});
+                await BpmnWork.update({assigned_to: data.leader_id},
+                    {where: {prj_id: prj.id, task_id: {[Op.is]: null}, status: {[Op.ne]: 2}}, transaction: t});
+                if (flow_cases && flow_cases.length > 0) {
+                    for (let flow_case of flow_cases) {
+                        let engine = FlowEngine.case_engine_map.get(flow_case.id);
+                        if (engine) engine.owner = data.leader_id;
+                    }
+                }
+            }
+
             let contract_id = prj.contract_id;
             // 修改合同id,需要在项目文档中移除原合同附件,移入新合同附件
             Logger.trace(`new contract_id: ${data.contract_id} old contract id: ${contract_id}`);
@@ -124,7 +169,11 @@ function modify(content: IRequest, params: IMethodParams, cached_data: ICachedDa
                     await PrjFile.destroy({where: {prj_id: data.id, category_id: 'contract'}, transaction: t});
                 }
                 /// 将新合同附件移入项目文档
-                let contract = await BizContractInfo.findOne({where: {id: data.contract_id}, raw: true, transaction: t});
+                let contract = await BizContractInfo.findOne({
+                    where: {id: data.contract_id},
+                    raw: true,
+                    transaction: t
+                });
                 if (!contract) throw Resp.gen_err(Resp.ResourceNotFound, '合同不存在.');
                 Logger.trace(contract);
                 if (contract.doc_uploaded) {
@@ -285,7 +334,7 @@ const v1_0: IApiProcessor = {
         model: PrjInfo,
         where: get_where,
         fixed_input_data: {
-          updated_at: dayjs().format('YYYY-MM-DD HH:mm:ss')
+            updated_at: dayjs().format('YYYY-MM-DD HH:mm:ss')
         },
         input_data_map: {
             name: "name",

+ 52 - 10
pmr-biz-manager/src/routes/api/prj/info/overview.ts

@@ -14,6 +14,11 @@ import {AcsRole} from "@core-models/AcsRole";
 import {BpmnWork} from "@core-models/BpmnWork";
 import {BpmnCase} from "@core-models/BpmnCase";
 import {is_project_accessible} from "@src/utils/prj_premission_helper";
+import {BpmnModel} from "@core-models/BpmnModel";
+import {FlowEngine} from "@src/bpmn/flow_engine";
+import {bpmn_flow_on_end} from "@src/utils/bpmn_work_helper";
+import dayjs from "dayjs";
+
 interface IData {
     id: string;
 }
@@ -93,27 +98,64 @@ async function overview(json: IRequest, params: IMethodParams, cached_data: ICac
             customer_bizman.id, customer_bizman.name
     `;
     let replacements = {prj_id: data.id}
-    let result: any = await PrjInfo.sequelize!.query(sql, {type: QueryTypes.SELECT, replacements: replacements, raw: true});
+    let result: any = await PrjInfo.sequelize!.query(sql, {
+        type: QueryTypes.SELECT,
+        replacements: replacements,
+        raw: true
+    });
     if (!result || !result[0]) throw Resp.gen_err(Resp.ResourceNotFound);
     result = DataCURD.filter_null(result[0]);
     // let members = await PrjMembers.findAll({where: {prj_id: data.id}, raw: true});
     // if (!members) members = [];
 
-    sql = `
-                select member.member_id as account_id, userinfo.name as name,
-                       member.role_id, role.name as role_name,
-                       member.memo
-                from ${PrjMembers.table_name} member
-                left join ${AcsUserInfo.table_name} userinfo on member.member_id = userinfo.id 
-                left join ${AcsRole.table_name} role on member.role_id = role.id
-                where prj_id = :prj_id and draft = false
+    sql = `select member.member_id as account_id, userinfo.name as name,
+               member.role_id, role.name as role_name,
+               member.memo
+        from ${PrjMembers.table_name} member
+        left join ${AcsUserInfo.table_name} userinfo on member.member_id = userinfo.id 
+        left join ${AcsRole.table_name} role on member.role_id = role.id
+        where prj_id = :prj_id and draft = false
             `
     replacements = {prj_id: data.id};
-    let members = await PrjMembers.sequelize!.query(sql, {type: QueryTypes.SELECT, replacements: replacements, raw: true});
+    let members = await PrjMembers.sequelize!.query(sql, {
+        type: QueryTypes.SELECT,
+        replacements: replacements,
+        raw: true
+    });
     if (!members) members = [];
     members = DataCURD.filter_null(members);
     result.members = members;
 
+    /// 如果创建项目时,流程没有正确启动,则在这里补上(通常不会发生)
+    let flow = await BpmnCase.findOne({where: {prj_id: data.id, model_id: 'prj'}, raw: true});
+    if (!flow) {
+        let t = await PrjInfo.sequelize!.transaction();
+        try {
+            // 启动工作流
+            let bpmn = await BpmnModel.findOne({where: {id: 'prj'}, transaction: t});
+            if (!bpmn) throw Resp.gen_err(Resp.InternalServerError, '项目流程模型数据缺失!');
+            let flow = new FlowEngine(bpmn.id, bpmn.content, {
+                prj_id: data.id,
+                owner: result.leader_id
+            }, bpmn_flow_on_end);
+            await flow.run();
+            await PrjInfo.update({flow_case_id: flow.id}, {where: {id: data.id}, transaction: t});
+            await BpmnCase.create({
+                id: flow.id,
+                model_id: bpmn.id,
+                model: bpmn.content,
+                started_at: dayjs(),
+                creator_id: result.leader_id,
+                prj_id: data.id,
+            }, {transaction: t});
+
+            await t.commit();
+        } catch (err) {
+            await t.rollback();
+            throw err;
+        }
+    }
+
     return result;
 }
 

+ 6 - 5
pmr-biz-manager/src/routes/api/prj/info/set_checkers.ts

@@ -4,7 +4,7 @@ import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 import {IHandler} from "@src/bpmn/flow_engine";
 import {PrjLogger} from "../../../../utils/prj_logger";
-import {is_project_modifiable} from "../../../../utils/prj_premission_helper";
+import {is_project_modifiable, is_project_privileged_account} from "../../../../utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -27,12 +27,13 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
 
         let prj_info = await PrjInfo.findOne({where: {id: data.id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        if (!await is_project_modifiable(cached_data.user_id, prj_info.id))
-            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改该项目,请确认您是项目负责人或特权人员。'));
+
         let phase = await PrjPhaseDefine.findOne({where: {id: prj_info.phase_id}, raw: true});
         if (!phase) return reject(Resp.gen_err(Resp.ResourceNotFound, '项目状态信息出错,id: ' + prj_info.id));
-        if (phase.id !== 'new' && phase.id !== 'doing') return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许修改审核人。'));
-
+        if (phase.id !== 'new' && phase.id !== 'doing' && !await is_project_privileged_account(user))
+            return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许修改审核人,请确认您是特权人员。。'));
+        if (!await is_project_modifiable(cached_data.user_id, prj_info.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改该项目,请确认您是项目负责人或特权人员。'));
         resolve();
     });
 }

+ 6 - 3
pmr-biz-manager/src/routes/api/prj/member/add.ts

@@ -39,9 +39,8 @@ export function statusGuard(json: IRequest, cached_data: ICachedData): Promise<v
         if (user === ADMINISTRATOR) return resolve();
         let prj_info = await PrjInfo.findOne({where: {id: data.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        // if (prj_info.leader_id !== cached_data.user_id)
-        if (!await is_project_modifiable(user, prj_info.id))
-            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限添加项目组成员,请确认您是项目负责人或特权人员。'));
+
+
         let phase = await PrjPhaseDefine.findOne({where: {id: prj_info.phase_id}, raw: true});
         if (!phase) return reject(Resp.gen_err(Resp.ResourceNotFound, '项目状态信息出错,id: ' + prj_info.id));
 
@@ -49,6 +48,10 @@ export function statusGuard(json: IRequest, cached_data: ICachedData): Promise<v
         if (phase.order_index !== 0 && !await is_project_privileged_account(user))
             return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许添加新成员。'));
 
+        // 项目负责人或特权人员可以修改项目组成员
+        if (!await is_project_modifiable(user, prj_info.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限添加项目组成员,请确认您是项目负责人或特权人员。'));
+
         resolve();
     });
 }

+ 11 - 3
pmr-biz-manager/src/routes/api/prj/member/modify.ts

@@ -5,7 +5,8 @@ import {PrjInfo} from "@core-models/PrjInfo";
 import dayjs from "dayjs";
 import {Resp} from "@util/Resp";
 import {PrjMembers} from "@core-models/PrjMembers";
-import {is_project_modifiable} from "@src/utils/prj_premission_helper";
+import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
+import {is_project_modifiable, is_project_privileged_account} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -41,11 +42,18 @@ function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
         if (user === ADMINISTRATOR) return resolve();
         let prj_info = await PrjInfo.findOne({where: {id: data.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        if (!await is_project_modifiable(user, prj_info.id))
-            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改项目组成员,请确认您是项目负责人或特权人员。'));
+
         if (prj_info.leader_id === data.account_id && data.role_id) return reject(Resp.gen_err(Resp.Forbidden, '不允许修改项目负责人的角色。'));
         if (user === data.account_id && data.role_id) return reject(Resp.gen_err(Resp.PrjModifySelfRoleForbidden, '不允许修改自己的项目组成员角色。'));
 
+        let phase = await PrjPhaseDefine.findOne({where: {id: prj_info.phase_id}, raw: true});
+        if (!phase) return reject(Resp.gen_err(Resp.ResourceNotFound, '项目状态信息出错,id: ' + prj_info.id));
+
+        // 当前项目阶段不允许添加新成员, 但特权人员除外
+        if (phase.order_index !== 0 && !await is_project_privileged_account(user))
+            return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许修改成员,请确认您是特权人员。'));
+        if (!await is_project_modifiable(user, prj_info.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改项目组成员,请确认您是项目负责人或特权人员。'));
         resolve();
     });
 }

+ 6 - 2
pmr-biz-manager/src/routes/api/prj/member/remove.ts

@@ -3,7 +3,7 @@ import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjMembers} from "@core-models/PrjMembers";
 import {Resp} from "@util/Resp";
 import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
-import {is_project_modifiable} from "../../../../utils/prj_premission_helper";
+import {is_project_modifiable, is_project_privileged_account} from "../../../../utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -55,7 +55,11 @@ export function statusGuard(json: IRequest, cached_data: ICachedData): Promise<v
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
         let phase = await PrjPhaseDefine.findOne({where: {id: prj_info.phase_id}, raw: true});
         if (!phase) return reject(Resp.gen_err(Resp.ResourceNotFound, '项目状态信息出错,id: ' + prj_info.id));
-        if (phase.order_index !== 0) return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许删除成员。'));
+        if (phase.order_index !== 0 && !await is_project_privileged_account(user))
+            return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许删除成员,请确认您是特权人员。'));
+        // 项目负责人或特权人员可以修改项目组成员
+        if (!await is_project_modifiable(user, prj_info.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限添加项目组成员,请确认您是项目负责人或特权人员。'));
 
         resolve();
     });