Ver código fonte

feature: 对项目、任务做了更精细的访问和修改权限控制

eyes4 6 meses atrás
pai
commit
5bf1bb02e5

+ 2 - 2
base-lib/src/core-models/AcsRole.ts

@@ -32,10 +32,10 @@ export class AcsRole extends Model {
             ('cto', '技术总监', 'sys')
             ON CONFLICT DO NOTHING `,
         `insert into tb_acs_role (id, name, tag) values
-            ('prj_leader', '项目负责人', 'prj')
+            ('prj_leader', '项目组长', 'prj')
             ON CONFLICT DO NOTHING `,
         `insert into tb_acs_role (id, name, tag) values
-            ('prj_member', '项目成员', 'prj')
+            ('prj_member', '项目成员', 'prj')
             ON CONFLICT DO NOTHING `
     ]
 }

+ 2 - 1
base-lib/src/core-models/PrjInfo.ts

@@ -15,6 +15,7 @@ export class PrjInfo extends Model {
     declare phase_id: string;
     declare flow_case_id: string;
     declare leader_id: string;
+    declare bizman_id: string;
     declare progress: number;
     declare checkers: any;
     declare contract_id: string;
@@ -80,7 +81,7 @@ export class PrjInfo extends Model {
         bizman_id: {
             type: DataTypes.STRING,
             allowNull: true,
-            comment: '设备负责人账号id',
+            comment: '商务负责人账号id',
             references: {
                 model: AcsUserInfo,
                 key: 'id'

+ 11 - 1
pmr-biz-manager/src/routes/api/prj/info/get_logs.ts

@@ -12,6 +12,7 @@ import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 import {BpmnWork} from "@core-models/BpmnWork";
 import {BpmnCase} from "@core-models/BpmnCase";
 import {PrjLogs} from "@core-models/PrjLogs";
+import {is_project_accessible} from "../../../../utils/prj_premission_helper";
 
 interface IData  {
     /**
@@ -36,6 +37,14 @@ interface IData  {
     page_size: number;
 }
 
+function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
+    return new Promise<void>(async (resolve, reject) => {
+        let data = <IData>json.data;
+        if (!await is_project_accessible(cached_data.user_id, data.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '无权访问该项目'));
+        resolve();
+    });
+}
 
 
 function get_logs(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<WhereOptions> {
@@ -176,7 +185,8 @@ const v1_0: IApiProcessor = {
         ]
     },
     method: get_logs,
-    method_params: {}
+    method_params: {},
+    guards: [guard]
 }
 
 

+ 3 - 2
pmr-biz-manager/src/routes/api/prj/info/modify.ts

@@ -13,6 +13,7 @@ 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";
 
 interface IData {
     /**
@@ -64,8 +65,8 @@ function permission_guard(content: IRequest, cached_data: ICachedData): Promise<
         if (!prj)
             throw Resp.gen_err(Resp.ResourceNotFound, '项目不存在。');
 
-        if (prj.leader_id !== 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, '您没有权限修改该项目,请确认您是项目负责人或特权人员。'));
 
         resolve();
 

+ 13 - 2
pmr-biz-manager/src/routes/api/prj/info/overview.ts

@@ -13,10 +13,21 @@ import DataCURD, {ISQLReplacements} from "@core/DataCURD";
 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";
 interface IData {
     id: string;
 }
 
+function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
+    return new Promise<void>(async (resolve, reject) => {
+        let data = <IData>json.data;
+        if (!await is_project_accessible(cached_data.user_id, data.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '无权访问该项目'));
+        resolve();
+    });
+}
+
+
 async function overview(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<WhereOptions> {
     let data = <IData>json.data;
     let sql = `
@@ -152,8 +163,8 @@ const v1_0: IApiProcessor = {
         ]
     },
     method: overview,
