Bladeren bron

feature:
1. 增加了对周报的访问控制

eyes4 5 maanden geleden
bovenliggende
commit
cdec08e687

+ 4 - 1
base-lib/src/core-models/PrjWeekReport.ts

@@ -13,6 +13,9 @@ export class PrjWeekReport extends Model {
     declare status: number;
     declare reporter_id: string;
     declare prj_id: string;
+    declare year: number;
+    declare month: number;
+    declare week: number;
 
     static table_name = 'tb_prj_week_report';
 
@@ -98,7 +101,7 @@ export class PrjWeekReport extends Model {
 
     ]
 
-    static associate(sequelize: Sequelize) {
+    static associate(_sequelize: Sequelize) {
 
     }
 

+ 0 - 1
base-lib/src/core/Infrastructure.ts

@@ -17,7 +17,6 @@ import {Oss} from "@util/Oss";
 import {IdGen} from "@util/IdGen";
 import Mongo from "@util/mongodb/Mongo";
 import {Services} from "@util/Services";
-import {Logger} from "@util/Logger";
 import {WsService} from "@util/WsService";
 
 export function init_infrastructure(): Promise<void> {

+ 27 - 2
pmr-biz-manager/src/routes/api/prj/doc/copy.ts

@@ -4,6 +4,7 @@ import {PrjFile} from "@core-models/PrjFile";
 import {is_project_document_modifiable} from "@src/utils/prj_premission_helper";
 import {PrjFileCategory} from "@core-models/PrjFileCategory";
 import {Oss} from "@util/Oss";
+import {IdGen} from "@util/IdGen";
 
 interface IData {
     /**
@@ -30,8 +31,32 @@ function copy(json: IRequest, params: IMethodParams, cached_data: ICachedData):
             if (!await is_project_document_modifiable(cached_data.user_id, file.prj_id, file.id)) {
                 return reject(Resp.gen_err(Resp.Forbidden, '您没有权限复制此文档。'));
             }
+
+
             let oss = Oss.get_instance('pmr-doc');
-            let new_id = file.id + '_c';
+            // 检查文件名,如果文件名后面没有"_副本x",则添加"_副本1",如有,则在后面加上序号,多次复制时序号每次递增1
+
+            let filename = file.filename;
+            // 获取去除了后缀的文件名
+            let base_name = filename.substring(0, filename.lastIndexOf('.'));
+            let ext = filename.substring(filename.lastIndexOf('.'));
+
+            let index = base_name.lastIndexOf('_副本');
+            if (index === -1) {
+                base_name = base_name + '_副本1';
+            } else {
+                let num = parseInt(base_name.substring(index + 3, base_name.length));
+                base_name = base_name.substring(0, index + 3) + (num + 1);
+            }
+            filename = base_name + ext;
+
+            let new_id;
+            let idx = file.id.lastIndexOf('_');
+            if (idx === -1) {
+                new_id = file.id + `_${IdGen.id()}`;
+            } else {
+                new_id = file.id.substring(0, idx) + `_${IdGen.id()}`;
+            }
             await oss.copy_object(oss.bucket, file.id, new_id);
             await PrjFile.create({
                 id: new_id,
@@ -42,7 +67,7 @@ function copy(json: IRequest, params: IMethodParams, cached_data: ICachedData):
                 req_upload_time: file.req_upload_time,
                 uploaded_at: new Date(),
                 creator_id: cached_data.user_id,
-                filename: file.filename,
+                filename: filename,
                 size: file.size,
                 archived: file.archived
 

+ 9 - 1
pmr-biz-manager/src/routes/api/prj/info/modify.ts

@@ -10,7 +10,11 @@ import {Resp} from "@util/Resp";
 import {Logger} from "@util/Logger";
 import {PrjFile} from "@core-models/PrjFile";
 import {BizContractInfo} from "@core-models/BizContractInfo";
-import {is_project_modifiable, is_project_privileged_account} from "@src/utils/prj_premission_helper";
+import {
+    is_project_member,
+    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";
@@ -87,6 +91,10 @@ function permission_guard(content: IRequest, cached_data: ICachedData): Promise<
             if (!is_privileged_account) {
                 return  reject(Resp.gen_err(Resp.Forbidden, '您没有权限修改项目负责人,请确认您是特权人员。'));
             }
+            // 新的项目负责人必须是项目组成员
+            if (!await is_project_member(data.leader_id, prj.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, '您没有权限修改本项目的信息,请确认您是项目负责人或特权人员。'));

+ 8 - 3
pmr-biz-manager/src/routes/api/prj/week_report/add.ts

@@ -8,6 +8,7 @@ import {PrjWeekReport} from "@core-models/PrjWeekReport";
 import "dayjs/locale/zh-cn";
 import weekOfYear from 'dayjs/plugin/weekOfYear';
 import {PrjFile} from "@core-models/PrjFile";
+import {is_project_member} from "@src/utils/prj_premission_helper";
 dayjs.locale("zh-cn");
 dayjs.extend(weekOfYear);
 interface IData {
@@ -26,7 +27,7 @@ interface IData {
     /**
      * 项目id
      */
-    prj_id?: string;
+    prj_id: string;
     /**
      * 本周总结
      */
@@ -34,12 +35,15 @@ interface IData {
 }
 
 /// 检查项目是否存在
-function existsGuard(json: IRequest, _cached_data: ICachedData): Promise<void> {
+function existsGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
     return new Promise<void>(async (resolve, reject) => {
         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.ResourceNotFound, '项目不存在'));
-
+        // 检查用户是否是本项目的负责人或成员
+        if (!await is_project_member(cached_data.user_id, prj.id)) {
+            return reject(Resp.gen_err(Resp.Forbidden, '您不是项目成员,无法创建项目周报。'));
+        }
         resolve();
     });
 }
@@ -122,6 +126,7 @@ const v1_0: IApiProcessor = {
                 ],
                 "title": "请求参数内容",
                 "required": [
+                    "prj_id",
                     "summary",
                     "plan",
                     "attachments"

+ 17 - 5
pmr-biz-manager/src/routes/api/prj/week_report/detail.ts

@@ -1,15 +1,13 @@
 import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {QueryTypes, WhereOptions} from "sequelize";
+import {QueryTypes} from "sequelize";
 import {PrjInfo} from "@core-models/PrjInfo";
 import {AcsUserInfo} from "@core-models/AcsUserInfo";
 import DataCURD from "@core/DataCURD";
-import {BizContractInfo} from "@core-models/BizContractInfo";
-import {BizCustomer} from "@core-models/BizCustomer";
 import {PrjWeekReport} from "@core-models/PrjWeekReport";
-import {PrjWeekReportAttachment} from "@core-models/PrjWeekReportAttachment";
 import dayjs from "dayjs";
 import {PrjFile} from "@core-models/PrjFile";
+import {is_project_leader, is_project_privileged_account} from "@src/utils/prj_premission_helper";
 
 interface IData {
     /**
@@ -18,11 +16,25 @@ interface IData {
     id: string;
 }
 
-
 function detail(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
     return new Promise<any>(async (resolve, reject) => {
         try {
             let data = <IData>json.data;
+            let rep = await PrjWeekReport.findOne({
+                where: {
+                    id: data.id
+                },
+                raw: true
+            });
+            if (!rep) {
+                return reject(Resp.gen_err(Resp.ResourceNotFound, "周报不存在"));
+            }
+            if (!await is_project_privileged_account(cached_data.user_id) &&
+                !await is_project_leader(cached_data.user_id, rep.prj_id) &&
+                cached_data.user_id !== rep.reporter_id
+            ) {
+                return reject(Resp.gen_err(Resp.Forbidden, "无权限查看该周报"));
+            }
             let report = await PrjWeekReport.sequelize!.query(`
                 select report.id, report.summary, report.plan, report.issue, report.status,
                     report.reporter_id, reporter.name as reporter_name, 

+ 5 - 1
pmr-biz-manager/src/routes/api/prj/week_report/download_file.ts

@@ -2,6 +2,7 @@ import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined
 import {Resp} from "@util/Resp";
 import {Oss} from "@util/Oss";
 import {PrjFile} from "@core-models/PrjFile";
+import {is_project_accessible} from "@src/utils/prj_premission_helper";
 
 interface IData {
     id: string;
@@ -15,7 +16,10 @@ function download(json: IRequest, params: IMethodParams, cached_data: ICachedDat
             let data = <IData>json.data;
             let file = await PrjFile.findOne({where: {id: data.id}, raw: true});
             if (!file) throw Resp.gen_err(Resp.ResourceNotFound, `附件(id:${data.id})不存在。`);
-            if (file.uploaded === false) throw Resp.gen_err(Resp.ResourceNotFound, '周报附件尚未上传。')
+            if (!file.uploaded) throw Resp.gen_err(Resp.ResourceNotFound, '周报附件尚未上传。');
+            if (!await is_project_accessible(cached_data.user_id, file.prj_id)) {
+                throw Resp.gen_err(Resp.Forbidden, '附件所属项目您没有权限访问。');
+            }
             let object_name = file.id;
             let oss = Oss.get_instance('pmr-doc');
             let result = await oss.presigned_url(oss.bucket, object_name, 300, file.filename);

+ 15 - 11
pmr-biz-manager/src/routes/api/prj/week_report/get_list.ts

@@ -1,12 +1,11 @@
 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 {AcsUserInfo} from "@core-models/AcsUserInfo";
-import {BizContractInfo} from "@core-models/BizContractInfo";
 import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjWeekReport} from "@core-models/PrjWeekReport";
+import {is_project_leader, is_project_privileged_account} from "@src/utils/prj_premission_helper";
 
 
 interface IData {
@@ -29,7 +28,7 @@ interface IData {
     /**
      * 精确通过项目查找合同
      */
-    prj_id?: string;
+    prj_id: string;
     /**
      * 根据项目名称过滤(模糊匹配)
      */
@@ -70,18 +69,22 @@ function get_list(json: IRequest, params: IMethodParams, cached_data: ICachedDat
                 from ${PrjWeekReport.table_name} report
                 left join ${PrjInfo.table_name} prj on prj.id = report.prj_id
                 left join ${AcsUserInfo.table_name} reporter on reporter.id = report.reporter_id
-                where 1=1 
+                where prj.id = :prj_id 
             `;
-            let replacements: ISQLReplacements = {};
+            let replacements: ISQLReplacements = {prj_id: data.prj_id};
+
+            // 如果是项目经理或特权人员,可以看到全部周报,否则只能看到自己的周报
+            if (!await is_project_privileged_account(cached_data.user_id)&&
+                !await is_project_leader(cached_data.user_id, data.prj_id)) {
+                condition += ` and reporter.id = :reporter_id `;
+                replacements.reporter_id = cached_data.user_id;
+            }
 
             if (data.prj_name !== undefined) {
                 condition += ` and prj.name like :keyword `;
                 replacements.keyword = `%${data.prj_name}%`;
             }
-            if (data.prj_id !== undefined) {
-                condition += ` and prj.id = :prj_id `;
-                replacements.prj_id = data.prj_id;
-            }
+
             if (data.reporter_id !== undefined) {
                 condition += ` and reporter.id = :reporter_id `;
                 replacements.reporter_id = data.reporter_id;
@@ -188,7 +191,8 @@ const v1_0: IApiProcessor = {
                 "title": "请求参数内容",
                 "required": [
                     "page_no",
-                    "page_size"
+                    "page_size",
+                    "prj_id"
                 ]
             },
             "ver": {

+ 7 - 8
pmr-biz-manager/src/routes/api/prj/week_report/modify.ts

@@ -1,13 +1,10 @@
-import {ADMINISTRATOR, IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import DataCURD from "@core/DataCURD";
 import {WhereOptions} from "sequelize";
-import {PrjInfo} from "@core-models/PrjInfo";
-import dayjs from "dayjs";
 import {Resp} from "@util/Resp";
-import {PrjPhaseDefine} from "@core-models/PrjPhaseDefine";
-import {PrjPlanTask} from "@core-models/PrjPlanTask";
 import {PrjWeekReport} from "@core-models/PrjWeekReport";
-import {PrjFile} from "@core-models/PrjFile";
+import dayjs from "dayjs";
+
 
 interface IData {
     /**
@@ -41,14 +38,16 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         let report = await PrjWeekReport.findOne({where: {id: data.id}, raw: true});
         if (!report) return reject(Resp.gen_err(Resp.ResourceNotFound));
         if (report.reporter_id !== user) return reject(Resp.gen_err(Resp.Forbidden, '只允许修改自己的周报。'));
-
+        if (report.year !== dayjs().year() || report.month !== dayjs().month() || report.week !== dayjs().week()){
+            return reject(Resp.gen_err(Resp.InvalidFlow, '只允许修改本周的周报。'));
+        }
         if (report.status > 0 ) return reject(Resp.gen_err(Resp.InvalidFlow, '周报已提交,不允许修改,在未审阅前可撤回后再修改。'));
 
         resolve();
     });
 }
 
-async function get_where(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<WhereOptions> {
+async function get_where(json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<WhereOptions> {
     let data = <IData>json.data;
     return {id: data.id};
 }

+ 6 - 4
pmr-biz-manager/src/routes/api/prj/week_report/remove.ts

@@ -1,8 +1,7 @@
-import {ADMINISTRATOR, IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
+import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined";
 import {Resp} from "@util/Resp";
-import {BizContractInfo} from "@core-models/BizContractInfo";
-import {PrjInfo} from "@core-models/PrjInfo";
 import {PrjWeekReport} from "@core-models/PrjWeekReport";
+import dayjs from "dayjs";
 
 interface IData {
     /**
@@ -21,13 +20,16 @@ function statusGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
         if (!report) return reject(Resp.gen_err(Resp.ResourceNotFound));
         if (report.reporter_id !== user) return reject(Resp.gen_err(Resp.Forbidden, '只允许删除自己的周报。'));
 
+        if (report.year !== dayjs().year() || report.month !== dayjs().month() || report.week !== dayjs().week()){
+            return reject(Resp.gen_err(Resp.InvalidFlow, '只允许删除本周的周报。'));
+        }
         if (report.status > 0 ) return reject(Resp.gen_err(Resp.InvalidFlow, '周报已提交,不允许删除,在未审阅前可撤回后再删除。'));
 
         resolve();
     });
 }
 
-async function remove(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
+async function remove(json: IRequest, _params: IMethodParams, _cached_data: ICachedData): Promise<any> {
     let data = <IData>json.data;
     let t = await PrjWeekReport.sequelize!.transaction();
     try {

+ 3 - 5
pmr-biz-manager/src/routes/api/prj/week_report/remove_file.ts

@@ -2,7 +2,6 @@ import {IApiProcessor, ICachedData, IMethodParams, IRequest} from "@core/Defined
 import {Resp} from "@util/Resp";
 import {Oss} from "@util/Oss";
 import {BizContractInfo} from "@core-models/BizContractInfo";
-import {PrjWeekReport} from "@core-models/PrjWeekReport";
 import {PrjFile} from "@core-models/PrjFile";
 
 interface IData {
@@ -20,12 +19,11 @@ function remove_file(json: IRequest, params: IMethodParams, cached_data: ICached
             let data = <IData>json.data;
             let file = await PrjFile.findOne({where: {id: data.id, category_id: 'week_report'}, raw: true, transaction: t});
             if (!file) throw Resp.gen_err(Resp.ResourceNotFound, `合同附件(id:${data.id})不存在。`);
-            let prj_id = file.prj_id;
-            // TODO: 检查是否是项目组成员或有访问项目的权限
 
             if (file.uploaded === false) throw Resp.gen_err(Resp.ResourceNotFound, '附件尚未上传。')
-
-
+            if (file.creator_id !== cached_data.user_id) {
+                throw Resp.gen_err(Resp.ResourceNotFound, '您不是附件的创建者,没有权限删除。');
+            }
             let oss = Oss.get_instance('pmr-doc');
             await oss.remove_object(oss.bucket, file.id);
             await PrjFile.destroy({where: {id: data.id}, transaction: t});

+ 1 - 4
pmr-biz-manager/src/routes/api/prj/week_report/upload_file.ts

@@ -22,9 +22,6 @@ interface IData {
     report_id?: string;
 }
 
-
-// TODO: 所有文档都放在一个表中,记录项目id和文档类型
-
 function get_upload_url(json: IRequest, params: IMethodParams, cached_data: ICachedData): Promise<any> {
     return new Promise(async (resolve, reject) => {
         let t = await PrjTaskOutcome.sequelize!.transaction();
@@ -56,7 +53,7 @@ function get_upload_url(json: IRequest, params: IMethodParams, cached_data: ICac
 }
 
 /// 检查项目是否存在
-function existsGuard(json: IRequest, cached_data: ICachedData): Promise<void> {
+function existsGuard(json: IRequest, _cached_data: ICachedData): Promise<void> {
     return new Promise<void>(async (resolve, reject) => {
         let data = <IData>json.data;
         let prj = await PrjInfo.findOne({where: {id: data.prj_id}, raw: true});