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 index ab63fa4..18b0497 100644 --- 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 @@ -2,11 +2,21 @@ package com.njcn.rdms.module.project.controller.admin.overtime.team.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; @Schema(description = "管理后台 - 团队加班申请统计 Request VO") @Data public class TeamOvertimeSummaryReqVO { - @Schema(description = "统计月份,不传默认当前月", example = "2026-06") - private String month; + @Schema(description = "加班日期区间起始(不传默认当月第一天)", example = "2026-06-01") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate overtimeDateStart; + + @Schema(description = "加班日期区间结束(不传默认当月最后一天)", example = "2026-06-30") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate overtimeDateEnd; } 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 index c16a6ea..6f1e802 100644 --- 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 @@ -3,12 +3,17 @@ package com.njcn.rdms.module.project.controller.admin.overtime.team.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.time.LocalDate; + @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) + private LocalDate overtimeDateStart; + + @Schema(description = "实际查询加班日期区间结束", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDate overtimeDateEnd; @Schema(description = "申请总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12") private Integer totalApplicationCount; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java index bc90347..f5e9561 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java @@ -1,10 +1,11 @@ package com.njcn.rdms.module.project.controller.admin.workreport.common; -import com.njcn.rdms.framework.common.util.http.HttpUtils; import com.njcn.rdms.module.project.service.workreport.export.WorkReportExportFile; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; public final class WorkReportExportResponseUtils { @@ -12,7 +13,8 @@ public final class WorkReportExportResponseUtils { } public static void write(HttpServletResponse response, WorkReportExportFile file) throws IOException { - response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(file.filename())); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + + URLEncoder.encode(file.filename(), StandardCharsets.UTF_8).replace("+", "%20")); response.setContentType(file.contentType()); response.setContentLength(file.content().length); response.getOutputStream().write(file.content()); 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 index 38bac61..a27e523 100644 --- 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 @@ -3,6 +3,11 @@ 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 org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; @Schema(description = "管理后台 - 团队工作报告统计 Request VO") @Data @@ -12,7 +17,14 @@ public class TeamReportSummaryReqVO { @NotBlank(message = "报告类型不能为空") private String reportType; - @Schema(description = "周期主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "weekly-2026-06-08-2026-06-14") - @NotBlank(message = "周期主键不能为空") + @Schema(description = "周期主键(单周期查询时使用,与日期区间二选一)", example = "weekly-2026-06-08-2026-06-14") private String periodKey; + + @Schema(description = "周期起始日期(区间查询时使用,与 periodKey 二选一)", example = "2026-06-15") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate periodStartDate; + + @Schema(description = "周期结束日期(区间查询时使用,与 periodKey 二选一)", example = "2026-06-28") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate periodEndDate; } 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 index 1d44481..e55a135 100644 --- 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 @@ -4,12 +4,19 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.time.LocalDate; import java.util.List; @Schema(description = "管理后台 - 团队工作报告统计 Response VO") @Data public class TeamReportSummaryRespVO { + @Schema(description = "实际查询周期起始日期") + private LocalDate periodStartDate; + + @Schema(description = "实际查询周期结束日期") + private LocalDate periodEndDate; + @Schema(description = "应填人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "18") private Integer totalShouldSubmit; 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 4669489..d21dd0c 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 @@ -10,6 +10,7 @@ import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyRep import org.apache.ibatis.annotations.Mapper; import org.springframework.util.StringUtils; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -24,7 +25,7 @@ public interface MonthlyReportMapper extends BaseMapperX { } default List selectListByReporterIdsAndPeriodKey(Collection reporterIds, String periodKey, - Collection allowedStatusCodes) { + Collection allowedStatusCodes) { if (reporterIds == null || reporterIds.isEmpty() || !StringUtils.hasText(periodKey)) { return List.of(); } @@ -40,12 +41,34 @@ public interface MonthlyReportMapper extends BaseMapperX { return selectList(wrapper); } + /** + * 按报告人 ID 列表与周期日期区间查询月报(periodStartDate 落在 [startDate, endDate] 内)。 + */ + default List selectListByReporterIdsAndPeriodDateRange(Collection reporterIds, + LocalDate startDate, LocalDate endDate, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty()) { + return List.of(); + } + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .in(MonthlyReportDO::getReporterId, reporterIds) + .geIfPresent(MonthlyReportDO::getPeriodStartDate, startDate) + .leIfPresent(MonthlyReportDO::getPeriodStartDate, endDate); + 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); } default PageResult selectReporterPage(Long reporterId, MonthlyReportPageReqVO reqVO, - Collection allowedStatusCodes) { + Collection allowedStatusCodes) { if (allowedStatusCodes != null && allowedStatusCodes.isEmpty()) { return new PageResult<>(List.of(), 0L); } @@ -60,8 +83,9 @@ public interface MonthlyReportMapper extends BaseMapperX { } default PageResult selectReporterPage(Collection reporterIds, MonthlyReportPageReqVO reqVO, - Collection allowedStatusCodes) { - if (reporterIds == null || reporterIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() + || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { return new PageResult<>(List.of(), 0L); } LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) @@ -90,9 +114,9 @@ public interface MonthlyReportMapper extends BaseMapperX { } default int updateSubmitFieldsByIdAndStatus(Long id, String fromStatus, String reporterDeptName, - String reporterPostName, Long supervisorUserId, - String supervisorName, String toStatus, LocalDateTime submitTime, - String updater) { + String reporterPostName, Long supervisorUserId, + String supervisorName, String toStatus, LocalDateTime submitTime, + String updater) { return update(null, new LambdaUpdateWrapper() .set(MonthlyReportDO::getReporterDeptName, reporterDeptName) .set(MonthlyReportDO::getReporterPostName, reporterPostName) @@ -110,8 +134,8 @@ public interface MonthlyReportMapper extends BaseMapperX { } default int updateApprovalFieldsByIdAndStatus(Long id, String fromStatus, String toStatus, - LocalDateTime approvalTime, String approvalComment, - String lastStatusReason, String updater) { + LocalDateTime approvalTime, String approvalComment, + String lastStatusReason, String updater) { return update(null, new LambdaUpdateWrapper() .set(MonthlyReportDO::getStatusCode, toStatus) .set(MonthlyReportDO::getApprovalTime, approvalTime) @@ -124,7 +148,7 @@ public interface MonthlyReportMapper extends BaseMapperX { } default int updateByIdAndStatusesAndReporterId(MonthlyReportDO update, Long id, Collection statuses, - Long reporterId) { + Long reporterId) { return update(update, new LambdaQueryWrapperX() .eq(MonthlyReportDO::getId, id) .eq(MonthlyReportDO::getReporterId, reporterId) 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 17403b3..35b959d 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 @@ -12,6 +12,7 @@ import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectRep import org.apache.ibatis.annotations.Mapper; import org.springframework.util.StringUtils; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -22,7 +23,7 @@ public interface ProjectReportMapper extends BaseMapperX { String JACKSON_TYPE_HANDLER_MAPPING = "typeHandler=" + JacksonTypeHandler.class.getCanonicalName(); default ProjectReportDO selectByProjectIdAndPeriodKeyAndProjectOwnerId(Long projectId, String periodKey, - Long projectOwnerId) { + Long projectOwnerId) { return selectOne(new LambdaQueryWrapperX() .eq(ProjectReportDO::getProjectId, projectId) .eq(ProjectReportDO::getPeriodKey, periodKey) @@ -30,8 +31,8 @@ public interface ProjectReportMapper extends BaseMapperX { } default List selectListByProjectOwnerIdsAndPeriodKey(Collection projectOwnerIds, - String periodKey, - Collection allowedStatusCodes) { + String periodKey, + Collection allowedStatusCodes) { if (projectOwnerIds == null || projectOwnerIds.isEmpty() || !StringUtils.hasText(periodKey)) { return List.of(); } @@ -47,12 +48,34 @@ public interface ProjectReportMapper extends BaseMapperX { return selectList(wrapper); } + /** + * 按项目负责人 ID 列表与周期日期区间查询项目半月报(periodStartDate 落在 [startDate, endDate] 内)。 + */ + default List selectListByProjectOwnerIdsAndPeriodDateRange(Collection projectOwnerIds, + LocalDate startDate, LocalDate endDate, + Collection allowedStatusCodes) { + if (projectOwnerIds == null || projectOwnerIds.isEmpty()) { + return List.of(); + } + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .in(ProjectReportDO::getProjectOwnerId, projectOwnerIds) + .geIfPresent(ProjectReportDO::getPeriodStartDate, startDate) + .leIfPresent(ProjectReportDO::getPeriodStartDate, endDate); + 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); } default PageResult selectReporterPage(Long reporterId, ProjectReportPageReqVO reqVO, - Collection allowedStatusCodes) { + Collection allowedStatusCodes) { if (allowedStatusCodes != null && allowedStatusCodes.isEmpty()) { return new PageResult<>(List.of(), 0L); } @@ -67,8 +90,9 @@ public interface ProjectReportMapper extends BaseMapperX { } default PageResult selectReporterPage(Collection reporterIds, ProjectReportPageReqVO reqVO, - Collection allowedStatusCodes) { - if (reporterIds == null || reporterIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() + || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { return new PageResult<>(List.of(), 0L); } LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) @@ -97,10 +121,10 @@ public interface ProjectReportMapper extends BaseMapperX { } default int updateSubmitFieldsByIdAndStatus(Long id, String fromStatus, String projectName, - Long projectOwnerId, String projectOwnerName, - List projectMemberSnapshot, - Long supervisorUserId, String supervisorName, String toStatus, - LocalDateTime submitTime, String updater) { + Long projectOwnerId, String projectOwnerName, + List projectMemberSnapshot, + Long supervisorUserId, String supervisorName, String toStatus, + LocalDateTime submitTime, String updater) { return update(null, new LambdaUpdateWrapper() .set(ProjectReportDO::getProjectName, projectName) .set(ProjectReportDO::getProjectOwnerId, projectOwnerId) @@ -120,8 +144,8 @@ public interface ProjectReportMapper extends BaseMapperX { } default int updateApprovalFieldsByIdAndStatus(Long id, String fromStatus, String toStatus, - LocalDateTime approvalTime, String approvalComment, - String lastStatusReason, String updater) { + LocalDateTime approvalTime, String approvalComment, + String lastStatusReason, String updater) { return update(null, new LambdaUpdateWrapper() .set(ProjectReportDO::getStatusCode, toStatus) .set(ProjectReportDO::getApprovalTime, approvalTime) @@ -134,7 +158,7 @@ public interface ProjectReportMapper extends BaseMapperX { } default int updateByIdAndStatusesAndReporterId(ProjectReportDO update, Long id, Collection statuses, - Long reporterId) { + Long reporterId) { return update(update, new LambdaQueryWrapperX() .eq(ProjectReportDO::getId, id) .eq(ProjectReportDO::getProjectOwnerId, reporterId) 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 6ffb7db..6b9f0bc 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 @@ -10,6 +10,7 @@ import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyRepor import org.apache.ibatis.annotations.Mapper; import org.springframework.util.StringUtils; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -24,7 +25,7 @@ public interface WeeklyReportMapper extends BaseMapperX { } default List selectListByReporterIdsAndPeriodKey(Collection reporterIds, String periodKey, - Collection allowedStatusCodes) { + Collection allowedStatusCodes) { if (reporterIds == null || reporterIds.isEmpty() || !StringUtils.hasText(periodKey)) { return List.of(); } @@ -40,12 +41,34 @@ public interface WeeklyReportMapper extends BaseMapperX { return selectList(wrapper); } + /** + * 按报告人 ID 列表与周期日期区间查询周报(periodStartDate 落在 [startDate, endDate] 内)。 + */ + default List selectListByReporterIdsAndPeriodDateRange(Collection reporterIds, + LocalDate startDate, LocalDate endDate, + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty()) { + return List.of(); + } + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .in(WeeklyReportDO::getReporterId, reporterIds) + .geIfPresent(WeeklyReportDO::getPeriodStartDate, startDate) + .leIfPresent(WeeklyReportDO::getPeriodStartDate, endDate); + 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); } default PageResult selectReporterPage(Long reporterId, WeeklyReportPageReqVO reqVO, - Collection allowedStatusCodes) { + Collection allowedStatusCodes) { if (allowedStatusCodes != null && allowedStatusCodes.isEmpty()) { return new PageResult<>(List.of(), 0L); } @@ -60,8 +83,9 @@ public interface WeeklyReportMapper extends BaseMapperX { } default PageResult selectReporterPage(Collection reporterIds, WeeklyReportPageReqVO reqVO, - Collection allowedStatusCodes) { - if (reporterIds == null || reporterIds.isEmpty() || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { + Collection allowedStatusCodes) { + if (reporterIds == null || reporterIds.isEmpty() + || (allowedStatusCodes != null && allowedStatusCodes.isEmpty())) { return new PageResult<>(List.of(), 0L); } LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) @@ -90,9 +114,9 @@ public interface WeeklyReportMapper extends BaseMapperX { } default int updateSubmitFieldsByIdAndStatus(Long id, String fromStatus, String reporterDeptName, - String reporterPostName, Long supervisorUserId, - String supervisorName, String toStatus, LocalDateTime submitTime, - String updater) { + String reporterPostName, Long supervisorUserId, + String supervisorName, String toStatus, LocalDateTime submitTime, + String updater) { return update(null, new LambdaUpdateWrapper() .set(WeeklyReportDO::getReporterDeptName, reporterDeptName) .set(WeeklyReportDO::getReporterPostName, reporterPostName) @@ -110,8 +134,8 @@ public interface WeeklyReportMapper extends BaseMapperX { } default int updateApprovalFieldsByIdAndStatus(Long id, String fromStatus, String toStatus, - LocalDateTime approvalTime, String approvalComment, - String lastStatusReason, String updater) { + LocalDateTime approvalTime, String approvalComment, + String lastStatusReason, String updater) { return update(null, new LambdaUpdateWrapper() .set(WeeklyReportDO::getStatusCode, toStatus) .set(WeeklyReportDO::getApprovalTime, approvalTime) @@ -124,7 +148,7 @@ public interface WeeklyReportMapper extends BaseMapperX { } default int updateByIdAndStatusesAndReporterId(WeeklyReportDO update, Long id, Collection statuses, - Long reporterId) { + Long reporterId) { return update(update, new LambdaQueryWrapperX() .eq(WeeklyReportDO::getId, id) .eq(WeeklyReportDO::getReporterId, reporterId) 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 index ab2b371..08a7fa5 100644 --- 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 @@ -11,21 +11,14 @@ 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 @@ -36,10 +29,15 @@ public class TeamOvertimeServiceImpl implements TeamOvertimeService { teamDashboardAccessService.validateTeamDashboardPermission( OvertimeApplicationConstants.PERMISSION_TEAM_DASHBOARD); List subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds(); - YearMonth month = parseMonth(reqVO == null ? null : reqVO.getMonth()); + LocalDate[] dateRange = normalizeDateRange( + reqVO == null ? null : reqVO.getOvertimeDateStart(), + reqVO == null ? null : reqVO.getOvertimeDateEnd()); + LocalDate startDate = dateRange[0]; + LocalDate endDate = dateRange[1]; TeamOvertimeSummaryRespVO respVO = new TeamOvertimeSummaryRespVO(); - respVO.setMonth(month.format(MONTH_FORMATTER)); + respVO.setOvertimeDateStart(startDate); + respVO.setOvertimeDateEnd(endDate); if (subordinateIds.isEmpty()) { respVO.setTotalApplicationCount(0); respVO.setPendingCount(0); @@ -52,7 +50,7 @@ public class TeamOvertimeServiceImpl implements TeamOvertimeService { pageReqVO.setApplicantIds(subordinateIds); pageReqVO.setPageNo(1); pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - pageReqVO.setOvertimeDate(new LocalDate[]{month.atDay(1), month.atEndOfMonth()}); + pageReqVO.setOvertimeDate(new LocalDate[] { startDate, endDate }); PageResult page = overtimeApplicationService.getMyPage(pageReqVO); int pendingCount = 0; @@ -74,14 +72,20 @@ public class TeamOvertimeServiceImpl implements TeamOvertimeService { return respVO; } - private YearMonth parseMonth(String month) { - if (!StringUtils.hasText(month)) { - return YearMonth.now(); + /** + * 规范化日期区间:两者都不传时默认当月 [月初, 月末];支持半开区间。 + */ + private LocalDate[] normalizeDateRange(LocalDate start, LocalDate end) { + if (start == null && end == null) { + YearMonth currentMonth = YearMonth.now(); + return new LocalDate[] { currentMonth.atDay(1), currentMonth.atEndOfMonth() }; } - try { - return YearMonth.parse(month, MONTH_FORMATTER); - } catch (DateTimeParseException ex) { - throw invalidParamException("统计月份格式不正确,应为 yyyy-MM"); + if (start == null) { + start = end.withDayOfMonth(1); } + if (end == null) { + end = start.withDayOfMonth(start.lengthOfMonth()); + } + return new LocalDate[] { start, end }; } } 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 index e52de18..77dd993 100644 --- 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 @@ -28,6 +28,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -70,12 +71,28 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService { @Override public TeamReportSummaryRespVO getSummary(TeamReportSummaryReqVO reqVO) { teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD); - ReportContext context = buildReportContext(normalizeReportType(reqVO.getReportType()), reqVO.getPeriodKey()); + String reportType = normalizeReportType(reqVO.getReportType()); + boolean useDateRange = reqVO.getPeriodStartDate() != null || reqVO.getPeriodEndDate() != null; + ReportContext context; + LocalDate respStartDate; + LocalDate respEndDate; + if (useDateRange) { + respStartDate = reqVO.getPeriodStartDate(); + respEndDate = reqVO.getPeriodEndDate(); + context = buildReportContextByDateRange(reportType, respStartDate, respEndDate); + } else { + context = buildReportContext(reportType, reqVO.getPeriodKey()); + respStartDate = null; + respEndDate = null; + } TeamReportSummaryRespVO respVO = new TeamReportSummaryRespVO(); + respVO.setPeriodStartDate(respStartDate); + respVO.setPeriodEndDate(respEndDate); respVO.setTotalShouldSubmit(context.expectedUserIds().size()); respVO.setSubmittedCount(context.submittedUserIds().size()); respVO.setPendingApprovalCount(context.pendingApprovalUserIds().size()); - List unsubmittedUsers = buildPendingUsers(context.expectedUserIds(), context.submittedUserIds()); + List unsubmittedUsers = buildPendingUsers(context.expectedUserIds(), + context.submittedUserIds()); respVO.setUnsubmittedUsers(unsubmittedUsers); respVO.setUnsubmittedCount(unsubmittedUsers.size()); return respVO; @@ -205,7 +222,8 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService { return Collections.emptyList(); } List terminalStatusCodes = objectStatusModelMapper - .selectTerminalStatusCodesByObjectTypeEnabled(com.njcn.rdms.module.project.constant.ProjectObjectConstants.OBJECT_TYPE); + .selectTerminalStatusCodesByObjectTypeEnabled( + com.njcn.rdms.module.project.constant.ProjectObjectConstants.OBJECT_TYPE); List allProjects = projectMapper.selectListByManagerUserIdsAndStatusCodesNotIn( subordinateIds, terminalStatusCodes); if (allProjects.isEmpty()) { @@ -231,7 +249,7 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService { } private List buildPendingUsers(List expectedUserIds, - Set submittedUserIds) { + Set submittedUserIds) { LinkedHashSet pendingIds = new LinkedHashSet<>(expectedUserIds); pendingIds.removeAll(submittedUserIds); if (pendingIds.isEmpty()) { @@ -245,7 +263,8 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService { Map userMap = users.stream() .filter(Objects::nonNull) .filter(user -> user.getId() != null) - .collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left, LinkedHashMap::new)); + .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); @@ -279,6 +298,103 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService { return StringUtils.hasText(text) ? text.trim() : ""; } - private record ReportContext(List expectedUserIds, Set submittedUserIds, Set pendingApprovalUserIds) { + private ReportContext buildReportContextByDateRange(String reportType, LocalDate startDate, LocalDate endDate) { + if (WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) { + return buildProjectContextByDateRange(startDate, endDate); + } + List subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds(); + if (subordinateIds.isEmpty()) { + return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet()); + } + if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)) { + return buildWeeklyContextByDateRange(startDate, endDate, subordinateIds); + } + return buildMonthlyContextByDateRange(startDate, endDate, subordinateIds); + } + + private ReportContext buildWeeklyContextByDateRange(LocalDate startDate, LocalDate endDate, + List subordinateIds) { + List reports = weeklyReportMapper.selectListByReporterIdsAndPeriodDateRange( + subordinateIds, startDate, endDate, 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 buildMonthlyContextByDateRange(LocalDate startDate, LocalDate endDate, + List subordinateIds) { + List reports = monthlyReportMapper.selectListByReporterIdsAndPeriodDateRange( + subordinateIds, startDate, endDate, 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 buildProjectContextByDateRange(LocalDate startDate, LocalDate endDate) { + 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.selectListByProjectOwnerIdsAndPeriodDateRange( + expectedUserIds, startDate, endDate, 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 record ReportContext(List expectedUserIds, Set submittedUserIds, + Set pendingApprovalUserIds) { } } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dept/DeptController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dept/DeptController.java index 5f414b4..f3df39b 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dept/DeptController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dept/DeptController.java @@ -18,7 +18,11 @@ import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static com.njcn.rdms.framework.common.pojo.CommonResult.success; @@ -90,4 +94,24 @@ public class DeptController { return success(BeanUtils.toBean(dept, DeptRespVO.class)); } + @GetMapping("/list-self-and-children") + @Operation(summary = "获取部门自身及全部子部门") + @Parameter(name = "id", description = "部门编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:dept:query')") + public CommonResult> getDeptSelfAndChildren(@RequestParam("id") Long id) { + DeptDO self = deptService.getDept(id); + if (self == null) { + return success(List.of()); + } + List combined = new ArrayList<>(); + combined.add(self); + combined.addAll(deptService.getChildDeptList(id)); + Map distinctMap = new LinkedHashMap<>(); + combined.forEach(dept -> distinctMap.putIfAbsent(dept.getId(), dept)); + List result = new ArrayList<>(distinctMap.values()); + result.sort(Comparator.comparing(DeptDO::getSort, Comparator.nullsLast(Integer::compareTo)) + .thenComparing(DeptDO::getId, Comparator.nullsLast(Long::compareTo))); + return success(BeanUtils.toBean(result, DeptSimpleRespVO.class)); + } + } 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 ce3d5d9..40dc68f 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 @@ -15,6 +15,7 @@ import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO; import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; import com.njcn.rdms.module.system.dal.dataobject.user.UserManagementRelationDO; import com.njcn.rdms.module.system.service.dept.DeptService; +import com.njcn.rdms.module.system.service.user.AdminUserService; import com.njcn.rdms.module.system.service.user.UserManagementRelationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -27,9 +28,13 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Comparator; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static com.njcn.rdms.framework.common.pojo.CommonResult.success; @@ -58,6 +63,9 @@ public class UserManagementRelationController { @Resource private UserManagementRelationService userManagementRelationService; + @Resource + private AdminUserService adminUserService; + /** * 创建用户管理链路 * @@ -192,6 +200,47 @@ public class UserManagementRelationController { return success(UserConvert.INSTANCE.convertSimpleList(users, deptMap)); } + /** + * 获取某用户当前生效的直属下级列表。 + * + * 口径:只返回直接下级,不递归孙级;仅返回当前生效的管理链路。 + */ + @GetMapping("/direct-subordinates") + @Operation(summary = "获取某用户当前生效的直属下级列表") + @Parameter(name = "userId", description = "用户ID", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:user-management-relation:query')") + public CommonResult> getDirectSubordinates(@RequestParam("userId") Long userId) { + List relations = userManagementRelationService.getRelationListByManagerUserId(userId); + if (relations.isEmpty()) { + return success(Collections.emptyList()); + } + LocalDateTime now = LocalDateTime.now(); + Set subordinateIds = new LinkedHashSet<>(); + relations.stream() + .filter(relation -> relation.getSubordinateUserId() != null) + .filter(relation -> relation.getEffectiveFrom() == null || !relation.getEffectiveFrom().isAfter(now)) + .filter(relation -> relation.getEffectiveUntil() == null || relation.getEffectiveUntil().isAfter(now)) + .map(UserManagementRelationDO::getSubordinateUserId) + .forEach(subordinateIds::add); + if (subordinateIds.isEmpty()) { + return success(Collections.emptyList()); + } + List users = adminUserService.getUserList(subordinateIds).stream() + .filter(adminUserService::isUserAvailable) + .sorted(Comparator.comparing(AdminUserDO::getSort, Comparator.nullsLast(Integer::compareTo)) + .thenComparing(AdminUserDO::getId, Comparator.nullsLast(Long::compareTo))) + .toList(); + if (users.isEmpty()) { + return success(Collections.emptyList()); + } + Map deptMap = deptService.getDeptMap(convertList(users, AdminUserDO::getDeptId)); + List result = UserConvert.INSTANCE.convertSimpleList(users, deptMap).stream() + .sorted(Comparator.comparing(UserSimpleRespVO::getSort, Comparator.nullsLast(Integer::compareTo)) + .thenComparing(UserSimpleRespVO::getId, Comparator.nullsLast(Long::compareTo))) + .toList(); + return success(result); + } + /** * 获取用户管理链路树形结构 *