feat(工作报告定时生成): 工作报告现在可以定时生成,并且可以刷新当前报告。

This commit is contained in:
dk
2026-06-13 22:09:49 +08:00
parent 896ef0f127
commit 32cc855cf0
23 changed files with 1070 additions and 8 deletions

View File

@@ -2,11 +2,13 @@ package com.njcn.rdms.module.project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 项目交付域服务启动类
*/
@SpringBootApplication
@EnableScheduling
public class ProjectServerApplication {
public static void main(String[] args) {

View File

@@ -14,6 +14,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.Month
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService;
@@ -66,6 +67,13 @@ public class MonthlyReportController {
return success(monthlyReportService.previewMonthlyDefaultDraft(reqVO));
}
@PostMapping("/refresh-draft")
@Operation(summary = "手动刷新月报默认稿")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')")
public CommonResult<MonthlyReportRespVO> refreshMonthlyDraft(@Valid @RequestBody MonthlyReportRefreshDraftReqVO reqVO) {
return success(monthlyReportService.refreshMonthlyDraft(reqVO));
}
@PostMapping
@Operation(summary = "新建月报草稿")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')")

View File

@@ -0,0 +1,40 @@
package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 月报手动刷新默认稿 Request VO")
@Data
public class MonthlyReportRefreshDraftReqVO {
@NotBlank(message = "周期编码不能为空")
private String periodKey;
@NotBlank(message = "周期名称不能为空")
private String periodLabel;
@NotNull(message = "周期开始日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate periodStartDate;
@NotNull(message = "周期结束日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate periodEndDate;
@Valid
private List<PersonalReportReviewItemReqVO> reviewItems;
@Valid
private List<PersonalReportPlanItemReqVO> planItems;
}

View File

@@ -14,6 +14,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.Proje
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportOwnerProjectOptionRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportSaveReqVO;
import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService;
@@ -75,6 +76,14 @@ public class ProjectReportController {
return success(projectReportService.previewProjectDefaultDraft(projectId, reqVO));
}
@PostMapping("/{projectId}/refresh-draft")
@Operation(summary = "手动刷新项目半月报默认稿")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')")
public CommonResult<ProjectReportRespVO> refreshProjectDraft(@PathVariable("projectId") Long projectId,
@Valid @RequestBody ProjectReportRefreshDraftReqVO reqVO) {
return success(projectReportService.refreshProjectDraft(projectId, reqVO));
}
@PostMapping
@Operation(summary = "新建项目半月报草稿")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')")

View File

@@ -0,0 +1,49 @@
package com.njcn.rdms.module.project.controller.admin.workreport.project.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 项目半月报手动刷新默认稿 Request VO")
@Data
public class ProjectReportRefreshDraftReqVO {
@NotBlank(message = "周期编码不能为空")
private String periodKey;
@NotBlank(message = "周期名称不能为空")
private String periodLabel;
@NotNull(message = "周期开始日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate periodStartDate;
@NotNull(message = "周期结束日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate periodEndDate;
@NotNull(message = "上半月/下半月标记不能为空")
private Integer flag;
private String projectStatusDesc;
private String projectProgressPlan;
private String projectKeyPoints;
private String projectProblems;
@Valid
private List<ProjectReportItemReqVO> currentItems;
@Valid
private List<ProjectReportItemReqVO> nextItems;
}

View File

@@ -13,6 +13,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.Weekly
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportSaveReqVO;
import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService;
@@ -65,6 +66,13 @@ public class WeeklyReportController {
return success(weeklyReportService.previewWeeklyDefaultDraft(reqVO));
}
@PostMapping("/refresh-draft")
@Operation(summary = "手动刷新周报默认稿")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')")
public CommonResult<WeeklyReportRespVO> refreshWeeklyDraft(@Valid @RequestBody WeeklyReportRefreshDraftReqVO reqVO) {
return success(weeklyReportService.refreshWeeklyDraft(reqVO));
}
@PostMapping
@Operation(summary = "新建周报草稿")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')")

View File

@@ -0,0 +1,46 @@
package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 周报手动刷新默认稿 Request VO")
@Data
public class WeeklyReportRefreshDraftReqVO {
@NotBlank(message = "周期编码不能为空")
private String periodKey;
@NotBlank(message = "周期名称不能为空")
private String periodLabel;
@NotNull(message = "周期开始日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate periodStartDate;
@NotNull(message = "周期结束日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate periodEndDate;
@NotNull(message = "是否出差不能为空")
private Boolean isBusinessTrip;
@Valid
private List<PersonalReportReviewItemReqVO> reviewItems;
@Valid
private List<PersonalReportPlanItemReqVO> planItems;
@Valid
private List<WeeklyReportTravelSegmentReqVO> travelSegments;
}

View File

@@ -0,0 +1,50 @@
package com.njcn.rdms.module.project.dal.dataobject.job;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* RDMS 任务运行日志表
*/
@TableName("rdms_work_report_job_run_log")
@Data
@EqualsAndHashCode(callSuper = true)
public class JobRunLogDO extends BaseDO {
@TableId
private Long id;
private String runId;
private String jobName;
private String jobStatus;
private LocalDate runDate;
private String periodKey;
private String triggerSource;
private String nodeName;
private LocalDateTime startedAt;
private LocalDateTime finishedAt;
private Integer successCount;
private Integer skipCount;
private Integer failCount;
private Long costMs;
private String message;
}

View File

@@ -0,0 +1,9 @@
package com.njcn.rdms.module.project.dal.mysql.job;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.module.project.dal.dataobject.job.JobRunLogDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface JobRunLogMapper extends BaseMapperX<JobRunLogDO> {
}

View File

@@ -65,6 +65,16 @@ public interface ProjectMapper extends BaseMapperX<ProjectDO> {
.orderByDesc(BaseDO::getCreateTime));
}
default List<ProjectDO> selectListByStatusCodesNotIn(Collection<String> statusCodes) {
LambdaQueryWrapperX<ProjectDO> queryWrapper = new LambdaQueryWrapperX<>();
queryWrapper.isNotNull(ProjectDO::getManagerUserId);
queryWrapper.orderByDesc(BaseDO::getCreateTime);
if (statusCodes != null && !statusCodes.isEmpty()) {
queryWrapper.notIn(ProjectDO::getStatusCode, statusCodes);
}
return selectList(queryWrapper);
}
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
ProjectDO update = new ProjectDO();
update.setStatusCode(toStatus);

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.service.workreport.autogen;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDate;
@Data
@Accessors(chain = true)
public class AutoGenPeriod {
private String periodKey;
private String periodLabel;
private LocalDate periodStartDate;
private LocalDate periodEndDate;
private Integer flag;
}

View File

@@ -0,0 +1,23 @@
package com.njcn.rdms.module.project.service.workreport.autogen;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class AutoGenResult {
private LocalDateTime startedAt;
private LocalDateTime finishedAt;
private int successCount;
private int skipCount;
private int failCount;
private final List<String> failMessages = new ArrayList<>();
}

View File

@@ -0,0 +1,38 @@
package com.njcn.rdms.module.project.service.workreport.autogen;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@ConfigurationProperties(prefix = "rdms.work-report.auto-gen")
@Data
public class WorkReportAutoGenProperties {
private boolean enabled = false;
private TriggerProperties weekly = new TriggerProperties();
private TriggerProperties monthly = new TriggerProperties();
private TriggerProperties project = new TriggerProperties();
private ScopeProperties scope = new ScopeProperties();
@Data
public static class TriggerProperties {
private boolean enabled = false;
private String cron;
}
@Data
public static class ScopeProperties {
private List<Long> deptIds = new ArrayList<>();
}
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.project.service.workreport.autogen;
import jakarta.annotation.Resource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class WorkReportAutoGenerateScheduler {
@Resource
private WorkReportAutoGenerateService workReportAutoGenerateService;
@Scheduled(cron = "${rdms.work-report.auto-gen.weekly.cron:0 0 12 ? * FRI}", zone = "Asia/Shanghai")
public void weekly() {
workReportAutoGenerateService.autoGenerateWeeklyBySchedule();
}
@Scheduled(cron = "${rdms.work-report.auto-gen.monthly.cron:0 0 12 1-31 * ?}", zone = "Asia/Shanghai")
public void monthlyDaily() {
workReportAutoGenerateService.autoGenerateMonthlyBySchedule();
}
@Scheduled(cron = "${rdms.work-report.auto-gen.project.cron:0 0 12 1-31 * ?}", zone = "Asia/Shanghai")
public void projectDaily() {
workReportAutoGenerateService.autoGenerateProjectBySchedule();
}
}

View File

@@ -0,0 +1,486 @@
package com.njcn.rdms.module.project.service.workreport.autogen;
import com.njcn.rdms.framework.common.enums.UserTypeEnum;
import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.json.JsonUtils;
import com.njcn.rdms.framework.security.core.LoginUser;
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
import com.njcn.rdms.module.project.constant.WorkReportConstants;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportSaveReqVO;
import com.njcn.rdms.module.project.dal.dataobject.job.JobRunLogDO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.dal.mysql.job.JobRunLogMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService;
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.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.net.InetAddress;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Slf4j
@Service
public class WorkReportAutoGenerateService {
private static final ZoneId ASIA_SHANGHAI = ZoneId.of("Asia/Shanghai");
private static final String TRIGGER_SOURCE_SCHEDULE = "SCHEDULE";
private static final String STATUS_RUNNING = "RUNNING";
private static final String STATUS_SUCCESS = "SUCCESS";
private static final String STATUS_PARTIAL_SUCCESS = "PARTIAL_SUCCESS";
private static final String STATUS_FAILED = "FAILED";
private static final String JOB_NAME_WEEKLY = "WORK_REPORT_AUTO_GEN_WEEKLY";
private static final String JOB_NAME_MONTHLY = "WORK_REPORT_AUTO_GEN_MONTHLY";
private static final String JOB_NAME_PROJECT = "WORK_REPORT_AUTO_GEN_PROJECT";
private static final String LOCK_KEY_TEMPLATE = "auto-gen:work-report:%s:%s:%s";
private static final int MAX_FAIL_MESSAGES = 20;
@Resource
private WorkReportAutoGenProperties properties;
@Resource
private WorkReportCommonService workReportCommonService;
@Resource
private WeeklyReportMapper weeklyReportMapper;
@Resource
private MonthlyReportMapper monthlyReportMapper;
@Resource
private ProjectReportMapper projectReportMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private ObjectStatusModelMapper objectStatusModelMapper;
@Resource
private JobRunLogMapper jobRunLogMapper;
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@Resource
private RedissonClient redissonClient;
public void autoGenerateWeeklyBySchedule() {
if (!properties.isEnabled() || !properties.getWeekly().isEnabled()) {
return;
}
LocalDate today = LocalDate.now(ASIA_SHANGHAI);
AutoGenPeriod period = buildWeeklyPeriod(today);
runAs(buildSystemLoginUser(), () -> executeWeekly(period, today));
}
public void autoGenerateMonthlyBySchedule() {
if (!properties.isEnabled() || !properties.getMonthly().isEnabled()) {
return;
}
LocalDate today = LocalDate.now(ASIA_SHANGHAI);
if (!today.equals(today.with(TemporalAdjusters.lastDayOfMonth()))) {
return;
}
AutoGenPeriod period = buildMonthlyPeriod(today);
runAs(buildSystemLoginUser(), () -> executeMonthly(period, today));
}
public void autoGenerateProjectBySchedule() {
if (!properties.isEnabled() || !properties.getProject().isEnabled()) {
return;
}
LocalDate today = LocalDate.now(ASIA_SHANGHAI);
if (today.getDayOfMonth() != 15) {
return;
}
AutoGenPeriod period = buildProjectFirstHalfPeriod(today);
runAs(buildSystemLoginUser(), () -> executeProject(period, today));
}
private void executeWeekly(AutoGenPeriod period, LocalDate runDate) {
JobRunLogDO jobLog = createRunningJobLog(JOB_NAME_WEEKLY, runDate, period.getPeriodKey());
AutoGenResult result = new AutoGenResult();
result.setStartedAt(jobLog.getStartedAt());
try {
for (AdminUserRespDTO user : loadRdUsers()) {
processWeeklyUser(period, result, user);
}
} catch (Exception ex) {
appendFailMessage(result, "job=" + JOB_NAME_WEEKLY + ", message=" + ex.getMessage());
log.error("[executeWeekly][periodKey={}]", period.getPeriodKey(), ex);
} finally {
finishJobLog(jobLog, result);
}
}
private void executeMonthly(AutoGenPeriod period, LocalDate runDate) {
JobRunLogDO jobLog = createRunningJobLog(JOB_NAME_MONTHLY, runDate, period.getPeriodKey());
AutoGenResult result = new AutoGenResult();
result.setStartedAt(jobLog.getStartedAt());
try {
for (AdminUserRespDTO user : loadRdUsers()) {
processMonthlyUser(period, result, user);
}
} catch (Exception ex) {
appendFailMessage(result, "job=" + JOB_NAME_MONTHLY + ", message=" + ex.getMessage());
log.error("[executeMonthly][periodKey={}]", period.getPeriodKey(), ex);
} finally {
finishJobLog(jobLog, result);
}
}
private void executeProject(AutoGenPeriod period, LocalDate runDate) {
JobRunLogDO jobLog = createRunningJobLog(JOB_NAME_PROJECT, runDate, period.getPeriodKey());
AutoGenResult result = new AutoGenResult();
result.setStartedAt(jobLog.getStartedAt());
try {
for (ProjectAutoGenCandidate candidate : loadProjectCandidates()) {
processProjectCandidate(period, result, candidate);
}
} catch (Exception ex) {
appendFailMessage(result, "job=" + JOB_NAME_PROJECT + ", message=" + ex.getMessage());
log.error("[executeProject][periodKey={}]", period.getPeriodKey(), ex);
} finally {
finishJobLog(jobLog, result);
}
}
private void processWeeklyUser(AutoGenPeriod period, AutoGenResult result, AdminUserRespDTO user) {
String subjectKey = String.valueOf(user.getId());
executeWithLock(WorkReportConstants.REPORT_TYPE_WEEKLY, period.getPeriodKey(), subjectKey, result, () -> {
if (weeklyReportMapper.selectByReporterIdAndPeriodKey(user.getId(), period.getPeriodKey()) != null) {
result.setSkipCount(result.getSkipCount() + 1);
return;
}
WeeklyReportSaveReqVO reqVO = new WeeklyReportSaveReqVO();
reqVO.setPeriodKey(period.getPeriodKey());
reqVO.setPeriodLabel(period.getPeriodLabel());
reqVO.setPeriodStartDate(period.getPeriodStartDate());
reqVO.setPeriodEndDate(period.getPeriodEndDate());
reqVO.setIsBusinessTrip(Boolean.FALSE);
reqVO.setReviewItems(Collections.emptyList());
reqVO.setPlanItems(Collections.emptyList());
reqVO.setTravelSegments(Collections.emptyList());
runAs(buildLoginUser(user), () -> workReportCommonService.createWeeklyReport(reqVO, user.getId()));
result.setSuccessCount(result.getSuccessCount() + 1);
}, () -> "userId=" + user.getId());
}
private void processMonthlyUser(AutoGenPeriod period, AutoGenResult result, AdminUserRespDTO user) {
String subjectKey = String.valueOf(user.getId());
executeWithLock(WorkReportConstants.REPORT_TYPE_MONTHLY, period.getPeriodKey(), subjectKey, result, () -> {
if (monthlyReportMapper.selectByReporterIdAndPeriodKey(user.getId(), period.getPeriodKey()) != null) {
result.setSkipCount(result.getSkipCount() + 1);
return;
}
MonthlyReportSaveReqVO reqVO = new MonthlyReportSaveReqVO();
reqVO.setPeriodKey(period.getPeriodKey());
reqVO.setPeriodLabel(period.getPeriodLabel());
reqVO.setPeriodStartDate(period.getPeriodStartDate());
reqVO.setPeriodEndDate(period.getPeriodEndDate());
reqVO.setReviewItems(Collections.emptyList());
reqVO.setPlanItems(Collections.emptyList());
runAs(buildLoginUser(user), () -> workReportCommonService.createMonthlyReport(reqVO, user.getId()));
result.setSuccessCount(result.getSuccessCount() + 1);
}, () -> "userId=" + user.getId());
}
private void processProjectCandidate(AutoGenPeriod period, AutoGenResult result, ProjectAutoGenCandidate candidate) {
String subjectKey = candidate.projectId() + ":" + candidate.user().getId();
executeWithLock(WorkReportConstants.REPORT_TYPE_PROJECT, period.getPeriodKey(), subjectKey, result, () -> {
if (projectReportMapper.selectByProjectIdAndPeriodKeyAndProjectOwnerId(
candidate.projectId(), period.getPeriodKey(), candidate.user().getId()) != null) {
result.setSkipCount(result.getSkipCount() + 1);
return;
}
ProjectReportSaveReqVO reqVO = new ProjectReportSaveReqVO();
reqVO.setProjectId(candidate.projectId());
reqVO.setPeriodKey(period.getPeriodKey());
reqVO.setPeriodLabel(period.getPeriodLabel());
reqVO.setPeriodStartDate(period.getPeriodStartDate());
reqVO.setPeriodEndDate(period.getPeriodEndDate());
reqVO.setFlag(period.getFlag());
reqVO.setCurrentItems(Collections.emptyList());
reqVO.setNextItems(Collections.emptyList());
runAs(buildLoginUser(candidate.user()), () -> workReportCommonService.createProjectReport(reqVO, candidate.user().getId()));
result.setSuccessCount(result.getSuccessCount() + 1);
}, () -> "projectId=" + candidate.projectId() + ", userId=" + candidate.user().getId());
}
private void executeWithLock(String reportType, String periodKey, String subjectKey, AutoGenResult result,
Runnable action, Supplier<String> failContextSupplier) {
RLock lock = redissonClient.getLock(String.format(LOCK_KEY_TEMPLATE, reportType, periodKey, subjectKey));
try {
if (!lock.tryLock(0, 30, TimeUnit.SECONDS)) {
result.setSkipCount(result.getSkipCount() + 1);
return;
}
try {
action.run();
} catch (ServiceException ex) {
if (Objects.equals(ex.getCode(), ErrorCodeConstants.WORK_REPORT_PERIOD_DUPLICATE.getCode())) {
result.setSkipCount(result.getSkipCount() + 1);
return;
}
result.setFailCount(result.getFailCount() + 1);
appendFailMessage(result, failContextSupplier.get() + ", code=" + ex.getCode() + ", message=" + ex.getMessage());
log.error("[executeWithLock][reportType={}][periodKey={}][subjectKey={}]", reportType, periodKey, subjectKey, ex);
} catch (Exception ex) {
result.setFailCount(result.getFailCount() + 1);
appendFailMessage(result, failContextSupplier.get() + ", message=" + ex.getMessage());
log.error("[executeWithLock][reportType={}][periodKey={}][subjectKey={}]", reportType, periodKey, subjectKey, ex);
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
result.setFailCount(result.getFailCount() + 1);
appendFailMessage(result, failContextSupplier.get() + ", message=lock interrupted");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private List<AdminUserRespDTO> loadRdUsers() {
List<Long> rootDeptIds = properties.getScope().getDeptIds();
if (rootDeptIds == null || rootDeptIds.isEmpty()) {
log.warn("[loadRdUsers][未配置 rdms.work-report.auto-gen.scope.dept-ids自动生成功能跳过]");
return Collections.emptyList();
}
Set<Long> deptIds = new LinkedHashSet<>();
for (Long rootDeptId : rootDeptIds) {
if (rootDeptId == null) {
continue;
}
deptIds.add(rootDeptId);
CommonResult<List<DeptRespDTO>> childResult = deptApi.getChildDeptList(rootDeptId);
List<DeptRespDTO> childDepts = childResult == null || childResult.getCheckedData() == null
? Collections.emptyList()
: childResult.getCheckedData();
for (DeptRespDTO dept : childDepts) {
if (dept != null && dept.getId() != null) {
deptIds.add(dept.getId());
}
}
}
if (deptIds.isEmpty()) {
return Collections.emptyList();
}
CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserListByDeptIds(deptIds);
List<AdminUserRespDTO> users = result == null || result.getCheckedData() == null
? Collections.emptyList()
: result.getCheckedData();
Map<Long, AdminUserRespDTO> deduplicated = new LinkedHashMap<>();
for (AdminUserRespDTO user : users) {
if (user == null || user.getId() == null || !Objects.equals(user.getStatus(), 0)) {
continue;
}
deduplicated.put(user.getId(), user);
}
return new ArrayList<>(deduplicated.values());
}
private List<ProjectAutoGenCandidate> loadProjectCandidates() {
List<String> terminalStatusCodes = objectStatusModelMapper
.selectTerminalStatusCodesByObjectTypeEnabled(ProjectObjectConstants.OBJECT_TYPE);
List<ProjectDO> projects = projectMapper.selectListByStatusCodesNotIn(terminalStatusCodes);
if (projects == null || projects.isEmpty()) {
return Collections.emptyList();
}
Set<Long> managerUserIds = projects.stream()
.map(ProjectDO::getManagerUserId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (managerUserIds.isEmpty()) {
return Collections.emptyList();
}
CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserList(managerUserIds);
List<AdminUserRespDTO> users = result == null || result.getCheckedData() == null
? Collections.emptyList()
: result.getCheckedData();
Map<Long, AdminUserRespDTO> userMap = users.stream()
.filter(Objects::nonNull)
.filter(user -> user.getId() != null)
.collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left, LinkedHashMap::new));
List<ProjectAutoGenCandidate> candidates = new ArrayList<>();
for (ProjectDO project : projects) {
if (project.getManagerUserId() == null) {
continue;
}
AdminUserRespDTO user = userMap.get(project.getManagerUserId());
if (user == null || !Objects.equals(user.getStatus(), 0)) {
continue;
}
candidates.add(new ProjectAutoGenCandidate(project.getId(), user));
}
return candidates;
}
private AutoGenPeriod buildWeeklyPeriod(LocalDate today) {
LocalDate periodStartDate = today.minusDays(today.getDayOfWeek().getValue() - 1L);
LocalDate periodEndDate = periodStartDate.plusDays(6);
int week = periodStartDate.get(java.time.temporal.IsoFields.WEEK_OF_WEEK_BASED_YEAR);
int year = periodStartDate.get(java.time.temporal.IsoFields.WEEK_BASED_YEAR);
return new AutoGenPeriod()
.setPeriodKey(String.format("%d-W%02d", year, week))
.setPeriodLabel(String.format("%d 年第 %02d 周", year, week))
.setPeriodStartDate(periodStartDate)
.setPeriodEndDate(periodEndDate);
}
private AutoGenPeriod buildMonthlyPeriod(LocalDate today) {
return new AutoGenPeriod()
.setPeriodKey(String.format("%d-%02d", today.getYear(), today.getMonthValue()))
.setPeriodLabel(String.format("%d 年 %02d 月", today.getYear(), today.getMonthValue()))
.setPeriodStartDate(today.withDayOfMonth(1))
.setPeriodEndDate(today.withDayOfMonth(today.lengthOfMonth()));
}
private AutoGenPeriod buildProjectFirstHalfPeriod(LocalDate today) {
return new AutoGenPeriod()
.setPeriodKey(String.format("%d-%02d-01", today.getYear(), today.getMonthValue()))
.setPeriodLabel(String.format("%d 年 %02d 月 上半月", today.getYear(), today.getMonthValue()))
.setPeriodStartDate(today.withDayOfMonth(1))
.setPeriodEndDate(today.withDayOfMonth(15))
.setFlag(1);
}
private JobRunLogDO createRunningJobLog(String jobName, LocalDate runDate, String periodKey) {
JobRunLogDO jobLog = new JobRunLogDO();
jobLog.setRunId(UUID.randomUUID().toString().replace("-", ""));
jobLog.setJobName(jobName);
jobLog.setJobStatus(STATUS_RUNNING);
jobLog.setRunDate(runDate);
jobLog.setPeriodKey(periodKey);
jobLog.setTriggerSource(TRIGGER_SOURCE_SCHEDULE);
jobLog.setNodeName(resolveNodeName());
jobLog.setStartedAt(LocalDateTime.now(ASIA_SHANGHAI));
jobLog.setSuccessCount(0);
jobLog.setSkipCount(0);
jobLog.setFailCount(0);
jobRunLogMapper.insert(jobLog);
return jobLog;
}
private void finishJobLog(JobRunLogDO jobLog, AutoGenResult result) {
LocalDateTime finishedAt = LocalDateTime.now(ASIA_SHANGHAI);
result.setFinishedAt(finishedAt);
jobLog.setFinishedAt(finishedAt);
jobLog.setSuccessCount(result.getSuccessCount());
jobLog.setSkipCount(result.getSkipCount());
jobLog.setFailCount(result.getFailCount());
jobLog.setCostMs(java.time.Duration.between(result.getStartedAt(), finishedAt).toMillis());
jobLog.setJobStatus(resolveJobStatus(result));
jobLog.setMessage(buildMessage(result.getFailMessages()));
jobRunLogMapper.updateById(jobLog);
}
private String resolveJobStatus(AutoGenResult result) {
if (result.getFailCount() == 0) {
return STATUS_SUCCESS;
}
if (result.getSuccessCount() > 0 || result.getSkipCount() > 0) {
return STATUS_PARTIAL_SUCCESS;
}
return STATUS_FAILED;
}
private String buildMessage(List<String> failMessages) {
if (failMessages == null || failMessages.isEmpty()) {
return null;
}
String message = JsonUtils.toJsonString(failMessages);
return message.length() > 2000 ? message.substring(0, 2000) : message;
}
private void appendFailMessage(AutoGenResult result, String message) {
if (result.getFailMessages().size() >= MAX_FAIL_MESSAGES) {
return;
}
result.getFailMessages().add(StringUtils.hasText(message) ? message : "unknown error");
}
private LoginUser buildLoginUser(AdminUserRespDTO user) {
LoginUser loginUser = new LoginUser();
loginUser.setId(user.getId());
loginUser.setUserType(UserTypeEnum.ADMIN.getValue());
Map<String, String> info = new LinkedHashMap<>();
info.put(LoginUser.INFO_KEY_NICKNAME, safeText(user.getNickname()));
if (user.getDeptId() != null) {
info.put(LoginUser.INFO_KEY_DEPT_ID, String.valueOf(user.getDeptId()));
}
loginUser.setInfo(info);
return loginUser;
}
private LoginUser buildSystemLoginUser() {
LoginUser loginUser = new LoginUser();
loginUser.setId(0L);
loginUser.setUserType(UserTypeEnum.ADMIN.getValue());
Map<String, String> info = new LinkedHashMap<>();
info.put(LoginUser.INFO_KEY_NICKNAME, "system");
loginUser.setInfo(info);
return loginUser;
}
private void runAs(LoginUser loginUser, Runnable action) {
runAs(loginUser, () -> {
action.run();
return null;
});
}
private <T> T runAs(LoginUser loginUser, Supplier<T> supplier) {
SecurityContext previousContext = SecurityContextHolder.getContext();
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList()));
SecurityContextHolder.setContext(context);
try {
return supplier.get();
} finally {
SecurityContextHolder.setContext(previousContext);
}
}
private String resolveNodeName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception ex) {
return "unknown";
}
}
private String safeText(String value) {
return value == null ? "" : value.trim();
}
private record ProjectAutoGenCandidate(Long projectId, AdminUserRespDTO user) {
}
}

View File

@@ -166,8 +166,30 @@ public class WorkReportCommonService {
@Transactional(rollbackFor = Exception.class)
public Long createWeeklyReport(WeeklyReportSaveReqVO reqVO) {
return createWeeklyReportInternal(reqVO, null);
}
@Transactional(rollbackFor = Exception.class)
public Long createWeeklyReport(WeeklyReportSaveReqVO reqVO, Long overrideReporterId) {
return createWeeklyReportInternal(reqVO, overrideReporterId);
}
public WeeklyReportRespVO mergeWeeklyDraft(WeeklyReportRespVO latestDraft, WeeklyReportRefreshDraftReqVO reqVO) {
WeeklyReportRespVO respVO = latestDraft;
respVO.setIsBusinessTrip(Boolean.TRUE.equals(reqVO.getIsBusinessTrip()));
respVO.setTravelSegments(BeanUtils.toBean(defaultList(reqVO.getTravelSegments()), WeeklyReportTravelSegmentRespVO.class));
respVO.setReviewItems(mergeReviewItems(reqVO.getReviewItems(), latestDraft.getReviewItems()));
respVO.setPlanItems(mergePlanItems(reqVO.getPlanItems(), latestDraft.getPlanItems()));
respVO.setTotalTravelDays(Boolean.TRUE.equals(reqVO.getIsBusinessTrip())
? defaultIfNull(sumTravelDays(reqVO.getTravelSegments()))
: BigDecimal.ZERO);
respVO.setTotalWorkHours(sumReviewWorkHoursResp(respVO.getReviewItems()));
return respVO;
}
private Long createWeeklyReportInternal(WeeklyReportSaveReqVO reqVO, Long overrideReporterId) {
validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate());
CurrentUserProfile profile = loadCurrentUserProfile(true);
CurrentUserProfile profile = resolveProfile(overrideReporterId, true);
validateWeeklyDuplicate(profile.userId(), reqVO.getPeriodKey(), null);
WeeklyReportDO report = new WeeklyReportDO();
@@ -308,8 +330,25 @@ public class WorkReportCommonService {
@Transactional(rollbackFor = Exception.class)
public Long createMonthlyReport(MonthlyReportSaveReqVO reqVO) {
return createMonthlyReportInternal(reqVO, null);
}
@Transactional(rollbackFor = Exception.class)
public Long createMonthlyReport(MonthlyReportSaveReqVO reqVO, Long overrideReporterId) {
return createMonthlyReportInternal(reqVO, overrideReporterId);
}
public MonthlyReportRespVO mergeMonthlyDraft(MonthlyReportRespVO latestDraft, MonthlyReportRefreshDraftReqVO reqVO) {
MonthlyReportRespVO respVO = latestDraft;
respVO.setReviewItems(mergeReviewItems(reqVO.getReviewItems(), latestDraft.getReviewItems()));
respVO.setPlanItems(mergePlanItems(reqVO.getPlanItems(), latestDraft.getPlanItems()));
respVO.setTotalWorkHours(sumReviewWorkHoursResp(respVO.getReviewItems()));
return respVO;
}
private Long createMonthlyReportInternal(MonthlyReportSaveReqVO reqVO, Long overrideReporterId) {
validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate());
CurrentUserProfile profile = loadCurrentUserProfile(true);
CurrentUserProfile profile = resolveProfile(overrideReporterId, true);
validateMonthlyDuplicate(profile.userId(), reqVO.getPeriodKey(), null);
MonthlyReportDO report = new MonthlyReportDO();
@@ -460,9 +499,30 @@ public class WorkReportCommonService {
@Transactional(rollbackFor = Exception.class)
public Long createProjectReport(ProjectReportSaveReqVO reqVO) {
return createProjectReportInternal(reqVO, null);
}
@Transactional(rollbackFor = Exception.class)
public Long createProjectReport(ProjectReportSaveReqVO reqVO, Long overrideReporterId) {
return createProjectReportInternal(reqVO, overrideReporterId);
}
public ProjectReportRespVO mergeProjectDraft(ProjectReportRespVO latestDraft, ProjectReportRefreshDraftReqVO reqVO) {
ProjectReportRespVO respVO = latestDraft;
respVO.setProjectStatusDesc(normalizeNullableText(reqVO.getProjectStatusDesc()));
respVO.setProjectProgressPlan(normalizeNullableText(reqVO.getProjectProgressPlan()));
respVO.setProjectKeyPoints(normalizeNullableText(reqVO.getProjectKeyPoints()));
respVO.setProjectProblems(normalizeNullableText(reqVO.getProjectProblems()));
respVO.setCurrentItems(mergeProjectItems(reqVO.getCurrentItems(), latestDraft.getCurrentItems()));
respVO.setNextItems(mergeProjectItems(reqVO.getNextItems(), latestDraft.getNextItems()));
respVO.setTotalWorkHours(sumProjectCurrentWorkHoursResp(respVO.getCurrentItems()));
return respVO;
}
private Long createProjectReportInternal(ProjectReportSaveReqVO reqVO, Long overrideReporterId) {
validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate());
validateProjectFlag(reqVO.getFlag());
CurrentUserProfile profile = loadCurrentUserProfile(true);
CurrentUserProfile profile = resolveProfile(overrideReporterId, true);
validateProjectReportDuplicate(reqVO.getProjectId(), reqVO.getPeriodKey(), profile.userId(), null);
ProjectDO project = validateProjectExists(reqVO.getProjectId());
@@ -827,12 +887,22 @@ public class WorkReportCommonService {
}
}
private CurrentUserProfile resolveProfile(Long overrideReporterId, boolean requireManager) {
return overrideReporterId != null
? loadUserProfile(overrideReporterId, null, requireManager)
: loadCurrentUserProfile(requireManager);
}
private CurrentUserProfile loadCurrentUserProfile(boolean requireManager) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
return loadUserProfile(userId, SecurityFrameworkUtils.getLoginUserNickname(), requireManager);
}
private CurrentUserProfile loadUserProfile(Long userId, String fallbackUserName, boolean requireManager) {
AdminUserRespDTO user = loadUser(userId);
String userName = StringUtils.hasText(user.getNickname())
? user.getNickname().trim()
: defaultText(SecurityFrameworkUtils.getLoginUserNickname());
: defaultText(fallbackUserName);
String deptName = null;
if (user.getDeptId() != null) {
@@ -1010,6 +1080,74 @@ public class WorkReportCommonService {
insertProjectNextItems(reportId, reqVO.getNextItems());
}
private List<PersonalReportReviewItemRespVO> mergeReviewItems(List<PersonalReportReviewItemReqVO> currentItems,
List<PersonalReportReviewItemRespVO> latestItems) {
List<PersonalReportReviewItemRespVO> merged = BeanUtils.toBean(defaultList(currentItems),
PersonalReportReviewItemRespVO.class);
Set<String> existingKeys = merged.stream()
.map(item -> normalizeMergeText(item.getItemTitle()))
.filter(StringUtils::hasText)
.collect(Collectors.toCollection(LinkedHashSet::new));
for (PersonalReportReviewItemRespVO item : defaultList(latestItems)) {
String key = normalizeMergeText(item.getItemTitle());
if (StringUtils.hasText(key) && existingKeys.contains(key)) {
continue;
}
merged.add(BeanUtils.toBean(item, PersonalReportReviewItemRespVO.class));
if (StringUtils.hasText(key)) {
existingKeys.add(key);
}
}
for (int i = 0; i < merged.size(); i++) {
merged.get(i).setItemNumber(i + 1);
}
return merged;
}
private List<PersonalReportPlanItemRespVO> mergePlanItems(List<PersonalReportPlanItemReqVO> currentItems,
List<PersonalReportPlanItemRespVO> latestItems) {
List<PersonalReportPlanItemRespVO> merged = BeanUtils.toBean(defaultList(currentItems),
PersonalReportPlanItemRespVO.class);
Set<String> existingKeys = merged.stream()
.map(item -> normalizeMergeText(item.getItemTitle()))
.filter(StringUtils::hasText)
.collect(Collectors.toCollection(LinkedHashSet::new));
for (PersonalReportPlanItemRespVO item : defaultList(latestItems)) {
String key = normalizeMergeText(item.getItemTitle());
if (StringUtils.hasText(key) && existingKeys.contains(key)) {
continue;
}
merged.add(BeanUtils.toBean(item, PersonalReportPlanItemRespVO.class));
if (StringUtils.hasText(key)) {
existingKeys.add(key);
}
}
for (int i = 0; i < merged.size(); i++) {
merged.get(i).setItemNumber(i + 1);
}
return merged;
}
private List<ProjectReportItemRespVO> mergeProjectItems(List<ProjectReportItemReqVO> currentItems,
List<ProjectReportItemRespVO> latestItems) {
List<ProjectReportItemRespVO> merged = BeanUtils.toBean(defaultList(currentItems), ProjectReportItemRespVO.class);
Set<String> existingKeys = merged.stream()
.map(item -> buildProjectItemMergeKey(item.getItemTitle(), item.getPriorityCode()))
.filter(StringUtils::hasText)
.collect(Collectors.toCollection(LinkedHashSet::new));
for (ProjectReportItemRespVO item : defaultList(latestItems)) {
String key = buildProjectItemMergeKey(item.getItemTitle(), item.getPriorityCode());
if (StringUtils.hasText(key) && existingKeys.contains(key)) {
continue;
}
merged.add(BeanUtils.toBean(item, ProjectReportItemRespVO.class));
if (StringUtils.hasText(key)) {
existingKeys.add(key);
}
}
return merged;
}
private void insertReviewItems(String reportType, Long reportId, List<PersonalReportReviewItemReqVO> items) {
List<PersonalReportReviewItemReqVO> source = defaultList(items);
for (int i = 0; i < source.size(); i++) {
@@ -1326,6 +1464,16 @@ public class WorkReportCommonService {
return total.compareTo(BigDecimal.ZERO) == 0 ? null : total;
}
private BigDecimal sumReviewWorkHoursResp(List<PersonalReportReviewItemRespVO> items) {
BigDecimal total = BigDecimal.ZERO;
for (PersonalReportReviewItemRespVO item : defaultList(items)) {
if (item.getWorkHours() != null) {
total = total.add(item.getWorkHours());
}
}
return total.compareTo(BigDecimal.ZERO) == 0 ? null : total;
}
private BigDecimal sumProjectCurrentWorkHours(List<ProjectReportItemReqVO> items) {
BigDecimal total = BigDecimal.ZERO;
for (ProjectReportItemReqVO item : defaultList(items)) {
@@ -1336,6 +1484,16 @@ public class WorkReportCommonService {
return total.compareTo(BigDecimal.ZERO) == 0 ? null : total;
}
private BigDecimal sumProjectCurrentWorkHoursResp(List<ProjectReportItemRespVO> items) {
BigDecimal total = BigDecimal.ZERO;
for (ProjectReportItemRespVO item : defaultList(items)) {
if (item.getWorkHours() != null) {
total = total.add(item.getWorkHours());
}
}
return total.compareTo(BigDecimal.ZERO) == 0 ? null : total;
}
private BigDecimal sumTravelDays(List<WeeklyReportTravelSegmentReqVO> items) {
BigDecimal total = BigDecimal.ZERO;
for (WeeklyReportTravelSegmentReqVO item : defaultList(items)) {
@@ -1376,6 +1534,23 @@ public class WorkReportCommonService {
return value.trim();
}
private String normalizeMergeText(String value) {
return StringUtils.hasText(value) ? value.trim() : null;
}
private String buildProjectItemMergeKey(String itemTitle, String priorityCode) {
String normalizedTitle = normalizeMergeText(itemTitle);
String normalizedPriorityCode = normalizeMergeText(priorityCode);
if (!StringUtils.hasText(normalizedTitle)) {
return null;
}
return normalizedTitle + "||" + defaultText(normalizedPriorityCode);
}
private BigDecimal defaultIfNull(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
private String defaultText(String value) {
return StringUtils.hasText(value) ? value.trim() : "";
}
@@ -1387,4 +1562,4 @@ public class WorkReportCommonService {
private record CurrentUserProfile(Long userId, String userName, String deptName, String postName,
Long directManagerId, String directManagerName) {
}
}
}

View File

@@ -6,6 +6,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.Month
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO;
@@ -19,6 +20,8 @@ public interface MonthlyReportService {
MonthlyReportRespVO previewMonthlyDefaultDraft(MonthlyReportDefaultDraftReqVO reqVO);
MonthlyReportRespVO refreshMonthlyDraft(MonthlyReportRefreshDraftReqVO reqVO);
Long createMonthlyReport(MonthlyReportSaveReqVO reqVO);
void updateMonthlyReport(Long id, MonthlyReportSaveReqVO reqVO);

View File

@@ -6,6 +6,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.Month
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO;
@@ -35,9 +36,20 @@ public class MonthlyReportServiceImpl implements MonthlyReportService {
return workReportDefaultDraftService.previewMonthlyDefaultDraft(reqVO);
}
@Override
public MonthlyReportRespVO refreshMonthlyDraft(MonthlyReportRefreshDraftReqVO reqVO) {
MonthlyReportDefaultDraftReqVO draftReqVO = new MonthlyReportDefaultDraftReqVO();
draftReqVO.setPeriodKey(reqVO.getPeriodKey());
draftReqVO.setPeriodLabel(reqVO.getPeriodLabel());
draftReqVO.setPeriodStartDate(reqVO.getPeriodStartDate());
draftReqVO.setPeriodEndDate(reqVO.getPeriodEndDate());
MonthlyReportRespVO latestDraft = workReportDefaultDraftService.previewMonthlyDefaultDraft(draftReqVO);
return workReportCommonService.mergeMonthlyDraft(latestDraft, reqVO);
}
@Override
public Long createMonthlyReport(MonthlyReportSaveReqVO reqVO) {
return workReportCommonService.createMonthlyReport(reqVO);
return workReportCommonService.createMonthlyReport(reqVO, null);
}
@Override

View File

@@ -5,6 +5,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.Proje
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportOwnerProjectOptionRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportApprovalRecordRespVO;
@@ -21,6 +22,8 @@ public interface ProjectReportService {
ProjectReportRespVO previewProjectDefaultDraft(Long projectId, ProjectReportDefaultDraftReqVO reqVO);
ProjectReportRespVO refreshProjectDraft(Long projectId, ProjectReportRefreshDraftReqVO reqVO);
Long createProjectReport(ProjectReportSaveReqVO reqVO);
void updateProjectReport(Long id, ProjectReportSaveReqVO reqVO);

View File

@@ -5,6 +5,7 @@ import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.Proje
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportOwnerProjectOptionRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportApprovalRecordRespVO;
@@ -42,10 +43,23 @@ public class ProjectReportServiceImpl implements ProjectReportService {
return workReportDefaultDraftService.previewProjectDefaultDraft(projectId, reqVO);
}
@Override
public ProjectReportRespVO refreshProjectDraft(Long projectId, ProjectReportRefreshDraftReqVO reqVO) {
workReportCommonService.validateCurrentUserIsProjectReportProjectOwner(projectId);
ProjectReportDefaultDraftReqVO draftReqVO = new ProjectReportDefaultDraftReqVO();
draftReqVO.setPeriodKey(reqVO.getPeriodKey());
draftReqVO.setPeriodLabel(reqVO.getPeriodLabel());
draftReqVO.setPeriodStartDate(reqVO.getPeriodStartDate());
draftReqVO.setPeriodEndDate(reqVO.getPeriodEndDate());
draftReqVO.setFlag(reqVO.getFlag());
ProjectReportRespVO latestDraft = workReportDefaultDraftService.previewProjectDefaultDraft(projectId, draftReqVO);
return workReportCommonService.mergeProjectDraft(latestDraft, reqVO);
}
@Override
public Long createProjectReport(ProjectReportSaveReqVO reqVO) {
workReportCommonService.validateCurrentUserIsProjectReportProjectOwner(reqVO.getProjectId());
return workReportCommonService.createProjectReport(reqVO);
return workReportCommonService.createProjectReport(reqVO, null);
}
@Override

View File

@@ -4,6 +4,7 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportApprovalRecordRespVO;
@@ -18,6 +19,8 @@ public interface WeeklyReportService {
WeeklyReportRespVO previewWeeklyDefaultDraft(WeeklyReportDefaultDraftReqVO reqVO);
WeeklyReportRespVO refreshWeeklyDraft(WeeklyReportRefreshDraftReqVO reqVO);
Long createWeeklyReport(WeeklyReportSaveReqVO reqVO);
void updateWeeklyReport(Long id, WeeklyReportSaveReqVO reqVO);

View File

@@ -4,6 +4,7 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportExportVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportDefaultDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportPageReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportApprovalRecordRespVO;
@@ -34,9 +35,20 @@ public class WeeklyReportServiceImpl implements WeeklyReportService {
return workReportDefaultDraftService.previewWeeklyDefaultDraft(reqVO);
}
@Override
public WeeklyReportRespVO refreshWeeklyDraft(WeeklyReportRefreshDraftReqVO reqVO) {
WeeklyReportDefaultDraftReqVO draftReqVO = new WeeklyReportDefaultDraftReqVO();
draftReqVO.setPeriodKey(reqVO.getPeriodKey());
draftReqVO.setPeriodLabel(reqVO.getPeriodLabel());
draftReqVO.setPeriodStartDate(reqVO.getPeriodStartDate());
draftReqVO.setPeriodEndDate(reqVO.getPeriodEndDate());
WeeklyReportRespVO latestDraft = workReportDefaultDraftService.previewWeeklyDefaultDraft(draftReqVO);
return workReportCommonService.mergeWeeklyDraft(latestDraft, reqVO);
}
@Override
public Long createWeeklyReport(WeeklyReportSaveReqVO reqVO) {
return workReportCommonService.createWeeklyReport(reqVO);
return workReportCommonService.createWeeklyReport(reqVO, null);
}
@Override

View File

@@ -106,5 +106,19 @@ rdms:
email: dev@example.com
license: Apache 2.0
license-url: https://www.apache.org/licenses/LICENSE-2.0.html
work-report:
auto-gen:
enabled: true
weekly:
enabled: true
cron: "0 0 12 ? * FRI"
monthly:
enabled: true
cron: "0 0 12 1-31 * ?"
project:
enabled: true
cron: "0 0 12 1-31 * ?"
scope:
dept-ids: [101]
debug: false