fix(工作报告、我的绩效、产品/项目、消息通知): 修复已知的一些问题。
This commit is contained in:
@@ -13,7 +13,7 @@ public class TeamPerformanceSummaryRespVO {
|
|||||||
|
|
||||||
private String periodMonthStart;
|
private String periodMonthStart;
|
||||||
private String periodMonthEnd;
|
private String periodMonthEnd;
|
||||||
private Integer totalSheetCount;
|
private Integer expectedSheetCount;
|
||||||
private Integer pendingSendCount;
|
private Integer pendingSendCount;
|
||||||
private Integer pendingConfirmCount;
|
private Integer pendingConfirmCount;
|
||||||
private BigDecimal confirmedRate;
|
private BigDecimal confirmedRate;
|
||||||
@@ -25,6 +25,7 @@ public class TeamPerformanceSummaryRespVO {
|
|||||||
public static class PendingSendUser {
|
public static class PendingSendUser {
|
||||||
private Long userId;
|
private Long userId;
|
||||||
private String userNickname;
|
private String userNickname;
|
||||||
|
private String periodMonth;
|
||||||
private Long managerUserId;
|
private Long managerUserId;
|
||||||
private String managerName;
|
private String managerName;
|
||||||
private Long sheetId;
|
private Long sheetId;
|
||||||
@@ -35,6 +36,7 @@ public class TeamPerformanceSummaryRespVO {
|
|||||||
public static class PendingConfirmUser {
|
public static class PendingConfirmUser {
|
||||||
private Long userId;
|
private Long userId;
|
||||||
private String userNickname;
|
private String userNickname;
|
||||||
|
private String periodMonth;
|
||||||
private Long sheetId;
|
private Long sheetId;
|
||||||
private LocalDateTime sentTime;
|
private LocalDateTime sentTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,7 @@ public class TeamReportRemindReqVO {
|
|||||||
|
|
||||||
@Schema(description = "催办用户 ID 列表;不传则催办全部待提交用户")
|
@Schema(description = "催办用户 ID 列表;不传则催办全部待提交用户")
|
||||||
private List<Long> userIds;
|
private List<Long> userIds;
|
||||||
|
|
||||||
|
@Schema(description = "项目 ID,仅项目半月报按单个项目槽位催办时传入", example = "2042074259501088770")
|
||||||
|
private Long projectId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ import lombok.Data;
|
|||||||
@Data
|
@Data
|
||||||
public class TeamReportRemindRespVO {
|
public class TeamReportRemindRespVO {
|
||||||
|
|
||||||
@Schema(description = "实际催办人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
|
@Schema(description = "实际催办数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
|
||||||
private Integer remindedCount;
|
private Integer remindedCount;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,24 +17,24 @@ public class TeamReportSummaryRespVO {
|
|||||||
@Schema(description = "实际查询周期结束日期")
|
@Schema(description = "实际查询周期结束日期")
|
||||||
private LocalDate periodEndDate;
|
private LocalDate periodEndDate;
|
||||||
|
|
||||||
@Schema(description = "应填人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "18")
|
@Schema(description = "应填报告数", requiredMode = Schema.RequiredMode.REQUIRED, example = "18")
|
||||||
private Integer totalShouldSubmit;
|
private Integer expectedReportCount;
|
||||||
|
|
||||||
@Schema(description = "已提交人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
|
@Schema(description = "已提交报告数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
|
||||||
private Integer submittedCount;
|
private Integer submittedReportCount;
|
||||||
|
|
||||||
@Schema(description = "未提交人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
|
@Schema(description = "未提交报告数", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
|
||||||
private Integer unsubmittedCount;
|
private Integer unsubmittedReportCount;
|
||||||
|
|
||||||
@Schema(description = "待审批人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
|
@Schema(description = "待审批报告数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
|
||||||
private Integer pendingApprovalCount;
|
private Integer pendingApprovalReportCount;
|
||||||
|
|
||||||
@Schema(description = "未提交人员列表")
|
@Schema(description = "未提交报告槽位列表")
|
||||||
private List<PendingUser> unsubmittedUsers;
|
private List<UnsubmittedReport> unsubmittedReports;
|
||||||
|
|
||||||
@Schema(description = "未提交人员")
|
@Schema(description = "未提交报告槽位")
|
||||||
@Data
|
@Data
|
||||||
public static class PendingUser {
|
public static class UnsubmittedReport {
|
||||||
|
|
||||||
@Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2042074259501088770")
|
@Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2042074259501088770")
|
||||||
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = ToStringSerializer.class)
|
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = ToStringSerializer.class)
|
||||||
@@ -42,5 +42,19 @@ public class TeamReportSummaryRespVO {
|
|||||||
|
|
||||||
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "戴坤")
|
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "戴坤")
|
||||||
private String userNickname;
|
private String userNickname;
|
||||||
|
|
||||||
|
@Schema(description = "周期主键", requiredMode = Schema.RequiredMode.REQUIRED,
|
||||||
|
example = "weekly-2026-06-22-2026-06-28")
|
||||||
|
private String periodKey;
|
||||||
|
|
||||||
|
@Schema(description = "周期名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06-22 至 2026-06-28")
|
||||||
|
private String periodLabel;
|
||||||
|
|
||||||
|
@Schema(description = "项目 ID,仅项目半月报返回", example = "2042074259501088770")
|
||||||
|
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = ToStringSerializer.class)
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
@Schema(description = "项目名称,仅项目半月报返回", example = "RDMS")
|
||||||
|
private String projectName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.njcn.rdms.module.project.dal.dataobject.product;
|
package com.njcn.rdms.module.project.dal.dataobject.product;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||||
@@ -42,6 +44,7 @@ public class ProductDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 产品描述
|
* 产品描述
|
||||||
*/
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||||
private String description;
|
private String description;
|
||||||
/**
|
/**
|
||||||
* 最近一次状态动作原因
|
* 最近一次状态动作原因
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.njcn.rdms.module.project.dal.dataobject.project;
|
package com.njcn.rdms.module.project.dal.dataobject.project;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||||
@@ -81,6 +83,7 @@ public class ProjectDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 项目说明
|
* 项目说明
|
||||||
*/
|
*/
|
||||||
|
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||||
private String projectDesc;
|
private String projectDesc;
|
||||||
/**
|
/**
|
||||||
* 最近一次状态动作原因
|
* 最近一次状态动作原因
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ public class NotifyTemplateCodeConstants {
|
|||||||
/** 工作报告团队催办:主管催办下属提交指定周期工作报告 */
|
/** 工作报告团队催办:主管催办下属提交指定周期工作报告 */
|
||||||
public static final String WORK_REPORT_TEAM_REMIND = "work_report_team_remind";
|
public static final String WORK_REPORT_TEAM_REMIND = "work_report_team_remind";
|
||||||
|
|
||||||
|
/** 项目半月报团队催办:主管按项目槽位催办下属提交项目半月报 */
|
||||||
|
public static final String WORK_REPORT_PROJECT_HALF_MONTH_TEAM_REMIND =
|
||||||
|
"work_report_project_half_month_team_remind";
|
||||||
|
|
||||||
/** 执行指派:创建执行后通知负责人 + 协办人 */
|
/** 执行指派:创建执行后通知负责人 + 协办人 */
|
||||||
public static final String EXECUTION_ASSIGNED = "execution_assigned";
|
public static final String EXECUTION_ASSIGNED = "execution_assigned";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.njcn.rdms.module.project.service.performance.team;
|
package com.njcn.rdms.module.project.service.performance.team;
|
||||||
|
|
||||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
|
||||||
import com.njcn.rdms.module.project.constant.PerformanceConstants;
|
import com.njcn.rdms.module.project.constant.PerformanceConstants;
|
||||||
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindReqVO;
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindReqVO;
|
||||||
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindRespVO;
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindRespVO;
|
||||||
@@ -12,6 +11,8 @@ import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetMapper
|
|||||||
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
|
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
|
||||||
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
|
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
|
||||||
import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService;
|
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.dto.DeptRespDTO;
|
||||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
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.UserManagementRelationApi;
|
||||||
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
@@ -48,6 +49,8 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
@Resource
|
@Resource
|
||||||
private AdminUserApi adminUserApi;
|
private AdminUserApi adminUserApi;
|
||||||
@Resource
|
@Resource
|
||||||
|
private DeptApi deptApi;
|
||||||
|
@Resource
|
||||||
private UserManagementRelationApi userManagementRelationApi;
|
private UserManagementRelationApi userManagementRelationApi;
|
||||||
@Resource
|
@Resource
|
||||||
private ApplicationEventPublisher applicationEventPublisher;
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
@@ -58,23 +61,59 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
String[] range = normalizePeriodMonthRange(
|
String[] range = normalizePeriodMonthRange(
|
||||||
reqVO == null ? null : reqVO.getPeriodMonthStart(),
|
reqVO == null ? null : reqVO.getPeriodMonthStart(),
|
||||||
reqVO == null ? null : reqVO.getPeriodMonthEnd());
|
reqVO == null ? null : reqVO.getPeriodMonthEnd());
|
||||||
|
List<String> periodMonths = buildPeriodMonths(range[0], range[1]);
|
||||||
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
||||||
|
Map<Long, AdminUserRespDTO> userMap = loadUserMap(subordinateIds);
|
||||||
|
Map<Long, DeptRespDTO> deptMap = loadDeptMap(userMap.values());
|
||||||
List<PerformanceSheetDO> sheets = performanceSheetMapper
|
List<PerformanceSheetDO> sheets = performanceSheetMapper
|
||||||
.selectListByEmployeeIdsAndPeriodMonthRange(subordinateIds, range[0], range[1]);
|
.selectListByEmployeeIdsAndPeriodMonthRange(subordinateIds, range[0], range[1]);
|
||||||
Map<Long, List<PerformanceSheetDO>> sheetsByEmployee = sheets.stream()
|
Map<String, PerformanceSheetDO> sheetBySlot = buildSheetBySlot(sheets);
|
||||||
.collect(Collectors.groupingBy(PerformanceSheetDO::getEmployeeId));
|
Map<Long, AdminUserRespDTO> directManagerCache = new HashMap<>();
|
||||||
Map<Long, AdminUserRespDTO> userMap = loadUserMap(subordinateIds);
|
List<TeamPerformanceSummaryRespVO.PendingSendUser> pendingSendUsers = new ArrayList<>();
|
||||||
|
List<TeamPerformanceSummaryRespVO.PendingConfirmUser> pendingConfirmUsers = new ArrayList<>();
|
||||||
|
Map<String, DeptAverageAccumulator> deptAverageMap = new LinkedHashMap<>();
|
||||||
|
int confirmedCount = 0;
|
||||||
|
|
||||||
|
for (Long subordinateId : subordinateIds) {
|
||||||
|
AdminUserRespDTO user = userMap.get(subordinateId);
|
||||||
|
for (String periodMonth : periodMonths) {
|
||||||
|
PerformanceSheetDO sheet = sheetBySlot.get(slotKey(subordinateId, periodMonth));
|
||||||
|
DeptBucket deptBucket = resolveDeptBucket(sheet, user, deptMap);
|
||||||
|
DeptAverageAccumulator accumulator = touchDeptAverageAccumulator(deptAverageMap, deptBucket);
|
||||||
|
if (accumulator != null) {
|
||||||
|
accumulator.incrementExpectedCount();
|
||||||
|
}
|
||||||
|
if (isPendingSendStatus(sheet == null ? null : sheet.getStatusCode())) {
|
||||||
|
pendingSendUsers.add(buildPendingSendUser(
|
||||||
|
subordinateId, periodMonth, sheet, user, directManagerCache));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode())) {
|
||||||
|
pendingConfirmUsers.add(buildPendingConfirmUser(sheet, user, periodMonth));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode())) {
|
||||||
|
confirmedCount++;
|
||||||
|
if (accumulator != null) {
|
||||||
|
accumulator.addConfirmedScore(sheet.getActualScoreTotal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int expectedSheetCount = subordinateIds.size() * periodMonths.size();
|
||||||
TeamPerformanceSummaryRespVO respVO = new TeamPerformanceSummaryRespVO();
|
TeamPerformanceSummaryRespVO respVO = new TeamPerformanceSummaryRespVO();
|
||||||
respVO.setPeriodMonthStart(range[0]);
|
respVO.setPeriodMonthStart(range[0]);
|
||||||
respVO.setPeriodMonthEnd(range[1]);
|
respVO.setPeriodMonthEnd(range[1]);
|
||||||
respVO.setTotalSheetCount(sheets.size());
|
respVO.setExpectedSheetCount(expectedSheetCount);
|
||||||
respVO.setPendingSendUsers(buildPendingSendUsers(subordinateIds, sheetsByEmployee, userMap));
|
respVO.setPendingSendUsers(pendingSendUsers);
|
||||||
respVO.setPendingConfirmUsers(buildPendingConfirmUsers(sheets, userMap));
|
respVO.setPendingConfirmUsers(pendingConfirmUsers);
|
||||||
respVO.setPendingSendCount(respVO.getPendingSendUsers().size());
|
respVO.setPendingSendCount(pendingSendUsers.size());
|
||||||
respVO.setPendingConfirmCount(respVO.getPendingConfirmUsers().size());
|
respVO.setPendingConfirmCount(pendingConfirmUsers.size());
|
||||||
respVO.setConfirmedRate(calculateConfirmedRate(sheets));
|
respVO.setConfirmedRate(calculateConfirmedRate(expectedSheetCount, confirmedCount));
|
||||||
respVO.setDeptOrgAverages(buildDeptOrgAverages(sheets));
|
respVO.setDeptOrgAverages(deptAverageMap.values().stream()
|
||||||
|
.map(DeptAverageAccumulator::toRespVO)
|
||||||
|
.toList());
|
||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +122,7 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
public TeamPerformanceRemindRespVO remind(TeamPerformanceRemindReqVO reqVO) {
|
public TeamPerformanceRemindRespVO remind(TeamPerformanceRemindReqVO reqVO) {
|
||||||
teamDashboardAccessService.validateTeamDashboardPermission(PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
teamDashboardAccessService.validateTeamDashboardPermission(PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
String[] range = normalizePeriodMonthRange(reqVO.getPeriodMonthStart(), reqVO.getPeriodMonthEnd());
|
String[] range = normalizePeriodMonthRange(reqVO.getPeriodMonthStart(), reqVO.getPeriodMonthEnd());
|
||||||
|
List<String> periodMonths = buildPeriodMonths(range[0], range[1]);
|
||||||
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
||||||
List<Long> targetEmployeeIds = reqVO.getUserIds() == null
|
List<Long> targetEmployeeIds = reqVO.getUserIds() == null
|
||||||
? subordinateIds
|
? subordinateIds
|
||||||
@@ -90,15 +130,14 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
reqVO.getUserIds(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
reqVO.getUserIds(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
List<PerformanceSheetDO> sheets = performanceSheetMapper
|
List<PerformanceSheetDO> sheets = performanceSheetMapper
|
||||||
.selectListByEmployeeIdsAndPeriodMonthRange(targetEmployeeIds, range[0], range[1]);
|
.selectListByEmployeeIdsAndPeriodMonthRange(targetEmployeeIds, range[0], range[1]);
|
||||||
Map<Long, List<PerformanceSheetDO>> sheetsByEmployee = sheets.stream()
|
Map<String, PerformanceSheetDO> sheetBySlot = buildSheetBySlot(sheets);
|
||||||
.collect(Collectors.groupingBy(PerformanceSheetDO::getEmployeeId));
|
|
||||||
Map<Long, AdminUserRespDTO> userMap = loadUserMap(targetEmployeeIds);
|
Map<Long, AdminUserRespDTO> userMap = loadUserMap(targetEmployeeIds);
|
||||||
|
|
||||||
int remindedCount;
|
int remindedCount;
|
||||||
if (PerformanceConstants.REMIND_TYPE_PENDING_CONFIRM.equals(reqVO.getRemindType())) {
|
if (PerformanceConstants.REMIND_TYPE_PENDING_CONFIRM.equals(reqVO.getRemindType())) {
|
||||||
remindedCount = remindPendingConfirm(sheets);
|
remindedCount = remindPendingConfirm(sheets);
|
||||||
} else if (PerformanceConstants.REMIND_TYPE_PENDING_SEND.equals(reqVO.getRemindType())) {
|
} else if (PerformanceConstants.REMIND_TYPE_PENDING_SEND.equals(reqVO.getRemindType())) {
|
||||||
remindedCount = remindPendingSend(targetEmployeeIds, sheetsByEmployee, userMap, range[0]);
|
remindedCount = remindPendingSend(targetEmployeeIds, periodMonths, sheetBySlot, userMap);
|
||||||
} else {
|
} else {
|
||||||
throw invalidParamException("催办类型不合法");
|
throw invalidParamException("催办类型不合法");
|
||||||
}
|
}
|
||||||
@@ -107,78 +146,12 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TeamPerformanceSummaryRespVO.PendingSendUser> buildPendingSendUsers(
|
private BigDecimal calculateConfirmedRate(int expectedSheetCount, int confirmedCount) {
|
||||||
List<Long> subordinateIds, Map<Long, List<PerformanceSheetDO>> sheetsByEmployee,
|
if (expectedSheetCount <= 0) {
|
||||||
Map<Long, AdminUserRespDTO> userMap) {
|
|
||||||
List<TeamPerformanceSummaryRespVO.PendingSendUser> result = new ArrayList<>();
|
|
||||||
for (Long subordinateId : subordinateIds) {
|
|
||||||
List<PerformanceSheetDO> employeeSheets = sheetsByEmployee.getOrDefault(subordinateId, List.of());
|
|
||||||
if (!hasPendingSend(employeeSheets)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
AdminUserRespDTO user = userMap.get(subordinateId);
|
|
||||||
PerformanceSheetDO firstPending = employeeSheets.isEmpty() ? null : employeeSheets.get(0);
|
|
||||||
AdminUserRespDTO manager = firstPending == null ? loadDirectManagerQuietly(subordinateId) : null;
|
|
||||||
TeamPerformanceSummaryRespVO.PendingSendUser item = new TeamPerformanceSummaryRespVO.PendingSendUser();
|
|
||||||
item.setUserId(subordinateId);
|
|
||||||
item.setUserNickname(user == null ? "" : defaultText(user.getNickname()));
|
|
||||||
item.setManagerUserId(
|
|
||||||
firstPending == null ? (manager == null ? null : manager.getId()) : firstPending.getManagerId());
|
|
||||||
item.setManagerName(firstPending == null ? (manager == null ? "" : defaultText(manager.getNickname()))
|
|
||||||
: firstPending.getManagerName());
|
|
||||||
item.setSheetId(firstPending == null ? null : firstPending.getId());
|
|
||||||
item.setStatusCode(firstPending == null ? null : firstPending.getStatusCode());
|
|
||||||
result.add(item);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TeamPerformanceSummaryRespVO.PendingConfirmUser> buildPendingConfirmUsers(
|
|
||||||
List<PerformanceSheetDO> sheets, Map<Long, AdminUserRespDTO> userMap) {
|
|
||||||
return sheets.stream()
|
|
||||||
.filter(sheet -> PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode()))
|
|
||||||
.map(sheet -> {
|
|
||||||
AdminUserRespDTO user = userMap.get(sheet.getEmployeeId());
|
|
||||||
TeamPerformanceSummaryRespVO.PendingConfirmUser item = new TeamPerformanceSummaryRespVO.PendingConfirmUser();
|
|
||||||
item.setUserId(sheet.getEmployeeId());
|
|
||||||
item.setUserNickname(
|
|
||||||
user == null ? defaultText(sheet.getEmployeeName()) : defaultText(user.getNickname()));
|
|
||||||
item.setSheetId(sheet.getId());
|
|
||||||
item.setSentTime(sheet.getSentTime());
|
|
||||||
return item;
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TeamPerformanceSummaryRespVO.DeptOrgAverage> buildDeptOrgAverages(List<PerformanceSheetDO> sheets) {
|
|
||||||
Map<String, DeptAverageAccumulator> averages = new LinkedHashMap<>();
|
|
||||||
for (PerformanceSheetDO sheet : sheets) {
|
|
||||||
if (!PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode())
|
|
||||||
|| sheet.getActualScoreTotal() == null
|
|
||||||
|| sheet.getEmployeeDeptId() == null
|
|
||||||
|| (!PerformanceConstants.ORG_TYPE_DIRECTION.equals(sheet.getDeptOrgType())
|
|
||||||
&& !PerformanceConstants.ORG_TYPE_FUNCTION.equals(sheet.getDeptOrgType()))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String key = sheet.getEmployeeDeptId() + ":" + sheet.getDeptOrgType();
|
|
||||||
averages.computeIfAbsent(key, ignored -> new DeptAverageAccumulator(
|
|
||||||
sheet.getEmployeeDeptId(), sheet.getEmployeeDeptName(), sheet.getDeptOrgType()))
|
|
||||||
.add(sheet.getActualScoreTotal());
|
|
||||||
}
|
|
||||||
return averages.values().stream()
|
|
||||||
.map(DeptAverageAccumulator::toRespVO)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal calculateConfirmedRate(List<PerformanceSheetDO> sheets) {
|
|
||||||
if (sheets.isEmpty()) {
|
|
||||||
return BigDecimal.ZERO.setScale(2);
|
return BigDecimal.ZERO.setScale(2);
|
||||||
}
|
}
|
||||||
long confirmed = sheets.stream()
|
return BigDecimal.valueOf(confirmedCount * 100L)
|
||||||
.filter(sheet -> PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode()))
|
.divide(BigDecimal.valueOf(expectedSheetCount), 2, RoundingMode.HALF_UP);
|
||||||
.count();
|
|
||||||
return BigDecimal.valueOf(confirmed * 100)
|
|
||||||
.divide(BigDecimal.valueOf(sheets.size()), 2, RoundingMode.HALF_UP);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int remindPendingConfirm(List<PerformanceSheetDO> sheets) {
|
private int remindPendingConfirm(List<PerformanceSheetDO> sheets) {
|
||||||
@@ -194,27 +167,31 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int remindPendingSend(List<Long> targetEmployeeIds, Map<Long, List<PerformanceSheetDO>> sheetsByEmployee,
|
private int remindPendingSend(List<Long> targetEmployeeIds, List<String> periodMonths,
|
||||||
Map<Long, AdminUserRespDTO> userMap, String defaultPeriodMonth) {
|
Map<String, PerformanceSheetDO> sheetBySlot, Map<Long, AdminUserRespDTO> userMap) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
Map<Long, AdminUserRespDTO> directManagerCache = new HashMap<>();
|
||||||
for (Long employeeId : targetEmployeeIds) {
|
for (Long employeeId : targetEmployeeIds) {
|
||||||
List<PerformanceSheetDO> employeeSheets = sheetsByEmployee.getOrDefault(employeeId, List.of());
|
PendingSendContext pendingContext = findPendingSendContext(employeeId, periodMonths, sheetBySlot);
|
||||||
if (!hasPendingSend(employeeSheets)) {
|
if (pendingContext == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PerformanceSheetDO firstPending = employeeSheets.isEmpty() ? null : employeeSheets.get(0);
|
PerformanceSheetDO pendingSheet = pendingContext.sheet();
|
||||||
AdminUserRespDTO manager = firstPending == null ? loadDirectManagerQuietly(employeeId) : null;
|
AdminUserRespDTO manager = pendingSheet == null
|
||||||
Long managerId = firstPending == null ? (manager == null ? null : manager.getId())
|
? directManagerCache.computeIfAbsent(employeeId, this::loadDirectManagerQuietly)
|
||||||
: firstPending.getManagerId();
|
: null;
|
||||||
|
Long managerId = pendingSheet != null && pendingSheet.getManagerId() != null
|
||||||
|
? pendingSheet.getManagerId()
|
||||||
|
: (manager == null ? null : manager.getId());
|
||||||
if (managerId == null) {
|
if (managerId == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String employeeName = firstPending == null
|
String employeeName = pendingSheet == null
|
||||||
? defaultText(userMap.get(employeeId) == null ? null : userMap.get(employeeId).getNickname())
|
? defaultText(userMap.get(employeeId) == null ? null : userMap.get(employeeId).getNickname())
|
||||||
: firstPending.getEmployeeName();
|
: defaultText(pendingSheet.getEmployeeName());
|
||||||
PerformanceSheetDO noticeSheet = firstPending == null
|
PerformanceSheetDO noticeSheet = pendingSheet == null
|
||||||
? buildVirtualSheet(defaultPeriodMonth, employeeId, employeeName, manager)
|
? buildVirtualSheet(pendingContext.periodMonth(), employeeId, employeeName, manager)
|
||||||
: firstPending;
|
: pendingSheet;
|
||||||
publishNotice(List.of(managerId),
|
publishNotice(List.of(managerId),
|
||||||
NotifyTemplateCodeConstants.PERFORMANCE_PENDING_SEND_REMIND, noticeSheet, employeeName);
|
NotifyTemplateCodeConstants.PERFORMANCE_PENDING_SEND_REMIND, noticeSheet, employeeName);
|
||||||
count++;
|
count++;
|
||||||
@@ -248,6 +225,21 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
templateCode, params, NotifyMessageLevelConstants.REMIND));
|
templateCode, params, NotifyMessageLevelConstants.REMIND));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<Long, DeptRespDTO> loadDeptMap(Collection<AdminUserRespDTO> users) {
|
||||||
|
if (users == null || users.isEmpty()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
LinkedHashSet<Long> deptIds = users.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(AdminUserRespDTO::getDeptId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
if (deptIds.isEmpty()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
return deptApi.getDeptMap(deptIds);
|
||||||
|
}
|
||||||
|
|
||||||
private Map<Long, AdminUserRespDTO> loadUserMap(Collection<Long> userIds) {
|
private Map<Long, AdminUserRespDTO> loadUserMap(Collection<Long> userIds) {
|
||||||
if (userIds == null || userIds.isEmpty()) {
|
if (userIds == null || userIds.isEmpty()) {
|
||||||
return Map.of();
|
return Map.of();
|
||||||
@@ -271,9 +263,142 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private Map<String, PerformanceSheetDO> buildSheetBySlot(List<PerformanceSheetDO> sheets) {
|
||||||
* 校验并归一化月份区间,两者均为空时默认当前月。
|
if (sheets == null || sheets.isEmpty()) {
|
||||||
*/
|
return Map.of();
|
||||||
|
}
|
||||||
|
return sheets.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(sheet -> sheet.getEmployeeId() != null)
|
||||||
|
.filter(sheet -> StringUtils.hasText(sheet.getPeriodMonth()))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
sheet -> slotKey(sheet.getEmployeeId(), sheet.getPeriodMonth()),
|
||||||
|
sheet -> sheet,
|
||||||
|
this::preferLatestSheet,
|
||||||
|
LinkedHashMap::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetDO preferLatestSheet(PerformanceSheetDO left, PerformanceSheetDO right) {
|
||||||
|
if (left == null) {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
if (right == null) {
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
if (left.getUpdateTime() != null && right.getUpdateTime() != null) {
|
||||||
|
return left.getUpdateTime().isAfter(right.getUpdateTime()) ? left : right;
|
||||||
|
}
|
||||||
|
if (left.getCreateTime() != null && right.getCreateTime() != null) {
|
||||||
|
return left.getCreateTime().isAfter(right.getCreateTime()) ? left : right;
|
||||||
|
}
|
||||||
|
if (left.getId() != null && right.getId() != null) {
|
||||||
|
return left.getId() >= right.getId() ? left : right;
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> buildPeriodMonths(String periodMonthStart, String periodMonthEnd) {
|
||||||
|
YearMonth start = YearMonth.parse(periodMonthStart);
|
||||||
|
YearMonth end = YearMonth.parse(periodMonthEnd);
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (YearMonth current = start; !current.isAfter(end); current = current.plusMonths(1)) {
|
||||||
|
result.add(current.toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String slotKey(Long employeeId, String periodMonth) {
|
||||||
|
return employeeId + ":" + periodMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingSendContext findPendingSendContext(Long employeeId, List<String> periodMonths,
|
||||||
|
Map<String, PerformanceSheetDO> sheetBySlot) {
|
||||||
|
for (String periodMonth : periodMonths) {
|
||||||
|
PerformanceSheetDO sheet = sheetBySlot.get(slotKey(employeeId, periodMonth));
|
||||||
|
if (isPendingSendStatus(sheet == null ? null : sheet.getStatusCode())) {
|
||||||
|
return new PendingSendContext(periodMonth, sheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPendingSendStatus(String statusCode) {
|
||||||
|
return statusCode == null
|
||||||
|
|| PerformanceConstants.STATUS_DRAFT.equals(statusCode)
|
||||||
|
|| PerformanceConstants.STATUS_REJECTED.equals(statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TeamPerformanceSummaryRespVO.PendingSendUser buildPendingSendUser(Long userId, String periodMonth,
|
||||||
|
PerformanceSheetDO sheet, AdminUserRespDTO user, Map<Long, AdminUserRespDTO> directManagerCache) {
|
||||||
|
AdminUserRespDTO manager = null;
|
||||||
|
if (sheet == null || sheet.getManagerId() == null || !StringUtils.hasText(sheet.getManagerName())) {
|
||||||
|
manager = directManagerCache.computeIfAbsent(userId, this::loadDirectManagerQuietly);
|
||||||
|
}
|
||||||
|
TeamPerformanceSummaryRespVO.PendingSendUser item = new TeamPerformanceSummaryRespVO.PendingSendUser();
|
||||||
|
item.setUserId(userId);
|
||||||
|
item.setUserNickname(resolveUserNickname(user, sheet));
|
||||||
|
item.setPeriodMonth(periodMonth);
|
||||||
|
item.setManagerUserId(sheet != null && sheet.getManagerId() != null
|
||||||
|
? sheet.getManagerId()
|
||||||
|
: (manager == null ? null : manager.getId()));
|
||||||
|
item.setManagerName(sheet != null && StringUtils.hasText(sheet.getManagerName())
|
||||||
|
? defaultText(sheet.getManagerName())
|
||||||
|
: (manager == null ? "" : defaultText(manager.getNickname())));
|
||||||
|
item.setSheetId(sheet == null ? null : sheet.getId());
|
||||||
|
item.setStatusCode(sheet == null ? null : sheet.getStatusCode());
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TeamPerformanceSummaryRespVO.PendingConfirmUser buildPendingConfirmUser(PerformanceSheetDO sheet,
|
||||||
|
AdminUserRespDTO user, String periodMonth) {
|
||||||
|
TeamPerformanceSummaryRespVO.PendingConfirmUser item = new TeamPerformanceSummaryRespVO.PendingConfirmUser();
|
||||||
|
item.setUserId(sheet.getEmployeeId());
|
||||||
|
item.setUserNickname(resolveUserNickname(user, sheet));
|
||||||
|
item.setPeriodMonth(periodMonth);
|
||||||
|
item.setSheetId(sheet.getId());
|
||||||
|
item.setSentTime(sheet.getSentTime());
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveUserNickname(AdminUserRespDTO user, PerformanceSheetDO sheet) {
|
||||||
|
if (user != null && StringUtils.hasText(user.getNickname())) {
|
||||||
|
return defaultText(user.getNickname());
|
||||||
|
}
|
||||||
|
return sheet == null ? "" : defaultText(sheet.getEmployeeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeptAverageAccumulator touchDeptAverageAccumulator(Map<String, DeptAverageAccumulator> averageMap,
|
||||||
|
DeptBucket deptBucket) {
|
||||||
|
if (deptBucket == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return averageMap.computeIfAbsent(deptBucket.key(), ignored ->
|
||||||
|
new DeptAverageAccumulator(deptBucket.deptId(), deptBucket.deptName(), deptBucket.deptOrgType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeptBucket resolveDeptBucket(PerformanceSheetDO sheet, AdminUserRespDTO user,
|
||||||
|
Map<Long, DeptRespDTO> deptMap) {
|
||||||
|
if (sheet != null
|
||||||
|
&& sheet.getEmployeeDeptId() != null
|
||||||
|
&& isDeptAverageOrgType(sheet.getDeptOrgType())) {
|
||||||
|
return new DeptBucket(sheet.getEmployeeDeptId(),
|
||||||
|
defaultText(sheet.getEmployeeDeptName()), sheet.getDeptOrgType());
|
||||||
|
}
|
||||||
|
if (user == null || user.getDeptId() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
DeptRespDTO dept = deptMap.get(user.getDeptId());
|
||||||
|
if (dept == null || !isDeptAverageOrgType(dept.getOrgType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new DeptBucket(dept.getId(), defaultText(dept.getName()), dept.getOrgType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDeptAverageOrgType(String orgType) {
|
||||||
|
return PerformanceConstants.ORG_TYPE_DIRECTION.equals(orgType)
|
||||||
|
|| PerformanceConstants.ORG_TYPE_FUNCTION.equals(orgType);
|
||||||
|
}
|
||||||
|
|
||||||
private String[] normalizePeriodMonthRange(String start, String end) {
|
private String[] normalizePeriodMonthRange(String start, String end) {
|
||||||
String normalizedStart = StringUtils.hasText(start) ? normalizeSinglePeriodMonth(start) : null;
|
String normalizedStart = StringUtils.hasText(start) ? normalizeSinglePeriodMonth(start) : null;
|
||||||
String normalizedEnd = StringUtils.hasText(end) ? normalizeSinglePeriodMonth(end) : null;
|
String normalizedEnd = StringUtils.hasText(end) ? normalizeSinglePeriodMonth(end) : null;
|
||||||
@@ -281,7 +406,13 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
String currentMonth = YearMonth.now().toString();
|
String currentMonth = YearMonth.now().toString();
|
||||||
return new String[] { currentMonth, currentMonth };
|
return new String[] { currentMonth, currentMonth };
|
||||||
}
|
}
|
||||||
if (normalizedStart != null && normalizedEnd != null && normalizedStart.compareTo(normalizedEnd) > 0) {
|
if (normalizedStart == null) {
|
||||||
|
normalizedStart = normalizedEnd;
|
||||||
|
}
|
||||||
|
if (normalizedEnd == null) {
|
||||||
|
normalizedEnd = normalizedStart;
|
||||||
|
}
|
||||||
|
if (normalizedStart.compareTo(normalizedEnd) > 0) {
|
||||||
throw invalidParamException("绩效月份起始不能晚于结束月份");
|
throw invalidParamException("绩效月份起始不能晚于结束月份");
|
||||||
}
|
}
|
||||||
return new String[] { normalizedStart, normalizedEnd };
|
return new String[] { normalizedStart, normalizedEnd };
|
||||||
@@ -295,17 +426,6 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断员工在区间内的绩效表是否存在“待发送”状态(无记录 / 含 draft / 含 rejected)。
|
|
||||||
*/
|
|
||||||
private boolean hasPendingSend(List<PerformanceSheetDO> sheets) {
|
|
||||||
if (sheets == null || sheets.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return sheets.stream().anyMatch(sheet -> PerformanceConstants.STATUS_DRAFT.equals(sheet.getStatusCode())
|
|
||||||
|| PerformanceConstants.STATUS_REJECTED.equals(sheet.getStatusCode()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String statusName(String statusCode) {
|
private String statusName(String statusCode) {
|
||||||
if (statusCode == null) {
|
if (statusCode == null) {
|
||||||
return "未创建";
|
return "未创建";
|
||||||
@@ -329,7 +449,8 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
private final String deptName;
|
private final String deptName;
|
||||||
private final String deptOrgType;
|
private final String deptOrgType;
|
||||||
private BigDecimal total = BigDecimal.ZERO;
|
private BigDecimal total = BigDecimal.ZERO;
|
||||||
private int count = 0;
|
private int expectedCount = 0;
|
||||||
|
private int confirmedCount = 0;
|
||||||
|
|
||||||
DeptAverageAccumulator(Long deptId, String deptName, String deptOrgType) {
|
DeptAverageAccumulator(Long deptId, String deptName, String deptOrgType) {
|
||||||
this.deptId = deptId;
|
this.deptId = deptId;
|
||||||
@@ -337,9 +458,15 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
this.deptOrgType = deptOrgType;
|
this.deptOrgType = deptOrgType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(BigDecimal score) {
|
void incrementExpectedCount() {
|
||||||
|
expectedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addConfirmedScore(BigDecimal score) {
|
||||||
|
confirmedCount++;
|
||||||
|
if (score != null) {
|
||||||
total = total.add(score);
|
total = total.add(score);
|
||||||
count++;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamPerformanceSummaryRespVO.DeptOrgAverage toRespVO() {
|
TeamPerformanceSummaryRespVO.DeptOrgAverage toRespVO() {
|
||||||
@@ -347,11 +474,20 @@ public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
|||||||
respVO.setDeptId(deptId);
|
respVO.setDeptId(deptId);
|
||||||
respVO.setDeptName(deptName);
|
respVO.setDeptName(deptName);
|
||||||
respVO.setDeptOrgType(deptOrgType);
|
respVO.setDeptOrgType(deptOrgType);
|
||||||
respVO.setConfirmedCount(count);
|
respVO.setConfirmedCount(confirmedCount);
|
||||||
respVO.setAverageScore(count == 0
|
respVO.setAverageScore(expectedCount == 0
|
||||||
? BigDecimal.ZERO.setScale(2)
|
? BigDecimal.ZERO.setScale(2)
|
||||||
: total.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
|
: total.divide(BigDecimal.valueOf(expectedCount), 2, RoundingMode.HALF_UP));
|
||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record PendingSendContext(String periodMonth, PerformanceSheetDO sheet) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record DeptBucket(Long deptId, String deptName, String deptOrgType) {
|
||||||
|
private String key() {
|
||||||
|
return deptId + ":" + deptOrgType;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.time.temporal.ChronoField;
|
||||||
|
import java.time.temporal.TemporalAdjusters;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -72,29 +76,23 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService {
|
|||||||
public TeamReportSummaryRespVO getSummary(TeamReportSummaryReqVO reqVO) {
|
public TeamReportSummaryRespVO getSummary(TeamReportSummaryReqVO reqVO) {
|
||||||
teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
|
teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
String reportType = normalizeReportType(reqVO.getReportType());
|
String reportType = normalizeReportType(reqVO.getReportType());
|
||||||
boolean useDateRange = reqVO.getPeriodStartDate() != null || reqVO.getPeriodEndDate() != null;
|
DateRange dateRange = normalizeDateRange(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate());
|
||||||
ReportContext context;
|
ReportContext context;
|
||||||
LocalDate respStartDate;
|
if (dateRange != null) {
|
||||||
LocalDate respEndDate;
|
context = buildReportContextByDateRange(reportType, dateRange.startDate(), dateRange.endDate());
|
||||||
if (useDateRange) {
|
|
||||||
respStartDate = reqVO.getPeriodStartDate();
|
|
||||||
respEndDate = reqVO.getPeriodEndDate();
|
|
||||||
context = buildReportContextByDateRange(reportType, respStartDate, respEndDate);
|
|
||||||
} else {
|
} else {
|
||||||
context = buildReportContext(reportType, reqVO.getPeriodKey());
|
context = buildReportContext(reportType, reqVO.getPeriodKey());
|
||||||
respStartDate = null;
|
|
||||||
respEndDate = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<TeamReportSummaryRespVO.UnsubmittedReport> unsubmittedReports = buildUnsubmittedReports(context);
|
||||||
TeamReportSummaryRespVO respVO = new TeamReportSummaryRespVO();
|
TeamReportSummaryRespVO respVO = new TeamReportSummaryRespVO();
|
||||||
respVO.setPeriodStartDate(respStartDate);
|
respVO.setPeriodStartDate(dateRange == null ? null : dateRange.startDate());
|
||||||
respVO.setPeriodEndDate(respEndDate);
|
respVO.setPeriodEndDate(dateRange == null ? null : dateRange.endDate());
|
||||||
respVO.setTotalShouldSubmit(context.expectedUserIds().size());
|
respVO.setExpectedReportCount(context.expectedSlots().size());
|
||||||
respVO.setSubmittedCount(context.submittedUserIds().size());
|
respVO.setSubmittedReportCount(context.submittedSlotKeys().size());
|
||||||
respVO.setPendingApprovalCount(context.pendingApprovalUserIds().size());
|
respVO.setUnsubmittedReportCount(unsubmittedReports.size());
|
||||||
List<TeamReportSummaryRespVO.PendingUser> unsubmittedUsers = buildPendingUsers(context.expectedUserIds(),
|
respVO.setPendingApprovalReportCount(context.pendingApprovalSlotKeys().size());
|
||||||
context.submittedUserIds());
|
respVO.setUnsubmittedReports(unsubmittedReports);
|
||||||
respVO.setUnsubmittedUsers(unsubmittedUsers);
|
|
||||||
respVO.setUnsubmittedCount(unsubmittedUsers.size());
|
|
||||||
return respVO;
|
return respVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,117 +102,193 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService {
|
|||||||
teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
|
teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
String reportType = normalizeReportType(reqVO.getReportType());
|
String reportType = normalizeReportType(reqVO.getReportType());
|
||||||
ReportContext context = buildReportContext(reportType, reqVO.getPeriodKey());
|
ReportContext context = buildReportContext(reportType, reqVO.getPeriodKey());
|
||||||
List<Long> remindUserIds = resolveRemindUserIds(reqVO.getUserIds(), context);
|
int remindedCount = WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)
|
||||||
|
? remindProjectReport(reqVO, context)
|
||||||
|
: remindPersonalReport(reportType, reqVO, context);
|
||||||
|
|
||||||
if (!remindUserIds.isEmpty()) {
|
TeamReportRemindRespVO respVO = new TeamReportRemindRespVO();
|
||||||
|
respVO.setRemindedCount(remindedCount);
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int remindPersonalReport(String reportType, TeamReportRemindReqVO reqVO, ReportContext context) {
|
||||||
|
List<Long> remindUserIds = resolveRemindUserIds(reqVO.getUserIds(), context);
|
||||||
|
if (remindUserIds.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
params.put("reportTypeName", reportTypeDisplayName(reportType));
|
params.put("reportTypeName", reportTypeDisplayName(reportType));
|
||||||
params.put("periodKey", reqVO.getPeriodKey());
|
params.put("periodKey", formatPeriodKeyForRemind(reportType, reqVO.getPeriodKey()));
|
||||||
params.put("managerName", defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
params.put("managerName", defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||||
applicationEventPublisher.publishEvent(NotifySendEvent.of(remindUserIds,
|
applicationEventPublisher.publishEvent(NotifySendEvent.of(remindUserIds,
|
||||||
NotifyTemplateCodeConstants.WORK_REPORT_TEAM_REMIND, params, NotifyMessageLevelConstants.REMIND));
|
NotifyTemplateCodeConstants.WORK_REPORT_TEAM_REMIND, params, NotifyMessageLevelConstants.REMIND));
|
||||||
|
return remindUserIds.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamReportRemindRespVO respVO = new TeamReportRemindRespVO();
|
private int remindProjectReport(TeamReportRemindReqVO reqVO, ReportContext context) {
|
||||||
respVO.setRemindedCount(remindUserIds.size());
|
List<ExpectedReportSlot> remindSlots = resolveProjectRemindSlots(reqVO, context);
|
||||||
return respVO;
|
if (remindSlots.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String managerName = defaultText(SecurityFrameworkUtils.getLoginUserNickname());
|
||||||
|
for (ExpectedReportSlot slot : remindSlots) {
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("projectName", slot.projectName());
|
||||||
|
params.put("periodLabel", slot.periodLabel());
|
||||||
|
params.put("managerName", managerName);
|
||||||
|
applicationEventPublisher.publishEvent(NotifySendEvent.of(List.of(slot.userId()),
|
||||||
|
NotifyTemplateCodeConstants.WORK_REPORT_PROJECT_HALF_MONTH_TEAM_REMIND,
|
||||||
|
params, NotifyMessageLevelConstants.REMIND));
|
||||||
|
}
|
||||||
|
return remindSlots.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportContext buildReportContext(String reportType, String periodKey) {
|
private ReportContext buildReportContext(String reportType, String periodKey) {
|
||||||
if (!StringUtils.hasText(periodKey)) {
|
if (!StringUtils.hasText(periodKey)) {
|
||||||
throw invalidParamException("周期主键不能为空");
|
throw invalidParamException("周期主键不能为空");
|
||||||
}
|
}
|
||||||
|
ReportPeriod period = parseRequiredPeriod(reportType, periodKey);
|
||||||
|
return buildReportContextByPeriods(reportType, List.of(period));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportContext buildReportContextByDateRange(String reportType, LocalDate startDate, LocalDate endDate) {
|
||||||
|
return buildReportContextByPeriods(reportType, buildPeriodsByDateRange(reportType, startDate, endDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportContext buildReportContextByPeriods(String reportType, List<ReportPeriod> periods) {
|
||||||
if (WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) {
|
if (WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) {
|
||||||
return buildProjectContext(periodKey);
|
return buildProjectContext(periods);
|
||||||
}
|
}
|
||||||
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
||||||
if (subordinateIds.isEmpty()) {
|
if (subordinateIds.isEmpty() || periods.isEmpty()) {
|
||||||
return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
|
return ReportContext.empty();
|
||||||
}
|
}
|
||||||
if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)) {
|
if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)) {
|
||||||
return buildWeeklyContext(periodKey, subordinateIds);
|
return buildWeeklyContext(periods, subordinateIds);
|
||||||
}
|
}
|
||||||
return buildMonthlyContext(periodKey, subordinateIds);
|
return buildMonthlyContext(periods, subordinateIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportContext buildWeeklyContext(String periodKey, List<Long> subordinateIds) {
|
private ReportContext buildWeeklyContext(List<ReportPeriod> periods, List<Long> subordinateIds) {
|
||||||
List<WeeklyReportDO> reports = weeklyReportMapper.selectListByReporterIdsAndPeriodKey(
|
Map<String, ExpectedReportSlot> expectedSlotMap = buildPersonalExpectedSlotMap(subordinateIds, periods);
|
||||||
subordinateIds, periodKey, SUBMITTED_STATUS_CODES);
|
List<WeeklyReportDO> reports = weeklyReportMapper.selectListByReporterIdsAndPeriodDateRange(
|
||||||
Set<Long> submittedUserIds = new LinkedHashSet<>();
|
subordinateIds, periods.get(0).startDate(), periods.get(periods.size() - 1).startDate(),
|
||||||
Set<Long> pendingApprovalUserIds = new LinkedHashSet<>();
|
SUBMITTED_STATUS_CODES);
|
||||||
for (WeeklyReportDO report : reports) {
|
return buildPersonalReportContext(expectedSlotMap, reports.stream()
|
||||||
if (report == null || report.getReporterId() == null) {
|
.filter(Objects::nonNull)
|
||||||
continue;
|
.filter(report -> report.getReporterId() != null)
|
||||||
}
|
.map(report -> new ActualReportSlot(
|
||||||
submittedUserIds.add(report.getReporterId());
|
personalSlotKey(report.getReporterId(), report.getPeriodKey()),
|
||||||
if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
|
report.getStatusCode()))
|
||||||
pendingApprovalUserIds.add(report.getReporterId());
|
.toList());
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ReportContext(subordinateIds, submittedUserIds, pendingApprovalUserIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportContext buildMonthlyContext(String periodKey, List<Long> subordinateIds) {
|
private ReportContext buildMonthlyContext(List<ReportPeriod> periods, List<Long> subordinateIds) {
|
||||||
List<MonthlyReportDO> reports = monthlyReportMapper.selectListByReporterIdsAndPeriodKey(
|
Map<String, ExpectedReportSlot> expectedSlotMap = buildPersonalExpectedSlotMap(subordinateIds, periods);
|
||||||
subordinateIds, periodKey, SUBMITTED_STATUS_CODES);
|
List<MonthlyReportDO> reports = monthlyReportMapper.selectListByReporterIdsAndPeriodDateRange(
|
||||||
Set<Long> submittedUserIds = new LinkedHashSet<>();
|
subordinateIds, periods.get(0).startDate(), periods.get(periods.size() - 1).startDate(),
|
||||||
Set<Long> pendingApprovalUserIds = new LinkedHashSet<>();
|
SUBMITTED_STATUS_CODES);
|
||||||
for (MonthlyReportDO report : reports) {
|
return buildPersonalReportContext(expectedSlotMap, reports.stream()
|
||||||
if (report == null || report.getReporterId() == null) {
|
.filter(Objects::nonNull)
|
||||||
continue;
|
.filter(report -> report.getReporterId() != null)
|
||||||
}
|
.map(report -> new ActualReportSlot(
|
||||||
submittedUserIds.add(report.getReporterId());
|
personalSlotKey(report.getReporterId(), report.getPeriodKey()),
|
||||||
if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
|
report.getStatusCode()))
|
||||||
pendingApprovalUserIds.add(report.getReporterId());
|
.toList());
|
||||||
}
|
|
||||||
}
|
|
||||||
return new ReportContext(subordinateIds, submittedUserIds, pendingApprovalUserIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportContext buildProjectContext(String periodKey) {
|
private ReportContext buildProjectContext(List<ReportPeriod> periods) {
|
||||||
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
||||||
if (subordinateIds.isEmpty()) {
|
if (subordinateIds.isEmpty() || periods.isEmpty()) {
|
||||||
return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
|
return ReportContext.empty();
|
||||||
}
|
}
|
||||||
List<ProjectDO> activeProjects = loadActiveProjectsForSubordinates(subordinateIds);
|
List<ProjectDO> activeProjects = loadActiveProjectsForSubordinates(subordinateIds);
|
||||||
if (activeProjects.isEmpty()) {
|
if (activeProjects.isEmpty()) {
|
||||||
return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
|
return ReportContext.empty();
|
||||||
}
|
}
|
||||||
Map<Long, List<ProjectDO>> projectsByOwner = activeProjects.stream()
|
Map<String, ExpectedReportSlot> expectedSlotMap = buildProjectExpectedSlotMap(activeProjects, periods);
|
||||||
|
LinkedHashSet<Long> ownerIds = activeProjects.stream()
|
||||||
|
.map(ProjectDO::getManagerUserId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.filter(project -> project.getManagerUserId() != null)
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
.collect(Collectors.groupingBy(ProjectDO::getManagerUserId, LinkedHashMap::new, Collectors.toList()));
|
if (ownerIds.isEmpty()) {
|
||||||
LinkedHashSet<Long> expectedUserIds = new LinkedHashSet<>(projectsByOwner.keySet());
|
return ReportContext.empty();
|
||||||
List<ProjectReportDO> reports = projectReportMapper.selectListByProjectOwnerIdsAndPeriodKey(
|
}
|
||||||
expectedUserIds, periodKey, SUBMITTED_STATUS_CODES);
|
List<ProjectReportDO> reports = projectReportMapper.selectListByProjectOwnerIdsAndPeriodDateRange(
|
||||||
Map<Long, Set<Long>> submittedProjectsByOwner = new HashMap<>();
|
ownerIds, periods.get(0).startDate(), periods.get(periods.size() - 1).startDate(),
|
||||||
Set<Long> submittedUserIds = new LinkedHashSet<>();
|
SUBMITTED_STATUS_CODES);
|
||||||
Set<Long> pendingApprovalUserIds = new LinkedHashSet<>();
|
Set<String> submittedSlotKeys = new LinkedHashSet<>();
|
||||||
|
Set<String> pendingApprovalSlotKeys = new LinkedHashSet<>();
|
||||||
for (ProjectReportDO report : reports) {
|
for (ProjectReportDO report : reports) {
|
||||||
if (report == null || report.getProjectOwnerId() == null || report.getProjectId() == null) {
|
if (report == null || report.getProjectOwnerId() == null || report.getProjectId() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Long ownerId = report.getProjectOwnerId();
|
String slotKey = projectSlotKey(report.getProjectOwnerId(), report.getProjectId(), report.getPeriodKey());
|
||||||
if (!expectedUserIds.contains(ownerId)) {
|
if (!expectedSlotMap.containsKey(slotKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
submittedSlotKeys.add(slotKey);
|
||||||
if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
|
if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
|
||||||
pendingApprovalUserIds.add(ownerId);
|
pendingApprovalSlotKeys.add(slotKey);
|
||||||
}
|
|
||||||
submittedProjectsByOwner.computeIfAbsent(ownerId, key -> new LinkedHashSet<>()).add(report.getProjectId());
|
|
||||||
}
|
|
||||||
for (Map.Entry<Long, List<ProjectDO>> entry : projectsByOwner.entrySet()) {
|
|
||||||
Long ownerId = entry.getKey();
|
|
||||||
Set<Long> 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);
|
return new ReportContext(new ArrayList<>(expectedSlotMap.values()), submittedSlotKeys, pendingApprovalSlotKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportContext buildPersonalReportContext(Map<String, ExpectedReportSlot> expectedSlotMap,
|
||||||
|
List<ActualReportSlot> actualSlots) {
|
||||||
|
if (expectedSlotMap.isEmpty()) {
|
||||||
|
return ReportContext.empty();
|
||||||
|
}
|
||||||
|
Set<String> submittedSlotKeys = new LinkedHashSet<>();
|
||||||
|
Set<String> pendingApprovalSlotKeys = new LinkedHashSet<>();
|
||||||
|
for (ActualReportSlot actualSlot : actualSlots) {
|
||||||
|
if (actualSlot == null || !expectedSlotMap.containsKey(actualSlot.slotKey())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
submittedSlotKeys.add(actualSlot.slotKey());
|
||||||
|
if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(actualSlot.statusCode())) {
|
||||||
|
pendingApprovalSlotKeys.add(actualSlot.slotKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ReportContext(new ArrayList<>(expectedSlotMap.values()), submittedSlotKeys, pendingApprovalSlotKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, ExpectedReportSlot> buildPersonalExpectedSlotMap(List<Long> subordinateIds,
|
||||||
|
List<ReportPeriod> periods) {
|
||||||
|
LinkedHashMap<String, ExpectedReportSlot> result = new LinkedHashMap<>();
|
||||||
|
for (Long subordinateId : subordinateIds) {
|
||||||
|
if (subordinateId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ReportPeriod period : periods) {
|
||||||
|
String slotKey = personalSlotKey(subordinateId, period.periodKey());
|
||||||
|
result.put(slotKey, new ExpectedReportSlot(
|
||||||
|
slotKey, subordinateId, period.periodKey(), period.periodLabel(), null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, ExpectedReportSlot> buildProjectExpectedSlotMap(List<ProjectDO> activeProjects,
|
||||||
|
List<ReportPeriod> periods) {
|
||||||
|
LinkedHashMap<String, ExpectedReportSlot> result = new LinkedHashMap<>();
|
||||||
|
for (ProjectDO project : activeProjects) {
|
||||||
|
if (project == null || project.getManagerUserId() == null || project.getId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (ReportPeriod period : periods) {
|
||||||
|
String slotKey = projectSlotKey(project.getManagerUserId(), project.getId(), period.periodKey());
|
||||||
|
result.put(slotKey, new ExpectedReportSlot(
|
||||||
|
slotKey,
|
||||||
|
project.getManagerUserId(),
|
||||||
|
period.periodKey(),
|
||||||
|
period.periodLabel(),
|
||||||
|
project.getId(),
|
||||||
|
defaultText(project.getProjectName())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ProjectDO> loadActiveProjectsForSubordinates(Collection<Long> subordinateIds) {
|
private List<ProjectDO> loadActiveProjectsForSubordinates(Collection<Long> subordinateIds) {
|
||||||
@@ -236,8 +310,11 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Long> resolveRemindUserIds(List<Long> requestedUserIds, ReportContext context) {
|
private List<Long> resolveRemindUserIds(List<Long> requestedUserIds, ReportContext context) {
|
||||||
LinkedHashSet<Long> unsubmittedUserIds = new LinkedHashSet<>(context.expectedUserIds());
|
LinkedHashSet<Long> unsubmittedUserIds = context.expectedSlots().stream()
|
||||||
unsubmittedUserIds.removeAll(context.submittedUserIds());
|
.filter(slot -> !context.submittedSlotKeys().contains(slot.slotKey()))
|
||||||
|
.map(ExpectedReportSlot::userId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
if (requestedUserIds == null) {
|
if (requestedUserIds == null) {
|
||||||
return new ArrayList<>(unsubmittedUserIds);
|
return new ArrayList<>(unsubmittedUserIds);
|
||||||
}
|
}
|
||||||
@@ -248,34 +325,216 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TeamReportSummaryRespVO.PendingUser> buildPendingUsers(List<Long> expectedUserIds,
|
private List<ExpectedReportSlot> resolveProjectRemindSlots(TeamReportRemindReqVO reqVO, ReportContext context) {
|
||||||
Set<Long> submittedUserIds) {
|
List<ExpectedReportSlot> pendingSlots = context.expectedSlots().stream()
|
||||||
LinkedHashSet<Long> pendingIds = new LinkedHashSet<>(expectedUserIds);
|
.filter(slot -> !context.submittedSlotKeys().contains(slot.slotKey()))
|
||||||
pendingIds.removeAll(submittedUserIds);
|
.toList();
|
||||||
if (pendingIds.isEmpty()) {
|
if (pendingSlots.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserList(pendingIds);
|
|
||||||
List<AdminUserRespDTO> users = result == null ? null : result.getCheckedData();
|
Set<Long> validatedUserIds = reqVO.getUserIds() == null
|
||||||
if (users == null || users.isEmpty()) {
|
? null
|
||||||
users = Collections.emptyList();
|
: new LinkedHashSet<>(teamDashboardAccessService.resolveRequestedSubordinateUserIds(
|
||||||
|
reqVO.getUserIds(), WorkReportConstants.PERMISSION_TEAM_DASHBOARD));
|
||||||
|
|
||||||
|
Long requestedProjectId = reqVO.getProjectId();
|
||||||
|
return pendingSlots.stream()
|
||||||
|
.filter(slot -> validatedUserIds == null || validatedUserIds.contains(slot.userId()))
|
||||||
|
.filter(slot -> requestedProjectId == null || Objects.equals(slot.projectId(), requestedProjectId))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
Map<Long, AdminUserRespDTO> userMap = users.stream()
|
|
||||||
|
private List<TeamReportSummaryRespVO.UnsubmittedReport> buildUnsubmittedReports(ReportContext context) {
|
||||||
|
List<ExpectedReportSlot> pendingSlots = context.expectedSlots().stream()
|
||||||
|
.filter(slot -> !context.submittedSlotKeys().contains(slot.slotKey()))
|
||||||
|
.toList();
|
||||||
|
if (pendingSlots.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
LinkedHashSet<Long> userIds = pendingSlots.stream()
|
||||||
|
.map(ExpectedReportSlot::userId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.filter(user -> user.getId() != null)
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
.collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left,
|
Map<Long, AdminUserRespDTO> userMap = loadUserMap(userIds);
|
||||||
LinkedHashMap::new));
|
|
||||||
List<TeamReportSummaryRespVO.PendingUser> respList = new ArrayList<>();
|
List<TeamReportSummaryRespVO.UnsubmittedReport> respList = new ArrayList<>();
|
||||||
for (Long pendingId : pendingIds) {
|
for (ExpectedReportSlot pendingSlot : pendingSlots) {
|
||||||
AdminUserRespDTO user = userMap.get(pendingId);
|
AdminUserRespDTO user = userMap.get(pendingSlot.userId());
|
||||||
TeamReportSummaryRespVO.PendingUser item = new TeamReportSummaryRespVO.PendingUser();
|
TeamReportSummaryRespVO.UnsubmittedReport item = new TeamReportSummaryRespVO.UnsubmittedReport();
|
||||||
item.setUserId(pendingId);
|
item.setUserId(pendingSlot.userId());
|
||||||
item.setUserNickname(user == null ? "" : defaultText(user.getNickname()));
|
item.setUserNickname(user == null ? "" : defaultText(user.getNickname()));
|
||||||
|
item.setPeriodKey(pendingSlot.periodKey());
|
||||||
|
item.setPeriodLabel(pendingSlot.periodLabel());
|
||||||
|
item.setProjectId(pendingSlot.projectId());
|
||||||
|
item.setProjectName(pendingSlot.projectName());
|
||||||
respList.add(item);
|
respList.add(item);
|
||||||
}
|
}
|
||||||
return respList;
|
return respList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<Long, AdminUserRespDTO> loadUserMap(Collection<Long> userIds) {
|
||||||
|
if (userIds == null || userIds.isEmpty()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserList(userIds);
|
||||||
|
List<AdminUserRespDTO> users = result == null ? null : result.getCheckedData();
|
||||||
|
if (users == null || users.isEmpty()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
return users.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(user -> user.getId() != null)
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
AdminUserRespDTO::getId, user -> user, (left, right) -> left, LinkedHashMap::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ReportPeriod> buildPeriodsByDateRange(String reportType, LocalDate startDate, LocalDate endDate) {
|
||||||
|
return switch (reportType) {
|
||||||
|
case WorkReportConstants.REPORT_TYPE_WEEKLY -> buildWeeklyPeriodsByDateRange(startDate, endDate);
|
||||||
|
case WorkReportConstants.REPORT_TYPE_MONTHLY -> buildMonthlyPeriodsByDateRange(startDate, endDate);
|
||||||
|
case WorkReportConstants.REPORT_TYPE_PROJECT -> buildProjectPeriodsByDateRange(startDate, endDate);
|
||||||
|
default -> throw invalidParamException("报告类型不合法");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ReportPeriod> buildWeeklyPeriodsByDateRange(LocalDate startDate, LocalDate endDate) {
|
||||||
|
LocalDate firstStart = startDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
|
||||||
|
if (firstStart.isAfter(endDate)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<ReportPeriod> periods = new ArrayList<>();
|
||||||
|
for (LocalDate currentStart = firstStart; !currentStart.isAfter(endDate); currentStart = currentStart.plusWeeks(1)) {
|
||||||
|
periods.add(buildWeeklyPeriod(currentStart, currentStart.plusDays(6)));
|
||||||
|
}
|
||||||
|
return periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ReportPeriod> buildMonthlyPeriodsByDateRange(LocalDate startDate, LocalDate endDate) {
|
||||||
|
LocalDate firstStart = startDate.withDayOfMonth(1);
|
||||||
|
if (firstStart.isBefore(startDate)) {
|
||||||
|
firstStart = firstStart.plusMonths(1);
|
||||||
|
}
|
||||||
|
if (firstStart.isAfter(endDate)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<ReportPeriod> periods = new ArrayList<>();
|
||||||
|
for (LocalDate currentStart = firstStart; !currentStart.isAfter(endDate); currentStart = currentStart.plusMonths(1)) {
|
||||||
|
periods.add(buildMonthlyPeriod(currentStart));
|
||||||
|
}
|
||||||
|
return periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ReportPeriod> buildProjectPeriodsByDateRange(LocalDate startDate, LocalDate endDate) {
|
||||||
|
LocalDate firstStart;
|
||||||
|
int day = startDate.getDayOfMonth();
|
||||||
|
if (day <= 1) {
|
||||||
|
firstStart = startDate.withDayOfMonth(1);
|
||||||
|
} else if (day <= 16) {
|
||||||
|
firstStart = startDate.withDayOfMonth(16);
|
||||||
|
} else {
|
||||||
|
firstStart = startDate.plusMonths(1).withDayOfMonth(1);
|
||||||
|
}
|
||||||
|
if (firstStart.isAfter(endDate)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<ReportPeriod> periods = new ArrayList<>();
|
||||||
|
for (LocalDate currentStart = firstStart; !currentStart.isAfter(endDate); currentStart = nextHalfMonthStart(currentStart)) {
|
||||||
|
periods.add(buildProjectPeriod(currentStart));
|
||||||
|
}
|
||||||
|
return periods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate nextHalfMonthStart(LocalDate currentStart) {
|
||||||
|
if (currentStart.getDayOfMonth() == 1) {
|
||||||
|
return currentStart.withDayOfMonth(16);
|
||||||
|
}
|
||||||
|
return currentStart.plusMonths(1).withDayOfMonth(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod parseRequiredPeriod(String reportType, String periodKey) {
|
||||||
|
return switch (reportType) {
|
||||||
|
case WorkReportConstants.REPORT_TYPE_WEEKLY -> parseWeeklyPeriod(periodKey);
|
||||||
|
case WorkReportConstants.REPORT_TYPE_MONTHLY -> parseMonthlyPeriod(periodKey);
|
||||||
|
case WorkReportConstants.REPORT_TYPE_PROJECT -> parseProjectPeriod(periodKey);
|
||||||
|
default -> throw invalidParamException("报告类型不合法");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod parseWeeklyPeriod(String periodKey) {
|
||||||
|
String[] parts = periodKey.split("-");
|
||||||
|
if (parts.length != 7 || !WorkReportConstants.REPORT_TYPE_WEEKLY.equals(parts[0])) {
|
||||||
|
throw invalidParamException("周报周期主键不合法");
|
||||||
|
}
|
||||||
|
LocalDate startDate = parsePeriodDate(parts, 1);
|
||||||
|
LocalDate endDate = parsePeriodDate(parts, 4);
|
||||||
|
return buildWeeklyPeriod(startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod parseMonthlyPeriod(String periodKey) {
|
||||||
|
String[] parts = periodKey.split("-");
|
||||||
|
if (parts.length != 7 || !WorkReportConstants.REPORT_TYPE_MONTHLY.equals(parts[0])) {
|
||||||
|
throw invalidParamException("月报周期主键不合法");
|
||||||
|
}
|
||||||
|
LocalDate startDate = parsePeriodDate(parts, 1);
|
||||||
|
return buildMonthlyPeriod(startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod parseProjectPeriod(String periodKey) {
|
||||||
|
String[] parts = periodKey.split("-");
|
||||||
|
if (parts.length != 8 || !WorkReportConstants.REPORT_TYPE_PROJECT.equals(parts[0])) {
|
||||||
|
throw invalidParamException("项目半月报周期主键不合法");
|
||||||
|
}
|
||||||
|
LocalDate startDate = parsePeriodDate(parts, 1);
|
||||||
|
return buildProjectPeriod(startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod buildWeeklyPeriod(LocalDate startDate, LocalDate endDate) {
|
||||||
|
return new ReportPeriod(
|
||||||
|
String.format("weekly-%s-%s", startDate, endDate),
|
||||||
|
String.format("%s 至 %s", startDate, endDate),
|
||||||
|
startDate,
|
||||||
|
endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod buildMonthlyPeriod(LocalDate startDate) {
|
||||||
|
LocalDate normalizedStartDate = startDate.withDayOfMonth(1);
|
||||||
|
LocalDate endDate = normalizedStartDate.withDayOfMonth(normalizedStartDate.lengthOfMonth());
|
||||||
|
return new ReportPeriod(
|
||||||
|
String.format("monthly-%s-%s", normalizedStartDate, endDate),
|
||||||
|
String.format("%d-%02d", normalizedStartDate.getYear(), normalizedStartDate.getMonthValue()),
|
||||||
|
normalizedStartDate,
|
||||||
|
endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportPeriod buildProjectPeriod(LocalDate startDate) {
|
||||||
|
LocalDate normalizedStartDate = startDate.getDayOfMonth() <= 15
|
||||||
|
? startDate.withDayOfMonth(1)
|
||||||
|
: startDate.withDayOfMonth(16);
|
||||||
|
boolean firstHalf = normalizedStartDate.getDayOfMonth() == 1;
|
||||||
|
LocalDate endDate = firstHalf
|
||||||
|
? normalizedStartDate.withDayOfMonth(15)
|
||||||
|
: normalizedStartDate.withDayOfMonth(normalizedStartDate.lengthOfMonth());
|
||||||
|
String half = firstHalf ? "上半月" : "下半月";
|
||||||
|
return new ReportPeriod(
|
||||||
|
String.format("project-%s-%s-%d", normalizedStartDate, endDate, firstHalf ? 1 : 2),
|
||||||
|
String.format("%d-%02d %s", normalizedStartDate.getYear(), normalizedStartDate.getMonthValue(), half),
|
||||||
|
normalizedStartDate,
|
||||||
|
endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateRange normalizeDateRange(LocalDate startDate, LocalDate endDate) {
|
||||||
|
if (startDate == null && endDate == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LocalDate normalizedStartDate = startDate == null ? endDate : startDate;
|
||||||
|
LocalDate normalizedEndDate = endDate == null ? startDate : endDate;
|
||||||
|
if (normalizedStartDate.isAfter(normalizedEndDate)) {
|
||||||
|
throw invalidParamException("周期起始日期不能晚于结束日期");
|
||||||
|
}
|
||||||
|
return new DateRange(normalizedStartDate, normalizedEndDate);
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeReportType(String reportType) {
|
private String normalizeReportType(String reportType) {
|
||||||
if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)
|
if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)
|
||||||
|| WorkReportConstants.REPORT_TYPE_MONTHLY.equals(reportType)
|
|| WorkReportConstants.REPORT_TYPE_MONTHLY.equals(reportType)
|
||||||
@@ -294,107 +553,105 @@ public class TeamWorkReportServiceImpl implements TeamWorkReportService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatPeriodKeyForRemind(String reportType, String periodKey) {
|
||||||
|
if (!StringUtils.hasText(periodKey)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return switch (reportType) {
|
||||||
|
case WorkReportConstants.REPORT_TYPE_WEEKLY -> formatWeeklyPeriodKey(periodKey);
|
||||||
|
case WorkReportConstants.REPORT_TYPE_MONTHLY -> formatMonthlyPeriodKey(periodKey);
|
||||||
|
case WorkReportConstants.REPORT_TYPE_PROJECT -> formatProjectPeriodKey(periodKey);
|
||||||
|
default -> periodKey;
|
||||||
|
};
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
return periodKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatWeeklyPeriodKey(String periodKey) {
|
||||||
|
String[] parts = periodKey.split("-");
|
||||||
|
if (parts.length != 7 || !WorkReportConstants.REPORT_TYPE_WEEKLY.equals(parts[0])) {
|
||||||
|
return periodKey;
|
||||||
|
}
|
||||||
|
LocalDate startDate = parsePeriodDate(parts, 1);
|
||||||
|
LocalDate endDate = parsePeriodDate(parts, 4);
|
||||||
|
int weekOfYear = startDate.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
|
||||||
|
return startDate.getYear() + "年" + weekOfYear + "周(" + formatMonthDayRange(startDate, endDate) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatMonthlyPeriodKey(String periodKey) {
|
||||||
|
String[] parts = periodKey.split("-");
|
||||||
|
if (parts.length != 7 || !WorkReportConstants.REPORT_TYPE_MONTHLY.equals(parts[0])) {
|
||||||
|
return periodKey;
|
||||||
|
}
|
||||||
|
LocalDate startDate = parsePeriodDate(parts, 1);
|
||||||
|
return startDate.getYear() + "年" + startDate.getMonthValue() + "月";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatProjectPeriodKey(String periodKey) {
|
||||||
|
String[] parts = periodKey.split("-");
|
||||||
|
if (parts.length != 8 || !WorkReportConstants.REPORT_TYPE_PROJECT.equals(parts[0])) {
|
||||||
|
return periodKey;
|
||||||
|
}
|
||||||
|
LocalDate startDate = parsePeriodDate(parts, 1);
|
||||||
|
String half = switch (parts[7]) {
|
||||||
|
case "1" -> "上半月";
|
||||||
|
case "2" -> "下半月";
|
||||||
|
default -> startDate.getDayOfMonth() <= 15 ? "上半月" : "下半月";
|
||||||
|
};
|
||||||
|
return startDate.getYear() + "年" + startDate.getMonthValue() + "月" + half;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDate parsePeriodDate(String[] parts, int startIndex) {
|
||||||
|
try {
|
||||||
|
return LocalDate.parse(parts[startIndex] + "-" + parts[startIndex + 1] + "-" + parts[startIndex + 2]);
|
||||||
|
} catch (DateTimeParseException ex) {
|
||||||
|
throw new IllegalArgumentException("Invalid period date", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatMonthDayRange(LocalDate startDate, LocalDate endDate) {
|
||||||
|
return startDate.getMonthValue() + "月" + startDate.getDayOfMonth()
|
||||||
|
+ "日-" + endDate.getMonthValue() + "月" + endDate.getDayOfMonth() + "日";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String personalSlotKey(Long userId, String periodKey) {
|
||||||
|
return userId + ":" + periodKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String projectSlotKey(Long userId, Long projectId, String periodKey) {
|
||||||
|
return userId + ":" + projectId + ":" + periodKey;
|
||||||
|
}
|
||||||
|
|
||||||
private String defaultText(String text) {
|
private String defaultText(String text) {
|
||||||
return StringUtils.hasText(text) ? text.trim() : "";
|
return StringUtils.hasText(text) ? text.trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReportContext buildReportContextByDateRange(String reportType, LocalDate startDate, LocalDate endDate) {
|
private record DateRange(LocalDate startDate, LocalDate endDate) {
|
||||||
if (WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) {
|
|
||||||
return buildProjectContextByDateRange(startDate, endDate);
|
|
||||||
}
|
}
|
||||||
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
|
||||||
if (subordinateIds.isEmpty()) {
|
private record ReportPeriod(String periodKey, String periodLabel, LocalDate startDate, LocalDate endDate) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ExpectedReportSlot(
|
||||||
|
String slotKey,
|
||||||
|
Long userId,
|
||||||
|
String periodKey,
|
||||||
|
String periodLabel,
|
||||||
|
Long projectId,
|
||||||
|
String projectName) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ActualReportSlot(String slotKey, String statusCode) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ReportContext(
|
||||||
|
List<ExpectedReportSlot> expectedSlots,
|
||||||
|
Set<String> submittedSlotKeys,
|
||||||
|
Set<String> pendingApprovalSlotKeys) {
|
||||||
|
private static ReportContext empty() {
|
||||||
return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
|
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<Long> subordinateIds) {
|
|
||||||
List<WeeklyReportDO> reports = weeklyReportMapper.selectListByReporterIdsAndPeriodDateRange(
|
|
||||||
subordinateIds, startDate, endDate, SUBMITTED_STATUS_CODES);
|
|
||||||
Set<Long> submittedUserIds = new LinkedHashSet<>();
|
|
||||||
Set<Long> 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<Long> subordinateIds) {
|
|
||||||
List<MonthlyReportDO> reports = monthlyReportMapper.selectListByReporterIdsAndPeriodDateRange(
|
|
||||||
subordinateIds, startDate, endDate, SUBMITTED_STATUS_CODES);
|
|
||||||
Set<Long> submittedUserIds = new LinkedHashSet<>();
|
|
||||||
Set<Long> 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<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
|
||||||
if (subordinateIds.isEmpty()) {
|
|
||||||
return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
|
|
||||||
}
|
|
||||||
List<ProjectDO> activeProjects = loadActiveProjectsForSubordinates(subordinateIds);
|
|
||||||
if (activeProjects.isEmpty()) {
|
|
||||||
return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
|
|
||||||
}
|
|
||||||
Map<Long, List<ProjectDO>> projectsByOwner = activeProjects.stream()
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.filter(project -> project.getManagerUserId() != null)
|
|
||||||
.collect(Collectors.groupingBy(ProjectDO::getManagerUserId, LinkedHashMap::new, Collectors.toList()));
|
|
||||||
LinkedHashSet<Long> expectedUserIds = new LinkedHashSet<>(projectsByOwner.keySet());
|
|
||||||
List<ProjectReportDO> reports = projectReportMapper.selectListByProjectOwnerIdsAndPeriodDateRange(
|
|
||||||
expectedUserIds, startDate, endDate, SUBMITTED_STATUS_CODES);
|
|
||||||
Map<Long, Set<Long>> submittedProjectsByOwner = new HashMap<>();
|
|
||||||
Set<Long> submittedUserIds = new LinkedHashSet<>();
|
|
||||||
Set<Long> 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<Long, List<ProjectDO>> entry : projectsByOwner.entrySet()) {
|
|
||||||
Long ownerId = entry.getKey();
|
|
||||||
Set<Long> 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<Long> expectedUserIds, Set<Long> submittedUserIds,
|
|
||||||
Set<Long> pendingApprovalUserIds) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user