-    method_params: {
-    }
+    method_params: {},
+    guards: [guard]
 }
 
 module.exports = {

+ 3 - 2
pmr-biz-manager/src/routes/api/prj/info/remove.ts

@@ -7,6 +7,7 @@ import {BpmnCase} from "@core-models/BpmnCase";
 import {BpmnWork} from "@core-models/BpmnWork";
 import {FlowEngine} from "@src/bpmn/flow_engine";
 import {PrjLogger} from "@src/utils/prj_logger";
+import {is_project_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     id: string;//项目id
@@ -21,8 +22,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.id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        if (prj_info.leader_id !== cached_data.user_id)
-            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限删除该项目,请确认您是项目负责人。'));
+        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.order_index > 0) return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许删除项目,可使用作废功能使项目作废。'));

+ 3 - 2
pmr-biz-manager/src/routes/api/prj/info/set_checkers.ts

@@ -4,6 +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";
 
 interface IData {
     /**
@@ -26,8 +27,8 @@ 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 (prj_info.leader_id !== cached_data.user_id)
-            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限设置该项目的审核组成员,请确认您是项目负责人。'));
+        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, '当前项目阶段不允许修改审核人。'));

+ 8 - 1
pmr-biz-manager/src/routes/api/prj/member/add.ts

@@ -7,6 +7,7 @@ import dayjs from "dayjs";
 import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjMembers} from "@core-models/PrjMembers";
 import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
+import {is_project_privileged_account, is_project_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -38,9 +39,15 @@ 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, '您没有权限添加项目组成员,请确认您是项目负责人ak。'));
         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, '当前项目阶段不允许添加新成员。'));
 
         resolve();
     });

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

@@ -5,6 +5,7 @@ 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";
 
 interface IData {
     /**
@@ -32,13 +33,18 @@ async function get_where(json: IRequest, params: IMethodParams, cached_data: ICa
 }
 
 /// 不允许修改自己的项目组成员角色
-export function leaderGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
+function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
     return new Promise<void>(async (resolve, reject) => {
         let user = cached_data.user_id;
         let data = <IData>json.data;
         if (!user) return reject(Resp.gen_err(Resp.Forbidden));
         if (user === ADMINISTRATOR) return resolve();
-        if (user === data.account_id && data.role_id) return reject(Resp.gen_err(Resp.PrjModifySelfRoleForbidden));
+        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, '不允许修改自己的项目组成员角色。'));
 
         resolve();
     });
@@ -138,7 +144,7 @@ const v1_0: IApiProcessor = {
             updated_at: 'updated_at'
         }
     },
-    guards: [leaderGuard]
+    guards: [guard]
 }
 
 module.exports = {

+ 3 - 0
pmr-biz-manager/src/routes/api/prj/member/remove.ts

@@ -3,6 +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";
 
 interface IData {
     /**
@@ -35,6 +36,8 @@ export function accountGuard(json: IRequest, cached_data: ICachedData): Promise<
         let data = <IData>json.data;
         if (!user) return reject(Resp.gen_err(Resp.Forbidden));
         if (user === ADMINISTRATOR) return resolve();
+        if (!await is_project_modifiable(user, data.prj_id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限删除项目组成员,请确认您是项目负责人或特权人员。'));
         if (user === data.account_id) return reject(Resp.gen_err(Resp.PrjRemoveSelfForbidden));
 
         resolve();

+ 4 - 3
pmr-biz-manager/src/routes/api/prj/plan/add_task.ts

@@ -15,6 +15,7 @@ import {BpmnModel} from "@core-models/BpmnModel";
 import {bpmn_flow_on_end} from "@src/utils/bpmn_work_helper";
 import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 import {ChangeMarker} from "@src/utils/define";
+import {is_project_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -60,9 +61,9 @@ function statusGuard(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 (cached_data.user_id !== prj_info.leader_id) {
-            return reject(Resp.gen_err(Resp.Forbidden, '您不是项目负责人,不能添加任务。'));
+        // 检查用户是否是项目负责人,只有项目负责人或特权人员才可以添加任务
+        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));

+ 15 - 5
pmr-biz-manager/src/routes/api/prj/plan/get_check_flow.ts

@@ -1,11 +1,8 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {QueryTypes, WhereOptions} from "sequelize";
 import {PrjPlanTask} from "@core-models/PrjPlanTask";
 import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
-import {PrjInfo} from "@core-models/PrjInfo";
-import {AcsUserInfo} from "@core-models/AcsUserInfo";
-import DataCURD from "@core/DataCURD";
+import {is_project_accessible} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -18,6 +15,19 @@ interface IData {
     task_id: string;
 }
 
+// 接口访问守卫
+function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
+    return new Promise<void>(async (resolve, reject) => {
+        let user = cached_data.user_id;
+        let data = <IData>json.data;
+        let model = data.draft ? PrjPlanTaskDraft : PrjPlanTask;
+        let task = await model.findOne({where: {id: data.task_id}, raw: true});
+        if (!task) return reject(Resp.gen_err(Resp.ResourceNotFound, '任务不存在。'));
+        if (!await is_project_accessible(user, task.prj_id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限访问该项目。'));
+        resolve();
+    });
+}
 
 function get_check_flow(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
     return new Promise<any>(async (resolve, reject) => {
@@ -94,7 +104,7 @@ const v1_0: IApiProcessor = {
     },
     method: get_check_flow,
     method_params: {},
-    guards: []
+    guards: [guard]
 }
 
 

+ 16 - 11
pmr-biz-manager/src/routes/api/prj/plan/get_logs.ts

@@ -1,17 +1,12 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {Op, QueryTypes, WhereOptions} from "sequelize";
+import { WhereOptions} from "sequelize";
 import DataCURD, {ISQLReplacements} from "@core/DataCURD";
 import {BizCustomer} from "@core-models/BizCustomer";
-import {BizCustomerIndustry} from "@core-models/BizCustomerIndustry";
-import {AcsUserInfo} from "@core-models/AcsUserInfo";
-import {AcsDomain} from "@core-models/AcsDomain";
-import {PrjInfo} from "@core-models/PrjInfo";
-import {BizContractInfo} from "@core-models/BizContractInfo";
-import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
-import {BpmnWork} from "@core-models/BpmnWork";
-import {BpmnCase} from "@core-models/BpmnCase";
 import {PrjLogs} from "@core-models/PrjLogs";
+import {is_project_accessible} from "../../../../utils/prj_premission_helper";
+import {PrjPlanTask} from "@core-models/PrjPlanTask";
+import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 
 interface IData  {
     /**
@@ -40,7 +35,16 @@ interface IData  {
     page_size: number;
 }
 
-
+// 接口访问守卫
+function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
+    return new Promise<void>(async (resolve, reject) => {
+        let user = cached_data.user_id;
+        let data = <IData>json.data;
+        if (!await is_project_accessible(user, data.prj_id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限访问该项目。'));
+        resolve();
+    });
+}
 
 function get_logs(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<WhereOptions> {
     return new Promise<WhereOptions>(async (resolve, reject) => {
@@ -186,7 +190,8 @@ const v1_0: IApiProcessor = {
         ]
     },
     method: get_logs,
-    method_params: {}
+    method_params: {},
+    guards: [guard]
 }
 
 

+ 4 - 0
pmr-biz-manager/src/routes/api/prj/plan/get_tasks.ts

@@ -15,6 +15,7 @@ import {PrjTaskOutcomeDraft} from "@core-models/PrjTaskOutcomeDraft";
 import {ChangeMarker} from "@src/utils/define";
 import {Logger} from "@util/Logger";
 import {clone} from "@util/JsonToolkit";
+import {is_project_accessible} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -158,6 +159,9 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
 
         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_accessible(user, data.prj_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.order_index < 20) return reject(Resp.gen_err(Resp.InvalidFlow, '需要先对项目进行立项,立项后才可制订计划。'));

+ 3 - 0
pmr-biz-manager/src/routes/api/prj/plan/modify_task.ts

@@ -10,6 +10,7 @@ import {start_plan_alt_flow, update_parent_task_info} from "../../../../utils/pl
 import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 import {Logger} from "@util/Logger";
 import {ChangeMarker, TaskStatus} from "@src/utils/define";
+import {is_project_task_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -72,6 +73,8 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         let task = await model.findOne({where: {id: data.task_id}, raw: true});
         if (!task)
             return reject(Resp.gen_err(Resp.ResourceNotFound, '计划任务项不存在。'));
+        if (!await is_project_task_modifiable(user, task.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改任务。'));
 
         let prj_info = await PrjInfo.findOne({where: {id: task.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));

+ 7 - 3
pmr-biz-manager/src/routes/api/prj/plan/recover_task.ts

@@ -6,6 +6,7 @@ import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
 import {update_parent_task_info} from "@src/utils/plan_task_helper";
 import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 import {ChangeMarker} from "@src/utils/define";
+import {is_project_task_modifiable} from "../../../../utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -30,9 +31,12 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         let prj_info = await PrjInfo.findOne({where: {id: task.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
         // 检查用户是否是项目负责人,只有项目负责人才可以恢复任务
-        if (cached_data.user_id !== prj_info.leader_id) {
-            return reject(Resp.gen_err(Resp.Forbidden, '您不是项目负责人,不能恢复任务。'));
-        }
+        // if (cached_data.user_id !== prj_info.leader_id) {
+        //     return reject(Resp.gen_err(Resp.Forbidden, '您不是项目负责人,不能恢复任务。'));
+        // }
+        if (!await is_project_task_modifiable(user, task.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限恢复任务。'));
+
         if (task.change_marker !== ChangeMarker.Deleted) {
             return reject(Resp.gen_err(Resp.InvalidFlow, '任务没有被删除,不用恢复。'));
         }

+ 4 - 4
pmr-biz-manager/src/routes/api/prj/plan/remove_task.ts

@@ -10,6 +10,7 @@ import {PrjTaskOutcome} from "@core-models/PrjTaskOutcome";
 import {Op} from "sequelize";
 import {PrjTaskOutcomeDraft} from "@core-models/PrjTaskOutcomeDraft";
 import {ChangeMarker, TaskStatus} from "@src/utils/define";
+import {is_project_task_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -39,10 +40,9 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
 
         let prj_info = await PrjInfo.findOne({where: {id: task.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        // 检查用户是否是项目负责人,只有项目负责人才可以删除任务
-        if (cached_data.user_id !== prj_info.leader_id) {
-            return reject(Resp.gen_err(Resp.Forbidden, '您不是项目负责人,不能删除任务。'));
-        }
+        if (!await is_project_task_modifiable(user, task.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限删除任务。'));
+
         if (task.status === TaskStatus.Completed || task.status === TaskStatus.Cancelled) {
             return reject(Resp.gen_err(Resp.InvalidFlow, '任务已完成或已取消,不允许删除。'));
         }

+ 5 - 4
pmr-biz-manager/src/routes/api/prj/plan/set_check_flow.ts

@@ -6,6 +6,7 @@ import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 import {PrjPlanTask} from "@core-models/PrjPlanTask";
 import {ChangeMarker, TaskStatus} from "@src/utils/define";
 import {start_plan_alt_flow} from "@src/utils/plan_task_helper";
+import {is_project_task_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -53,10 +54,10 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         if (data.draft) return resolve();/// 草稿允许随意修改
         let prj_info = await PrjInfo.findOne({where: {id: task.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        // 检查用户是否是项目负责人,只有项目负责人才可以设置任务审核流程
-        if (cached_data.user_id !== prj_info.leader_id) {
-            return reject(Resp.gen_err(Resp.Forbidden, '您不是项目负责人,不能设置任务审核流程。'));
-        }
+
+        if (!await is_project_task_modifiable(user, task.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限设置任务审核流程。'));
+
         if (task.status >= TaskStatus.ApplyForReview)
             return reject(Resp.gen_err(Resp.InvalidFlow, '计划任务项已提交审核,不允许修改。'));
 

+ 7 - 4
pmr-biz-manager/src/routes/api/prj/plan/set_progress.ts

@@ -6,6 +6,7 @@ import {PrjInfo} from "@core-models/PrjInfo";
 import {update_parent_task_info} from "@src/utils/plan_task_helper";
 import {TaskStatus} from "@src/utils/define";
 import {create_task_approve_flow_if_not_exists} from "@src/utils/bpmn_work_helper";
+import {is_project_task_progress_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     progress: number;
@@ -25,6 +26,11 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         let task = await PrjPlanTask.findOne({where: {id: data.task_id}, raw: true});
         if (!task)
             return reject(Resp.gen_err(Resp.ResourceNotFound, '计划任务项不存在。'));
+
+        // 检查用户是否是项目负责人或任务主责人,只有他们才可以对设置任务进度
+        if (!await is_project_task_progress_modifiable(user, task.id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改任务进度。'));
+
         if (task.status === TaskStatus.ApplyForReview || task.status === TaskStatus.Reviewing) {
             return reject(Resp.gen_err(Resp.InvalidFlow, '计划任务项审核中,当前阶段不允许修改进度。'));
         }
@@ -34,10 +40,7 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
 
         let prj_info = await PrjInfo.findOne({where: {id: task.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
-        // 检查用户是否是项目负责人或任务主责人,只有他们才可以对设置任务进度
-        if (cached_data.user_id !== prj_info.leader_id || cached_data.user_id !== task.handler_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.order_index < 20) return reject(Resp.gen_err(Resp.InvalidFlow, '需要先对项目进行立项,立项后才可制订计划。'));

+ 4 - 3
pmr-biz-manager/src/routes/api/prj/plan/sort_task.ts

@@ -8,6 +8,7 @@ import {IdGen} from "@util/IdGen";
 import {start_plan_alt_flow, update_parent_task_info} from "@src/utils/plan_task_helper";
 import {PrjPlanTaskDraft} from "@core-models/PrjPlanTaskDraft";
 import {ChangeMarker} from "@src/utils/define";
+import {is_project_modifiable, is_project_task_modifiable} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -40,9 +41,9 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         let prj_info = await PrjInfo.findOne({where: {id: data.prj_id}, raw: true});
         if (!prj_info) return reject(Resp.gen_err(Resp.ResourceNotFound));
         // 检查用户是否是项目负责人,只有项目负责人才可以对任务进行排序
-        if (cached_data.user_id !== prj_info.leader_id) {
-            return reject(Resp.gen_err(Resp.Forbidden, '您不是项目负责人,不能对任务进行排序。'));
-        }
+        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));
         // if (phase.order_index >= 30 && data.draft === false) return reject(Resp.gen_err(Resp.InvalidFlow, '当前项目阶段不允许直接修改计划,应在草稿中修改后重新提交审核。'));

+ 12 - 4
pmr-biz-manager/src/routes/api/prj/plan/task_detail.ts

@@ -7,6 +7,7 @@ import {PrjInfo} from "@core-models/PrjInfo";
 import {AcsUserInfo} from "@core-models/AcsUserInfo";
 import {BpmnWork} from "@core-models/BpmnWork";
 import DataCURD from "@core/DataCURD";
+import {is_project_task_accessible} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -17,16 +18,23 @@ interface IData {
      * 任务id
      */
     task_id: string;
-    [property: string]: any;
 }
 
+// 接口访问守卫
+function guard(json: IRequest, cached_data: ICachedData): Promise<void> {
+    return new Promise<void>(async (resolve, reject) => {
+        let user = cached_data.user_id;
+        let data = <IData>json.data;
+        if (!await is_project_task_accessible(user, data.task_id))
+            return reject(Resp.gen_err(Resp.Forbidden, '您没有权限访问该项目。'));
+        resolve();
+    });
+}
 
 function task_detail(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
     return new Promise<any>(async (resolve, reject) => {
         try {
             let data = <IData>json.data;
-            // let prj = await PrjInfo.findOne({where: {id: data.prj_id}, raw: true});
-            // if (!prj) return reject(Resp.gen_err(Resp.DataExists, `项目不存在,id:${data.prj_id}`));
             let model = data.draft ? PrjPlanTaskDraft : PrjPlanTask;
             let sql = `
                 select task.id as task_id, task.pid,
@@ -147,7 +155,7 @@ const v1_0: IApiProcessor = {
     },
     method: task_detail,
     method_params: {},
-    guards: []
+    guards: [guard]
 }
 
 

+ 117 - 0
pmr-biz-manager/src/utils/prj_premission_helper.ts

@@ -0,0 +1,117 @@
+import {AcsUserInfo} from "@core-models/AcsUserInfo";
+import {PrjMembers} from "@core-models/PrjMembers";
+import {PrjInfo} from "@core-models/PrjInfo";
+import {QueryTypes} from "sequelize";
+import {PrjPlanTask} from "@core-models/PrjPlanTask";
+
+/// 是否是项目特权账户
+export async function is_project_privileged_account(account_id: string): Promise<boolean> {
+    // 检查账户是否为特权账户
+    let result = await AcsUserInfo.findOne({where: {id: account_id}, raw: true});
+    if (result && result.ext_info.full_range === true) return true;
+    return false;
+}
+
+/// 是否是项目组成员
+export async function is_project_member(account_id: string, project_id: string): Promise<boolean> {
+    // 检查账户是否为项目组成员
+    let result = await PrjMembers.findOne({
+        where: {member_id: account_id, prj_id: project_id, draft: false},
+        raw: true
+    });
+    if (result) return true;
+    return false;
+}
+
+/// 是否是项目负责人
+export async function is_project_leader(account_id: string, project_id: string): Promise<boolean> {
+    let prj = await PrjInfo.findOne({where: {id: project_id}, raw: true});
+    if (prj && prj.leader_id === account_id) return true;
+    return false;
+}
+
+/// 是否是项目商务负责人
+export async function is_project_business_leader(account_id: string, project_id: string): Promise<boolean> {
+    let prj = await PrjInfo.findOne({where: {id: project_id}, raw: true});
+    if (prj && prj.bizman_id === account_id) return true;
+    return false;
+}
+
+/// 是否是项目审核组成员
+export async function is_project_checker(account_id: string, project_id: string): Promise<boolean> {
+    let result = await PrjInfo.sequelize!.query(`
+        select 1 
+        from tb_prj_info prj_info, jsonb_array_elements(prj_info.checkers) AS checker_obj
+        where checker_obj->>'id' = :user_id and prj_info.id = :prj_id
+    `, {replacements: {user_id: account_id, prj_id: project_id}, type: QueryTypes.SELECT});
+
+    if (result && result.length > 0) return true;
+
+    return false;
+}
+
+/// 是否是项目任务审核组成员
+export async function is_project_task_checker(account_id: string, project_id: string): Promise<boolean> {
+
+    let result = await PrjInfo.sequelize!.query(`
+        SELECT 1
+        FROM tb_prj_plan_task prj_task, jsonb_array_elements(prj_task.checkers) AS checker_obj
+        WHERE  (checker_obj->>'handlers')::jsonb ? :user_id
+          and prj_task.prj_id = :prj_id
+    `, {replacements: {user_id: account_id, prj_id: project_id}, type: QueryTypes.SELECT});
+
+    if (result && result.length > 0) return true;
+
+    return false;
+}
+
+/// 是否有权访问项目
+export async function is_project_accessible(account_id: string, project_id: string): Promise<boolean> {
+    let prj = await PrjInfo.findOne({where: {id: project_id}, raw: true});
+    if (prj && (
+        prj.leader_id === account_id ||
+        prj.bizman_id === account_id ||
+        await is_project_privileged_account(account_id) ||
+        await is_project_member(account_id, project_id) ||
+        await is_project_checker(account_id, project_id) ||
+        await is_project_task_checker(account_id, project_id)
+    )) return true;
+
+    return false;
+}
+
+/// 是否有修改项目信息的权限
+export async function is_project_modifiable(account_id: string, project_id: string): Promise<boolean> {
+    if (
+        await is_project_leader(account_id, project_id) ||
+        await is_project_privileged_account(account_id)
+    ) return true;
+
+    return false;
+}
+
+/// 是否有访问项目任务的权限
+export async function is_project_task_accessible(account_id: string, task_id: string): Promise<boolean> {
+    let task = await PrjPlanTask.findOne({where: {id: task_id}, raw: true});
+    if (task && (
+        task.handler_id === account_id ||
+        await is_project_accessible(account_id, task.prj_id)
+    )) return true;
+
+    return false;
+}
+
+/// 是否有修改项目任务的权限
+export async function is_project_task_modifiable(account_id: string, task_id: string): Promise<boolean> {
+    let task = await PrjPlanTask.findOne({where: {id: task_id}, raw: true});
+    if (task &&  await is_project_modifiable(account_id, task.prj_id)) return true;
+
+    return false;
+}
+
+/// 是否有修改项目任务进度的权限
+export async function is_project_task_progress_modifiable(account_id: string, task_id: string): Promise<boolean> {
+    let task = await PrjPlanTask.findOne({where: {id: task_id}, raw: true});
+    if (task && (task.handler_id === account_id || await is_project_modifiable(account_id, task.prj_id))) return true;
+    return false;
+}

+ 0 - 0
pmr-biz-manager/src/utils/prj_stat.ts