From 05deca3b5c01f796e35143432d6317d14f38e81f Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Sun, 14 Jun 2026 23:54:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B7=A5=E4=BD=9C=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E5=9B=A2=E9=98=9F=E8=A7=86=E8=A7=92):=20=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E7=8E=B0=E5=9C=A8=E5=8F=AF=E4=BB=A5=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E5=9B=A2=E9=98=9F=E8=A7=86=E8=A7=92=E4=BA=86=EF=BC=88?= =?UTF-8?q?=E6=9F=A5=E7=9C=8B=E4=B8=8B=E5=B1=9E=EF=BC=89=E3=80=82=20fix(?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=8A=A5=E5=91=8A=E5=AE=9A=E6=97=B6=E7=94=9F?= =?UTF-8?q?=E6=88=90):=20=E4=BF=AE=E5=A4=8D=E5=B7=A5=E4=BD=9C=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E5=AE=9A=E6=97=B6=E7=94=9F=E6=88=90=E5=B8=A6=E6=9D=A5?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E4=BA=9B=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/enums/ErrorCodeConstants.java | 2 + .../constant/TeamDashboardConstants.java | 12 + .../overtime/team/TeamOvertimeController.java | 35 +++ .../team/vo/TeamOvertimeSummaryReqVO.java | 12 + .../team/vo/TeamOvertimeSummaryRespVO.java | 24 ++ .../vo/OvertimeApplicationPageReqVO.java | 4 + .../monthly/vo/MonthlyReportPageReqVO.java | 5 + .../project/vo/ProjectReportPageReqVO.java | 5 + .../team/TeamWorkReportController.java | 46 +++ .../team/vo/TeamReportRemindReqVO.java | 23 ++ .../team/vo/TeamReportRemindRespVO.java | 12 + .../team/vo/TeamReportSummaryReqVO.java | 18 ++ .../team/vo/TeamReportSummaryRespVO.java | 39 +++ .../weekly/vo/WeeklyReportPageReqVO.java | 5 + .../overtime/OvertimeApplicationMapper.java | 17 ++ .../dal/mysql/project/ProjectMapper.java | 14 + .../monthly/MonthlyReportMapper.java | 32 ++ .../project/ProjectReportMapper.java | 33 ++ .../workreport/weekly/WeeklyReportMapper.java | 32 ++ .../notify/NotifyTemplateCodeConstants.java | 3 + .../OvertimeApplicationServiceImpl.java | 20 +- .../overtime/team/TeamOvertimeService.java | 9 + .../team/TeamOvertimeServiceImpl.java | 86 ++++++ .../team/TeamDashboardAccessService.java | 43 +++ .../team/TeamDashboardAccessServiceImpl.java | 112 +++++++ .../WorkReportAutoGenerateService.java | 215 ++++++++++--- .../common/WorkReportCommonService.java | 38 ++- .../team/TeamWorkReportService.java | 13 + .../team/TeamWorkReportServiceImpl.java | 283 ++++++++++++++++++ .../src/main/resources/application.yaml | 2 +- .../api/user/UserManagementRelationApi.java | 6 + .../user/UserManagementRelationApiImpl.java | 6 + .../UserManagementRelationController.java | 8 + .../MySubordinateTreeRespVO.java | 29 ++ .../user/UserManagementRelationService.java | 18 ++ .../UserManagementRelationServiceImpl.java | 91 ++++++ 36 files changed, 1305 insertions(+), 47 deletions(-) create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/TeamDashboardConstants.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/TeamOvertimeController.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryReqVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryRespVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/TeamWorkReportController.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindReqVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindRespVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryReqVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryRespVO.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeService.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeServiceImpl.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessService.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessServiceImpl.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportService.java create mode 100644 rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportServiceImpl.java create mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/userManagementRelation/MySubordinateTreeRespVO.java diff --git a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java index 60b3096..2c1f1e7 100644 --- a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java +++ b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java @@ -241,6 +241,8 @@ public interface ErrorCodeConstants { ErrorCode OVERTIME_APPLICATION_APPROVER_SELF_FORBIDDEN = new ErrorCode(1_008_009_009, "审核人不能选择申请人本人"); ErrorCode OVERTIME_APPLICATION_READ_FORBIDDEN = new ErrorCode(1_008_009_010, "无权查看该加班申请"); ErrorCode OVERTIME_APPLICATION_DELETE_ONLY_REJECTED = new ErrorCode(1_008_009_011, "仅已退回的加班申请允许删除"); + ErrorCode TEAM_DASHBOARD_PERMISSION_REQUIRED = new ErrorCode(1_008_009_012, "当前用户无团队视角权限"); + ErrorCode TEAM_DASHBOARD_SUBORDINATE_SCOPE_INVALID = new ErrorCode(1_008_009_013, "查询目标超出当前用户下属范围"); // ========== 工作报告 1_008_010_xxx ========== ErrorCode WORK_REPORT_NOT_EXISTS = new ErrorCode(1_008_010_001, "工作报告不存在"); ErrorCode WORK_REPORT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_010_002, "工作报告状态定义不存在或已停用"); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/TeamDashboardConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/TeamDashboardConstants.java new file mode 100644 index 0000000..b573d2f --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/TeamDashboardConstants.java @@ -0,0 +1,12 @@ +package com.njcn.rdms.module.project.constant; + +/** + * 团队视角常量。 + */ +public final class TeamDashboardConstants { + + private TeamDashboardConstants() { + } + + public static final String PERMISSION = "project:work-report:team-dashboard"; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/TeamOvertimeController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/TeamOvertimeController.java new file mode 100644 index 0000000..e6f9257 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/TeamOvertimeController.java @@ -0,0 +1,35 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.team; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.module.project.constant.TeamDashboardConstants; +import com.njcn.rdms.module.project.controller.admin.overtime.team.vo.TeamOvertimeSummaryReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.team.vo.TeamOvertimeSummaryRespVO; +import com.njcn.rdms.module.project.service.overtime.team.TeamOvertimeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 团队加班申请") +@RestController +@RequestMapping("/project/overtime-applications/team") +@Validated +public class TeamOvertimeController { + + @Resource + private TeamOvertimeService teamOvertimeService; + + @GetMapping("/summary") + @Operation(summary = "获取团队加班申请统计") + @PreAuthorize("@ss.hasPermission('" + TeamDashboardConstants.PERMISSION + "')") + public CommonResult getSummary(@Valid TeamOvertimeSummaryReqVO reqVO) { + return success(teamOvertimeService.getSummary(reqVO)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryReqVO.java new file mode 100644 index 0000000..ab63fa4 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryReqVO.java @@ -0,0 +1,12 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.team.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 团队加班申请统计 Request VO") +@Data +public class TeamOvertimeSummaryReqVO { + + @Schema(description = "统计月份,不传默认当前月", example = "2026-06") + private String month; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryRespVO.java new file mode 100644 index 0000000..c16a6ea --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/team/vo/TeamOvertimeSummaryRespVO.java @@ -0,0 +1,24 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.team.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 团队加班申请统计 Response VO") +@Data +public class TeamOvertimeSummaryRespVO { + + @Schema(description = "统计月份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06") + private String month; + + @Schema(description = "申请总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12") + private Integer totalApplicationCount; + + @Schema(description = "待审批数", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") + private Integer pendingCount; + + @Schema(description = "已通过数", requiredMode = Schema.RequiredMode.REQUIRED, example = "7") + private Integer approvedCount; + + @Schema(description = "已退回数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer rejectedCount; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java index 22f3bb8..13427c1 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java @@ -9,6 +9,7 @@ import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.List; import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @@ -18,6 +19,9 @@ import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MON @EqualsAndHashCode(callSuper = true) public class OvertimeApplicationPageReqVO extends PageParam { + @Schema(description = "团队视角下的申请人用户编号列表") + private List applicantIds; + @Schema(description = "关键词,匹配加班原因或加班内容", example = "上线") private String keyword; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java index d50e782..bc4cec4 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java @@ -5,8 +5,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; + @Schema(description = "管理后台 - 月报分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) public class MonthlyReportPageReqVO extends WorkReportBasePageReqVO { + + @Schema(description = "团队视角下的填报人用户编号列表") + private List reporterIds; } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java index 134fdfc..7a874ea 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java @@ -5,11 +5,16 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; + @Schema(description = "管理后台 - 项目半月报分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) public class ProjectReportPageReqVO extends WorkReportBasePageReqVO { + @Schema(description = "团队视角下的项目负责人用户编号列表") + private List projectOwnerIds; + @Schema(description = "项目编号") private Long projectId; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/TeamWorkReportController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/TeamWorkReportController.java new file mode 100644 index 0000000..c6b3004 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/TeamWorkReportController.java @@ -0,0 +1,46 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.team; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.module.project.constant.TeamDashboardConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryRespVO; +import com.njcn.rdms.module.project.service.workreport.team.TeamWorkReportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 团队工作报告") +@RestController +@RequestMapping("/project/work-reports/team") +@Validated +public class TeamWorkReportController { + + @Resource + private TeamWorkReportService teamWorkReportService; + + @GetMapping("/summary") + @Operation(summary = "获取团队工作报告统计") + @PreAuthorize("@ss.hasPermission('" + TeamDashboardConstants.PERMISSION + "')") + public CommonResult getSummary(@Valid TeamReportSummaryReqVO reqVO) { + return success(teamWorkReportService.getSummary(reqVO)); + } + + @PostMapping("/remind") + @Operation(summary = "催办团队工作报告") + @PreAuthorize("@ss.hasPermission('" + TeamDashboardConstants.PERMISSION + "')") + public CommonResult remind(@Valid @RequestBody TeamReportRemindReqVO reqVO) { + return success(teamWorkReportService.remind(reqVO)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindReqVO.java new file mode 100644 index 0000000..dbd3c82 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindReqVO.java @@ -0,0 +1,23 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.team.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 团队工作报告催办 Request VO") +@Data +public class TeamReportRemindReqVO { + + @Schema(description = "报告类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "weekly") + @NotBlank(message = "报告类型不能为空") + private String reportType; + + @Schema(description = "周期主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "weekly-2026-06-08-2026-06-14") + @NotBlank(message = "周期主键不能为空") + private String periodKey; + + @Schema(description = "催办用户 ID 列表;不传则催办全部待提交用户") + private List userIds; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindRespVO.java new file mode 100644 index 0000000..5a8c50e --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportRemindRespVO.java @@ -0,0 +1,12 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.team.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 团队工作报告催办 Response VO") +@Data +public class TeamReportRemindRespVO { + + @Schema(description = "实际催办人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "4") + private Integer remindedCount; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryReqVO.java new file mode 100644 index 0000000..38bac61 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryReqVO.java @@ -0,0 +1,18 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.team.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Schema(description = "管理后台 - 团队工作报告统计 Request VO") +@Data +public class TeamReportSummaryReqVO { + + @Schema(description = "报告类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "weekly") + @NotBlank(message = "报告类型不能为空") + private String reportType; + + @Schema(description = "周期主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "weekly-2026-06-08-2026-06-14") + @NotBlank(message = "周期主键不能为空") + private String periodKey; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryRespVO.java new file mode 100644 index 0000000..1d44481 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/team/vo/TeamReportSummaryRespVO.java @@ -0,0 +1,39 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.team.vo; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 团队工作报告统计 Response VO") +@Data +public class TeamReportSummaryRespVO { + + @Schema(description = "应填人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "18") + private Integer totalShouldSubmit; + + @Schema(description = "已提交人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12") + private Integer submittedCount; + + @Schema(description = "未提交人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "6") + private Integer unsubmittedCount; + + @Schema(description = "待审批人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") + private Integer pendingApprovalCount; + + @Schema(description = "未提交人员列表") + private List unsubmittedUsers; + + @Schema(description = "未提交人员") + @Data + public static class PendingUser { + + @Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2042074259501088770") + @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = ToStringSerializer.class) + private Long userId; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "戴坤") + private String userNickname; + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java index 24ba9aa..eaddd5e 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java @@ -5,11 +5,16 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; + @Schema(description = "管理后台 - 周报分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) public class WeeklyReportPageReqVO extends WorkReportBasePageReqVO { + @Schema(description = "团队视角下的填报人用户编号列表") + private List reporterIds; + @Schema(description = "是否出差") private Boolean isBusinessTrip; } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java index 7ea3f30..015b5a3 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java @@ -10,6 +10,7 @@ import org.apache.ibatis.annotations.Mapper; import org.springframework.util.StringUtils; import java.util.Collection; +import java.util.List; @Mapper public interface OvertimeApplicationMapper extends BaseMapperX { @@ -22,6 +23,22 @@ public interface OvertimeApplicationMapper extends BaseMapperX selectMyPage(Collection applicantIds, + OvertimeApplicationPageReqVO reqVO, + Collection allowedStatusCodes) { + if (applicantIds == null || applicantIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX queryWrapper = buildPageQuery(reqVO); + queryWrapper.in(OvertimeApplicationDO::getApplicantId, applicantIds); + if (allowedStatusCodes != null) { + queryWrapper.in(OvertimeApplicationDO::getStatusCode, allowedStatusCodes); + } + queryWrapper.orderByDesc(OvertimeApplicationDO::getSubmitTime) + .orderByDesc(OvertimeApplicationDO::getId); + return selectPage(reqVO, queryWrapper); + } + default PageResult selectApprovalPage(Long approverId, OvertimeApplicationPageReqVO reqVO) { LambdaQueryWrapperX queryWrapper = buildPageQuery(reqVO); queryWrapper.eq(OvertimeApplicationDO::getApproverId, approverId); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java index 6f8e15d..194115d 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java @@ -75,6 +75,20 @@ public interface ProjectMapper extends BaseMapperX { return selectList(queryWrapper); } + default List selectListByManagerUserIdsAndStatusCodesNotIn(Collection managerUserIds, + Collection statusCodes) { + if (managerUserIds == null || managerUserIds.isEmpty()) { + return List.of(); + } + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .in(ProjectDO::getManagerUserId, managerUserIds) + .orderByDesc(BaseDO::getCreateTime); + if (statusCodes != null && !statusCodes.isEmpty()) { + queryWrapper.notIn(ProjectDO::getStatusCode, statusCodes); + } + return selectList(queryWrapper); + } + default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) { ProjectDO update = new ProjectDO(); update.setStatusCode(toStatus); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java index 01f0732..4669489 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java @@ -23,6 +23,23 @@ public interface MonthlyReportMapper extends BaseMapperX { .eq(MonthlyReportDO::getPeriodKey, periodKey)); } + default List selectListByReporterIdsAndPeriodKey(Collection reporterIds, String periodKey, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() || !StringUtils.hasText(periodKey)) { + return List.of(); + } + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .in(MonthlyReportDO::getReporterId, reporterIds) + .eq(MonthlyReportDO::getPeriodKey, periodKey); + if (allowedStatusCodes != null) { + if (allowedStatusCodes.isEmpty()) { + return List.of(); + } + wrapper.in(MonthlyReportDO::getStatusCode, allowedStatusCodes); + } + return selectList(wrapper); + } + default PageResult selectReporterPage(Long reporterId, MonthlyReportPageReqVO reqVO) { return selectReporterPage(reporterId, reqVO, null); } @@ -42,6 +59,21 @@ public interface MonthlyReportMapper extends BaseMapperX { return selectPage(reqVO, wrapper); } + default PageResult selectReporterPage(Collection reporterIds, MonthlyReportPageReqVO reqVO, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .in(MonthlyReportDO::getReporterId, reporterIds) + .orderByDesc(MonthlyReportDO::getPeriodStartDate) + .orderByDesc(MonthlyReportDO::getId); + if (allowedStatusCodes != null) { + wrapper.in(MonthlyReportDO::getStatusCode, allowedStatusCodes); + } + return selectPage(reqVO, wrapper); + } + default PageResult selectApprovalPage(Long supervisorUserId, MonthlyReportPageReqVO reqVO) { LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) .eq(MonthlyReportDO::getSupervisorUserId, supervisorUserId) diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java index 7281a1d..17403b3 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java @@ -29,6 +29,24 @@ public interface ProjectReportMapper extends BaseMapperX { .eq(ProjectReportDO::getProjectOwnerId, projectOwnerId)); } + default List selectListByProjectOwnerIdsAndPeriodKey(Collection projectOwnerIds, + String periodKey, + Collection allowedStatusCodes) { + if (projectOwnerIds == null || projectOwnerIds.isEmpty() || !StringUtils.hasText(periodKey)) { + return List.of(); + } + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .in(ProjectReportDO::getProjectOwnerId, projectOwnerIds) + .eq(ProjectReportDO::getPeriodKey, periodKey); + if (allowedStatusCodes != null) { + if (allowedStatusCodes.isEmpty()) { + return List.of(); + } + wrapper.in(ProjectReportDO::getStatusCode, allowedStatusCodes); + } + return selectList(wrapper); + } + default PageResult selectReporterPage(Long reporterId, ProjectReportPageReqVO reqVO) { return selectReporterPage(reporterId, reqVO, null); } @@ -48,6 +66,21 @@ public interface ProjectReportMapper extends BaseMapperX { return selectPage(reqVO, wrapper); } + default PageResult selectReporterPage(Collection reporterIds, ProjectReportPageReqVO reqVO, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .in(ProjectReportDO::getProjectOwnerId, reporterIds) + .orderByDesc(ProjectReportDO::getPeriodStartDate) + .orderByDesc(ProjectReportDO::getId); + if (allowedStatusCodes != null) { + wrapper.in(ProjectReportDO::getStatusCode, allowedStatusCodes); + } + return selectPage(reqVO, wrapper); + } + default PageResult selectApprovalPage(Long supervisorUserId, ProjectReportPageReqVO reqVO) { LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) .eq(ProjectReportDO::getSupervisorUserId, supervisorUserId) diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java index cd05f10..6ffb7db 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java @@ -23,6 +23,23 @@ public interface WeeklyReportMapper extends BaseMapperX { .eq(WeeklyReportDO::getPeriodKey, periodKey)); } + default List selectListByReporterIdsAndPeriodKey(Collection reporterIds, String periodKey, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() || !StringUtils.hasText(periodKey)) { + return List.of(); + } + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .in(WeeklyReportDO::getReporterId, reporterIds) + .eq(WeeklyReportDO::getPeriodKey, periodKey); + if (allowedStatusCodes != null) { + if (allowedStatusCodes.isEmpty()) { + return List.of(); + } + wrapper.in(WeeklyReportDO::getStatusCode, allowedStatusCodes); + } + return selectList(wrapper); + } + default PageResult selectReporterPage(Long reporterId, WeeklyReportPageReqVO reqVO) { return selectReporterPage(reporterId, reqVO, null); } @@ -42,6 +59,21 @@ public interface WeeklyReportMapper extends BaseMapperX { return selectPage(reqVO, wrapper); } + default PageResult selectReporterPage(Collection reporterIds, WeeklyReportPageReqVO reqVO, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .in(WeeklyReportDO::getReporterId, reporterIds) + .orderByDesc(WeeklyReportDO::getPeriodStartDate) + .orderByDesc(WeeklyReportDO::getId); + if (allowedStatusCodes != null) { + wrapper.in(WeeklyReportDO::getStatusCode, allowedStatusCodes); + } + return selectPage(reqVO, wrapper); + } + default PageResult selectApprovalPage(Long supervisorUserId, WeeklyReportPageReqVO reqVO) { LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) .eq(WeeklyReportDO::getSupervisorUserId, supervisorUserId) diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/notify/NotifyTemplateCodeConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/notify/NotifyTemplateCodeConstants.java index bde7bbe..c505b8f 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/notify/NotifyTemplateCodeConstants.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/notify/NotifyTemplateCodeConstants.java @@ -25,4 +25,7 @@ public class NotifyTemplateCodeConstants { /** 逾期提醒-协办人:等级低一等的弱化文案 */ public static final String DUE_ALERT_OVERDUE_ASSIGNEE = "due_alert_overdue_assignee"; + /** 工作报告团队催办:主管催办下属提交指定周期工作报告 */ + public static final String WORK_REPORT_TEAM_REMIND = "work_report_team_remind"; + } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java index 34cc379..8c8028e 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java @@ -32,6 +32,7 @@ import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper; import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper; import com.njcn.rdms.module.project.enums.ErrorCodeConstants; import com.njcn.rdms.module.project.service.status.StatusActionTextResolver; +import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService; import com.njcn.rdms.module.system.api.user.AdminUserApi; import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -46,6 +47,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Collection; import java.util.function.Function; import java.util.stream.Collectors; @@ -55,6 +57,11 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil @Service public class OvertimeApplicationServiceImpl implements OvertimeApplicationService { + private static final List TEAM_VISIBLE_STATUS_CODES = List.of( + OvertimeApplicationConstants.STATUS_PENDING, + OvertimeApplicationConstants.STATUS_APPROVED, + OvertimeApplicationConstants.STATUS_REJECTED); + @Resource private OvertimeApplicationMapper overtimeApplicationMapper; @Resource @@ -71,6 +78,8 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic private StatusActionTextResolver statusActionTextResolver; @Resource private AdminUserApi adminUserApi; + @Resource + private TeamDashboardAccessService teamDashboardAccessService; @Override @Transactional(rollbackFor = Exception.class) @@ -185,7 +194,13 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic @Override public PageResult getMyPage(OvertimeApplicationPageReqVO reqVO) { Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); - PageResult page = overtimeApplicationMapper.selectMyPage(loginUserId, reqVO); + PageResult page; + if (reqVO.getApplicantIds() != null) { + List applicantIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(reqVO.getApplicantIds()); + page = overtimeApplicationMapper.selectMyPage(applicantIds, reqVO, TEAM_VISIBLE_STATUS_CODES); + } else { + page = overtimeApplicationMapper.selectMyPage(loginUserId, reqVO); + } return BeanUtils.toBean(page, OvertimeApplicationRespVO.class, this::applyStatusView); } @@ -368,7 +383,8 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic OvertimeApplicationDO application = validateApplicationExists(id); Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); if (!Objects.equals(application.getApplicantId(), loginUserId) - && !Objects.equals(application.getApproverId(), loginUserId)) { + && !Objects.equals(application.getApproverId(), loginUserId) + && !teamDashboardAccessService.canReadSubordinateUser(application.getApplicantId())) { throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_READ_FORBIDDEN); } return application; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeService.java new file mode 100644 index 0000000..88eebb1 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeService.java @@ -0,0 +1,9 @@ +package com.njcn.rdms.module.project.service.overtime.team; + +import com.njcn.rdms.module.project.controller.admin.overtime.team.vo.TeamOvertimeSummaryReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.team.vo.TeamOvertimeSummaryRespVO; + +public interface TeamOvertimeService { + + TeamOvertimeSummaryRespVO getSummary(TeamOvertimeSummaryReqVO reqVO); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeServiceImpl.java new file mode 100644 index 0000000..f57cfaf --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/team/TeamOvertimeServiceImpl.java @@ -0,0 +1,86 @@ +package com.njcn.rdms.module.project.service.overtime.team; + +import com.njcn.rdms.framework.common.pojo.PageParam; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants; +import com.njcn.rdms.module.project.controller.admin.overtime.team.vo.TeamOvertimeSummaryReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.team.vo.TeamOvertimeSummaryRespVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; +import com.njcn.rdms.module.project.service.overtime.OvertimeApplicationService; +import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +@Service +public class TeamOvertimeServiceImpl implements TeamOvertimeService { + + private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM"); + + @Resource + private TeamDashboardAccessService teamDashboardAccessService; + @Resource + private OvertimeApplicationService overtimeApplicationService; + + @Override + public TeamOvertimeSummaryRespVO getSummary(TeamOvertimeSummaryReqVO reqVO) { + teamDashboardAccessService.validateTeamDashboardPermission(); + List subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds(); + YearMonth month = parseMonth(reqVO == null ? null : reqVO.getMonth()); + + TeamOvertimeSummaryRespVO respVO = new TeamOvertimeSummaryRespVO(); + respVO.setMonth(month.format(MONTH_FORMATTER)); + if (subordinateIds.isEmpty()) { + respVO.setTotalApplicationCount(0); + respVO.setPendingCount(0); + respVO.setApprovedCount(0); + respVO.setRejectedCount(0); + return respVO; + } + + OvertimeApplicationPageReqVO pageReqVO = new OvertimeApplicationPageReqVO(); + pageReqVO.setApplicantIds(subordinateIds); + pageReqVO.setPageNo(1); + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + pageReqVO.setOvertimeDate(new LocalDate[]{month.atDay(1), month.atEndOfMonth()}); + PageResult page = overtimeApplicationService.getMyPage(pageReqVO); + + int pendingCount = 0; + int approvedCount = 0; + int rejectedCount = 0; + for (OvertimeApplicationRespVO item : page.getList()) { + if (OvertimeApplicationConstants.STATUS_PENDING.equals(item.getStatusCode())) { + pendingCount++; + } else if (OvertimeApplicationConstants.STATUS_APPROVED.equals(item.getStatusCode())) { + approvedCount++; + } else if (OvertimeApplicationConstants.STATUS_REJECTED.equals(item.getStatusCode())) { + rejectedCount++; + } + } + respVO.setTotalApplicationCount(page.getList().size()); + respVO.setPendingCount(pendingCount); + respVO.setApprovedCount(approvedCount); + respVO.setRejectedCount(rejectedCount); + return respVO; + } + + private YearMonth parseMonth(String month) { + if (!StringUtils.hasText(month)) { + return YearMonth.now(); + } + try { + return YearMonth.parse(month, MONTH_FORMATTER); + } catch (DateTimeParseException ex) { + throw invalidParamException("统计月份格式不正确,应为 yyyy-MM"); + } + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessService.java new file mode 100644 index 0000000..f72ae9c --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessService.java @@ -0,0 +1,43 @@ +package com.njcn.rdms.module.project.service.team; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public interface TeamDashboardAccessService { + + /** + * 校验当前用户具备团队视角权限。 + */ + void validateTeamDashboardPermission(); + + /** + * 获取当前登录用户全部有效下属(不含本人)。 + * + * @return 下属 ID 列表 + */ + List getAllSubordinateUserIds(); + + /** + * 校验并解析团队查询的目标用户 ID。 + * + * @param candidateUserIds 前端传入的目标用户 ID;为空表示全部下属 + * @return 校验后的目标用户 ID(不含本人) + */ + List resolveRequestedSubordinateUserIds(Collection candidateUserIds); + + /** + * 判断当前登录用户是否可读取指定工作报告/加班申请所属人员的数据。 + * + * @param userId 目标人员 ID + * @return 是否可读 + */ + boolean canReadSubordinateUser(Long userId); + + /** + * 获取当前登录用户下属集合。 + * + * @return 下属集合 + */ + Set getSubordinateUserIdSet(); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessServiceImpl.java new file mode 100644 index 0000000..cafdb36 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/team/TeamDashboardAccessServiceImpl.java @@ -0,0 +1,112 @@ +package com.njcn.rdms.module.project.service.team; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.security.core.service.SecurityFrameworkService; +import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; +import com.njcn.rdms.module.project.constant.TeamDashboardConstants; +import com.njcn.rdms.module.project.enums.ErrorCodeConstants; +import com.njcn.rdms.module.system.api.user.AdminUserApi; +import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; + +@Service +public class TeamDashboardAccessServiceImpl implements TeamDashboardAccessService { + + @Resource + private UserManagementRelationApi userManagementRelationApi; + @Resource + private AdminUserApi adminUserApi; + @Resource + private SecurityFrameworkService securityFrameworkService; + + @Override + public void validateTeamDashboardPermission() { + if (!securityFrameworkService.hasPermission(TeamDashboardConstants.PERMISSION)) { + throw exception(ErrorCodeConstants.TEAM_DASHBOARD_PERMISSION_REQUIRED); + } + } + + @Override + public List getAllSubordinateUserIds() { + return new ArrayList<>(getSubordinateUserIdSet()); + } + + @Override + public List resolveRequestedSubordinateUserIds(Collection candidateUserIds) { + validateTeamDashboardPermission(); + Set allSubordinates = getSubordinateUserIdSet(); + if (allSubordinates.isEmpty()) { + return Collections.emptyList(); + } + if (candidateUserIds == null || candidateUserIds.isEmpty()) { + return new ArrayList<>(allSubordinates); + } + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + LinkedHashSet resolved = new LinkedHashSet<>(); + for (Long candidateUserId : candidateUserIds) { + if (candidateUserId == null || Objects.equals(candidateUserId, loginUserId)) { + continue; + } + if (!allSubordinates.contains(candidateUserId)) { + throw exception(ErrorCodeConstants.TEAM_DASHBOARD_SUBORDINATE_SCOPE_INVALID); + } + resolved.add(candidateUserId); + } + return new ArrayList<>(resolved); + } + + @Override + public boolean canReadSubordinateUser(Long userId) { + if (userId == null) { + return false; + } + if (!securityFrameworkService.hasPermission(TeamDashboardConstants.PERMISSION)) { + return false; + } + return getSubordinateUserIdSet().contains(userId); + } + + @Override + public Set getSubordinateUserIdSet() { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (loginUserId == null) { + return Collections.emptySet(); + } + CommonResult> result = userManagementRelationApi.getAllSubordinateUserIds(loginUserId); + Set rawIds = result == null || result.getCheckedData() == null + ? Collections.emptySet() + : result.getCheckedData(); + if (rawIds.isEmpty()) { + return Collections.emptySet(); + } + LinkedHashSet uniqueIds = new LinkedHashSet<>(rawIds); + uniqueIds.remove(loginUserId); + if (uniqueIds.isEmpty()) { + return Collections.emptySet(); + } + CommonResult> userResult = adminUserApi.getUserList(uniqueIds); + List users = userResult == null || userResult.getCheckedData() == null + ? Collections.emptyList() + : userResult.getCheckedData(); + LinkedHashSet availableIds = new LinkedHashSet<>(); + for (AdminUserRespDTO user : users) { + if (user == null || user.getId() == null || !Objects.equals(user.getStatus(), 0)) { + continue; + } + availableIds.add(user.getId()); + } + return availableIds; + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/autogen/WorkReportAutoGenerateService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/autogen/WorkReportAutoGenerateService.java index b1489f0..3753527 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/autogen/WorkReportAutoGenerateService.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/autogen/WorkReportAutoGenerateService.java @@ -7,9 +7,23 @@ import com.njcn.rdms.framework.common.util.json.JsonUtils; import com.njcn.rdms.framework.security.core.LoginUser; import com.njcn.rdms.module.project.constant.ProjectObjectConstants; import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportDefaultDraftReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportDefaultDraftReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportItemReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRespVO; import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportSaveReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportDefaultDraftReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRespVO; import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportSaveReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportTravelSegmentReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportTravelSegmentRespVO; import com.njcn.rdms.module.project.dal.dataobject.job.JobRunLogDO; import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO; import com.njcn.rdms.module.project.dal.mysql.job.JobRunLogMapper; @@ -20,6 +34,7 @@ import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMa import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapper; import com.njcn.rdms.module.project.enums.ErrorCodeConstants; import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService; +import com.njcn.rdms.module.project.service.workreport.defaultdraft.WorkReportDefaultDraftService; import com.njcn.rdms.module.system.api.dept.DeptApi; import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO; import com.njcn.rdms.module.system.api.user.AdminUserApi; @@ -73,6 +88,8 @@ public class WorkReportAutoGenerateService { @Resource private WorkReportCommonService workReportCommonService; @Resource + private WorkReportDefaultDraftService workReportDefaultDraftService; + @Resource private WeeklyReportMapper weeklyReportMapper; @Resource private MonthlyReportMapper monthlyReportMapper; @@ -179,15 +196,7 @@ public class WorkReportAutoGenerateService { result.setSkipCount(result.getSkipCount() + 1); return; } - WeeklyReportSaveReqVO reqVO = new WeeklyReportSaveReqVO(); - reqVO.setPeriodKey(period.getPeriodKey()); - reqVO.setPeriodLabel(period.getPeriodLabel()); - reqVO.setPeriodStartDate(period.getPeriodStartDate()); - reqVO.setPeriodEndDate(period.getPeriodEndDate()); - reqVO.setIsBusinessTrip(Boolean.FALSE); - reqVO.setReviewItems(Collections.emptyList()); - reqVO.setPlanItems(Collections.emptyList()); - reqVO.setTravelSegments(Collections.emptyList()); + WeeklyReportSaveReqVO reqVO = runAs(buildLoginUser(user), () -> buildWeeklySaveReqVOFromDefaultDraft(period)); runAs(buildLoginUser(user), () -> workReportCommonService.createWeeklyReport(reqVO, user.getId())); result.setSuccessCount(result.getSuccessCount() + 1); }, () -> "userId=" + user.getId()); @@ -200,13 +209,7 @@ public class WorkReportAutoGenerateService { result.setSkipCount(result.getSkipCount() + 1); return; } - MonthlyReportSaveReqVO reqVO = new MonthlyReportSaveReqVO(); - reqVO.setPeriodKey(period.getPeriodKey()); - reqVO.setPeriodLabel(period.getPeriodLabel()); - reqVO.setPeriodStartDate(period.getPeriodStartDate()); - reqVO.setPeriodEndDate(period.getPeriodEndDate()); - reqVO.setReviewItems(Collections.emptyList()); - reqVO.setPlanItems(Collections.emptyList()); + MonthlyReportSaveReqVO reqVO = runAs(buildLoginUser(user), () -> buildMonthlySaveReqVOFromDefaultDraft(period)); runAs(buildLoginUser(user), () -> workReportCommonService.createMonthlyReport(reqVO, user.getId())); result.setSuccessCount(result.getSuccessCount() + 1); }, () -> "userId=" + user.getId()); @@ -220,20 +223,156 @@ public class WorkReportAutoGenerateService { result.setSkipCount(result.getSkipCount() + 1); return; } - ProjectReportSaveReqVO reqVO = new ProjectReportSaveReqVO(); - reqVO.setProjectId(candidate.projectId()); - reqVO.setPeriodKey(period.getPeriodKey()); - reqVO.setPeriodLabel(period.getPeriodLabel()); - reqVO.setPeriodStartDate(period.getPeriodStartDate()); - reqVO.setPeriodEndDate(period.getPeriodEndDate()); - reqVO.setFlag(period.getFlag()); - reqVO.setCurrentItems(Collections.emptyList()); - reqVO.setNextItems(Collections.emptyList()); + ProjectReportSaveReqVO reqVO = runAs(buildLoginUser(candidate.user()), + () -> buildProjectSaveReqVOFromDefaultDraft(candidate.projectId(), period)); runAs(buildLoginUser(candidate.user()), () -> workReportCommonService.createProjectReport(reqVO, candidate.user().getId())); result.setSuccessCount(result.getSuccessCount() + 1); }, () -> "projectId=" + candidate.projectId() + ", userId=" + candidate.user().getId()); } + private WeeklyReportSaveReqVO buildWeeklySaveReqVOFromDefaultDraft(AutoGenPeriod period) { + WeeklyReportDefaultDraftReqVO draftReqVO = new WeeklyReportDefaultDraftReqVO(); + draftReqVO.setPeriodKey(period.getPeriodKey()); + draftReqVO.setPeriodLabel(period.getPeriodLabel()); + draftReqVO.setPeriodStartDate(period.getPeriodStartDate()); + draftReqVO.setPeriodEndDate(period.getPeriodEndDate()); + WeeklyReportRespVO draft = workReportDefaultDraftService.previewWeeklyDefaultDraft(draftReqVO); + + WeeklyReportSaveReqVO reqVO = new WeeklyReportSaveReqVO(); + reqVO.setPeriodKey(draft.getPeriodKey()); + reqVO.setPeriodLabel(draft.getPeriodLabel()); + reqVO.setPeriodStartDate(draft.getPeriodStartDate()); + reqVO.setPeriodEndDate(draft.getPeriodEndDate()); + reqVO.setIsBusinessTrip(Boolean.TRUE.equals(draft.getIsBusinessTrip())); + reqVO.setReviewItems(toReviewItemReqList(draft.getReviewItems())); + reqVO.setPlanItems(toPlanItemReqList(draft.getPlanItems())); + reqVO.setTravelSegments(toTravelSegmentReqList(draft.getTravelSegments())); + return reqVO; + } + + private MonthlyReportSaveReqVO buildMonthlySaveReqVOFromDefaultDraft(AutoGenPeriod period) { + MonthlyReportDefaultDraftReqVO draftReqVO = new MonthlyReportDefaultDraftReqVO(); + draftReqVO.setPeriodKey(period.getPeriodKey()); + draftReqVO.setPeriodLabel(period.getPeriodLabel()); + draftReqVO.setPeriodStartDate(period.getPeriodStartDate()); + draftReqVO.setPeriodEndDate(period.getPeriodEndDate()); + MonthlyReportRespVO draft = workReportDefaultDraftService.previewMonthlyDefaultDraft(draftReqVO); + + MonthlyReportSaveReqVO reqVO = new MonthlyReportSaveReqVO(); + reqVO.setPeriodKey(draft.getPeriodKey()); + reqVO.setPeriodLabel(draft.getPeriodLabel()); + reqVO.setPeriodStartDate(draft.getPeriodStartDate()); + reqVO.setPeriodEndDate(draft.getPeriodEndDate()); + reqVO.setReviewItems(toReviewItemReqList(draft.getReviewItems())); + reqVO.setPlanItems(toPlanItemReqList(draft.getPlanItems())); + return reqVO; + } + + private ProjectReportSaveReqVO buildProjectSaveReqVOFromDefaultDraft(Long projectId, AutoGenPeriod period) { + ProjectReportDefaultDraftReqVO draftReqVO = new ProjectReportDefaultDraftReqVO(); + draftReqVO.setPeriodKey(period.getPeriodKey()); + draftReqVO.setPeriodLabel(period.getPeriodLabel()); + draftReqVO.setPeriodStartDate(period.getPeriodStartDate()); + draftReqVO.setPeriodEndDate(period.getPeriodEndDate()); + draftReqVO.setFlag(period.getFlag()); + ProjectReportRespVO draft = workReportDefaultDraftService.previewProjectDefaultDraft(projectId, draftReqVO); + + ProjectReportSaveReqVO reqVO = new ProjectReportSaveReqVO(); + reqVO.setProjectId(projectId); + reqVO.setPeriodKey(draft.getPeriodKey()); + reqVO.setPeriodLabel(draft.getPeriodLabel()); + reqVO.setPeriodStartDate(draft.getPeriodStartDate()); + reqVO.setPeriodEndDate(draft.getPeriodEndDate()); + reqVO.setFlag(draft.getFlag()); + reqVO.setProjectStatusDesc(draft.getProjectStatusDesc()); + reqVO.setProjectProgressPlan(draft.getProjectProgressPlan()); + reqVO.setProjectKeyPoints(draft.getProjectKeyPoints()); + reqVO.setProjectProblems(draft.getProjectProblems()); + reqVO.setCurrentItems(toProjectItemReqList(draft.getCurrentItems())); + reqVO.setNextItems(toProjectItemReqList(draft.getNextItems())); + return reqVO; + } + + private List toReviewItemReqList(List source) { + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(source.size()); + for (PersonalReportReviewItemRespVO item : source) { + if (item == null) { + continue; + } + PersonalReportReviewItemReqVO target = new PersonalReportReviewItemReqVO(); + target.setItemNumber(item.getItemNumber()); + target.setItemTitle(item.getItemTitle()); + target.setWorkHours(item.getWorkHours()); + target.setContentText(item.getContentText()); + target.setContentJson(item.getContentJson()); + target.setReflectionText(item.getReflectionText()); + result.add(target); + } + return result; + } + + private List toPlanItemReqList(List source) { + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(source.size()); + for (PersonalReportPlanItemRespVO item : source) { + if (item == null) { + continue; + } + PersonalReportPlanItemReqVO target = new PersonalReportPlanItemReqVO(); + target.setItemNumber(item.getItemNumber()); + target.setItemTitle(item.getItemTitle()); + target.setTargetText(item.getTargetText()); + target.setTargetJson(item.getTargetJson()); + target.setSupportNeed(item.getSupportNeed()); + result.add(target); + } + return result; + } + + private List toProjectItemReqList(List source) { + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(source.size()); + for (ProjectReportItemRespVO item : source) { + if (item == null) { + continue; + } + ProjectReportItemReqVO target = new ProjectReportItemReqVO(); + target.setItemTitle(item.getItemTitle()); + target.setWorkHours(item.getWorkHours()); + target.setPriorityCode(item.getPriorityCode()); + target.setProgressRate(item.getProgressRate()); + result.add(target); + } + return result; + } + + private List toTravelSegmentReqList(List source) { + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(source.size()); + for (WeeklyReportTravelSegmentRespVO item : source) { + if (item == null) { + continue; + } + WeeklyReportTravelSegmentReqVO target = new WeeklyReportTravelSegmentReqVO(); + target.setSort(item.getSort()); + target.setStartDate(item.getStartDate()); + target.setEndDate(item.getEndDate()); + target.setTravelDays(item.getTravelDays()); + target.setLocation(item.getLocation()); + result.add(target); + } + return result; + } + private void executeWithLock(String reportType, String periodKey, String subjectKey, AutoGenResult result, Runnable action, Supplier failContextSupplier) { RLock lock = redissonClient.getLock(String.format(LOCK_KEY_TEMPLATE, reportType, periodKey, subjectKey)); @@ -346,29 +485,31 @@ public class WorkReportAutoGenerateService { private AutoGenPeriod buildWeeklyPeriod(LocalDate today) { LocalDate periodStartDate = today.minusDays(today.getDayOfWeek().getValue() - 1L); LocalDate periodEndDate = periodStartDate.plusDays(6); - int week = periodStartDate.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR); - int year = periodStartDate.get(java.time.temporal.IsoFields.WEEK_BASED_YEAR); return new AutoGenPeriod() - .setPeriodKey(String.format("%d-W%02d", year, week)) - .setPeriodLabel(String.format("%d 年第 %02d 周", year, week)) + .setPeriodKey(String.format("weekly-%s-%s", periodStartDate, periodEndDate)) + .setPeriodLabel(String.format("%s 至 %s", periodStartDate, periodEndDate)) .setPeriodStartDate(periodStartDate) .setPeriodEndDate(periodEndDate); } private AutoGenPeriod buildMonthlyPeriod(LocalDate today) { + LocalDate periodStartDate = today.withDayOfMonth(1); + LocalDate periodEndDate = today.withDayOfMonth(today.lengthOfMonth()); return new AutoGenPeriod() - .setPeriodKey(String.format("%d-%02d", today.getYear(), today.getMonthValue())) - .setPeriodLabel(String.format("%d 年 %02d 月", today.getYear(), today.getMonthValue())) - .setPeriodStartDate(today.withDayOfMonth(1)) - .setPeriodEndDate(today.withDayOfMonth(today.lengthOfMonth())); + .setPeriodKey(String.format("monthly-%s-%s", periodStartDate, periodEndDate)) + .setPeriodLabel(String.format("%d-%02d", today.getYear(), today.getMonthValue())) + .setPeriodStartDate(periodStartDate) + .setPeriodEndDate(periodEndDate); } private AutoGenPeriod buildProjectFirstHalfPeriod(LocalDate today) { + LocalDate periodStartDate = today.withDayOfMonth(1); + LocalDate periodEndDate = today.withDayOfMonth(15); return new AutoGenPeriod() - .setPeriodKey(String.format("%d-%02d-01", today.getYear(), today.getMonthValue())) - .setPeriodLabel(String.format("%d 年 %02d 月 上半月", today.getYear(), today.getMonthValue())) - .setPeriodStartDate(today.withDayOfMonth(1)) - .setPeriodEndDate(today.withDayOfMonth(15)) + .setPeriodKey(String.format("project-%s-%s-1", periodStartDate, periodEndDate)) + .setPeriodLabel(String.format("%d-%02d 上半月", today.getYear(), today.getMonthValue())) + .setPeriodStartDate(periodStartDate) + .setPeriodEndDate(periodEndDate) .setFlag(1); } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java index ff8c467..f555a3b 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java @@ -48,6 +48,7 @@ import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapp import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportTravelSegmentMapper; import com.njcn.rdms.module.project.enums.ErrorCodeConstants; import com.njcn.rdms.module.project.service.status.StatusActionTextResolver; +import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService; import com.njcn.rdms.module.system.api.dept.DeptApi; import com.njcn.rdms.module.system.api.dept.PostApi; import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO; @@ -77,6 +78,10 @@ public class WorkReportCommonService { private static final List ALLOW_DELETE_STATUSES = List.of( WorkReportConstants.STATUS_DRAFT, WorkReportConstants.STATUS_REJECTED); + private static final List TEAM_VISIBLE_STATUS_CODES = List.of( + WorkReportConstants.STATUS_PENDING_APPROVAL, + WorkReportConstants.STATUS_APPROVED, + WorkReportConstants.STATUS_REJECTED); @Resource private WeeklyReportMapper weeklyReportMapper; @@ -124,6 +129,8 @@ public class WorkReportCommonService { private ProjectMapper projectMapper; @Resource private UserObjectRoleMapper userObjectRoleMapper; + @Resource + private TeamDashboardAccessService teamDashboardAccessService; public List getStatusDict() { return objectStatusModelMapper.selectListByObjectTypeEnabled(WorkReportConstants.STATUS_OBJECT_TYPE).stream() @@ -233,8 +240,13 @@ public class WorkReportCommonService { public PageResult getWeeklyReportPage(WeeklyReportPageReqVO reqVO) { Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); - PageResult pageResult = weeklyReportMapper.selectReporterPage(loginUserId, reqVO, - getEnabledStatusCodes()); + PageResult pageResult; + if (reqVO.getReporterIds() != null) { + List reporterIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(reqVO.getReporterIds()); + pageResult = weeklyReportMapper.selectReporterPage(reporterIds, reqVO, TEAM_VISIBLE_STATUS_CODES); + } else { + pageResult = weeklyReportMapper.selectReporterPage(loginUserId, reqVO, getEnabledStatusCodes()); + } return new PageResult<>(pageResult.getList().stream() .map(report -> toWeeklyRespVO(report, false)) .collect(Collectors.toList()), pageResult.getTotal()); @@ -392,8 +404,13 @@ public class WorkReportCommonService { public PageResult getMonthlyReportPage(MonthlyReportPageReqVO reqVO) { Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); - PageResult pageResult = monthlyReportMapper.selectReporterPage(loginUserId, reqVO, - getEnabledStatusCodes()); + PageResult pageResult; + if (reqVO.getReporterIds() != null) { + List reporterIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(reqVO.getReporterIds()); + pageResult = monthlyReportMapper.selectReporterPage(reporterIds, reqVO, TEAM_VISIBLE_STATUS_CODES); + } else { + pageResult = monthlyReportMapper.selectReporterPage(loginUserId, reqVO, getEnabledStatusCodes()); + } return new PageResult<>(pageResult.getList().stream() .map(report -> toMonthlyRespVO(report, false)) .collect(Collectors.toList()), pageResult.getTotal()); @@ -570,8 +587,13 @@ public class WorkReportCommonService { public PageResult getProjectReportPage(ProjectReportPageReqVO reqVO) { Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); - PageResult pageResult = projectReportMapper.selectReporterPage(loginUserId, reqVO, - getEnabledStatusCodes()); + PageResult pageResult; + if (reqVO.getProjectOwnerIds() != null) { + List projectOwnerIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(reqVO.getProjectOwnerIds()); + pageResult = projectReportMapper.selectReporterPage(projectOwnerIds, reqVO, TEAM_VISIBLE_STATUS_CODES); + } else { + pageResult = projectReportMapper.selectReporterPage(loginUserId, reqVO, getEnabledStatusCodes()); + } return new PageResult<>(pageResult.getList().stream() .map(report -> toProjectRespVO(report, false)) .collect(Collectors.toList()), pageResult.getTotal()); @@ -764,7 +786,9 @@ public class WorkReportCommonService { private void validateReadable(Long reporterId, Long supervisorUserId) { Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); - if (!Objects.equals(loginUserId, reporterId) && !Objects.equals(loginUserId, supervisorUserId)) { + if (!Objects.equals(loginUserId, reporterId) + && !Objects.equals(loginUserId, supervisorUserId) + && !teamDashboardAccessService.canReadSubordinateUser(reporterId)) { throw exception(ErrorCodeConstants.WORK_REPORT_READ_FORBIDDEN); } } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportService.java new file mode 100644 index 0000000..8798808 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportService.java @@ -0,0 +1,13 @@ +package com.njcn.rdms.module.project.service.workreport.team; + +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryRespVO; + +public interface TeamWorkReportService { + + TeamReportSummaryRespVO getSummary(TeamReportSummaryReqVO reqVO); + + TeamReportRemindRespVO remind(TeamReportRemindReqVO reqVO); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportServiceImpl.java new file mode 100644 index 0000000..59c1591 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/team/TeamWorkReportServiceImpl.java @@ -0,0 +1,283 @@ +package com.njcn.rdms.module.project.service.workreport.team; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryRespVO; +import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportDO; +import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper; +import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapper; +import com.njcn.rdms.module.project.framework.notify.NotifySendEvent; +import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants; +import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService; +import com.njcn.rdms.module.system.api.user.AdminUserApi; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; +import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants; +import jakarta.annotation.Resource; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +@Service +public class TeamWorkReportServiceImpl implements TeamWorkReportService { + + private static final List SUBMITTED_STATUS_CODES = List.of( + WorkReportConstants.STATUS_PENDING_APPROVAL, + WorkReportConstants.STATUS_APPROVED, + WorkReportConstants.STATUS_REJECTED); + + @Resource + private TeamDashboardAccessService teamDashboardAccessService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private WeeklyReportMapper weeklyReportMapper; + @Resource + private MonthlyReportMapper monthlyReportMapper; + @Resource + private ProjectReportMapper projectReportMapper; + @Resource + private ProjectMapper projectMapper; + @Resource + private ObjectStatusModelMapper objectStatusModelMapper; + @Resource + private ApplicationEventPublisher applicationEventPublisher; + + @Override + public TeamReportSummaryRespVO getSummary(TeamReportSummaryReqVO reqVO) { + teamDashboardAccessService.validateTeamDashboardPermission(); + ReportContext context = buildReportContext(normalizeReportType(reqVO.getReportType()), reqVO.getPeriodKey()); + TeamReportSummaryRespVO respVO = new TeamReportSummaryRespVO(); + respVO.setTotalShouldSubmit(context.expectedUserIds().size()); + respVO.setSubmittedCount(context.submittedUserIds().size()); + respVO.setPendingApprovalCount(context.pendingApprovalUserIds().size()); + List unsubmittedUsers = buildPendingUsers(context.expectedUserIds(), context.submittedUserIds()); + respVO.setUnsubmittedUsers(unsubmittedUsers); + respVO.setUnsubmittedCount(unsubmittedUsers.size()); + return respVO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public TeamReportRemindRespVO remind(TeamReportRemindReqVO reqVO) { + teamDashboardAccessService.validateTeamDashboardPermission(); + String reportType = normalizeReportType(reqVO.getReportType()); + ReportContext context = buildReportContext(reportType, reqVO.getPeriodKey()); + List remindUserIds = resolveRemindUserIds(reqVO.getUserIds(), context); + + if (!remindUserIds.isEmpty()) { + Map params = new HashMap<>(); + params.put("reportTypeName", reportTypeDisplayName(reportType)); + params.put("periodKey", reqVO.getPeriodKey()); + params.put("managerName", defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + applicationEventPublisher.publishEvent(NotifySendEvent.of(remindUserIds, + NotifyTemplateCodeConstants.WORK_REPORT_TEAM_REMIND, params, NotifyMessageLevelConstants.REMIND)); + } + + TeamReportRemindRespVO respVO = new TeamReportRemindRespVO(); + respVO.setRemindedCount(remindUserIds.size()); + return respVO; + } + + private ReportContext buildReportContext(String reportType, String periodKey) { + if (!StringUtils.hasText(periodKey)) { + throw invalidParamException("周期主键不能为空"); + } + if (WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) { + return buildProjectContext(periodKey); + } + List subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds(); + if (subordinateIds.isEmpty()) { + return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet()); + } + if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)) { + return buildWeeklyContext(periodKey, subordinateIds); + } + return buildMonthlyContext(periodKey, subordinateIds); + } + + private ReportContext buildWeeklyContext(String periodKey, List subordinateIds) { + List reports = weeklyReportMapper.selectListByReporterIdsAndPeriodKey( + subordinateIds, periodKey, SUBMITTED_STATUS_CODES); + Set submittedUserIds = new LinkedHashSet<>(); + Set pendingApprovalUserIds = new LinkedHashSet<>(); + for (WeeklyReportDO report : reports) { + if (report == null || report.getReporterId() == null) { + continue; + } + submittedUserIds.add(report.getReporterId()); + if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) { + pendingApprovalUserIds.add(report.getReporterId()); + } + } + return new ReportContext(subordinateIds, submittedUserIds, pendingApprovalUserIds); + } + + private ReportContext buildMonthlyContext(String periodKey, List subordinateIds) { + List reports = monthlyReportMapper.selectListByReporterIdsAndPeriodKey( + subordinateIds, periodKey, SUBMITTED_STATUS_CODES); + Set submittedUserIds = new LinkedHashSet<>(); + Set pendingApprovalUserIds = new LinkedHashSet<>(); + for (MonthlyReportDO report : reports) { + if (report == null || report.getReporterId() == null) { + continue; + } + submittedUserIds.add(report.getReporterId()); + if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) { + pendingApprovalUserIds.add(report.getReporterId()); + } + } + return new ReportContext(subordinateIds, submittedUserIds, pendingApprovalUserIds); + } + + private ReportContext buildProjectContext(String periodKey) { + List subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds(); + if (subordinateIds.isEmpty()) { + return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet()); + } + List activeProjects = loadActiveProjectsForSubordinates(subordinateIds); + if (activeProjects.isEmpty()) { + return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet()); + } + Map> projectsByOwner = activeProjects.stream() + .filter(Objects::nonNull) + .filter(project -> project.getManagerUserId() != null) + .collect(Collectors.groupingBy(ProjectDO::getManagerUserId, LinkedHashMap::new, Collectors.toList())); + LinkedHashSet expectedUserIds = new LinkedHashSet<>(projectsByOwner.keySet()); + List reports = projectReportMapper.selectListByProjectOwnerIdsAndPeriodKey( + expectedUserIds, periodKey, SUBMITTED_STATUS_CODES); + Map> submittedProjectsByOwner = new HashMap<>(); + Set submittedUserIds = new LinkedHashSet<>(); + Set pendingApprovalUserIds = new LinkedHashSet<>(); + for (ProjectReportDO report : reports) { + if (report == null || report.getProjectOwnerId() == null || report.getProjectId() == null) { + continue; + } + Long ownerId = report.getProjectOwnerId(); + if (!expectedUserIds.contains(ownerId)) { + continue; + } + if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) { + pendingApprovalUserIds.add(ownerId); + } + submittedProjectsByOwner.computeIfAbsent(ownerId, key -> new LinkedHashSet<>()).add(report.getProjectId()); + } + for (Map.Entry> entry : projectsByOwner.entrySet()) { + Long ownerId = entry.getKey(); + Set submittedProjectIds = submittedProjectsByOwner.getOrDefault(ownerId, Collections.emptySet()); + boolean allSubmitted = entry.getValue().stream() + .map(ProjectDO::getId) + .filter(Objects::nonNull) + .allMatch(submittedProjectIds::contains); + if (allSubmitted) { + submittedUserIds.add(ownerId); + } + } + return new ReportContext(new ArrayList<>(expectedUserIds), submittedUserIds, pendingApprovalUserIds); + } + + private List loadActiveProjectsForSubordinates(Collection subordinateIds) { + if (subordinateIds == null || subordinateIds.isEmpty()) { + return Collections.emptyList(); + } + List terminalStatusCodes = objectStatusModelMapper + .selectTerminalStatusCodesByObjectTypeEnabled(com.njcn.rdms.module.project.constant.ProjectObjectConstants.OBJECT_TYPE); + List allProjects = projectMapper.selectListByManagerUserIdsAndStatusCodesNotIn( + subordinateIds, terminalStatusCodes); + if (allProjects.isEmpty()) { + return Collections.emptyList(); + } + return allProjects.stream() + .filter(Objects::nonNull) + .filter(project -> project.getManagerUserId() != null) + .collect(Collectors.toList()); + } + + private List resolveRemindUserIds(List requestedUserIds, ReportContext context) { + LinkedHashSet unsubmittedUserIds = new LinkedHashSet<>(context.expectedUserIds()); + unsubmittedUserIds.removeAll(context.submittedUserIds()); + if (requestedUserIds == null) { + return new ArrayList<>(unsubmittedUserIds); + } + List validatedIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(requestedUserIds); + return validatedIds.stream() + .filter(unsubmittedUserIds::contains) + .collect(Collectors.toList()); + } + + private List buildPendingUsers(List expectedUserIds, + Set submittedUserIds) { + LinkedHashSet pendingIds = new LinkedHashSet<>(expectedUserIds); + pendingIds.removeAll(submittedUserIds); + if (pendingIds.isEmpty()) { + return Collections.emptyList(); + } + CommonResult> result = adminUserApi.getUserList(pendingIds); + List users = result == null ? null : result.getCheckedData(); + if (users == null || users.isEmpty()) { + users = Collections.emptyList(); + } + Map userMap = users.stream() + .filter(Objects::nonNull) + .filter(user -> user.getId() != null) + .collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left, LinkedHashMap::new)); + List respList = new ArrayList<>(); + for (Long pendingId : pendingIds) { + AdminUserRespDTO user = userMap.get(pendingId); + TeamReportSummaryRespVO.PendingUser item = new TeamReportSummaryRespVO.PendingUser(); + item.setUserId(pendingId); + item.setUserNickname(user == null ? "" : defaultText(user.getNickname())); + respList.add(item); + } + return respList; + } + + private String normalizeReportType(String reportType) { + if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType) + || WorkReportConstants.REPORT_TYPE_MONTHLY.equals(reportType) + || WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) { + return reportType; + } + throw invalidParamException("报告类型不合法"); + } + + private String reportTypeDisplayName(String reportType) { + return switch (reportType) { + case WorkReportConstants.REPORT_TYPE_WEEKLY -> "周报"; + case WorkReportConstants.REPORT_TYPE_MONTHLY -> "月报"; + case WorkReportConstants.REPORT_TYPE_PROJECT -> "项目半月报"; + default -> reportType; + }; + } + + private String defaultText(String text) { + return StringUtils.hasText(text) ? text.trim() : ""; + } + + private record ReportContext(List expectedUserIds, Set submittedUserIds, Set pendingApprovalUserIds) { + } +} diff --git a/rdms-project/rdms-project-boot/src/main/resources/application.yaml b/rdms-project/rdms-project-boot/src/main/resources/application.yaml index 411a064..00b477e 100644 --- a/rdms-project/rdms-project-boot/src/main/resources/application.yaml +++ b/rdms-project/rdms-project-boot/src/main/resources/application.yaml @@ -119,6 +119,6 @@ rdms: enabled: true cron: "0 0 12 1-31 * ?" scope: - dept-ids: [101] + dept-ids: [100] debug: false diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java index 7a73153..29c5428 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestParam; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; @FeignClient(name = ApiConstants.NAME) @Tag(name = "RPC 服务 - 用户管理链路") @@ -44,6 +45,11 @@ public interface UserManagementRelationApi { @Parameter(name = "ids", description = "关系编号数组", example = "1,2", required = true) CommonResult> getRelationList(@RequestParam("ids") Collection ids); + @GetMapping(PREFIX + "/all-subordinate-user-ids") + @Operation(summary = "获取某管理者全部下属用户 ID") + @Parameter(name = "managerUserId", description = "管理者用户 ID", example = "1", required = true) + CommonResult> getAllSubordinateUserIds(@RequestParam("managerUserId") Long managerUserId); + default Map getRelationMap(Collection ids) { if (CollUtil.isEmpty(ids)) { return MapUtil.empty(); diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java index 5953436..e492980 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; import static com.njcn.rdms.framework.common.pojo.CommonResult.success; @@ -56,4 +57,9 @@ public class UserManagementRelationApiImpl implements UserManagementRelationApi return success(BeanUtils.toBean(list, UserManagementRelationRespDTO.class)); } + @Override + public CommonResult> getAllSubordinateUserIds(Long managerUserId) { + return success(userManagementRelationService.getAllSubordinateUserIds(managerUserId)); + } + } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java index 8259a7f..ce3d5d9 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java @@ -5,6 +5,7 @@ import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.excel.core.util.ExcelUtils; import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserSimpleRespVO; +import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.MySubordinateTreeRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationQueryReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationSaveReqVO; @@ -211,6 +212,13 @@ public class UserManagementRelationController { return success(userManagementRelationService.getRelationTree(reqVO)); } + @GetMapping("/my-subordinate-tree") + @Operation(summary = "获取当前用户下属树") + @PreAuthorize("@ss.hasPermission('project:work-report:team-dashboard')") + public CommonResult getMySubordinateTree() { + return success(userManagementRelationService.getMySubordinateTree(null)); + } + /** * 导出用户管理链路 Excel diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/userManagementRelation/MySubordinateTreeRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/userManagementRelation/MySubordinateTreeRespVO.java new file mode 100644 index 0000000..88231e4 --- /dev/null +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/userManagementRelation/MySubordinateTreeRespVO.java @@ -0,0 +1,29 @@ +package com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 我的下属树 Response VO") +@Data +public class MySubordinateTreeRespVO { + + @Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2042074259501088770") + @JsonSerialize(using = ToStringSerializer.class) + private Long userId; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "戴坤") + private String userNickname; + + @Schema(description = "是否为根节点", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean isRoot; + + @Schema(description = "下属总人数(递归)", requiredMode = Schema.RequiredMode.REQUIRED, example = "18") + private Integer subordinateCount; + + @Schema(description = "子节点") + private List children; +} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java index f304f87..30e499f 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java @@ -2,6 +2,7 @@ package com.njcn.rdms.module.system.service.user; import cn.hutool.core.collection.CollUtil; import com.njcn.rdms.framework.common.util.collection.CollectionUtils; +import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.MySubordinateTreeRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationQueryReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationSaveReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationTreeRespVO; @@ -12,6 +13,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * 用户管理链路 Service 接口 @@ -108,6 +110,14 @@ public interface UserManagementRelationService { */ List getRelationTree(UserManagementRelationQueryReqVO reqVO); + /** + * 获取当前用户为根节点的下属树。 + * + * @param rootUserId 根用户 ID;为空时默认当前登录用户 + * @return 下属树 + */ + MySubordinateTreeRespVO getMySubordinateTree(Long rootUserId); + /** * 获取还未绑定直属上级的候选下级用户列表 * @@ -115,6 +125,14 @@ public interface UserManagementRelationService { */ List getCandidateSubordinateUsers(); + /** + * 获取管理者全部直接/间接下属 ID(不含本人)。 + * + * @param managerUserId 管理者用户 ID + * @return 全部下属用户 ID + */ + Set getAllSubordinateUserIds(Long managerUserId); + /** * 获得用户管理链路 Map * diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java index 27d61b8..7edbc52 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java @@ -7,6 +7,8 @@ import com.njcn.rdms.framework.common.enums.CommonStatusEnum; import com.njcn.rdms.framework.common.exception.ServiceException; import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; +import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.MySubordinateTreeRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationQueryReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationSaveReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.userManagementRelation.UserManagementRelationTreeRespVO; @@ -349,6 +351,44 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation return buildFullTree(context); } + @Override + public MySubordinateTreeRespVO getMySubordinateTree(Long rootUserId) { + Long resolvedRootUserId = rootUserId != null ? rootUserId : SecurityFrameworkUtils.getLoginUserId(); + if (resolvedRootUserId == null) { + return null; + } + TreeBuildContext context = buildTreeContext(new UserManagementRelationQueryReqVO()); + if (context == null) { + AdminUserDO self = adminUserService.getUser(resolvedRootUserId); + if (!adminUserService.isUserAvailable(self)) { + return null; + } + MySubordinateTreeRespVO root = new MySubordinateTreeRespVO(); + root.setUserId(self.getId()); + root.setUserNickname(self.getNickname()); + root.setIsRoot(Boolean.TRUE); + root.setSubordinateCount(0); + root.setChildren(Collections.emptyList()); + return root; + } + return buildMySubordinateTreeNode(resolvedRootUserId, true, context); + } + + @Override + public Set getAllSubordinateUserIds(Long managerUserId) { + if (managerUserId == null) { + return Collections.emptySet(); + } + TreeBuildContext context = buildTreeContext(new UserManagementRelationQueryReqVO()); + if (context == null) { + return Collections.emptySet(); + } + Set result = new LinkedHashSet<>(); + collectSubordinateIds(managerUserId, context, result); + result.remove(managerUserId); + return result; + } + /** * 通过某个用户的id,判断管理链路表中是否有该用户的记录 * 判断原则: @@ -636,6 +676,57 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation return node; } + private MySubordinateTreeRespVO buildMySubordinateTreeNode(Long userId, boolean root, TreeBuildContext context) { + AdminUserDO user = context.getUserMap().get(userId); + if (!adminUserService.isUserAvailable(user)) { + return null; + } + List children = new ArrayList<>(); + for (Long subordinateId : context.getManagerToSubordinatesMap().getOrDefault(userId, Collections.emptyList())) { + if (Objects.equals(subordinateId, userId)) { + continue; + } + MySubordinateTreeRespVO child = buildMySubordinateTreeNode(subordinateId, false, context); + if (child != null) { + children.add(child); + } + } + MySubordinateTreeRespVO node = new MySubordinateTreeRespVO(); + node.setUserId(user.getId()); + node.setUserNickname(user.getNickname()); + node.setIsRoot(root); + node.setChildren(children); + node.setSubordinateCount(sumSubordinateCount(children)); + return node; + } + + private int sumSubordinateCount(List children) { + int total = 0; + for (MySubordinateTreeRespVO child : children) { + if (child == null) { + continue; + } + total += 1; + total += child.getSubordinateCount() == null ? 0 : child.getSubordinateCount(); + } + return total; + } + + private void collectSubordinateIds(Long managerUserId, TreeBuildContext context, Set result) { + for (Long subordinateId : context.getManagerToSubordinatesMap().getOrDefault(managerUserId, Collections.emptyList())) { + if (Objects.equals(subordinateId, managerUserId)) { + continue; + } + AdminUserDO subordinate = context.getUserMap().get(subordinateId); + if (!adminUserService.isUserAvailable(subordinate)) { + continue; + } + if (result.add(subordinateId)) { + collectSubordinateIds(subordinateId, context, result); + } + } + } + /** * 统一时间有效性过滤条件: *