diff --git a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java index e92172f..60b3096 100644 --- a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java +++ b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java @@ -240,5 +240,21 @@ public interface ErrorCodeConstants { ErrorCode OVERTIME_APPLICATION_APPROVER_INVALID = new ErrorCode(1_008_009_008, "审核人不是有效系统用户"); ErrorCode OVERTIME_APPLICATION_APPROVER_SELF_FORBIDDEN = new ErrorCode(1_008_009_009, "审核人不能选择申请人本人"); ErrorCode OVERTIME_APPLICATION_READ_FORBIDDEN = new ErrorCode(1_008_009_010, "无权查看该加班申请"); - ErrorCode OVERTIME_APPLICATION_DELETE_ONLY_CANCELLED = new ErrorCode(1_008_009_011, "仅已撤销的加班申请允许删除"); + ErrorCode OVERTIME_APPLICATION_DELETE_ONLY_REJECTED = new ErrorCode(1_008_009_011, "仅已退回的加班申请允许删除"); + // ========== 工作报告 1_008_010_xxx ========== + ErrorCode WORK_REPORT_NOT_EXISTS = new ErrorCode(1_008_010_001, "工作报告不存在"); + ErrorCode WORK_REPORT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_010_002, "工作报告状态定义不存在或已停用"); + ErrorCode WORK_REPORT_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_010_003, "当前工作报告状态不支持动作【{}】"); + ErrorCode WORK_REPORT_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_010_004, "动作【{}】必须填写原因"); + ErrorCode WORK_REPORT_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_010_005, "工作报告状态已发生变化,请刷新后重试"); + ErrorCode WORK_REPORT_REPORTER_ONLY = new ErrorCode(1_008_010_006, "仅填报人可执行该操作"); + ErrorCode WORK_REPORT_APPROVER_ONLY = new ErrorCode(1_008_010_007, "仅当前审核人可执行该操作"); + ErrorCode WORK_REPORT_READ_FORBIDDEN = new ErrorCode(1_008_010_008, "无权查看该工作报告"); + ErrorCode WORK_REPORT_DIRECT_MANAGER_NOT_EXISTS = new ErrorCode(1_008_010_009, "当前用户不存在生效中的直属上级,无法提交工作报告"); + ErrorCode WORK_REPORT_PROJECT_NOT_EXISTS = new ErrorCode(1_008_010_010, "项目半月报对应的项目不存在"); + ErrorCode WORK_REPORT_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_010_011, "当前工作报告状态不允许编辑"); + ErrorCode WORK_REPORT_DELETE_NOT_ALLOWED = new ErrorCode(1_008_010_012, "当前工作报告状态不允许删除"); + ErrorCode WORK_REPORT_PERIOD_DUPLICATE = new ErrorCode(1_008_010_013, "当前周期的工作报告已存在,请勿重复创建"); + ErrorCode WORK_REPORT_APPROVAL_RECORD_NOT_EXISTS = new ErrorCode(1_008_010_014, "工作报告审核记录不存在"); + ErrorCode WORK_REPORT_PROJECT_OWNER_ONLY = new ErrorCode(1_008_010_015, "仅项目负责人可创建或维护该项目的项目半月报"); } diff --git a/rdms-project/rdms-project-boot/pom.xml b/rdms-project/rdms-project-boot/pom.xml index b075fca..e24d952 100644 --- a/rdms-project/rdms-project-boot/pom.xml +++ b/rdms-project/rdms-project-boot/pom.xml @@ -65,6 +65,12 @@ rdms-spring-boot-starter-excel + + org.apache.poi + poi-ooxml + 5.4.1 + + com.alibaba.cloud diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java index 04b4371..de05c72 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java @@ -14,7 +14,6 @@ public final class OvertimeApplicationConstants { public static final String STATUS_PENDING = "pending"; public static final String STATUS_APPROVED = "approved"; public static final String STATUS_REJECTED = "rejected"; - public static final String STATUS_CANCELLED = "cancelled"; /** * 新建即提交的业务日志动作。 @@ -25,7 +24,6 @@ public final class OvertimeApplicationConstants { public static final String ACTION_RESUBMIT = "resubmit"; public static final String ACTION_APPROVE = "approve"; public static final String ACTION_REJECT = "reject"; - public static final String ACTION_CANCEL = "cancel"; public static final String ACTION_DELETE = "delete"; public static final String PERMISSION_QUERY = "project:overtime-application:query"; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/WorkReportConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/WorkReportConstants.java new file mode 100644 index 0000000..f4c3772 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/WorkReportConstants.java @@ -0,0 +1,41 @@ +package com.njcn.rdms.module.project.constant; + +/** + * 工作报告常量。 + */ +public final class WorkReportConstants { + + private WorkReportConstants() { + } + + public static final String STATUS_OBJECT_TYPE = "work_report"; + + public static final String REPORT_TYPE_WEEKLY = "weekly"; + public static final String REPORT_TYPE_MONTHLY = "monthly"; + public static final String REPORT_TYPE_PROJECT = "project"; + + public static final String BIZ_TYPE_WEEKLY = "weekly_report"; + public static final String BIZ_TYPE_MONTHLY = "monthly_report"; + public static final String BIZ_TYPE_PROJECT = "project_report"; + + public static final String STATUS_DRAFT = "draft"; + public static final String STATUS_PENDING_APPROVAL = "pending_approval"; + public static final String STATUS_APPROVED = "approved"; + public static final String STATUS_REJECTED = "rejected"; + + public static final String ACTION_CREATE = "create"; + public static final String ACTION_UPDATE = "update"; + public static final String ACTION_DELETE = "delete"; + public static final String ACTION_SUBMIT = "submit"; + public static final String ACTION_RESUBMIT = "resubmit"; + public static final String ACTION_APPROVE = "approve"; + public static final String ACTION_REJECT = "reject"; + + public static final String PERMISSION_QUERY = "project:work-report:query"; + public static final String PERMISSION_CREATE = "project:work-report:create"; + public static final String PERMISSION_UPDATE = "project:work-report:update"; + public static final String PERMISSION_DELETE = "project:work-report:delete"; + public static final String PERMISSION_APPROVE = "project:work-report:approve"; + public static final String PERMISSION_EXPORT = "project:work-report:export"; + public static final String PERMISSION_PROJECT_OWNER = "project:work-report:project-owner"; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java index a2557b5..19dc43a 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java @@ -5,6 +5,7 @@ import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.pojo.PageResult; import com.njcn.rdms.framework.excel.core.util.ExcelUtils; import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationApprovalRecordRespVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationExportVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; @@ -53,7 +54,7 @@ public class OvertimeApplicationController { } @PutMapping("/{id}") - @Operation(summary = "退回或撤销后修改并重新提交加班申请") + @Operation(summary = "退回后修改并重新提交加班申请") @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_UPDATE + "')") public CommonResult resubmit(@PathVariable("id") Long id, @Valid @RequestBody OvertimeApplicationSaveReqVO reqVO) { @@ -107,17 +108,8 @@ public class OvertimeApplicationController { return success(true); } - @PostMapping("/{id}/cancel") - @Operation(summary = "撤销加班申请") - @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_UPDATE + "')") - public CommonResult cancel(@PathVariable("id") Long id, - @Valid @RequestBody OvertimeApplicationStatusActionReqVO reqVO) { - overtimeApplicationService.cancel(id, reqVO); - return success(true); - } - @DeleteMapping("/{id}") - @Operation(summary = "删除已撤销的加班申请") + @Operation(summary = "删除已退回的加班申请") @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_DELETE + "')") public CommonResult delete(@PathVariable("id") Long id) { overtimeApplicationService.deleteApplication(id); @@ -131,6 +123,13 @@ public class OvertimeApplicationController { return success(overtimeApplicationService.getStatusLogs(id)); } + @GetMapping("/{id}/approval-records") + @Operation(summary = "获取加班申请审核记录") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_QUERY + "')") + public CommonResult> approvalRecords(@PathVariable("id") Long id) { + return success(overtimeApplicationService.getApprovalRecords(id)); + } + @GetMapping("/export") @Operation(summary = "导出我的加班申请") @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_EXPORT + "')") diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationApprovalRecordRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationApprovalRecordRespVO.java new file mode 100644 index 0000000..7b8e20e --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationApprovalRecordRespVO.java @@ -0,0 +1,29 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 加班申请审核记录 Response VO") +@Data +public class OvertimeApplicationApprovalRecordRespVO { + + private Long id; + + private Long overtimeApplicationId; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + private String opinion; + + private Long auditorUserId; + + private String auditorName; + + private LocalDateTime createTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java index f81b2e1..9d1a706 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java @@ -8,7 +8,7 @@ import lombok.Data; @Data public class OvertimeApplicationStatusActionReqVO { - @Schema(description = "动作原因或审核意见。是否必填以状态机配置为准;当前退回必填,撤销选填", + @Schema(description = "动作原因或审核意见。是否必填以状态机配置为准;当前退回必填", example = "请补充加班内容") @Size(max = 1000, message = "动作原因长度不能超过 1000 个字符") private String reason; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java new file mode 100644 index 0000000..bc90347 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportExportResponseUtils.java @@ -0,0 +1,20 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common; + +import com.njcn.rdms.framework.common.util.http.HttpUtils; +import com.njcn.rdms.module.project.service.workreport.export.WorkReportExportFile; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public final class WorkReportExportResponseUtils { + + private WorkReportExportResponseUtils() { + } + + public static void write(HttpServletResponse response, WorkReportExportFile file) throws IOException { + response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(file.filename())); + response.setContentType(file.contentType()); + response.setContentLength(file.content().length); + response.getOutputStream().write(file.content()); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportStatusController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportStatusController.java new file mode 100644 index 0000000..287795b --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/WorkReportStatusController.java @@ -0,0 +1,35 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusDictRespVO; +import com.njcn.rdms.module.project.service.workreport.common.WorkReportStatusService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 工作报告状态") +@RestController +@RequestMapping("/project/work-reports") +@Validated +public class WorkReportStatusController { + + @Resource + private WorkReportStatusService workReportStatusService; + + @GetMapping("/status/dict") + @Operation(summary = "获取工作报告所有状态字典") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getStatusDict() { + return success(workReportStatusService.getStatusDict()); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportPlanItemReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportPlanItemReqVO.java new file mode 100644 index 0000000..19c07ff --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportPlanItemReqVO.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 个人报告计划项 Request VO") +@Data +public class PersonalReportPlanItemReqVO { + + private Integer itemNumber; + + private String itemTitle; + + private String targetText; + + private Object targetJson; + + private String supportNeed; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportPlanItemRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportPlanItemRespVO.java new file mode 100644 index 0000000..5916d65 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportPlanItemRespVO.java @@ -0,0 +1,21 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 个人报告计划项 Response VO") +@Data +public class PersonalReportPlanItemRespVO { + + private Long id; + + private Integer itemNumber; + + private String itemTitle; + + private String targetText; + + private Object targetJson; + + private String supportNeed; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportReviewItemReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportReviewItemReqVO.java new file mode 100644 index 0000000..ed395d1 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportReviewItemReqVO.java @@ -0,0 +1,23 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 个人报告回顾项 Request VO") +@Data +public class PersonalReportReviewItemReqVO { + + private Integer itemNumber; + + private String itemTitle; + + private BigDecimal workHours; + + private String contentText; + + private Object contentJson; + + private String reflectionText; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportReviewItemRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportReviewItemRespVO.java new file mode 100644 index 0000000..ec8bb85 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/PersonalReportReviewItemRespVO.java @@ -0,0 +1,25 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 个人报告回顾项 Response VO") +@Data +public class PersonalReportReviewItemRespVO { + + private Long id; + + private Integer itemNumber; + + private String itemTitle; + + private BigDecimal workHours; + + private String contentText; + + private Object contentJson; + + private String reflectionText; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportApprovalRecordRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportApprovalRecordRespVO.java new file mode 100644 index 0000000..59fb02d --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportApprovalRecordRespVO.java @@ -0,0 +1,27 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 工作报告审核记录 Response VO") +@Data +public class WorkReportApprovalRecordRespVO { + + private Long id; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + private String opinion; + + private Long auditorUserId; + + private String auditorName; + + private LocalDateTime createTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportBasePageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportBasePageReqVO.java new file mode 100644 index 0000000..0c1aba8 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportBasePageReqVO.java @@ -0,0 +1,32 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import com.njcn.rdms.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkReportBasePageReqVO extends PageParam { + + @Schema(description = "关键字") + private String keyword; + + @Schema(description = "状态编码") + private String statusCode; + + @Schema(description = "周期开始日期范围") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate[] periodStartDate; + + @Schema(description = "提交时间范围") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] submitTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportMemberSnapshotRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportMemberSnapshotRespVO.java new file mode 100644 index 0000000..f734cf8 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportMemberSnapshotRespVO.java @@ -0,0 +1,13 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 项目成员快照 Response VO") +@Data +public class WorkReportMemberSnapshotRespVO { + + private Long userId; + + private String userName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusActionReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusActionReqVO.java new file mode 100644 index 0000000..3160326 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusActionReqVO.java @@ -0,0 +1,14 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - 工作报告状态动作 Request VO") +@Data +public class WorkReportStatusActionReqVO { + + @Schema(description = "原因或审核意见", example = "请补充下周计划") + @Size(max = 1000, message = "原因或审核意见长度不能超过 1000 个字符") + private String reason; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusDictRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusDictRespVO.java new file mode 100644 index 0000000..72ea1ba --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusDictRespVO.java @@ -0,0 +1,27 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 工作报告状态字典 Response VO") +@Data +public class WorkReportStatusDictRespVO { + + @Schema(description = "状态编码", example = "draft") + private String statusCode; + + @Schema(description = "状态名称", example = "待提交") + private String statusName; + + @Schema(description = "排序", example = "10") + private Integer sort; + + @Schema(description = "是否初始态") + private Boolean initialFlag; + + @Schema(description = "是否终态") + private Boolean terminalFlag; + + @Schema(description = "是否允许编辑") + private Boolean allowEdit; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusLogRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusLogRespVO.java new file mode 100644 index 0000000..bab9053 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/common/vo/WorkReportStatusLogRespVO.java @@ -0,0 +1,35 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.common.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 工作报告状态日志 Response VO") +@Data +public class WorkReportStatusLogRespVO { + + private Long id; + + private String reportType; + + private Long reportId; + + private String actionType; + + private String fromStatus; + + private String toStatus; + + private String reason; + + private Long operatorUserId; + + private String operatorName; + + private String periodLabelSnapshot; + + private String remark; + + private LocalDateTime createTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/MonthlyReportController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/MonthlyReportController.java new file mode 100644 index 0000000..4acf74a --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/MonthlyReportController.java @@ -0,0 +1,172 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly; + +import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.excel.core.util.ExcelUtils; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.WorkReportExportResponseUtils; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApprovalRecordRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApproveReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportContentExportReqVO; +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.MonthlyReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO; +import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService; +import com.njcn.rdms.module.project.service.workreport.monthly.MonthlyReportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; + +import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 个人月报") +@RestController +@RequestMapping("/project/work-reports/monthly") +@Validated +public class MonthlyReportController { + + @Resource + private MonthlyReportService monthlyReportService; + @Resource + private WorkReportContentExportService workReportContentExportService; + + @GetMapping("/init") + @Operation(summary = "获取月报新建默认数据") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')") + public CommonResult initMonthlyReport() { + return success(monthlyReportService.initMonthlyReport()); + } + + @GetMapping("/default-draft") + @Operation(summary = "预览月报默认稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')") + public CommonResult previewMonthlyDefaultDraft(@Valid MonthlyReportDefaultDraftReqVO reqVO) { + return success(monthlyReportService.previewMonthlyDefaultDraft(reqVO)); + } + + @PostMapping + @Operation(summary = "新建月报草稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')") + public CommonResult createMonthlyReport(@Valid @RequestBody MonthlyReportSaveReqVO reqVO) { + return success(monthlyReportService.createMonthlyReport(reqVO)); + } + + @PutMapping("/{id}") + @Operation(summary = "修改月报草稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')") + public CommonResult updateMonthlyReport(@PathVariable("id") Long id, + @Valid @RequestBody MonthlyReportSaveReqVO reqVO) { + monthlyReportService.updateMonthlyReport(id, reqVO); + return success(true); + } + + @GetMapping("/{id}") + @Operation(summary = "获取月报详情") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult getMonthlyReport(@PathVariable("id") Long id) { + return success(monthlyReportService.getMonthlyReport(id)); + } + + @GetMapping("/page") + @Operation(summary = "获取我的月报分页") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getMonthlyReportPage(@Valid MonthlyReportPageReqVO reqVO) { + return success(monthlyReportService.getMonthlyReportPage(reqVO)); + } + + @GetMapping("/approval-page") + @Operation(summary = "获取待我审批的月报分页") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult> getMonthlyApprovalPage(@Valid MonthlyReportPageReqVO reqVO) { + return success(monthlyReportService.getMonthlyApprovalPage(reqVO)); + } + + @PostMapping("/{id}/submit") + @Operation(summary = "提交月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')") + public CommonResult submitMonthlyReport(@PathVariable("id") Long id) { + monthlyReportService.submitMonthlyReport(id); + return success(true); + } + + @PostMapping("/{id}/approve") + @Operation(summary = "审批通过月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult approveMonthlyReport(@PathVariable("id") Long id, + @Valid @RequestBody MonthlyReportApproveReqVO reqVO) { + monthlyReportService.approveMonthlyReport(id, reqVO); + return success(true); + } + + @PostMapping("/{id}/reject") + @Operation(summary = "退回月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult rejectMonthlyReport(@PathVariable("id") Long id, + @Valid @RequestBody WorkReportStatusActionReqVO reqVO) { + monthlyReportService.rejectMonthlyReport(id, reqVO); + return success(true); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_DELETE + "')") + public CommonResult deleteMonthlyReport(@PathVariable("id") Long id) { + monthlyReportService.deleteMonthlyReport(id); + return success(true); + } + + @GetMapping("/{id}/status-logs") + @Operation(summary = "获取月报状态日志") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getMonthlyStatusLogs(@PathVariable("id") Long id) { + return success(monthlyReportService.getMonthlyStatusLogs(id)); + } + + @GetMapping("/{id}/approval-records") + @Operation(summary = "获取月报审批记录") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getMonthlyApprovalRecords(@PathVariable("id") Long id) { + return success(monthlyReportService.getMonthlyApprovalRecords(id)); + } + + @GetMapping("/export") + @Operation(summary = "导出我的月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void exportMonthlyReport(@Valid MonthlyReportPageReqVO reqVO, HttpServletResponse response) + throws IOException { + ExcelUtils.write(response, "个人月报_" + LocalDate.now() + ".xls", "个人月报", + MonthlyReportExportVO.class, monthlyReportService.getMonthlyExportList(reqVO)); + } + + @PostMapping("/content-export") + @Operation(summary = "导出月报内容 Word") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void exportMonthlyReportContent(@Valid @RequestBody MonthlyReportContentExportReqVO reqVO, + HttpServletResponse response) throws IOException { + WorkReportExportResponseUtils.write(response, workReportContentExportService.exportMonthly(reqVO)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportApprovalRecordRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportApprovalRecordRespVO.java new file mode 100644 index 0000000..2bc7682 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportApprovalRecordRespVO.java @@ -0,0 +1,50 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 月报审核记录 Response VO") +@Data +public class MonthlyReportApprovalRecordRespVO { + + private Long id; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + private String opinion; + + private LocalDate meetingDate; + + private String strengthDesc; + + private String strengthExample; + + private String weaknessDesc; + + private String weaknessExample; + + private String improvementSuggestion; + + private String performanceResult; + + private String employeeSignName; + + private LocalDate employeeSignedDate; + + private String supervisorSignName; + + private LocalDate supervisorSignedDate; + + private Long auditorUserId; + + private String auditorName; + + private LocalDateTime createTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportApproveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportApproveReqVO.java new file mode 100644 index 0000000..eb2791d --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportApproveReqVO.java @@ -0,0 +1,57 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 月报审核通过 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class MonthlyReportApproveReqVO extends WorkReportStatusActionReqVO { + + @Schema(description = "面谈时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @NotNull + private LocalDate meetingDate; + + @Schema(description = "优势描述") + private String strengthDesc; + + @Schema(description = "优势行为事例") + private String strengthExample; + + @Schema(description = "劣势描述") + private String weaknessDesc; + + @Schema(description = "劣势行为事例") + private String weaknessExample; + + @Schema(description = "改进建议") + private String improvementSuggestion; + + @Schema(description = "绩效考核结果") + @NotBlank(message = "绩效考核结果不能为空") + private String performanceResult; + + @Schema(description = "被考核人签名") + private String employeeSignName; + + @Schema(description = "被考核人签字日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate employeeSignedDate; + + @Schema(description = "上级签名") + private String supervisorSignName; + + @Schema(description = "上级签字日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate supervisorSignedDate; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportContentExportReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportContentExportReqVO.java new file mode 100644 index 0000000..d90c08e --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportContentExportReqVO.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Schema(description = "管理后台 - 月报内容导出 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class MonthlyReportContentExportReqVO extends MonthlyReportPageReqVO { + + @Schema(description = "是否按当前搜索条件全量导出") + private Boolean exportAll; + + @Schema(description = "选中的月报编号列表;exportAll=false 时必填") + private List ids; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportDefaultDraftReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportDefaultDraftReqVO.java new file mode 100644 index 0000000..2ceb835 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportDefaultDraftReqVO.java @@ -0,0 +1,30 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 月报默认稿预览 Request VO") +@Data +public class MonthlyReportDefaultDraftReqVO { + + @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; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportExportVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportExportVO.java new file mode 100644 index 0000000..82818a9 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportExportVO.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@ExcelIgnoreUnannotated +public class MonthlyReportExportVO { + + @ExcelProperty("填报人") + private String reporterName; + + @ExcelProperty("直属上级") + private String supervisorName; + + @ExcelProperty("周期") + private String periodLabel; + + @ExcelProperty("开始日期") + private LocalDate periodStartDate; + + @ExcelProperty("结束日期") + private LocalDate periodEndDate; + + @ExcelProperty("工时") + private BigDecimal totalWorkHours; + + @ExcelProperty("状态") + private String statusName; + + @ExcelProperty("审核意见") + private String approvalComment; + + @ExcelProperty("提交时间") + private LocalDateTime submitTime; + + @ExcelProperty("审核时间") + private LocalDateTime approvalTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java new file mode 100644 index 0000000..d50e782 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportPageReqVO.java @@ -0,0 +1,12 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportBasePageReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 月报分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class MonthlyReportPageReqVO extends WorkReportBasePageReqVO { +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportRespVO.java new file mode 100644 index 0000000..a2afa01 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportRespVO.java @@ -0,0 +1,41 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 月报 Response VO") +@Data +public class MonthlyReportRespVO { + + private Long id; + private Long reporterId; + private String reporterName; + private String reporterDeptName; + private String reporterPostName; + private Long supervisorUserId; + private String supervisorName; + private String periodKey; + private String periodLabel; + private LocalDate periodStartDate; + private LocalDate periodEndDate; + private String statusCode; + private String statusName; + private Boolean allowEdit; + private Boolean terminal; + private BigDecimal totalWorkHours; + private String approvalComment; + private String lastStatusReason; + private LocalDateTime submitTime; + private LocalDateTime approvalTime; + private LocalDateTime createTime; + private LocalDateTime updateTime; + private List reviewItems; + private List planItems; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportSaveReqVO.java new file mode 100644 index 0000000..fb925fd --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/monthly/vo/MonthlyReportSaveReqVO.java @@ -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 MonthlyReportSaveReqVO { + + @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 reviewItems; + + @Valid + private List planItems; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/ProjectReportController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/ProjectReportController.java new file mode 100644 index 0000000..f4ddc65 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/ProjectReportController.java @@ -0,0 +1,181 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project; + +import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.excel.core.util.ExcelUtils; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.WorkReportExportResponseUtils; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportApprovalRecordRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportContentExportReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportDefaultDraftReqVO; +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.ProjectReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportSaveReqVO; +import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService; +import com.njcn.rdms.module.project.service.workreport.project.ProjectReportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; + +import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 项目半月报") +@RestController +@RequestMapping("/project/work-reports/project") +@Validated +public class ProjectReportController { + + @Resource + private ProjectReportService projectReportService; + @Resource + private WorkReportContentExportService workReportContentExportService; + + @GetMapping("/owner-project-options") + @Operation(summary = "获取项目半月报负责人可选项目列表") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')") + public CommonResult> getOwnerProjectOptions() { + return success(projectReportService.getOwnerProjectOptions()); + } + + @GetMapping("/init") + @Operation(summary = "获取项目半月报新建默认数据") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')") + public CommonResult initProjectReport(@RequestParam("projectId") Long projectId) { + return success(projectReportService.initProjectReport(projectId)); + } + + @GetMapping("/{projectId}/default-draft") + @Operation(summary = "预览项目半月报默认稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')") + public CommonResult previewProjectDefaultDraft(@PathVariable("projectId") Long projectId, + @Valid ProjectReportDefaultDraftReqVO reqVO) { + return success(projectReportService.previewProjectDefaultDraft(projectId, reqVO)); + } + + @PostMapping + @Operation(summary = "新建项目半月报草稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')") + public CommonResult createProjectReport(@Valid @RequestBody ProjectReportSaveReqVO reqVO) { + return success(projectReportService.createProjectReport(reqVO)); + } + + @PutMapping("/{id}") + @Operation(summary = "修改项目半月报草稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_PROJECT_OWNER + "')") + public CommonResult updateProjectReport(@PathVariable("id") Long id, + @Valid @RequestBody ProjectReportSaveReqVO reqVO) { + projectReportService.updateProjectReport(id, reqVO); + return success(true); + } + + @GetMapping("/{id}") + @Operation(summary = "获取项目半月报详情") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult getProjectReport(@PathVariable("id") Long id) { + return success(projectReportService.getProjectReport(id)); + } + + @GetMapping("/page") + @Operation(summary = "获取我的项目半月报分页") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getProjectReportPage(@Valid ProjectReportPageReqVO reqVO) { + return success(projectReportService.getProjectReportPage(reqVO)); + } + + @GetMapping("/approval-page") + @Operation(summary = "获取待我审批的项目半月报分页") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult> getProjectApprovalPage(@Valid ProjectReportPageReqVO reqVO) { + return success(projectReportService.getProjectApprovalPage(reqVO)); + } + + @PostMapping("/{id}/submit") + @Operation(summary = "提交项目半月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')") + public CommonResult submitProjectReport(@PathVariable("id") Long id) { + projectReportService.submitProjectReport(id); + return success(true); + } + + @PostMapping("/{id}/approve") + @Operation(summary = "审批通过项目半月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult approveProjectReport(@PathVariable("id") Long id, + @Valid @RequestBody WorkReportStatusActionReqVO reqVO) { + projectReportService.approveProjectReport(id, reqVO); + return success(true); + } + + @PostMapping("/{id}/reject") + @Operation(summary = "退回项目半月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult rejectProjectReport(@PathVariable("id") Long id, + @Valid @RequestBody WorkReportStatusActionReqVO reqVO) { + projectReportService.rejectProjectReport(id, reqVO); + return success(true); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除项目半月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_DELETE + "')") + public CommonResult deleteProjectReport(@PathVariable("id") Long id) { + projectReportService.deleteProjectReport(id); + return success(true); + } + + @GetMapping("/{id}/status-logs") + @Operation(summary = "获取项目半月报状态日志") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getProjectStatusLogs(@PathVariable("id") Long id) { + return success(projectReportService.getProjectStatusLogs(id)); + } + + @GetMapping("/{id}/approval-records") + @Operation(summary = "获取项目半月报审批记录") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getProjectApprovalRecords(@PathVariable("id") Long id) { + return success(projectReportService.getProjectApprovalRecords(id)); + } + + @GetMapping("/export") + @Operation(summary = "导出我的项目半月报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void exportProjectReport(@Valid ProjectReportPageReqVO reqVO, HttpServletResponse response) + throws IOException { + ExcelUtils.write(response, "项目半月报_" + LocalDate.now() + ".xls", "项目半月报", + ProjectReportExportVO.class, projectReportService.getProjectExportList(reqVO)); + } + + @PostMapping("/content-export") + @Operation(summary = "导出项目半月报内容 Word") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void exportProjectReportContent(@Valid @RequestBody ProjectReportContentExportReqVO reqVO, + HttpServletResponse response) throws IOException { + WorkReportExportResponseUtils.write(response, workReportContentExportService.exportProject(reqVO)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportContentExportReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportContentExportReqVO.java new file mode 100644 index 0000000..29551ca --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportContentExportReqVO.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Schema(description = "管理后台 - 项目半月报内容导出 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectReportContentExportReqVO extends ProjectReportPageReqVO { + + @Schema(description = "是否按当前搜索条件全量导出") + private Boolean exportAll; + + @Schema(description = "选中的项目半月报编号列表;exportAll=false 时必填") + private List ids; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportDefaultDraftReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportDefaultDraftReqVO.java new file mode 100644 index 0000000..4bcf25c --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportDefaultDraftReqVO.java @@ -0,0 +1,33 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 项目半月报默认稿预览 Request VO") +@Data +public class ProjectReportDefaultDraftReqVO { + + @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; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportExportVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportExportVO.java new file mode 100644 index 0000000..8fe43d6 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportExportVO.java @@ -0,0 +1,47 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@ExcelIgnoreUnannotated +public class ProjectReportExportVO { + + @ExcelProperty("项目名称") + private String projectName; + + @ExcelProperty("填报人") + private String projectOwnerName; + + @ExcelProperty("直属上级") + private String supervisorName; + + @ExcelProperty("周期") + private String periodLabel; + + @ExcelProperty("开始日期") + private LocalDate periodStartDate; + + @ExcelProperty("结束日期") + private LocalDate periodEndDate; + + @ExcelProperty("工时") + private BigDecimal totalWorkHours; + + @ExcelProperty("状态") + private String statusName; + + @ExcelProperty("审核意见") + private String approvalComment; + + @ExcelProperty("提交时间") + private LocalDateTime submitTime; + + @ExcelProperty("审核时间") + private LocalDateTime approvalTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportItemReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportItemReqVO.java new file mode 100644 index 0000000..69155d0 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportItemReqVO.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 项目半月报工作项 Request VO") +@Data +public class ProjectReportItemReqVO { + + private String itemTitle; + + private BigDecimal workHours; + + private String priorityCode; + + private BigDecimal progressRate; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportItemRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportItemRespVO.java new file mode 100644 index 0000000..8beb64d --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportItemRespVO.java @@ -0,0 +1,21 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - 项目半月报工作项 Response VO") +@Data +public class ProjectReportItemRespVO { + + private Long id; + + private String itemTitle; + + private BigDecimal workHours; + + private String priorityCode; + + private BigDecimal progressRate; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportOwnerProjectOptionRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportOwnerProjectOptionRespVO.java new file mode 100644 index 0000000..70fdf3e --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportOwnerProjectOptionRespVO.java @@ -0,0 +1,18 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 项目半月报负责人可选项目 Response VO") +@Data +public class ProjectReportOwnerProjectOptionRespVO { + + @Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "项目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPJ2026001") + private String projectCode; + + @Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户交付项目") + private String projectName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java new file mode 100644 index 0000000..134fdfc --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportPageReqVO.java @@ -0,0 +1,18 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportBasePageReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 项目半月报分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectReportPageReqVO extends WorkReportBasePageReqVO { + + @Schema(description = "项目编号") + private Long projectId; + + @Schema(description = "上半月/下半月标记") + private Integer flag; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportRespVO.java new file mode 100644 index 0000000..d99882e --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportRespVO.java @@ -0,0 +1,47 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.project.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportMemberSnapshotRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 项目半月报 Response VO") +@Data +public class ProjectReportRespVO { + + private Long id; + private Long projectId; + private String projectName; + private Long projectOwnerId; + private String projectOwnerName; + private String technicalOwnerName; + private List projectMemberSnapshot; + private Long supervisorUserId; + private String supervisorName; + private String periodKey; + private String periodLabel; + private LocalDate periodStartDate; + private LocalDate periodEndDate; + private Integer flag; + private String statusCode; + private String statusName; + private Boolean allowEdit; + private Boolean terminal; + private String projectStatusDesc; + private String projectProgressPlan; + private String projectKeyPoints; + private String projectProblems; + private BigDecimal totalWorkHours; + private String approvalComment; + private String lastStatusReason; + private LocalDateTime submitTime; + private LocalDateTime approvalTime; + private LocalDateTime createTime; + private LocalDateTime updateTime; + private List currentItems; + private List nextItems; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportSaveReqVO.java new file mode 100644 index 0000000..55608f3 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/project/vo/ProjectReportSaveReqVO.java @@ -0,0 +1,52 @@ +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 ProjectReportSaveReqVO { + + @NotNull(message = "项目编号不能为空") + private Long projectId; + + @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 currentItems; + + @Valid + private List nextItems; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/WeeklyReportController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/WeeklyReportController.java new file mode 100644 index 0000000..78d4442 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/WeeklyReportController.java @@ -0,0 +1,171 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly; + +import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.excel.core.util.ExcelUtils; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.WorkReportExportResponseUtils; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportApprovalRecordRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportContentExportReqVO; +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.WeeklyReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportSaveReqVO; +import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService; +import com.njcn.rdms.module.project.service.workreport.weekly.WeeklyReportService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; + +import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 个人周报") +@RestController +@RequestMapping("/project/work-reports/weekly") +@Validated +public class WeeklyReportController { + + @Resource + private WeeklyReportService weeklyReportService; + @Resource + private WorkReportContentExportService workReportContentExportService; + + @GetMapping("/init") + @Operation(summary = "获取周报新建默认数据") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')") + public CommonResult initWeeklyReport() { + return success(weeklyReportService.initWeeklyReport()); + } + + @GetMapping("/default-draft") + @Operation(summary = "预览周报默认稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')") + public CommonResult previewWeeklyDefaultDraft(@Valid WeeklyReportDefaultDraftReqVO reqVO) { + return success(weeklyReportService.previewWeeklyDefaultDraft(reqVO)); + } + + @PostMapping + @Operation(summary = "新建周报草稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_CREATE + "')") + public CommonResult createWeeklyReport(@Valid @RequestBody WeeklyReportSaveReqVO reqVO) { + return success(weeklyReportService.createWeeklyReport(reqVO)); + } + + @PutMapping("/{id}") + @Operation(summary = "修改周报草稿") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')") + public CommonResult updateWeeklyReport(@PathVariable("id") Long id, + @Valid @RequestBody WeeklyReportSaveReqVO reqVO) { + weeklyReportService.updateWeeklyReport(id, reqVO); + return success(true); + } + + @GetMapping("/{id}") + @Operation(summary = "获取周报详情") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult getWeeklyReport(@PathVariable("id") Long id) { + return success(weeklyReportService.getWeeklyReport(id)); + } + + @GetMapping("/page") + @Operation(summary = "获取我的周报分页") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getWeeklyReportPage(@Valid WeeklyReportPageReqVO reqVO) { + return success(weeklyReportService.getWeeklyReportPage(reqVO)); + } + + @GetMapping("/approval-page") + @Operation(summary = "获取待我审批的周报分页") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult> getWeeklyApprovalPage(@Valid WeeklyReportPageReqVO reqVO) { + return success(weeklyReportService.getWeeklyApprovalPage(reqVO)); + } + + @PostMapping("/{id}/submit") + @Operation(summary = "提交周报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')") + public CommonResult submitWeeklyReport(@PathVariable("id") Long id) { + weeklyReportService.submitWeeklyReport(id); + return success(true); + } + + @PostMapping("/{id}/approve") + @Operation(summary = "审批通过周报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult approveWeeklyReport(@PathVariable("id") Long id, + @Valid @RequestBody WorkReportStatusActionReqVO reqVO) { + weeklyReportService.approveWeeklyReport(id, reqVO); + return success(true); + } + + @PostMapping("/{id}/reject") + @Operation(summary = "退回周报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_APPROVE + "')") + public CommonResult rejectWeeklyReport(@PathVariable("id") Long id, + @Valid @RequestBody WorkReportStatusActionReqVO reqVO) { + weeklyReportService.rejectWeeklyReport(id, reqVO); + return success(true); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除周报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_DELETE + "')") + public CommonResult deleteWeeklyReport(@PathVariable("id") Long id) { + weeklyReportService.deleteWeeklyReport(id); + return success(true); + } + + @GetMapping("/{id}/status-logs") + @Operation(summary = "获取周报状态日志") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getWeeklyStatusLogs(@PathVariable("id") Long id) { + return success(weeklyReportService.getWeeklyStatusLogs(id)); + } + + @GetMapping("/{id}/approval-records") + @Operation(summary = "获取周报审批记录") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_QUERY + "')") + public CommonResult> getWeeklyApprovalRecords(@PathVariable("id") Long id) { + return success(weeklyReportService.getWeeklyApprovalRecords(id)); + } + + @GetMapping("/export") + @Operation(summary = "导出我的周报") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void exportWeeklyReport(@Valid WeeklyReportPageReqVO reqVO, HttpServletResponse response) + throws IOException { + ExcelUtils.write(response, "个人周报_" + LocalDate.now() + ".xls", "个人周报", + WeeklyReportExportVO.class, weeklyReportService.getWeeklyExportList(reqVO)); + } + + @PostMapping("/content-export") + @Operation(summary = "导出周报内容 Word") + @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void exportWeeklyReportContent(@Valid @RequestBody WeeklyReportContentExportReqVO reqVO, + HttpServletResponse response) throws IOException { + WorkReportExportResponseUtils.write(response, workReportContentExportService.exportWeekly(reqVO)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportContentExportReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportContentExportReqVO.java new file mode 100644 index 0000000..06a736f --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportContentExportReqVO.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Schema(description = "管理后台 - 周报内容导出 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class WeeklyReportContentExportReqVO extends WeeklyReportPageReqVO { + + @Schema(description = "是否按当前搜索条件全量导出") + private Boolean exportAll; + + @Schema(description = "选中的周报编号列表;exportAll=false 时必填") + private List ids; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportDefaultDraftReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportDefaultDraftReqVO.java new file mode 100644 index 0000000..d2f2d43 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportDefaultDraftReqVO.java @@ -0,0 +1,30 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 周报默认稿预览 Request VO") +@Data +public class WeeklyReportDefaultDraftReqVO { + + @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; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportExportVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportExportVO.java new file mode 100644 index 0000000..4a5bb06 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportExportVO.java @@ -0,0 +1,50 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@ExcelIgnoreUnannotated +public class WeeklyReportExportVO { + + @ExcelProperty("填报人") + private String reporterName; + + @ExcelProperty("直属上级") + private String supervisorName; + + @ExcelProperty("周期") + private String periodLabel; + + @ExcelProperty("开始日期") + private LocalDate periodStartDate; + + @ExcelProperty("结束日期") + private LocalDate periodEndDate; + + @ExcelProperty("是否出差") + private Boolean isBusinessTrip; + + @ExcelProperty("出差天数") + private BigDecimal totalTravelDays; + + @ExcelProperty("工时") + private BigDecimal totalWorkHours; + + @ExcelProperty("状态") + private String statusName; + + @ExcelProperty("审核意见") + private String approvalComment; + + @ExcelProperty("提交时间") + private LocalDateTime submitTime; + + @ExcelProperty("审核时间") + private LocalDateTime approvalTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java new file mode 100644 index 0000000..24ba9aa --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportPageReqVO.java @@ -0,0 +1,15 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportBasePageReqVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Schema(description = "管理后台 - 周报分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class WeeklyReportPageReqVO extends WorkReportBasePageReqVO { + + @Schema(description = "是否出差") + private Boolean isBusinessTrip; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportRespVO.java new file mode 100644 index 0000000..f734cd7 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportRespVO.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemRespVO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 周报 Response VO") +@Data +public class WeeklyReportRespVO { + + private Long id; + private Long reporterId; + private String reporterName; + private String reporterDeptName; + private String reporterPostName; + private Long supervisorUserId; + private String supervisorName; + private String periodKey; + private String periodLabel; + private LocalDate periodStartDate; + private LocalDate periodEndDate; + private String statusCode; + private String statusName; + private Boolean allowEdit; + private Boolean terminal; + private Boolean isBusinessTrip; + private BigDecimal totalTravelDays; + private BigDecimal totalWorkHours; + private String approvalComment; + private String lastStatusReason; + private LocalDateTime submitTime; + private LocalDateTime approvalTime; + private LocalDateTime createTime; + private LocalDateTime updateTime; + private List reviewItems; + private List planItems; + private List travelSegments; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportSaveReqVO.java new file mode 100644 index 0000000..8fe9a89 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportSaveReqVO.java @@ -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 WeeklyReportSaveReqVO { + + @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 reviewItems; + + @Valid + private List planItems; + + @Valid + private List travelSegments; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportTravelSegmentReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportTravelSegmentReqVO.java new file mode 100644 index 0000000..de4b9b6 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportTravelSegmentReqVO.java @@ -0,0 +1,27 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDate; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + +@Schema(description = "管理后台 - 周报出差分段 Request VO") +@Data +public class WeeklyReportTravelSegmentReqVO { + + private Integer sort; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate startDate; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate endDate; + + private BigDecimal travelDays; + + private String location; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportTravelSegmentRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportTravelSegmentRespVO.java new file mode 100644 index 0000000..ce7438a --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/workreport/weekly/vo/WeeklyReportTravelSegmentRespVO.java @@ -0,0 +1,24 @@ +package com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Schema(description = "管理后台 - 周报出差分段 Response VO") +@Data +public class WeeklyReportTravelSegmentRespVO { + + private Long id; + + private Integer sort; + + private LocalDate startDate; + + private LocalDate endDate; + + private BigDecimal travelDays; + + private String location; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationApprovalRecordDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationApprovalRecordDO.java new file mode 100644 index 0000000..fc391fa --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationApprovalRecordDO.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.project.dal.dataobject.overtime; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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; + +/** + * 加班申请审核记录。 + */ +@TableName("rdms_overtime_application_approval_record") +@Data +@EqualsAndHashCode(callSuper = true) +public class OvertimeApplicationApprovalRecordDO extends BaseDO { + + @TableId + private Long id; + + private Long overtimeApplicationId; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String opinion; + + private Long auditorUserId; + + private String auditorName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/PersonalReportPlanItemDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/PersonalReportPlanItemDO.java new file mode 100644 index 0000000..e598594 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/PersonalReportPlanItemDO.java @@ -0,0 +1,40 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.common; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 个人周报/月报计划项。 + */ +@TableName(value = "rdms_work_report_personal_report_plan_item", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +public class PersonalReportPlanItemDO extends BaseDO { + + @TableId + private Long id; + + private String reportType; + + private Long reportId; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer itemNumber; + + private String itemTitle; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String targetText; + + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private Object targetJson; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String supportNeed; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/PersonalReportReviewItemDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/PersonalReportReviewItemDO.java new file mode 100644 index 0000000..b0ecbe7 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/PersonalReportReviewItemDO.java @@ -0,0 +1,45 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.common; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +/** + * 个人周报/月报回顾项。 + */ +@TableName(value = "rdms_work_report_personal_report_review_item", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +public class PersonalReportReviewItemDO extends BaseDO { + + @TableId + private Long id; + + private String reportType; + + private Long reportId; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer itemNumber; + + private String itemTitle; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal workHours; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String contentText; + + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private Object contentJson; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String reflectionText; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/WorkReportMemberSnapshotItem.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/WorkReportMemberSnapshotItem.java new file mode 100644 index 0000000..b073eb6 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/WorkReportMemberSnapshotItem.java @@ -0,0 +1,14 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.common; + +import lombok.Data; + +/** + * 项目半月报中的项目成员快照。 + */ +@Data +public class WorkReportMemberSnapshotItem { + + private Long userId; + + private String userName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/WorkReportStatusLogDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/WorkReportStatusLogDO.java new file mode 100644 index 0000000..65fd9fe --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/common/WorkReportStatusLogDO.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.common; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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; + +/** + * 工作报告状态日志。 + */ +@TableName("rdms_work_report_status_log") +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkReportStatusLogDO extends BaseDO { + + @TableId + private Long id; + + private String reportType; + + private Long reportId; + + private String actionType; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String fromStatus; + + private String toStatus; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String reason; + + private Long operatorUserId; + + private String operatorName; + + private String periodLabelSnapshot; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String remark; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/monthly/MonthlyReportApprovalRecordDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/monthly/MonthlyReportApprovalRecordDO.java new file mode 100644 index 0000000..6c953a8 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/monthly/MonthlyReportApprovalRecordDO.java @@ -0,0 +1,71 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.monthly; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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; + +/** + * 月报审核记录。 + */ +@TableName("rdms_work_report_monthly_report_approval_record") +@Data +@EqualsAndHashCode(callSuper = true) +public class MonthlyReportApprovalRecordDO extends BaseDO { + + @TableId + private Long id; + + private Long monthlyReportId; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String opinion; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDate meetingDate; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String strengthDesc; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String strengthExample; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String weaknessDesc; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String weaknessExample; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String improvementSuggestion; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String performanceResult; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String employeeSignName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDate employeeSignedDate; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String supervisorSignName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDate supervisorSignedDate; + + private Long auditorUserId; + + private String auditorName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/monthly/MonthlyReportDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/monthly/MonthlyReportDO.java new file mode 100644 index 0000000..6cf08c6 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/monthly/MonthlyReportDO.java @@ -0,0 +1,64 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.monthly; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 工作报告-月报主表。 + */ +@TableName("rdms_work_report_monthly_report") +@Data +@EqualsAndHashCode(callSuper = true) +public class MonthlyReportDO extends BaseDO { + + @TableId + private Long id; + + private Long reporterId; + + private String reporterName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String reporterDeptName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String reporterPostName; + + private Long supervisorUserId; + + private String supervisorName; + + private String periodKey; + + private String periodLabel; + + private LocalDate periodStartDate; + + private LocalDate periodEndDate; + + private String statusCode; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal totalWorkHours; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime submitTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime approvalTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String approvalComment; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String lastStatusReason; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportApprovalRecordDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportApprovalRecordDO.java new file mode 100644 index 0000000..ee40216 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportApprovalRecordDO.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.project; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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; + +/** + * 项目半月报审核记录。 + */ +@TableName("rdms_work_report_project_report_approval_record") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectReportApprovalRecordDO extends BaseDO { + + @TableId + private Long id; + + private Long projectReportId; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String opinion; + + private Long auditorUserId; + + private String auditorName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportCurrentItemDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportCurrentItemDO.java new file mode 100644 index 0000000..d159434 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportCurrentItemDO.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.project; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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.math.BigDecimal; + +/** + * 项目半月报本期工作项。 + */ +@TableName("rdms_work_report_project_report_current_item") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectReportCurrentItemDO extends BaseDO { + + @TableId + private Long id; + + private Long reportId; + + private String itemTitle; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal workHours; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String priorityCode; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal progressRate; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportDO.java new file mode 100644 index 0000000..5a65054 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportDO.java @@ -0,0 +1,85 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.project; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportMemberSnapshotItem; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 工作报告-项目半月报主表。 + */ +@TableName(value = "rdms_work_report_project_report", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectReportDO extends BaseDO { + + @TableId + private Long id; + + private Long projectId; + + private String projectName; + + private Long projectOwnerId; + + private String projectOwnerName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String technicalOwnerName; + + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private List projectMemberSnapshot; + + private Long supervisorUserId; + + private String supervisorName; + + private String periodKey; + + private String periodLabel; + + private LocalDate periodStartDate; + + private LocalDate periodEndDate; + + private Integer flag; + + private String statusCode; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String projectStatusDesc; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String projectProgressPlan; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String projectKeyPoints; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String projectProblems; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal totalWorkHours; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime submitTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime approvalTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String approvalComment; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String lastStatusReason; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportNextItemDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportNextItemDO.java new file mode 100644 index 0000000..56dd6b7 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/project/ProjectReportNextItemDO.java @@ -0,0 +1,33 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.project; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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.math.BigDecimal; + +/** + * 项目半月报下期工作项。 + */ +@TableName("rdms_work_report_project_report_next_item") +@Data +@EqualsAndHashCode(callSuper = true) +public class ProjectReportNextItemDO extends BaseDO { + + @TableId + private Long id; + + private Long reportId; + + private String itemTitle; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String priorityCode; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal progressRate; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportApprovalRecordDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportApprovalRecordDO.java new file mode 100644 index 0000000..bf9fe9a --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportApprovalRecordDO.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.weekly; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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; + +/** + * 周报审核记录。 + */ +@TableName("rdms_work_report_weekly_report_approval_record") +@Data +@EqualsAndHashCode(callSuper = true) +public class WeeklyReportApprovalRecordDO extends BaseDO { + + @TableId + private Long id; + + private Long weeklyReportId; + + private Long statusLogId; + + private Integer approvalRound; + + private String conclusion; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String opinion; + + private Long auditorUserId; + + private String auditorName; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportDO.java new file mode 100644 index 0000000..cc70544 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportDO.java @@ -0,0 +1,69 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.weekly; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +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.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 工作报告-周报主表。 + */ +@TableName("rdms_work_report_weekly_report") +@Data +@EqualsAndHashCode(callSuper = true) +public class WeeklyReportDO extends BaseDO { + + @TableId + private Long id; + + private Long reporterId; + + private String reporterName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String reporterDeptName; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String reporterPostName; + + private Long supervisorUserId; + + private String supervisorName; + + private String periodKey; + + private String periodLabel; + + private LocalDate periodStartDate; + + private LocalDate periodEndDate; + + private String statusCode; + + private Boolean isBusinessTrip; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal totalTravelDays; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private BigDecimal totalWorkHours; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime submitTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime approvalTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String approvalComment; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String lastStatusReason; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportTravelSegmentDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportTravelSegmentDO.java new file mode 100644 index 0000000..1aa3347 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/workreport/weekly/WeeklyReportTravelSegmentDO.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.project.dal.dataobject.workreport.weekly; + +import com.baomidou.mybatisplus.annotation.TableField; +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.math.BigDecimal; +import java.time.LocalDate; + +/** + * 周报出差分段。 + */ +@TableName("rdms_work_report_weekly_report_travel_segment") +@Data +@EqualsAndHashCode(callSuper = true) +public class WeeklyReportTravelSegmentDO extends BaseDO { + + @TableId + private Long id; + + private Long weeklyReportId; + + @TableField("sort") + private Integer sort; + + private LocalDate startDate; + + private LocalDate endDate; + + private BigDecimal travelDays; + + private String location; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationApprovalRecordMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationApprovalRecordMapper.java new file mode 100644 index 0000000..83eb091 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationApprovalRecordMapper.java @@ -0,0 +1,24 @@ +package com.njcn.rdms.module.project.dal.mysql.overtime; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationApprovalRecordDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface OvertimeApplicationApprovalRecordMapper extends BaseMapperX { + + default List selectListByApplicationId(Long applicationId) { + return selectList(new LambdaQueryWrapperX() + .eq(OvertimeApplicationApprovalRecordDO::getOvertimeApplicationId, applicationId) + .orderByDesc(OvertimeApplicationApprovalRecordDO::getApprovalRound) + .orderByDesc(OvertimeApplicationApprovalRecordDO::getId)); + } + + default int countByApplicationId(Long applicationId) { + return Math.toIntExact(selectCount(new LambdaQueryWrapperX() + .eq(OvertimeApplicationApprovalRecordDO::getOvertimeApplicationId, applicationId))); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/personal/PersonalItemMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/personal/PersonalItemMapper.java index 321c6e7..b06e746 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/personal/PersonalItemMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/personal/PersonalItemMapper.java @@ -9,6 +9,9 @@ import com.njcn.rdms.module.project.dal.dataobject.personal.PersonalItemDO; import org.apache.ibatis.annotations.Mapper; import org.springframework.util.StringUtils; +import java.time.LocalDate; +import java.util.List; + @Mapper public interface PersonalItemMapper extends BaseMapperX { @@ -45,4 +48,33 @@ public interface PersonalItemMapper extends BaseMapperX { .eq(PersonalItemDO::getId, id) .eq(PersonalItemDO::getStatusCode, fromStatus)); } + + default List selectPlannedListByOwnerIdAndOverlap(Long ownerId, LocalDate startDate, LocalDate endDate, + String excludedStatusCode) { + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .eq(PersonalItemDO::getOwnerId, ownerId); + if (StringUtils.hasText(excludedStatusCode)) { + queryWrapper.ne(PersonalItemDO::getStatusCode, excludedStatusCode); + } + queryWrapper.isNotNull(PersonalItemDO::getPlannedStartDate); + queryWrapper.isNotNull(PersonalItemDO::getPlannedEndDate); + queryWrapper.le(PersonalItemDO::getPlannedStartDate, endDate); + queryWrapper.ge(PersonalItemDO::getPlannedEndDate, startDate); + queryWrapper.orderByAsc(PersonalItemDO::getPlannedStartDate); + queryWrapper.orderByAsc(PersonalItemDO::getId); + return selectList(queryWrapper); + } + + default List selectListByOwnerIdAndStatusNot(Long ownerId, String excludedStatusCode) { + if (ownerId == null) { + return List.of(); + } + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .eq(PersonalItemDO::getOwnerId, ownerId); + if (StringUtils.hasText(excludedStatusCode)) { + queryWrapper.ne(PersonalItemDO::getStatusCode, excludedStatusCode); + } + queryWrapper.orderByAsc(PersonalItemDO::getId); + return selectList(queryWrapper); + } } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java index edf9fbf..8d8c0f5 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectMapper.java @@ -58,6 +58,12 @@ public interface ProjectMapper extends BaseMapperX { .orderByDesc(BaseDO::getCreateTime)); } + default List selectListByManagerUserId(Long managerUserId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectDO::getManagerUserId, managerUserId) + .orderByDesc(BaseDO::getCreateTime)); + } + default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) { ProjectDO update = new ProjectDO(); update.setStatusCode(toStatus); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/execution/ProjectExecutionMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/execution/ProjectExecutionMapper.java index 6d8b574..1d82ea7 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/execution/ProjectExecutionMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/execution/ProjectExecutionMapper.java @@ -12,6 +12,7 @@ import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExec import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import org.springframework.util.StringUtils; import java.time.LocalDate; import java.util.Collection; @@ -143,18 +144,6 @@ public interface ProjectExecutionMapper extends BaseMapperX return Math.toIntExact(selectCount(queryWrapper)); } - /** - * 统计指定项目下处于非终态的执行数。用于项目 complete 前置校验(TD-015)。 - */ - default Integer countNonTerminalByProjectId(Long projectId, List terminalStatusCodes) { - LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() - .eq(ProjectExecutionDO::getProjectId, projectId); - if (terminalStatusCodes != null && !terminalStatusCodes.isEmpty()) { - queryWrapper.notIn(ProjectExecutionDO::getStatusCode, terminalStatusCodes); - } - return Math.toIntExact(selectCount(queryWrapper)); - } - default Integer countByProjectIdAndStatusCode(Long projectId, ProjectExecutionStatusBoardReqVO reqVO, String statusCode, @@ -312,4 +301,45 @@ public interface ProjectExecutionMapper extends BaseMapperX @Param("projectIds") Collection projectIds, @Param("terminalStatusCodes") Collection terminalStatusCodes); + default List selectListByProjectId(Long projectId) { + if (projectId == null) { + return java.util.Collections.emptyList(); + } + return selectList(new LambdaQueryWrapperX() + .eq(ProjectExecutionDO::getProjectId, projectId) + .orderByAsc(ProjectExecutionDO::getId)); + } + + default List selectPlannedListByProjectIdAndOverlap(Long projectId, LocalDate startDate, + LocalDate endDate, String excludedStatusCode) { + if (projectId == null || startDate == null || endDate == null) { + return java.util.Collections.emptyList(); + } + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .eq(ProjectExecutionDO::getProjectId, projectId); + if (StringUtils.hasText(excludedStatusCode)) { + queryWrapper.ne(ProjectExecutionDO::getStatusCode, excludedStatusCode); + } + queryWrapper.isNotNull(ProjectExecutionDO::getPlannedStartDate); + queryWrapper.isNotNull(ProjectExecutionDO::getPlannedEndDate); + queryWrapper.le(ProjectExecutionDO::getPlannedStartDate, endDate); + queryWrapper.ge(ProjectExecutionDO::getPlannedEndDate, startDate); + queryWrapper.orderByAsc(ProjectExecutionDO::getPlannedStartDate); + queryWrapper.orderByAsc(ProjectExecutionDO::getId); + return selectList(queryWrapper); + } + + default List selectListByProjectIdAndStatusNot(Long projectId, String excludedStatusCode) { + if (projectId == null) { + return java.util.Collections.emptyList(); + } + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX() + .eq(ProjectExecutionDO::getProjectId, projectId); + if (StringUtils.hasText(excludedStatusCode)) { + queryWrapper.ne(ProjectExecutionDO::getStatusCode, excludedStatusCode); + } + queryWrapper.orderByAsc(ProjectExecutionDO::getId); + return selectList(queryWrapper); + } + } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/ProjectTaskMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/ProjectTaskMapper.java index f997fd4..e518fa6 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/ProjectTaskMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/ProjectTaskMapper.java @@ -791,4 +791,72 @@ public interface ProjectTaskMapper extends BaseMapperX { @Param("projectIds") Collection projectIds, @Param("terminalStatusCodes") Collection terminalStatusCodes); + default List selectListByProjectId(Long projectId) { + if (projectId == null) { + return List.of(); + } + return selectList(new LambdaQueryWrapperX() + .eq(ProjectTaskDO::getProjectId, projectId) + .orderByAsc(ProjectTaskDO::getExecutionId) + .orderByAsc(ProjectTaskDO::getId)); + } + + @Select(""" + + """) + List selectPlannedInvolvedListByUserIdAndOverlap(@Param("userId") Long userId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate, + @Param("excludedStatusCode") String excludedStatusCode); + + @Select(""" + + """) + List selectInvolvedListByUserIdAndStatusNot(@Param("userId") Long userId, + @Param("excludedStatusCode") String excludedStatusCode); + } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/TaskWorklogMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/TaskWorklogMapper.java index 4b69791..4998d60 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/TaskWorklogMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/task/TaskWorklogMapper.java @@ -188,4 +188,30 @@ public interface TaskWorklogMapper extends BaseMapperX { .eq(TaskWorklogDO::getTaskId, taskId))); } + default List selectListByUserIdAndPeriod(Long userId, LocalDate startDate, LocalDate endDate) { + if (userId == null || startDate == null || endDate == null) { + return List.of(); + } + return selectList(new LambdaQueryWrapperX() + .eq(TaskWorklogDO::getUserId, userId) + .le(TaskWorklogDO::getStartDate, endDate) + .ge(TaskWorklogDO::getEndDate, startDate) + .orderByAsc(TaskWorklogDO::getTaskId) + .orderByAsc(TaskWorklogDO::getEndDate) + .orderByAsc(TaskWorklogDO::getId)); + } + + default List selectListByTaskIdsAndPeriod(Collection taskIds, LocalDate startDate, LocalDate endDate) { + if (taskIds == null || taskIds.isEmpty() || startDate == null || endDate == null) { + return List.of(); + } + return selectList(new LambdaQueryWrapperX() + .in(TaskWorklogDO::getTaskId, taskIds) + .le(TaskWorklogDO::getStartDate, endDate) + .ge(TaskWorklogDO::getEndDate, startDate) + .orderByAsc(TaskWorklogDO::getTaskId) + .orderByAsc(TaskWorklogDO::getEndDate) + .orderByAsc(TaskWorklogDO::getId)); + } + } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/PersonalReportPlanItemMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/PersonalReportPlanItemMapper.java new file mode 100644 index 0000000..a1d50f8 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/PersonalReportPlanItemMapper.java @@ -0,0 +1,26 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.common; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.PersonalReportPlanItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PersonalReportPlanItemMapper extends BaseMapperX { + + default List selectListByReport(String reportType, Long reportId) { + return selectList(new LambdaQueryWrapperX() + .eq(PersonalReportPlanItemDO::getReportType, reportType) + .eq(PersonalReportPlanItemDO::getReportId, reportId) + .orderByAsc(PersonalReportPlanItemDO::getItemNumber) + .orderByAsc(PersonalReportPlanItemDO::getId)); + } + + default int deleteByReport(String reportType, Long reportId) { + return delete(new LambdaQueryWrapperX() + .eq(PersonalReportPlanItemDO::getReportType, reportType) + .eq(PersonalReportPlanItemDO::getReportId, reportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/PersonalReportReviewItemMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/PersonalReportReviewItemMapper.java new file mode 100644 index 0000000..b637394 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/PersonalReportReviewItemMapper.java @@ -0,0 +1,26 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.common; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.PersonalReportReviewItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PersonalReportReviewItemMapper extends BaseMapperX { + + default List selectListByReport(String reportType, Long reportId) { + return selectList(new LambdaQueryWrapperX() + .eq(PersonalReportReviewItemDO::getReportType, reportType) + .eq(PersonalReportReviewItemDO::getReportId, reportId) + .orderByAsc(PersonalReportReviewItemDO::getItemNumber) + .orderByAsc(PersonalReportReviewItemDO::getId)); + } + + default int deleteByReport(String reportType, Long reportId) { + return delete(new LambdaQueryWrapperX() + .eq(PersonalReportReviewItemDO::getReportType, reportType) + .eq(PersonalReportReviewItemDO::getReportId, reportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/WorkReportStatusLogMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/WorkReportStatusLogMapper.java new file mode 100644 index 0000000..23b41b9 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/common/WorkReportStatusLogMapper.java @@ -0,0 +1,26 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.common; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportStatusLogDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface WorkReportStatusLogMapper extends BaseMapperX { + + default List selectListByReport(String reportType, Long reportId) { + return selectList(new LambdaQueryWrapperX() + .eq(WorkReportStatusLogDO::getReportType, reportType) + .eq(WorkReportStatusLogDO::getReportId, reportId) + .orderByDesc(WorkReportStatusLogDO::getCreateTime) + .orderByDesc(WorkReportStatusLogDO::getId)); + } + + default int deleteByReport(String reportType, Long reportId) { + return delete(new LambdaQueryWrapperX() + .eq(WorkReportStatusLogDO::getReportType, reportType) + .eq(WorkReportStatusLogDO::getReportId, reportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportApprovalRecordMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportApprovalRecordMapper.java new file mode 100644 index 0000000..a1924de --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportApprovalRecordMapper.java @@ -0,0 +1,29 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.monthly; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportApprovalRecordDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MonthlyReportApprovalRecordMapper extends BaseMapperX { + + default List selectListByMonthlyReportId(Long monthlyReportId) { + return selectList(new LambdaQueryWrapperX() + .eq(MonthlyReportApprovalRecordDO::getMonthlyReportId, monthlyReportId) + .orderByDesc(MonthlyReportApprovalRecordDO::getApprovalRound) + .orderByDesc(MonthlyReportApprovalRecordDO::getId)); + } + + default int countByMonthlyReportId(Long monthlyReportId) { + return Math.toIntExact(selectCount(new LambdaQueryWrapperX() + .eq(MonthlyReportApprovalRecordDO::getMonthlyReportId, monthlyReportId))); + } + + default int deleteByMonthlyReportId(Long monthlyReportId) { + return delete(new LambdaQueryWrapperX() + .eq(MonthlyReportApprovalRecordDO::getMonthlyReportId, monthlyReportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java new file mode 100644 index 0000000..01f0732 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/monthly/MonthlyReportMapper.java @@ -0,0 +1,123 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.monthly; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportPageReqVO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@Mapper +public interface MonthlyReportMapper extends BaseMapperX { + + default MonthlyReportDO selectByReporterIdAndPeriodKey(Long reporterId, String periodKey) { + return selectOne(new LambdaQueryWrapperX() + .eq(MonthlyReportDO::getReporterId, reporterId) + .eq(MonthlyReportDO::getPeriodKey, periodKey)); + } + + default PageResult selectReporterPage(Long reporterId, MonthlyReportPageReqVO reqVO) { + return selectReporterPage(reporterId, reqVO, null); + } + + default PageResult selectReporterPage(Long reporterId, MonthlyReportPageReqVO reqVO, + Collection allowedStatusCodes) { + if (allowedStatusCodes != null && allowedStatusCodes.isEmpty()) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .eq(MonthlyReportDO::getReporterId, reporterId) + .orderByDesc(MonthlyReportDO::getPeriodStartDate) + .orderByDesc(MonthlyReportDO::getId); + if (allowedStatusCodes != null) { + wrapper.in(MonthlyReportDO::getStatusCode, allowedStatusCodes); + } + return selectPage(reqVO, wrapper); + } + + default PageResult selectApprovalPage(Long supervisorUserId, MonthlyReportPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .eq(MonthlyReportDO::getSupervisorUserId, supervisorUserId) + .eq(MonthlyReportDO::getStatusCode, WorkReportConstants.STATUS_PENDING_APPROVAL) + .orderByDesc(MonthlyReportDO::getSubmitTime) + .orderByDesc(MonthlyReportDO::getId); + return selectPage(reqVO, wrapper); + } + + default int updateByIdAndStatus(MonthlyReportDO update, Long id, String fromStatus) { + return update(update, new LambdaQueryWrapperX() + .eq(MonthlyReportDO::getId, id) + .eq(MonthlyReportDO::getStatusCode, fromStatus)); + } + + default int updateSubmitFieldsByIdAndStatus(Long id, String fromStatus, String reporterDeptName, + String reporterPostName, Long supervisorUserId, + String supervisorName, String toStatus, LocalDateTime submitTime, + String updater) { + return update(null, new LambdaUpdateWrapper() + .set(MonthlyReportDO::getReporterDeptName, reporterDeptName) + .set(MonthlyReportDO::getReporterPostName, reporterPostName) + .set(MonthlyReportDO::getSupervisorUserId, supervisorUserId) + .set(MonthlyReportDO::getSupervisorName, supervisorName) + .set(MonthlyReportDO::getStatusCode, toStatus) + .set(MonthlyReportDO::getSubmitTime, submitTime) + .set(MonthlyReportDO::getApprovalTime, null) + .set(MonthlyReportDO::getApprovalComment, null) + .set(MonthlyReportDO::getLastStatusReason, null) + .set(MonthlyReportDO::getUpdateTime, submitTime) + .set(MonthlyReportDO::getUpdater, updater) + .eq(MonthlyReportDO::getId, id) + .eq(MonthlyReportDO::getStatusCode, fromStatus)); + } + + default int updateApprovalFieldsByIdAndStatus(Long id, String fromStatus, String toStatus, + LocalDateTime approvalTime, String approvalComment, + String lastStatusReason, String updater) { + return update(null, new LambdaUpdateWrapper() + .set(MonthlyReportDO::getStatusCode, toStatus) + .set(MonthlyReportDO::getApprovalTime, approvalTime) + .set(MonthlyReportDO::getApprovalComment, approvalComment) + .set(MonthlyReportDO::getLastStatusReason, lastStatusReason) + .set(MonthlyReportDO::getUpdateTime, approvalTime) + .set(MonthlyReportDO::getUpdater, updater) + .eq(MonthlyReportDO::getId, id) + .eq(MonthlyReportDO::getStatusCode, fromStatus)); + } + + default int updateByIdAndStatusesAndReporterId(MonthlyReportDO update, Long id, Collection statuses, + Long reporterId) { + return update(update, new LambdaQueryWrapperX() + .eq(MonthlyReportDO::getId, id) + .eq(MonthlyReportDO::getReporterId, reporterId) + .in(MonthlyReportDO::getStatusCode, statuses)); + } + + default int deleteByIdAndStatusesAndReporterId(Long id, Collection statuses, Long reporterId) { + return delete(new LambdaQueryWrapperX() + .eq(MonthlyReportDO::getId, id) + .eq(MonthlyReportDO::getReporterId, reporterId) + .in(MonthlyReportDO::getStatusCode, statuses)); + } + + private LambdaQueryWrapperX buildPageQuery(MonthlyReportPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .eqIfPresent(MonthlyReportDO::getStatusCode, reqVO.getStatusCode()) + .betweenIfPresent(MonthlyReportDO::getPeriodStartDate, reqVO.getPeriodStartDate()) + .betweenIfPresent(MonthlyReportDO::getSubmitTime, reqVO.getSubmitTime()); + if (StringUtils.hasText(reqVO.getKeyword())) { + wrapper.and(w -> w.like(MonthlyReportDO::getPeriodLabel, reqVO.getKeyword()) + .or() + .like(MonthlyReportDO::getReporterName, reqVO.getKeyword()) + .or() + .like(MonthlyReportDO::getSupervisorName, reqVO.getKeyword())); + } + return wrapper; + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportApprovalRecordMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportApprovalRecordMapper.java new file mode 100644 index 0000000..ea5ddcd --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportApprovalRecordMapper.java @@ -0,0 +1,29 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.project; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportApprovalRecordDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ProjectReportApprovalRecordMapper extends BaseMapperX { + + default List selectListByProjectReportId(Long projectReportId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectReportApprovalRecordDO::getProjectReportId, projectReportId) + .orderByDesc(ProjectReportApprovalRecordDO::getApprovalRound) + .orderByDesc(ProjectReportApprovalRecordDO::getId)); + } + + default int countByProjectReportId(Long projectReportId) { + return Math.toIntExact(selectCount(new LambdaQueryWrapperX() + .eq(ProjectReportApprovalRecordDO::getProjectReportId, projectReportId))); + } + + default int deleteByProjectReportId(Long projectReportId) { + return delete(new LambdaQueryWrapperX() + .eq(ProjectReportApprovalRecordDO::getProjectReportId, projectReportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportCurrentItemMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportCurrentItemMapper.java new file mode 100644 index 0000000..b1ba562 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportCurrentItemMapper.java @@ -0,0 +1,23 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.project; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportCurrentItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ProjectReportCurrentItemMapper extends BaseMapperX { + + default List selectListByReportId(Long reportId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectReportCurrentItemDO::getReportId, reportId) + .orderByAsc(ProjectReportCurrentItemDO::getId)); + } + + default int deleteByReportId(Long reportId) { + return delete(new LambdaQueryWrapperX() + .eq(ProjectReportCurrentItemDO::getReportId, reportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java new file mode 100644 index 0000000..7281a1d --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportMapper.java @@ -0,0 +1,136 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.project; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportPageReqVO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportMemberSnapshotItem; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportDO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@Mapper +public interface ProjectReportMapper extends BaseMapperX { + + String JACKSON_TYPE_HANDLER_MAPPING = "typeHandler=" + JacksonTypeHandler.class.getCanonicalName(); + + default ProjectReportDO selectByProjectIdAndPeriodKeyAndProjectOwnerId(Long projectId, String periodKey, + Long projectOwnerId) { + return selectOne(new LambdaQueryWrapperX() + .eq(ProjectReportDO::getProjectId, projectId) + .eq(ProjectReportDO::getPeriodKey, periodKey) + .eq(ProjectReportDO::getProjectOwnerId, projectOwnerId)); + } + + default PageResult selectReporterPage(Long reporterId, ProjectReportPageReqVO reqVO) { + return selectReporterPage(reporterId, reqVO, null); + } + + default PageResult selectReporterPage(Long reporterId, ProjectReportPageReqVO reqVO, + Collection allowedStatusCodes) { + if (allowedStatusCodes != null && allowedStatusCodes.isEmpty()) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .eq(ProjectReportDO::getProjectOwnerId, reporterId) + .orderByDesc(ProjectReportDO::getPeriodStartDate) + .orderByDesc(ProjectReportDO::getId); + if (allowedStatusCodes != null) { + wrapper.in(ProjectReportDO::getStatusCode, allowedStatusCodes); + } + return selectPage(reqVO, wrapper); + } + + default PageResult selectApprovalPage(Long supervisorUserId, ProjectReportPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .eq(ProjectReportDO::getSupervisorUserId, supervisorUserId) + .eq(ProjectReportDO::getStatusCode, WorkReportConstants.STATUS_PENDING_APPROVAL) + .orderByDesc(ProjectReportDO::getSubmitTime) + .orderByDesc(ProjectReportDO::getId); + return selectPage(reqVO, wrapper); + } + + default int updateByIdAndStatus(ProjectReportDO update, Long id, String fromStatus) { + return update(update, new LambdaQueryWrapperX() + .eq(ProjectReportDO::getId, id) + .eq(ProjectReportDO::getStatusCode, fromStatus)); + } + + default int updateSubmitFieldsByIdAndStatus(Long id, String fromStatus, String projectName, + Long projectOwnerId, String projectOwnerName, + List projectMemberSnapshot, + Long supervisorUserId, String supervisorName, String toStatus, + LocalDateTime submitTime, String updater) { + return update(null, new LambdaUpdateWrapper() + .set(ProjectReportDO::getProjectName, projectName) + .set(ProjectReportDO::getProjectOwnerId, projectOwnerId) + .set(ProjectReportDO::getProjectOwnerName, projectOwnerName) + .set(ProjectReportDO::getProjectMemberSnapshot, projectMemberSnapshot, JACKSON_TYPE_HANDLER_MAPPING) + .set(ProjectReportDO::getSupervisorUserId, supervisorUserId) + .set(ProjectReportDO::getSupervisorName, supervisorName) + .set(ProjectReportDO::getStatusCode, toStatus) + .set(ProjectReportDO::getSubmitTime, submitTime) + .set(ProjectReportDO::getApprovalTime, null) + .set(ProjectReportDO::getApprovalComment, null) + .set(ProjectReportDO::getLastStatusReason, null) + .set(ProjectReportDO::getUpdateTime, submitTime) + .set(ProjectReportDO::getUpdater, updater) + .eq(ProjectReportDO::getId, id) + .eq(ProjectReportDO::getStatusCode, fromStatus)); + } + + default int updateApprovalFieldsByIdAndStatus(Long id, String fromStatus, String toStatus, + LocalDateTime approvalTime, String approvalComment, + String lastStatusReason, String updater) { + return update(null, new LambdaUpdateWrapper() + .set(ProjectReportDO::getStatusCode, toStatus) + .set(ProjectReportDO::getApprovalTime, approvalTime) + .set(ProjectReportDO::getApprovalComment, approvalComment) + .set(ProjectReportDO::getLastStatusReason, lastStatusReason) + .set(ProjectReportDO::getUpdateTime, approvalTime) + .set(ProjectReportDO::getUpdater, updater) + .eq(ProjectReportDO::getId, id) + .eq(ProjectReportDO::getStatusCode, fromStatus)); + } + + default int updateByIdAndStatusesAndReporterId(ProjectReportDO update, Long id, Collection statuses, + Long reporterId) { + return update(update, new LambdaQueryWrapperX() + .eq(ProjectReportDO::getId, id) + .eq(ProjectReportDO::getProjectOwnerId, reporterId) + .in(ProjectReportDO::getStatusCode, statuses)); + } + + default int deleteByIdAndStatusesAndReporterId(Long id, Collection statuses, Long reporterId) { + return delete(new LambdaQueryWrapperX() + .eq(ProjectReportDO::getId, id) + .eq(ProjectReportDO::getProjectOwnerId, reporterId) + .in(ProjectReportDO::getStatusCode, statuses)); + } + + private LambdaQueryWrapperX buildPageQuery(ProjectReportPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .eqIfPresent(ProjectReportDO::getProjectId, reqVO.getProjectId()) + .eqIfPresent(ProjectReportDO::getFlag, reqVO.getFlag()) + .eqIfPresent(ProjectReportDO::getStatusCode, reqVO.getStatusCode()) + .betweenIfPresent(ProjectReportDO::getPeriodStartDate, reqVO.getPeriodStartDate()) + .betweenIfPresent(ProjectReportDO::getSubmitTime, reqVO.getSubmitTime()); + if (StringUtils.hasText(reqVO.getKeyword())) { + wrapper.and(w -> w.like(ProjectReportDO::getProjectName, reqVO.getKeyword()) + .or() + .like(ProjectReportDO::getPeriodLabel, reqVO.getKeyword()) + .or() + .like(ProjectReportDO::getProjectOwnerName, reqVO.getKeyword()) + .or() + .like(ProjectReportDO::getSupervisorName, reqVO.getKeyword())); + } + return wrapper; + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportNextItemMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportNextItemMapper.java new file mode 100644 index 0000000..6542f10 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/project/ProjectReportNextItemMapper.java @@ -0,0 +1,23 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.project; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportNextItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface ProjectReportNextItemMapper extends BaseMapperX { + + default List selectListByReportId(Long reportId) { + return selectList(new LambdaQueryWrapperX() + .eq(ProjectReportNextItemDO::getReportId, reportId) + .orderByAsc(ProjectReportNextItemDO::getId)); + } + + default int deleteByReportId(Long reportId) { + return delete(new LambdaQueryWrapperX() + .eq(ProjectReportNextItemDO::getReportId, reportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportApprovalRecordMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportApprovalRecordMapper.java new file mode 100644 index 0000000..2bd8c4b --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportApprovalRecordMapper.java @@ -0,0 +1,29 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.weekly; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportApprovalRecordDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface WeeklyReportApprovalRecordMapper extends BaseMapperX { + + default List selectListByWeeklyReportId(Long weeklyReportId) { + return selectList(new LambdaQueryWrapperX() + .eq(WeeklyReportApprovalRecordDO::getWeeklyReportId, weeklyReportId) + .orderByDesc(WeeklyReportApprovalRecordDO::getApprovalRound) + .orderByDesc(WeeklyReportApprovalRecordDO::getId)); + } + + default int countByWeeklyReportId(Long weeklyReportId) { + return Math.toIntExact(selectCount(new LambdaQueryWrapperX() + .eq(WeeklyReportApprovalRecordDO::getWeeklyReportId, weeklyReportId))); + } + + default int deleteByWeeklyReportId(Long weeklyReportId) { + return delete(new LambdaQueryWrapperX() + .eq(WeeklyReportApprovalRecordDO::getWeeklyReportId, weeklyReportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java new file mode 100644 index 0000000..cd05f10 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportMapper.java @@ -0,0 +1,124 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.weekly; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportPageReqVO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportDO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +@Mapper +public interface WeeklyReportMapper extends BaseMapperX { + + default WeeklyReportDO selectByReporterIdAndPeriodKey(Long reporterId, String periodKey) { + return selectOne(new LambdaQueryWrapperX() + .eq(WeeklyReportDO::getReporterId, reporterId) + .eq(WeeklyReportDO::getPeriodKey, periodKey)); + } + + default PageResult selectReporterPage(Long reporterId, WeeklyReportPageReqVO reqVO) { + return selectReporterPage(reporterId, reqVO, null); + } + + default PageResult selectReporterPage(Long reporterId, WeeklyReportPageReqVO reqVO, + Collection allowedStatusCodes) { + if (allowedStatusCodes != null && allowedStatusCodes.isEmpty()) { + return new PageResult<>(List.of(), 0L); + } + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .eq(WeeklyReportDO::getReporterId, reporterId) + .orderByDesc(WeeklyReportDO::getPeriodStartDate) + .orderByDesc(WeeklyReportDO::getId); + if (allowedStatusCodes != null) { + wrapper.in(WeeklyReportDO::getStatusCode, allowedStatusCodes); + } + return selectPage(reqVO, wrapper); + } + + default PageResult selectApprovalPage(Long supervisorUserId, WeeklyReportPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = buildPageQuery(reqVO) + .eq(WeeklyReportDO::getSupervisorUserId, supervisorUserId) + .eq(WeeklyReportDO::getStatusCode, WorkReportConstants.STATUS_PENDING_APPROVAL) + .orderByDesc(WeeklyReportDO::getSubmitTime) + .orderByDesc(WeeklyReportDO::getId); + return selectPage(reqVO, wrapper); + } + + default int updateByIdAndStatus(WeeklyReportDO update, Long id, String fromStatus) { + return update(update, new LambdaQueryWrapperX() + .eq(WeeklyReportDO::getId, id) + .eq(WeeklyReportDO::getStatusCode, fromStatus)); + } + + default int updateSubmitFieldsByIdAndStatus(Long id, String fromStatus, String reporterDeptName, + String reporterPostName, Long supervisorUserId, + String supervisorName, String toStatus, LocalDateTime submitTime, + String updater) { + return update(null, new LambdaUpdateWrapper() + .set(WeeklyReportDO::getReporterDeptName, reporterDeptName) + .set(WeeklyReportDO::getReporterPostName, reporterPostName) + .set(WeeklyReportDO::getSupervisorUserId, supervisorUserId) + .set(WeeklyReportDO::getSupervisorName, supervisorName) + .set(WeeklyReportDO::getStatusCode, toStatus) + .set(WeeklyReportDO::getSubmitTime, submitTime) + .set(WeeklyReportDO::getApprovalTime, null) + .set(WeeklyReportDO::getApprovalComment, null) + .set(WeeklyReportDO::getLastStatusReason, null) + .set(WeeklyReportDO::getUpdateTime, submitTime) + .set(WeeklyReportDO::getUpdater, updater) + .eq(WeeklyReportDO::getId, id) + .eq(WeeklyReportDO::getStatusCode, fromStatus)); + } + + default int updateApprovalFieldsByIdAndStatus(Long id, String fromStatus, String toStatus, + LocalDateTime approvalTime, String approvalComment, + String lastStatusReason, String updater) { + return update(null, new LambdaUpdateWrapper() + .set(WeeklyReportDO::getStatusCode, toStatus) + .set(WeeklyReportDO::getApprovalTime, approvalTime) + .set(WeeklyReportDO::getApprovalComment, approvalComment) + .set(WeeklyReportDO::getLastStatusReason, lastStatusReason) + .set(WeeklyReportDO::getUpdateTime, approvalTime) + .set(WeeklyReportDO::getUpdater, updater) + .eq(WeeklyReportDO::getId, id) + .eq(WeeklyReportDO::getStatusCode, fromStatus)); + } + + default int updateByIdAndStatusesAndReporterId(WeeklyReportDO update, Long id, Collection statuses, + Long reporterId) { + return update(update, new LambdaQueryWrapperX() + .eq(WeeklyReportDO::getId, id) + .eq(WeeklyReportDO::getReporterId, reporterId) + .in(WeeklyReportDO::getStatusCode, statuses)); + } + + default int deleteByIdAndStatusesAndReporterId(Long id, Collection statuses, Long reporterId) { + return delete(new LambdaQueryWrapperX() + .eq(WeeklyReportDO::getId, id) + .eq(WeeklyReportDO::getReporterId, reporterId) + .in(WeeklyReportDO::getStatusCode, statuses)); + } + + private LambdaQueryWrapperX buildPageQuery(WeeklyReportPageReqVO reqVO) { + LambdaQueryWrapperX wrapper = new LambdaQueryWrapperX() + .eqIfPresent(WeeklyReportDO::getStatusCode, reqVO.getStatusCode()) + .eqIfPresent(WeeklyReportDO::getIsBusinessTrip, reqVO.getIsBusinessTrip()) + .betweenIfPresent(WeeklyReportDO::getPeriodStartDate, reqVO.getPeriodStartDate()) + .betweenIfPresent(WeeklyReportDO::getSubmitTime, reqVO.getSubmitTime()); + if (StringUtils.hasText(reqVO.getKeyword())) { + wrapper.and(w -> w.like(WeeklyReportDO::getPeriodLabel, reqVO.getKeyword()) + .or() + .like(WeeklyReportDO::getReporterName, reqVO.getKeyword()) + .or() + .like(WeeklyReportDO::getSupervisorName, reqVO.getKeyword())); + } + return wrapper; + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportTravelSegmentMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportTravelSegmentMapper.java new file mode 100644 index 0000000..ebf543c --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/workreport/weekly/WeeklyReportTravelSegmentMapper.java @@ -0,0 +1,24 @@ +package com.njcn.rdms.module.project.dal.mysql.workreport.weekly; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportTravelSegmentDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface WeeklyReportTravelSegmentMapper extends BaseMapperX { + + default List selectListByWeeklyReportId(Long weeklyReportId) { + return selectList(new LambdaQueryWrapperX() + .eq(WeeklyReportTravelSegmentDO::getWeeklyReportId, weeklyReportId) + .orderByAsc(WeeklyReportTravelSegmentDO::getSort) + .orderByAsc(WeeklyReportTravelSegmentDO::getId)); + } + + default int deleteByWeeklyReportId(Long weeklyReportId) { + return delete(new LambdaQueryWrapperX() + .eq(WeeklyReportTravelSegmentDO::getWeeklyReportId, weeklyReportId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/rpc/config/RpcConfiguration.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/rpc/config/RpcConfiguration.java index 986acf8..a5acaac 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/rpc/config/RpcConfiguration.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/framework/rpc/config/RpcConfiguration.java @@ -1,13 +1,15 @@ package com.njcn.rdms.module.project.framework.rpc.config; +import com.njcn.rdms.module.system.api.dept.DeptApi; import com.njcn.rdms.module.system.api.dept.OrgLeaderApi; +import com.njcn.rdms.module.system.api.dept.PostApi; import com.njcn.rdms.module.system.api.dict.DictDataApi; import com.njcn.rdms.module.system.api.file.FileApi; -import com.njcn.rdms.module.system.api.notify.NotifyMessageSendApi; import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi; import com.njcn.rdms.module.system.api.permission.PermissionApi; import com.njcn.rdms.module.system.api.permission.UserVisibilityConfigApi; import com.njcn.rdms.module.system.api.user.AdminUserApi; +import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Configuration; @@ -15,6 +17,9 @@ import org.springframework.context.annotation.Configuration; * Project 模块的 RPC 配置 */ @Configuration(value = "projectRpcConfiguration", proxyBeanMethods = false) -@EnableFeignClients(clients = {AdminUserApi.class, ObjectPermissionApi.class, DictDataApi.class, FileApi.class, PermissionApi.class, OrgLeaderApi.class, UserVisibilityConfigApi.class, NotifyMessageSendApi.class}) +@EnableFeignClients(clients = + {AdminUserApi.class, ObjectPermissionApi.class, DictDataApi.class, FileApi.class, + PermissionApi.class, OrgLeaderApi.class, UserVisibilityConfigApi.class, + DeptApi.class, PostApi.class, UserManagementRelationApi.class}) public class RpcConfiguration { } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java index d30aa05..c67cc10 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java @@ -1,6 +1,7 @@ package com.njcn.rdms.module.project.service.overtime; import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationApprovalRecordRespVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationExportVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; @@ -21,8 +22,6 @@ public interface OvertimeApplicationService { void reject(Long id, OvertimeApplicationStatusActionReqVO reqVO); - void cancel(Long id, OvertimeApplicationStatusActionReqVO reqVO); - void deleteApplication(Long id); OvertimeApplicationRespVO getApplication(Long id); @@ -35,5 +34,7 @@ public interface OvertimeApplicationService { List getStatusLogs(Long id); + List getApprovalRecords(Long id); + List getExportList(OvertimeApplicationPageReqVO reqVO); } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java index 7d6d479..6b165b1 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java @@ -7,6 +7,7 @@ import com.njcn.rdms.framework.common.util.json.JsonUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationApprovalRecordRespVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationExportVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; @@ -15,11 +16,13 @@ import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplica import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusDictRespVO; import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusLogRespVO; import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO; +import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationApprovalRecordDO; import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationDO; import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationStatusLogDO; import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO; import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO; import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper; +import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationApprovalRecordMapper; import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationMapper; import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationStatusLogMapper; import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper; @@ -53,6 +56,8 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic @Resource private OvertimeApplicationStatusLogMapper overtimeApplicationStatusLogMapper; @Resource + private OvertimeApplicationApprovalRecordMapper overtimeApplicationApprovalRecordMapper; + @Resource private BizAuditLogMapper bizAuditLogMapper; @Resource private ObjectStatusModelMapper objectStatusModelMapper; @@ -108,8 +113,7 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic update.setApprovalTime(null); int updateCount = overtimeApplicationMapper.updateByIdAndStatusesAndApplicantId(update, id, - List.of(OvertimeApplicationConstants.STATUS_REJECTED, OvertimeApplicationConstants.STATUS_CANCELLED), - loginUserId); + List.of(OvertimeApplicationConstants.STATUS_REJECTED), loginUserId); if (updateCount != 1) { throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_CONCURRENT_MODIFIED); } @@ -133,36 +137,6 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic processApprovalAction(id, OvertimeApplicationConstants.ACTION_REJECT, reqVO); } - @Override - @Transactional(rollbackFor = Exception.class) - public void cancel(Long id, OvertimeApplicationStatusActionReqVO reqVO) { - Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); - OvertimeApplicationDO current = validateApplicationExists(id); - if (!Objects.equals(current.getApplicantId(), loginUserId)) { - throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPLICANT_ONLY); - } - String reason = normalizeNullableText(reqVO == null ? null : reqVO.getReason()); - String fromStatus = current.getStatusCode(); - ObjectStatusTransitionDO transition = validateTransition(fromStatus, OvertimeApplicationConstants.ACTION_CANCEL, - reason); - - OvertimeApplicationDO update = new OvertimeApplicationDO(); - update.setStatusCode(transition.getToStatusCode()); - update.setApprovalComment(reason); - update.setApprovalTime(LocalDateTime.now()); - int updateCount = overtimeApplicationMapper.updateByIdAndStatusAndApplicantId(update, id, fromStatus, - loginUserId); - if (updateCount != 1) { - throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_CONCURRENT_MODIFIED); - } - - OvertimeApplicationDO after = mergeUpdated(current, update); - writeStatusLog(after, OvertimeApplicationConstants.ACTION_CANCEL, fromStatus, transition.getToStatusCode(), - reason); - writeAuditLog(after, OvertimeApplicationConstants.ACTION_CANCEL, fromStatus, transition.getToStatusCode(), - null, reason, null); - } - @Override @Transactional(rollbackFor = Exception.class) public void deleteApplication(Long id) { @@ -171,8 +145,8 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic if (!Objects.equals(current.getApplicantId(), loginUserId)) { throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPLICANT_ONLY); } - if (!OvertimeApplicationConstants.STATUS_CANCELLED.equals(current.getStatusCode())) { - throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_DELETE_ONLY_CANCELLED); + if (!OvertimeApplicationConstants.STATUS_REJECTED.equals(current.getStatusCode())) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_DELETE_ONLY_REJECTED); } overtimeApplicationMapper.deleteById(id); writeAuditLog(current, OvertimeApplicationConstants.ACTION_DELETE, current.getStatusCode(), null, null, null, @@ -215,6 +189,13 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic OvertimeApplicationStatusLogRespVO.class); } + @Override + public List getApprovalRecords(Long id) { + validateReadableApplication(id); + return BeanUtils.toBean(overtimeApplicationApprovalRecordMapper.selectListByApplicationId(id), + OvertimeApplicationApprovalRecordRespVO.class); + } + @Override public List getExportList(OvertimeApplicationPageReqVO reqVO) { reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); @@ -242,7 +223,9 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic } OvertimeApplicationDO after = mergeUpdated(current, update); - writeStatusLog(after, actionCode, fromStatus, transition.getToStatusCode(), reason); + OvertimeApplicationStatusLogDO statusLog = writeStatusLog(after, actionCode, fromStatus, + transition.getToStatusCode(), reason); + writeApprovalRecord(after, statusLog, reason); writeAuditLog(after, actionCode, fromStatus, transition.getToStatusCode(), null, reason, null); } @@ -354,8 +337,8 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic return respVO; } - private void writeStatusLog(OvertimeApplicationDO application, String actionType, String fromStatus, - String toStatus, String reason) { + private OvertimeApplicationStatusLogDO writeStatusLog(OvertimeApplicationDO application, String actionType, + String fromStatus, String toStatus, String reason) { OvertimeApplicationStatusLogDO log = new OvertimeApplicationStatusLogDO(); log.setApplicationId(application.getId()); log.setActionType(actionType); @@ -369,6 +352,20 @@ public class OvertimeApplicationServiceImpl implements OvertimeApplicationServic log.setOvertimeDurationSnapshot(application.getOvertimeDuration()); log.setRemark(buildSnapshotRemark(application)); overtimeApplicationStatusLogMapper.insert(log); + return log; + } + + private void writeApprovalRecord(OvertimeApplicationDO application, OvertimeApplicationStatusLogDO statusLog, + String reason) { + OvertimeApplicationApprovalRecordDO record = new OvertimeApplicationApprovalRecordDO(); + record.setOvertimeApplicationId(application.getId()); + record.setStatusLogId(statusLog.getId()); + record.setApprovalRound(overtimeApplicationApprovalRecordMapper.countByApplicationId(application.getId()) + 1); + record.setConclusion(statusLog.getToStatus()); + record.setOpinion(reason); + record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId()); + record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + overtimeApplicationApprovalRecordMapper.insert(record); } private void writeAuditLog(OvertimeApplicationDO application, String actionType, String fromStatus, diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java new file mode 100644 index 0000000..ec2323f --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportCommonService.java @@ -0,0 +1,1354 @@ +package com.njcn.rdms.module.project.service.workreport.common; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageParam; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.common.util.object.BeanUtils; +import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; +import com.njcn.rdms.module.project.constant.ProjectObjectConstants; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.*; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.*; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.*; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.*; +import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO; +import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO; +import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO; +import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO; +import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.PersonalReportPlanItemDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.PersonalReportReviewItemDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportMemberSnapshotItem; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportStatusLogDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportApprovalRecordDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportApprovalRecordDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportCurrentItemDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportNextItemDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportApprovalRecordDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportTravelSegmentDO; +import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper; +import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper; +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.status.ObjectStatusTransitionMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.common.PersonalReportPlanItemMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.common.PersonalReportReviewItemMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.common.WorkReportStatusLogMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportApprovalRecordMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportApprovalRecordMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportCurrentItemMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportNextItemMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportApprovalRecordMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapper; +import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportTravelSegmentMapper; +import com.njcn.rdms.module.project.enums.ErrorCodeConstants; +import com.njcn.rdms.module.system.api.dept.DeptApi; +import com.njcn.rdms.module.system.api.dept.PostApi; +import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO; +import com.njcn.rdms.module.system.api.dept.dto.PostRespDTO; +import com.njcn.rdms.module.system.api.user.AdminUserApi; +import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +@Service +public class WorkReportCommonService { + + private static final List ALLOW_DELETE_STATUSES = List.of( + WorkReportConstants.STATUS_DRAFT, + WorkReportConstants.STATUS_REJECTED); + + @Resource + private WeeklyReportMapper weeklyReportMapper; + @Resource + private MonthlyReportMapper monthlyReportMapper; + @Resource + private ProjectReportMapper projectReportMapper; + @Resource + private PersonalReportReviewItemMapper personalReportReviewItemMapper; + @Resource + private PersonalReportPlanItemMapper personalReportPlanItemMapper; + @Resource + private WeeklyReportTravelSegmentMapper weeklyReportTravelSegmentMapper; + @Resource + private ProjectReportCurrentItemMapper projectReportCurrentItemMapper; + @Resource + private ProjectReportNextItemMapper projectReportNextItemMapper; + @Resource + private WeeklyReportApprovalRecordMapper weeklyReportApprovalRecordMapper; + @Resource + private MonthlyReportApprovalRecordMapper monthlyReportApprovalRecordMapper; + @Resource + private ProjectReportApprovalRecordMapper projectReportApprovalRecordMapper; + @Resource + private WorkReportStatusLogMapper workReportStatusLogMapper; + @Resource + private BizAuditLogMapper bizAuditLogMapper; + @Resource + private ObjectStatusModelMapper objectStatusModelMapper; + @Resource + private ObjectStatusTransitionMapper objectStatusTransitionMapper; + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + @Resource + private PostApi postApi; + @Resource + private UserManagementRelationApi userManagementRelationApi; + @Resource + private ProjectMapper projectMapper; + @Resource + private UserObjectRoleMapper userObjectRoleMapper; + + public List getStatusDict() { + return objectStatusModelMapper.selectListByObjectTypeEnabled(WorkReportConstants.STATUS_OBJECT_TYPE).stream() + .map(this::toStatusDictRespVO) + .collect(Collectors.toList()); + } + + public List getProjectReportOwnerProjectOptions() { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + return projectMapper.selectListByManagerUserId(loginUserId).stream() + .map(project -> { + ProjectReportOwnerProjectOptionRespVO respVO = new ProjectReportOwnerProjectOptionRespVO(); + respVO.setId(project.getId()); + respVO.setProjectCode(project.getProjectCode()); + respVO.setProjectName(project.getProjectName()); + return respVO; + }) + .collect(Collectors.toList()); + } + + public void validateCurrentUserIsProjectReportProjectOwner(Long projectId) { + ProjectDO project = validateProjectExists(projectId); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (!Objects.equals(project.getManagerUserId(), loginUserId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_PROJECT_OWNER_ONLY); + } + } + + public WeeklyReportRespVO initWeeklyReport() { + CurrentUserProfile profile = loadCurrentUserProfile(true); + WeeklyReportRespVO respVO = new WeeklyReportRespVO(); + fillPersonalBase(respVO, profile); + applyStatusView(respVO, getInitialStatusModel()); + respVO.setIsBusinessTrip(false); + respVO.setReviewItems(Collections.emptyList()); + respVO.setPlanItems(Collections.emptyList()); + respVO.setTravelSegments(Collections.emptyList()); + return respVO; + } + + @Transactional(rollbackFor = Exception.class) + public Long createWeeklyReport(WeeklyReportSaveReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + CurrentUserProfile profile = loadCurrentUserProfile(true); + validateWeeklyDuplicate(profile.userId(), reqVO.getPeriodKey(), null); + + WeeklyReportDO report = new WeeklyReportDO(); + applyWeeklySaveFields(report, reqVO, profile); + report.setStatusCode(getInitialStatusCode()); + weeklyReportMapper.insert(report); + replaceWeeklyChildren(report.getId(), reqVO); + WeeklyReportDO latest = weeklyReportMapper.selectById(report.getId()); + writeAuditLog(WorkReportConstants.BIZ_TYPE_WEEKLY, latest.getId(), WorkReportConstants.ACTION_CREATE, + null, latest.getStatusCode(), null, buildWeeklyRemark(latest)); + return report.getId(); + } + + @Transactional(rollbackFor = Exception.class) + public void updateWeeklyReport(Long id, WeeklyReportSaveReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + WeeklyReportDO current = validateWeeklyReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getReporterId()); + validateAllowEdit(current.getStatusCode()); + validateWeeklyDuplicate(loginUserId, reqVO.getPeriodKey(), id); + + CurrentUserProfile profile = loadCurrentUserProfile(false); + WeeklyReportDO update = new WeeklyReportDO(); + update.setId(id); + applyWeeklySaveFields(update, reqVO, profile); + int updateCount = weeklyReportMapper.updateByIdAndStatus(update, id, current.getStatusCode()); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + replaceWeeklyChildren(id, reqVO); + WeeklyReportDO latest = weeklyReportMapper.selectById(id); + writeAuditLog(WorkReportConstants.BIZ_TYPE_WEEKLY, id, WorkReportConstants.ACTION_UPDATE, + current.getStatusCode(), latest.getStatusCode(), null, buildWeeklyRemark(latest)); + } + + public WeeklyReportRespVO getWeeklyReport(Long id) { + WeeklyReportDO report = validateReadableWeeklyReport(id); + return toWeeklyRespVO(report, true); + } + + public PageResult getWeeklyReportPage(WeeklyReportPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult pageResult = weeklyReportMapper.selectReporterPage(loginUserId, reqVO, + getEnabledStatusCodes()); + return new PageResult<>(pageResult.getList().stream() + .map(report -> toWeeklyRespVO(report, false)) + .collect(Collectors.toList()), pageResult.getTotal()); + } + + public PageResult getWeeklyApprovalPage(WeeklyReportPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult pageResult = weeklyReportMapper.selectApprovalPage(loginUserId, reqVO); + return new PageResult<>(pageResult.getList().stream() + .map(report -> toWeeklyRespVO(report, false)) + .collect(Collectors.toList()), pageResult.getTotal()); + } + + @Transactional(rollbackFor = Exception.class) + public void submitWeeklyReport(Long id) { + WeeklyReportDO current = validateWeeklyReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getReporterId()); + String actionCode = resolveSubmitAction(current.getStatusCode()); + ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionCode, null); + CurrentUserProfile profile = loadCurrentUserProfile(true); + + LocalDateTime submitTime = LocalDateTime.now(); + int updateCount = weeklyReportMapper.updateSubmitFieldsByIdAndStatus(id, current.getStatusCode(), + profile.deptName(), profile.postName(), profile.directManagerId(), profile.directManagerName(), + transition.getToStatusCode(), submitTime, String.valueOf(loginUserId)); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + + WeeklyReportDO latest = weeklyReportMapper.selectById(id); + writeStatusLog(latest, WorkReportConstants.REPORT_TYPE_WEEKLY, actionCode, current.getStatusCode(), + transition.getToStatusCode(), null, buildWeeklyRemark(latest)); + writeAuditLog(WorkReportConstants.BIZ_TYPE_WEEKLY, id, actionCode, current.getStatusCode(), + transition.getToStatusCode(), null, buildWeeklyRemark(latest)); + } + + @Transactional(rollbackFor = Exception.class) + public void approveWeeklyReport(Long id, WorkReportStatusActionReqVO reqVO) { + processWeeklyApproval(id, WorkReportConstants.ACTION_APPROVE, normalizeReason(reqVO)); + } + + @Transactional(rollbackFor = Exception.class) + public void rejectWeeklyReport(Long id, WorkReportStatusActionReqVO reqVO) { + processWeeklyApproval(id, WorkReportConstants.ACTION_REJECT, normalizeReason(reqVO)); + } + + @Transactional(rollbackFor = Exception.class) + public void deleteWeeklyReport(Long id) { + WeeklyReportDO current = validateWeeklyReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getReporterId()); + validateDeleteAllowed(current.getStatusCode()); + int deleteCount = weeklyReportMapper.deleteByIdAndStatusesAndReporterId(id, ALLOW_DELETE_STATUSES, loginUserId); + if (deleteCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_DELETE_NOT_ALLOWED); + } + personalReportReviewItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, id); + personalReportPlanItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, id); + weeklyReportTravelSegmentMapper.deleteByWeeklyReportId(id); + weeklyReportApprovalRecordMapper.deleteByWeeklyReportId(id); + workReportStatusLogMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, id); + writeAuditLog(WorkReportConstants.BIZ_TYPE_WEEKLY, id, WorkReportConstants.ACTION_DELETE, + current.getStatusCode(), null, null, buildWeeklyRemark(current)); + } + + public List getWeeklyStatusLogs(Long id) { + validateReadableWeeklyReport(id); + return BeanUtils.toBean(workReportStatusLogMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, id), + WorkReportStatusLogRespVO.class); + } + + public List getWeeklyApprovalRecords(Long id) { + validateReadableWeeklyReport(id); + return BeanUtils.toBean(weeklyReportApprovalRecordMapper.selectListByWeeklyReportId(id), + WorkReportApprovalRecordRespVO.class); + } + + public List getWeeklyExportList(WeeklyReportPageReqVO reqVO) { + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + return BeanUtils.toBean(getWeeklyReportPage(reqVO).getList(), WeeklyReportExportVO.class); + } + + public MonthlyReportRespVO initMonthlyReport() { + CurrentUserProfile profile = loadCurrentUserProfile(true); + MonthlyReportRespVO respVO = new MonthlyReportRespVO(); + fillPersonalBase(respVO, profile); + applyStatusView(respVO, getInitialStatusModel()); + respVO.setReviewItems(Collections.emptyList()); + respVO.setPlanItems(Collections.emptyList()); + return respVO; + } + + @Transactional(rollbackFor = Exception.class) + public Long createMonthlyReport(MonthlyReportSaveReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + CurrentUserProfile profile = loadCurrentUserProfile(true); + validateMonthlyDuplicate(profile.userId(), reqVO.getPeriodKey(), null); + + MonthlyReportDO report = new MonthlyReportDO(); + applyMonthlySaveFields(report, reqVO, profile); + report.setStatusCode(getInitialStatusCode()); + monthlyReportMapper.insert(report); + replaceMonthlyChildren(report.getId(), reqVO); + MonthlyReportDO latest = monthlyReportMapper.selectById(report.getId()); + writeAuditLog(WorkReportConstants.BIZ_TYPE_MONTHLY, latest.getId(), WorkReportConstants.ACTION_CREATE, + null, latest.getStatusCode(), null, buildMonthlyRemark(latest)); + return report.getId(); + } + + @Transactional(rollbackFor = Exception.class) + public void updateMonthlyReport(Long id, MonthlyReportSaveReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + MonthlyReportDO current = validateMonthlyReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getReporterId()); + validateAllowEdit(current.getStatusCode()); + validateMonthlyDuplicate(loginUserId, reqVO.getPeriodKey(), id); + + CurrentUserProfile profile = loadCurrentUserProfile(false); + MonthlyReportDO update = new MonthlyReportDO(); + update.setId(id); + applyMonthlySaveFields(update, reqVO, profile); + int updateCount = monthlyReportMapper.updateByIdAndStatus(update, id, current.getStatusCode()); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + replaceMonthlyChildren(id, reqVO); + MonthlyReportDO latest = monthlyReportMapper.selectById(id); + writeAuditLog(WorkReportConstants.BIZ_TYPE_MONTHLY, id, WorkReportConstants.ACTION_UPDATE, + current.getStatusCode(), latest.getStatusCode(), null, buildMonthlyRemark(latest)); + } + + public MonthlyReportRespVO getMonthlyReport(Long id) { + MonthlyReportDO report = validateReadableMonthlyReport(id); + return toMonthlyRespVO(report, true); + } + + public PageResult getMonthlyReportPage(MonthlyReportPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult pageResult = monthlyReportMapper.selectReporterPage(loginUserId, reqVO, + getEnabledStatusCodes()); + return new PageResult<>(pageResult.getList().stream() + .map(report -> toMonthlyRespVO(report, false)) + .collect(Collectors.toList()), pageResult.getTotal()); + } + + public PageResult getMonthlyApprovalPage(MonthlyReportPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult pageResult = monthlyReportMapper.selectApprovalPage(loginUserId, reqVO); + return new PageResult<>(pageResult.getList().stream() + .map(report -> toMonthlyRespVO(report, false)) + .collect(Collectors.toList()), pageResult.getTotal()); + } + + @Transactional(rollbackFor = Exception.class) + public void submitMonthlyReport(Long id) { + MonthlyReportDO current = validateMonthlyReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getReporterId()); + String actionCode = resolveSubmitAction(current.getStatusCode()); + ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionCode, null); + CurrentUserProfile profile = loadCurrentUserProfile(true); + + LocalDateTime submitTime = LocalDateTime.now(); + int updateCount = monthlyReportMapper.updateSubmitFieldsByIdAndStatus(id, current.getStatusCode(), + profile.deptName(), profile.postName(), profile.directManagerId(), profile.directManagerName(), + transition.getToStatusCode(), submitTime, String.valueOf(loginUserId)); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + + MonthlyReportDO latest = monthlyReportMapper.selectById(id); + writeStatusLog(latest, WorkReportConstants.REPORT_TYPE_MONTHLY, actionCode, current.getStatusCode(), + transition.getToStatusCode(), null, buildMonthlyRemark(latest)); + writeAuditLog(WorkReportConstants.BIZ_TYPE_MONTHLY, id, actionCode, current.getStatusCode(), + transition.getToStatusCode(), null, buildMonthlyRemark(latest)); + } + + @Transactional(rollbackFor = Exception.class) + public void approveMonthlyReport(Long id, MonthlyReportApproveReqVO reqVO) { + processMonthlyApproval(id, WorkReportConstants.ACTION_APPROVE, reqVO); + } + + @Transactional(rollbackFor = Exception.class) + public void rejectMonthlyReport(Long id, WorkReportStatusActionReqVO reqVO) { + MonthlyReportApproveReqVO rejectReqVO = new MonthlyReportApproveReqVO(); + rejectReqVO.setReason(normalizeReason(reqVO)); + processMonthlyApproval(id, WorkReportConstants.ACTION_REJECT, rejectReqVO); + } + + @Transactional(rollbackFor = Exception.class) + public void deleteMonthlyReport(Long id) { + MonthlyReportDO current = validateMonthlyReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getReporterId()); + validateDeleteAllowed(current.getStatusCode()); + int deleteCount = monthlyReportMapper.deleteByIdAndStatusesAndReporterId(id, ALLOW_DELETE_STATUSES, loginUserId); + if (deleteCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_DELETE_NOT_ALLOWED); + } + personalReportReviewItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, id); + personalReportPlanItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, id); + monthlyReportApprovalRecordMapper.deleteByMonthlyReportId(id); + workReportStatusLogMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, id); + writeAuditLog(WorkReportConstants.BIZ_TYPE_MONTHLY, id, WorkReportConstants.ACTION_DELETE, + current.getStatusCode(), null, null, buildMonthlyRemark(current)); + } + + public List getMonthlyStatusLogs(Long id) { + validateReadableMonthlyReport(id); + return BeanUtils.toBean(workReportStatusLogMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, id), + WorkReportStatusLogRespVO.class); + } + + public List getMonthlyApprovalRecords(Long id) { + validateReadableMonthlyReport(id); + return BeanUtils.toBean(monthlyReportApprovalRecordMapper.selectListByMonthlyReportId(id), + MonthlyReportApprovalRecordRespVO.class); + } + + public List getMonthlyExportList(MonthlyReportPageReqVO reqVO) { + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + return BeanUtils.toBean(getMonthlyReportPage(reqVO).getList(), MonthlyReportExportVO.class); + } + + public ProjectReportRespVO initProjectReport(Long projectId) { + CurrentUserProfile profile = loadCurrentUserProfile(true); + ProjectDO project = validateProjectExists(projectId); + ProjectReportRespVO respVO = new ProjectReportRespVO(); + respVO.setProjectId(project.getId()); + respVO.setProjectName(project.getProjectName()); + respVO.setProjectOwnerId(profile.userId()); + respVO.setProjectOwnerName(profile.userName()); + respVO.setProjectMemberSnapshot(BeanUtils.toBean(buildProjectMemberSnapshot(projectId), + WorkReportMemberSnapshotRespVO.class)); + respVO.setSupervisorUserId(profile.directManagerId()); + respVO.setSupervisorName(profile.directManagerName()); + applyStatusView(respVO, getInitialStatusModel()); + respVO.setCurrentItems(Collections.emptyList()); + respVO.setNextItems(Collections.emptyList()); + return respVO; + } + + @Transactional(rollbackFor = Exception.class) + public Long createProjectReport(ProjectReportSaveReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + validateProjectFlag(reqVO.getFlag()); + CurrentUserProfile profile = loadCurrentUserProfile(true); + validateProjectReportDuplicate(reqVO.getProjectId(), reqVO.getPeriodKey(), profile.userId(), null); + ProjectDO project = validateProjectExists(reqVO.getProjectId()); + + ProjectReportDO report = new ProjectReportDO(); + applyProjectSaveFields(report, reqVO, profile, project); + report.setStatusCode(getInitialStatusCode()); + projectReportMapper.insert(report); + replaceProjectChildren(report.getId(), reqVO); + ProjectReportDO latest = projectReportMapper.selectById(report.getId()); + writeAuditLog(WorkReportConstants.BIZ_TYPE_PROJECT, latest.getId(), WorkReportConstants.ACTION_CREATE, + null, latest.getStatusCode(), null, buildProjectRemark(latest)); + return report.getId(); + } + + @Transactional(rollbackFor = Exception.class) + public void updateProjectReport(Long id, ProjectReportSaveReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + validateProjectFlag(reqVO.getFlag()); + ProjectReportDO current = validateProjectReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getProjectOwnerId()); + validateAllowEdit(current.getStatusCode()); + validateProjectReportDuplicate(reqVO.getProjectId(), reqVO.getPeriodKey(), loginUserId, id); + + CurrentUserProfile profile = loadCurrentUserProfile(false); + ProjectDO project = validateProjectExists(reqVO.getProjectId()); + ProjectReportDO update = new ProjectReportDO(); + update.setId(id); + applyProjectSaveFields(update, reqVO, profile, project); + update.setTechnicalOwnerName(current.getTechnicalOwnerName()); + int updateCount = projectReportMapper.updateByIdAndStatus(update, id, current.getStatusCode()); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + replaceProjectChildren(id, reqVO); + ProjectReportDO latest = projectReportMapper.selectById(id); + writeAuditLog(WorkReportConstants.BIZ_TYPE_PROJECT, id, WorkReportConstants.ACTION_UPDATE, + current.getStatusCode(), latest.getStatusCode(), null, buildProjectRemark(latest)); + } + + public ProjectReportRespVO getProjectReport(Long id) { + ProjectReportDO report = validateReadableProjectReport(id); + return toProjectRespVO(report, true); + } + + public PageResult getProjectReportPage(ProjectReportPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult pageResult = projectReportMapper.selectReporterPage(loginUserId, reqVO, + getEnabledStatusCodes()); + return new PageResult<>(pageResult.getList().stream() + .map(report -> toProjectRespVO(report, false)) + .collect(Collectors.toList()), pageResult.getTotal()); + } + + public PageResult getProjectApprovalPage(ProjectReportPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult pageResult = projectReportMapper.selectApprovalPage(loginUserId, reqVO); + return new PageResult<>(pageResult.getList().stream() + .map(report -> toProjectRespVO(report, false)) + .collect(Collectors.toList()), pageResult.getTotal()); + } + + @Transactional(rollbackFor = Exception.class) + public void submitProjectReport(Long id) { + ProjectReportDO current = validateProjectReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getProjectOwnerId()); + String actionCode = resolveSubmitAction(current.getStatusCode()); + ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionCode, null); + CurrentUserProfile profile = loadCurrentUserProfile(true); + ProjectDO project = validateProjectExists(current.getProjectId()); + + LocalDateTime submitTime = LocalDateTime.now(); + int updateCount = projectReportMapper.updateSubmitFieldsByIdAndStatus(id, current.getStatusCode(), + project.getProjectName(), profile.userId(), profile.userName(), buildProjectMemberSnapshot(project.getId()), + profile.directManagerId(), profile.directManagerName(), transition.getToStatusCode(), submitTime, + String.valueOf(loginUserId)); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + + ProjectReportDO latest = projectReportMapper.selectById(id); + writeStatusLog(latest, WorkReportConstants.REPORT_TYPE_PROJECT, actionCode, current.getStatusCode(), + transition.getToStatusCode(), null, buildProjectRemark(latest)); + writeAuditLog(WorkReportConstants.BIZ_TYPE_PROJECT, id, actionCode, current.getStatusCode(), + transition.getToStatusCode(), null, buildProjectRemark(latest)); + } + + @Transactional(rollbackFor = Exception.class) + public void approveProjectReport(Long id, WorkReportStatusActionReqVO reqVO) { + processProjectApproval(id, WorkReportConstants.ACTION_APPROVE, normalizeReason(reqVO)); + } + + @Transactional(rollbackFor = Exception.class) + public void rejectProjectReport(Long id, WorkReportStatusActionReqVO reqVO) { + processProjectApproval(id, WorkReportConstants.ACTION_REJECT, normalizeReason(reqVO)); + } + + @Transactional(rollbackFor = Exception.class) + public void deleteProjectReport(Long id) { + ProjectReportDO current = validateProjectReportExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + validateReporter(loginUserId, current.getProjectOwnerId()); + validateDeleteAllowed(current.getStatusCode()); + int deleteCount = projectReportMapper.deleteByIdAndStatusesAndReporterId(id, ALLOW_DELETE_STATUSES, loginUserId); + if (deleteCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_DELETE_NOT_ALLOWED); + } + projectReportCurrentItemMapper.deleteByReportId(id); + projectReportNextItemMapper.deleteByReportId(id); + projectReportApprovalRecordMapper.deleteByProjectReportId(id); + workReportStatusLogMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_PROJECT, id); + writeAuditLog(WorkReportConstants.BIZ_TYPE_PROJECT, id, WorkReportConstants.ACTION_DELETE, + current.getStatusCode(), null, null, buildProjectRemark(current)); + } + + public List getProjectStatusLogs(Long id) { + validateReadableProjectReport(id); + return BeanUtils.toBean(workReportStatusLogMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_PROJECT, id), + WorkReportStatusLogRespVO.class); + } + + public List getProjectApprovalRecords(Long id) { + validateReadableProjectReport(id); + return BeanUtils.toBean(projectReportApprovalRecordMapper.selectListByProjectReportId(id), + WorkReportApprovalRecordRespVO.class); + } + + public List getProjectExportList(ProjectReportPageReqVO reqVO) { + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + return BeanUtils.toBean(getProjectReportPage(reqVO).getList(), ProjectReportExportVO.class); + } + + private void processWeeklyApproval(Long id, String actionCode, String reason) { + WeeklyReportDO current = validateWeeklyReportExists(id); + Long approverUserId = SecurityFrameworkUtils.getLoginUserId(); + validateApprover(approverUserId, current.getSupervisorUserId()); + ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionCode, reason); + + LocalDateTime approvalTime = LocalDateTime.now(); + int updateCount = weeklyReportMapper.updateApprovalFieldsByIdAndStatus(id, current.getStatusCode(), + transition.getToStatusCode(), approvalTime, reason, reason, String.valueOf(approverUserId)); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + + WeeklyReportDO latest = weeklyReportMapper.selectById(id); + WorkReportStatusLogDO statusLog = writeStatusLog(latest, WorkReportConstants.REPORT_TYPE_WEEKLY, actionCode, + current.getStatusCode(), transition.getToStatusCode(), reason, buildWeeklyRemark(latest)); + writeWeeklyApprovalRecord(latest, statusLog, reason); + writeAuditLog(WorkReportConstants.BIZ_TYPE_WEEKLY, id, actionCode, current.getStatusCode(), + transition.getToStatusCode(), reason, buildWeeklyRemark(latest)); + } + + private void processMonthlyApproval(Long id, String actionCode, MonthlyReportApproveReqVO reqVO) { + MonthlyReportDO current = validateMonthlyReportExists(id); + Long approverUserId = SecurityFrameworkUtils.getLoginUserId(); + validateApprover(approverUserId, current.getSupervisorUserId()); + String reason = normalizeReason(reqVO); + ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionCode, reason); + + LocalDateTime approvalTime = LocalDateTime.now(); + int updateCount = monthlyReportMapper.updateApprovalFieldsByIdAndStatus(id, current.getStatusCode(), + transition.getToStatusCode(), approvalTime, reason, reason, String.valueOf(approverUserId)); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + + MonthlyReportDO latest = monthlyReportMapper.selectById(id); + WorkReportStatusLogDO statusLog = writeStatusLog(latest, WorkReportConstants.REPORT_TYPE_MONTHLY, actionCode, + current.getStatusCode(), transition.getToStatusCode(), reason, buildMonthlyRemark(latest)); + writeMonthlyApprovalRecord(latest, statusLog, actionCode, reqVO); + writeAuditLog(WorkReportConstants.BIZ_TYPE_MONTHLY, id, actionCode, current.getStatusCode(), + transition.getToStatusCode(), reason, buildMonthlyRemark(latest)); + } + + private void processProjectApproval(Long id, String actionCode, String reason) { + ProjectReportDO current = validateProjectReportExists(id); + Long approverUserId = SecurityFrameworkUtils.getLoginUserId(); + validateApprover(approverUserId, current.getSupervisorUserId()); + ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionCode, reason); + + LocalDateTime approvalTime = LocalDateTime.now(); + int updateCount = projectReportMapper.updateApprovalFieldsByIdAndStatus(id, current.getStatusCode(), + transition.getToStatusCode(), approvalTime, reason, reason, String.valueOf(approverUserId)); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED); + } + + ProjectReportDO latest = projectReportMapper.selectById(id); + WorkReportStatusLogDO statusLog = writeStatusLog(latest, WorkReportConstants.REPORT_TYPE_PROJECT, actionCode, + current.getStatusCode(), transition.getToStatusCode(), reason, buildProjectRemark(latest)); + writeProjectApprovalRecord(latest, statusLog, reason); + writeAuditLog(WorkReportConstants.BIZ_TYPE_PROJECT, id, actionCode, current.getStatusCode(), + transition.getToStatusCode(), reason, buildProjectRemark(latest)); + } + + private WeeklyReportDO validateWeeklyReportExists(Long id) { + WeeklyReportDO report = weeklyReportMapper.selectById(id); + if (report == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_NOT_EXISTS); + } + return report; + } + + private MonthlyReportDO validateMonthlyReportExists(Long id) { + MonthlyReportDO report = monthlyReportMapper.selectById(id); + if (report == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_NOT_EXISTS); + } + return report; + } + + private ProjectReportDO validateProjectReportExists(Long id) { + ProjectReportDO report = projectReportMapper.selectById(id); + if (report == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_NOT_EXISTS); + } + return report; + } + + private WeeklyReportDO validateReadableWeeklyReport(Long id) { + WeeklyReportDO report = validateWeeklyReportExists(id); + validateReadable(report.getReporterId(), report.getSupervisorUserId()); + return report; + } + + private MonthlyReportDO validateReadableMonthlyReport(Long id) { + MonthlyReportDO report = validateMonthlyReportExists(id); + validateReadable(report.getReporterId(), report.getSupervisorUserId()); + return report; + } + + private ProjectReportDO validateReadableProjectReport(Long id) { + ProjectReportDO report = validateProjectReportExists(id); + validateReadable(report.getProjectOwnerId(), report.getSupervisorUserId()); + return report; + } + + private void validateReadable(Long reporterId, Long supervisorUserId) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (!Objects.equals(loginUserId, reporterId) && !Objects.equals(loginUserId, supervisorUserId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_READ_FORBIDDEN); + } + } + + private void validateReporter(Long loginUserId, Long reporterId) { + if (!Objects.equals(loginUserId, reporterId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_REPORTER_ONLY); + } + } + + private void validateApprover(Long loginUserId, Long approverId) { + if (!Objects.equals(loginUserId, approverId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_APPROVER_ONLY); + } + } + + private void validateDeleteAllowed(String statusCode) { + if (WorkReportConstants.STATUS_APPROVED.equals(statusCode)) { + throw exception(ErrorCodeConstants.WORK_REPORT_DELETE_NOT_ALLOWED); + } + } + + private void validateAllowEdit(String statusCode) { + ObjectStatusModelDO statusModel = getStatusModel(statusCode); + if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_NOT_ALLOW_EDIT); + } + } + + private ObjectStatusTransitionDO validateTransition(String fromStatus, String actionCode, String reason) { + ObjectStatusTransitionDO transition = objectStatusTransitionMapper + .selectByObjectTypeAndFromStatusAndAction(WorkReportConstants.STATUS_OBJECT_TYPE, fromStatus, actionCode); + if (transition == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_ACTION_NOT_ALLOWED, actionCode); + } + if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_ACTION_REASON_REQUIRED, actionCode); + } + getStatusModel(transition.getToStatusCode()); + return transition; + } + + private ObjectStatusModelDO getStatusModel(String statusCode) { + ObjectStatusModelDO statusModel = objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled( + WorkReportConstants.STATUS_OBJECT_TYPE, statusCode); + if (statusModel == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED); + } + return statusModel; + } + + private List getEnabledStatusCodes() { + return objectStatusModelMapper.selectListByObjectTypeEnabled(WorkReportConstants.STATUS_OBJECT_TYPE).stream() + .map(ObjectStatusModelDO::getStatusCode) + .filter(StringUtils::hasText) + .collect(Collectors.toList()); + } + + private ObjectStatusModelDO getInitialStatusModel() { + ObjectStatusModelDO statusModel = objectStatusModelMapper + .selectInitialByObjectTypeEnabled(WorkReportConstants.STATUS_OBJECT_TYPE); + if (statusModel == null || !StringUtils.hasText(statusModel.getStatusCode())) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED); + } + return statusModel; + } + + private String getInitialStatusCode() { + return getInitialStatusModel().getStatusCode(); + } + + private String resolveSubmitAction(String statusCode) { + if (WorkReportConstants.STATUS_DRAFT.equals(statusCode)) { + return WorkReportConstants.ACTION_SUBMIT; + } + if (WorkReportConstants.STATUS_REJECTED.equals(statusCode)) { + return WorkReportConstants.ACTION_RESUBMIT; + } + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_ACTION_NOT_ALLOWED, WorkReportConstants.ACTION_SUBMIT); + } + + private void validateWeeklyDuplicate(Long reporterId, String periodKey, Long excludeId) { + WeeklyReportDO exist = weeklyReportMapper.selectByReporterIdAndPeriodKey(reporterId, normalizeRequiredText(periodKey, "周期编码不能为空")); + if (exist != null && !Objects.equals(exist.getId(), excludeId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_PERIOD_DUPLICATE); + } + } + + private void validateMonthlyDuplicate(Long reporterId, String periodKey, Long excludeId) { + MonthlyReportDO exist = monthlyReportMapper.selectByReporterIdAndPeriodKey(reporterId, normalizeRequiredText(periodKey, "周期编码不能为空")); + if (exist != null && !Objects.equals(exist.getId(), excludeId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_PERIOD_DUPLICATE); + } + } + + private void validateProjectReportDuplicate(Long projectId, String periodKey, Long reporterId, Long excludeId) { + ProjectReportDO exist = projectReportMapper.selectByProjectIdAndPeriodKeyAndProjectOwnerId(projectId, + normalizeRequiredText(periodKey, "周期编码不能为空"), reporterId); + if (exist != null && !Objects.equals(exist.getId(), excludeId)) { + throw exception(ErrorCodeConstants.WORK_REPORT_PERIOD_DUPLICATE); + } + } + + private void validatePeriod(LocalDate startDate, LocalDate endDate) { + if (startDate == null || endDate == null) { + throw invalidParamException("周期开始日期和结束日期不能为空"); + } + if (endDate.isBefore(startDate)) { + throw invalidParamException("周期结束日期不能早于开始日期"); + } + } + + private void validateProjectFlag(Integer flag) { + if (!Objects.equals(flag, 1) && !Objects.equals(flag, 2)) { + throw invalidParamException("上半月/下半月标记只能为 1 或 2"); + } + } + + private CurrentUserProfile loadCurrentUserProfile(boolean requireManager) { + Long userId = SecurityFrameworkUtils.getLoginUserId(); + AdminUserRespDTO user = loadUser(userId); + String userName = StringUtils.hasText(user.getNickname()) + ? user.getNickname().trim() + : defaultText(SecurityFrameworkUtils.getLoginUserNickname()); + + String deptName = null; + if (user.getDeptId() != null) { + CommonResult deptResult = deptApi.getDept(user.getDeptId()); + DeptRespDTO dept = deptResult == null ? null : deptResult.getCheckedData(); + deptName = dept == null ? null : dept.getName(); + } + + String postName = null; + if (user.getPositionId() != null) { + Map postMap = postApi.getPostMap(Collections.singleton(user.getPositionId())); + PostRespDTO post = postMap.get(user.getPositionId()); + postName = post == null ? null : post.getName(); + } + + Long directManagerId = null; + String directManagerName = null; + CommonResult directManagerResult = userManagementRelationApi.getDirectManager(userId); + AdminUserRespDTO directManager = directManagerResult == null ? null : directManagerResult.getCheckedData(); + if (directManager != null) { + directManagerId = directManager.getId(); + directManagerName = defaultText(directManager.getNickname()); + } + if (requireManager && directManagerId == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_DIRECT_MANAGER_NOT_EXISTS); + } + return new CurrentUserProfile(userId, defaultText(userName), deptName, postName, directManagerId, directManagerName); + } + + private AdminUserRespDTO loadUser(Long userId) { + if (userId == null) { + throw invalidParamException("用户编号不能为空"); + } + adminUserApi.validateUserList(Collections.singleton(userId)).getCheckedData(); + CommonResult result = adminUserApi.getUser(userId); + AdminUserRespDTO user = result == null ? null : result.getCheckedData(); + if (user == null) { + throw invalidParamException("用户不存在:{}", userId); + } + return user; + } + + private ProjectDO validateProjectExists(Long projectId) { + ProjectDO project = projectMapper.selectById(projectId); + if (project == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_PROJECT_NOT_EXISTS); + } + return project; + } + + private List buildProjectMemberSnapshot(Long projectId) { + List members = userObjectRoleMapper.selectListByObject(ProjectObjectConstants.OBJECT_TYPE, projectId); + Set userIds = new LinkedHashSet<>(); + for (UserObjectRoleDO member : members) { + if (member == null || !Objects.equals(member.getStatus(), 0) || member.getUserId() == null) { + continue; + } + userIds.add(member.getUserId()); + } + if (userIds.isEmpty()) { + return Collections.emptyList(); + } + Map userMap = adminUserApi.getUserMap(userIds); + List snapshot = new ArrayList<>(userIds.size()); + for (Long userId : userIds) { + WorkReportMemberSnapshotItem item = new WorkReportMemberSnapshotItem(); + item.setUserId(userId); + AdminUserRespDTO user = userMap.get(userId); + item.setUserName(user == null ? "" : defaultText(user.getNickname())); + snapshot.add(item); + } + return snapshot; + } + + private void applyWeeklySaveFields(WeeklyReportDO target, WeeklyReportSaveReqVO reqVO, CurrentUserProfile profile) { + List travelSegments = Boolean.TRUE.equals(reqVO.getIsBusinessTrip()) + ? defaultList(reqVO.getTravelSegments()) : Collections.emptyList(); + validateTravelSegments(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate(), travelSegments); + target.setReporterId(profile.userId()); + target.setReporterName(profile.userName()); + target.setReporterDeptName(profile.deptName()); + target.setReporterPostName(profile.postName()); + target.setSupervisorUserId(profile.directManagerId()); + target.setSupervisorName(profile.directManagerName()); + target.setPeriodKey(normalizeRequiredText(reqVO.getPeriodKey(), "周期编码不能为空")); + target.setPeriodLabel(normalizeRequiredText(reqVO.getPeriodLabel(), "周期名称不能为空")); + target.setPeriodStartDate(reqVO.getPeriodStartDate()); + target.setPeriodEndDate(reqVO.getPeriodEndDate()); + target.setIsBusinessTrip(Boolean.TRUE.equals(reqVO.getIsBusinessTrip())); + target.setTotalTravelDays(sumTravelDays(travelSegments)); + target.setTotalWorkHours(sumReviewWorkHours(reqVO.getReviewItems())); + } + + private void applyMonthlySaveFields(MonthlyReportDO target, MonthlyReportSaveReqVO reqVO, CurrentUserProfile profile) { + target.setReporterId(profile.userId()); + target.setReporterName(profile.userName()); + target.setReporterDeptName(profile.deptName()); + target.setReporterPostName(profile.postName()); + target.setSupervisorUserId(profile.directManagerId()); + target.setSupervisorName(profile.directManagerName()); + target.setPeriodKey(normalizeRequiredText(reqVO.getPeriodKey(), "周期编码不能为空")); + target.setPeriodLabel(normalizeRequiredText(reqVO.getPeriodLabel(), "周期名称不能为空")); + target.setPeriodStartDate(reqVO.getPeriodStartDate()); + target.setPeriodEndDate(reqVO.getPeriodEndDate()); + target.setTotalWorkHours(sumReviewWorkHours(reqVO.getReviewItems())); + } + + private void applyProjectSaveFields(ProjectReportDO target, ProjectReportSaveReqVO reqVO, + CurrentUserProfile profile, ProjectDO project) { + target.setProjectId(project.getId()); + target.setProjectName(project.getProjectName()); + target.setProjectOwnerId(profile.userId()); + target.setProjectOwnerName(profile.userName()); + target.setProjectMemberSnapshot(buildProjectMemberSnapshot(project.getId())); + target.setSupervisorUserId(profile.directManagerId()); + target.setSupervisorName(profile.directManagerName()); + target.setPeriodKey(normalizeRequiredText(reqVO.getPeriodKey(), "周期编码不能为空")); + target.setPeriodLabel(normalizeRequiredText(reqVO.getPeriodLabel(), "周期名称不能为空")); + target.setPeriodStartDate(reqVO.getPeriodStartDate()); + target.setPeriodEndDate(reqVO.getPeriodEndDate()); + target.setFlag(reqVO.getFlag()); + target.setProjectStatusDesc(normalizeNullableText(reqVO.getProjectStatusDesc())); + target.setProjectProgressPlan(normalizeNullableText(reqVO.getProjectProgressPlan())); + target.setProjectKeyPoints(normalizeNullableText(reqVO.getProjectKeyPoints())); + target.setProjectProblems(normalizeNullableText(reqVO.getProjectProblems())); + target.setTotalWorkHours(sumProjectCurrentWorkHours(reqVO.getCurrentItems())); + } + + private void replaceWeeklyChildren(Long reportId, WeeklyReportSaveReqVO reqVO) { + personalReportReviewItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, reportId); + personalReportPlanItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, reportId); + weeklyReportTravelSegmentMapper.deleteByWeeklyReportId(reportId); + insertReviewItems(WorkReportConstants.REPORT_TYPE_WEEKLY, reportId, reqVO.getReviewItems()); + insertPlanItems(WorkReportConstants.REPORT_TYPE_WEEKLY, reportId, reqVO.getPlanItems()); + if (Boolean.TRUE.equals(reqVO.getIsBusinessTrip())) { + insertTravelSegments(reportId, reqVO.getTravelSegments()); + } + } + + private void replaceMonthlyChildren(Long reportId, MonthlyReportSaveReqVO reqVO) { + personalReportReviewItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, reportId); + personalReportPlanItemMapper.deleteByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, reportId); + insertReviewItems(WorkReportConstants.REPORT_TYPE_MONTHLY, reportId, reqVO.getReviewItems()); + insertPlanItems(WorkReportConstants.REPORT_TYPE_MONTHLY, reportId, reqVO.getPlanItems()); + } + + private void replaceProjectChildren(Long reportId, ProjectReportSaveReqVO reqVO) { + projectReportCurrentItemMapper.deleteByReportId(reportId); + projectReportNextItemMapper.deleteByReportId(reportId); + insertProjectCurrentItems(reportId, reqVO.getCurrentItems()); + insertProjectNextItems(reportId, reqVO.getNextItems()); + } + + private void insertReviewItems(String reportType, Long reportId, List items) { + List source = defaultList(items); + for (int i = 0; i < source.size(); i++) { + PersonalReportReviewItemReqVO item = source.get(i); + if (!StringUtils.hasText(item.getItemTitle())) { + continue; + } + PersonalReportReviewItemDO data = new PersonalReportReviewItemDO(); + data.setReportType(reportType); + data.setReportId(reportId); + data.setItemNumber(item.getItemNumber() != null ? item.getItemNumber() : i + 1); + data.setItemTitle(item.getItemTitle().trim()); + data.setWorkHours(item.getWorkHours()); + data.setContentText(normalizeNullableText(item.getContentText())); + data.setContentJson(item.getContentJson()); + data.setReflectionText(normalizeNullableText(item.getReflectionText())); + personalReportReviewItemMapper.insert(data); + } + } + + private void insertPlanItems(String reportType, Long reportId, List items) { + List source = defaultList(items); + for (int i = 0; i < source.size(); i++) { + PersonalReportPlanItemReqVO item = source.get(i); + if (!StringUtils.hasText(item.getItemTitle())) { + continue; + } + PersonalReportPlanItemDO data = new PersonalReportPlanItemDO(); + data.setReportType(reportType); + data.setReportId(reportId); + data.setItemNumber(item.getItemNumber() != null ? item.getItemNumber() : i + 1); + data.setItemTitle(item.getItemTitle().trim()); + data.setTargetText(normalizeNullableText(item.getTargetText())); + data.setTargetJson(item.getTargetJson()); + data.setSupportNeed(normalizeNullableText(item.getSupportNeed())); + personalReportPlanItemMapper.insert(data); + } + } + + private void insertTravelSegments(Long reportId, List items) { + List source = defaultList(items); + for (int i = 0; i < source.size(); i++) { + WeeklyReportTravelSegmentReqVO item = source.get(i); + if (item.getStartDate() == null || item.getEndDate() == null) { + continue; + } + WeeklyReportTravelSegmentDO data = new WeeklyReportTravelSegmentDO(); + data.setWeeklyReportId(reportId); + data.setSort(item.getSort() != null ? item.getSort() : i + 1); + data.setStartDate(item.getStartDate()); + data.setEndDate(item.getEndDate()); + data.setTravelDays(item.getTravelDays()); + data.setLocation(normalizeNullableText(item.getLocation())); + weeklyReportTravelSegmentMapper.insert(data); + } + } + + private void insertProjectCurrentItems(Long reportId, List items) { + List source = defaultList(items); + for (ProjectReportItemReqVO item : source) { + if (!StringUtils.hasText(item.getItemTitle())) { + continue; + } + ProjectReportCurrentItemDO data = new ProjectReportCurrentItemDO(); + data.setReportId(reportId); + data.setItemTitle(item.getItemTitle().trim()); + data.setWorkHours(item.getWorkHours()); + data.setPriorityCode(normalizeNullableText(item.getPriorityCode())); + data.setProgressRate(item.getProgressRate()); + projectReportCurrentItemMapper.insert(data); + } + } + + private void insertProjectNextItems(Long reportId, List items) { + List source = defaultList(items); + for (ProjectReportItemReqVO item : source) { + if (!StringUtils.hasText(item.getItemTitle())) { + continue; + } + ProjectReportNextItemDO data = new ProjectReportNextItemDO(); + data.setReportId(reportId); + data.setItemTitle(item.getItemTitle().trim()); + data.setPriorityCode(normalizeNullableText(item.getPriorityCode())); + data.setProgressRate(item.getProgressRate()); + projectReportNextItemMapper.insert(data); + } + } + + private WorkReportStatusLogDO writeStatusLog(Object report, String reportType, String actionType, + String fromStatus, String toStatus, String reason, String remark) { + WorkReportStatusLogDO log = new WorkReportStatusLogDO(); + log.setReportType(reportType); + log.setReportId(extractReportId(report)); + log.setActionType(actionType); + log.setFromStatus(fromStatus); + log.setToStatus(toStatus); + log.setReason(reason); + log.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId()); + log.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + log.setPeriodLabelSnapshot(extractPeriodLabel(report)); + log.setRemark(remark); + workReportStatusLogMapper.insert(log); + return log; + } + + private void writeAuditLog(String bizType, Long bizId, String actionType, String fromStatus, + String toStatus, String reason, String remark) { + BizAuditLogDO auditLog = new BizAuditLogDO(); + auditLog.setBizType(bizType); + auditLog.setBizId(bizId); + auditLog.setActionType(actionType); + auditLog.setFromStatus(fromStatus); + auditLog.setToStatus(toStatus); + auditLog.setReason(reason); + auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId()); + auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + auditLog.setRemark(remark); + bizAuditLogMapper.insert(auditLog); + } + + private void writeWeeklyApprovalRecord(WeeklyReportDO report, WorkReportStatusLogDO statusLog, String reason) { + WeeklyReportApprovalRecordDO record = new WeeklyReportApprovalRecordDO(); + record.setWeeklyReportId(report.getId()); + record.setStatusLogId(statusLog.getId()); + record.setApprovalRound(weeklyReportApprovalRecordMapper.countByWeeklyReportId(report.getId()) + 1); + record.setConclusion(statusLog.getToStatus()); + record.setOpinion(reason); + record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId()); + record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + weeklyReportApprovalRecordMapper.insert(record); + } + + private void writeMonthlyApprovalRecord(MonthlyReportDO report, WorkReportStatusLogDO statusLog, + String actionCode, MonthlyReportApproveReqVO reqVO) { + MonthlyReportApprovalRecordDO record = new MonthlyReportApprovalRecordDO(); + record.setMonthlyReportId(report.getId()); + record.setStatusLogId(statusLog.getId()); + record.setApprovalRound(monthlyReportApprovalRecordMapper.countByMonthlyReportId(report.getId()) + 1); + record.setConclusion(statusLog.getToStatus()); + record.setOpinion(normalizeReason(reqVO)); + if (WorkReportConstants.ACTION_APPROVE.equals(actionCode)) { + record.setMeetingDate(reqVO.getMeetingDate()); + record.setStrengthDesc(normalizeNullableText(reqVO.getStrengthDesc())); + record.setStrengthExample(normalizeNullableText(reqVO.getStrengthExample())); + record.setWeaknessDesc(normalizeNullableText(reqVO.getWeaknessDesc())); + record.setWeaknessExample(normalizeNullableText(reqVO.getWeaknessExample())); + record.setImprovementSuggestion(normalizeNullableText(reqVO.getImprovementSuggestion())); + record.setPerformanceResult(normalizeNullableText(reqVO.getPerformanceResult())); + record.setEmployeeSignName(normalizeNullableText(reqVO.getEmployeeSignName())); + record.setEmployeeSignedDate(reqVO.getEmployeeSignedDate()); + record.setSupervisorSignName(normalizeNullableText(reqVO.getSupervisorSignName())); + record.setSupervisorSignedDate(reqVO.getSupervisorSignedDate()); + } + record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId()); + record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + monthlyReportApprovalRecordMapper.insert(record); + } + + private void writeProjectApprovalRecord(ProjectReportDO report, WorkReportStatusLogDO statusLog, String reason) { + ProjectReportApprovalRecordDO record = new ProjectReportApprovalRecordDO(); + record.setProjectReportId(report.getId()); + record.setStatusLogId(statusLog.getId()); + record.setApprovalRound(projectReportApprovalRecordMapper.countByProjectReportId(report.getId()) + 1); + record.setConclusion(statusLog.getToStatus()); + record.setOpinion(reason); + record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId()); + record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + projectReportApprovalRecordMapper.insert(record); + } + + private Long extractReportId(Object report) { + if (report instanceof WeeklyReportDO weeklyReport) { + return weeklyReport.getId(); + } + if (report instanceof MonthlyReportDO monthlyReport) { + return monthlyReport.getId(); + } + if (report instanceof ProjectReportDO projectReport) { + return projectReport.getId(); + } + return null; + } + + private String extractPeriodLabel(Object report) { + if (report instanceof WeeklyReportDO weeklyReport) { + return weeklyReport.getPeriodLabel(); + } + if (report instanceof MonthlyReportDO monthlyReport) { + return monthlyReport.getPeriodLabel(); + } + if (report instanceof ProjectReportDO projectReport) { + return projectReport.getPeriodLabel(); + } + return null; + } + + private WeeklyReportRespVO toWeeklyRespVO(WeeklyReportDO report, boolean withChildren) { + WeeklyReportRespVO respVO = BeanUtils.toBean(report, WeeklyReportRespVO.class); + applyStatusView(respVO, getStatusModel(report.getStatusCode())); + if (withChildren) { + respVO.setReviewItems(BeanUtils.toBean( + personalReportReviewItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, report.getId()), + PersonalReportReviewItemRespVO.class)); + respVO.setPlanItems(BeanUtils.toBean( + personalReportPlanItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_WEEKLY, report.getId()), + PersonalReportPlanItemRespVO.class)); + respVO.setTravelSegments(BeanUtils.toBean( + weeklyReportTravelSegmentMapper.selectListByWeeklyReportId(report.getId()), + WeeklyReportTravelSegmentRespVO.class)); + } + return respVO; + } + + private MonthlyReportRespVO toMonthlyRespVO(MonthlyReportDO report, boolean withChildren) { + MonthlyReportRespVO respVO = BeanUtils.toBean(report, MonthlyReportRespVO.class); + applyStatusView(respVO, getStatusModel(report.getStatusCode())); + if (withChildren) { + respVO.setReviewItems(BeanUtils.toBean( + personalReportReviewItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, report.getId()), + PersonalReportReviewItemRespVO.class)); + respVO.setPlanItems(BeanUtils.toBean( + personalReportPlanItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, report.getId()), + PersonalReportPlanItemRespVO.class)); + } + return respVO; + } + + private ProjectReportRespVO toProjectRespVO(ProjectReportDO report, boolean withChildren) { + ProjectReportRespVO respVO = BeanUtils.toBean(report, ProjectReportRespVO.class); + applyStatusView(respVO, getStatusModel(report.getStatusCode())); + respVO.setProjectMemberSnapshot(BeanUtils.toBean(defaultList(report.getProjectMemberSnapshot()), + WorkReportMemberSnapshotRespVO.class)); + if (withChildren) { + respVO.setCurrentItems(BeanUtils.toBean(projectReportCurrentItemMapper.selectListByReportId(report.getId()), + ProjectReportItemRespVO.class)); + respVO.setNextItems(BeanUtils.toBean(projectReportNextItemMapper.selectListByReportId(report.getId()), + ProjectReportItemRespVO.class)); + } + return respVO; + } + + private void fillPersonalBase(WeeklyReportRespVO respVO, CurrentUserProfile profile) { + respVO.setReporterId(profile.userId()); + respVO.setReporterName(profile.userName()); + respVO.setReporterDeptName(profile.deptName()); + respVO.setReporterPostName(profile.postName()); + respVO.setSupervisorUserId(profile.directManagerId()); + respVO.setSupervisorName(profile.directManagerName()); + } + + private void fillPersonalBase(MonthlyReportRespVO respVO, CurrentUserProfile profile) { + respVO.setReporterId(profile.userId()); + respVO.setReporterName(profile.userName()); + respVO.setReporterDeptName(profile.deptName()); + respVO.setReporterPostName(profile.postName()); + respVO.setSupervisorUserId(profile.directManagerId()); + respVO.setSupervisorName(profile.directManagerName()); + } + + private void applyStatusView(WeeklyReportRespVO respVO, ObjectStatusModelDO statusModel) { + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private void applyStatusView(MonthlyReportRespVO respVO, ObjectStatusModelDO statusModel) { + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private void applyStatusView(ProjectReportRespVO respVO, ObjectStatusModelDO statusModel) { + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private WorkReportStatusDictRespVO toStatusDictRespVO(ObjectStatusModelDO statusModel) { + WorkReportStatusDictRespVO respVO = new WorkReportStatusDictRespVO(); + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setSort(statusModel.getSort()); + respVO.setInitialFlag(statusModel.getInitialFlag()); + respVO.setTerminalFlag(statusModel.getTerminalFlag()); + respVO.setAllowEdit(statusModel.getAllowEdit()); + return respVO; + } + + private void validateTravelSegments(LocalDate periodStartDate, LocalDate periodEndDate, + List travelSegments) { + for (WeeklyReportTravelSegmentReqVO item : defaultList(travelSegments)) { + if (item.getStartDate() == null || item.getEndDate() == null) { + continue; + } + if (item.getEndDate().isBefore(item.getStartDate())) { + throw invalidParamException("出差结束日期不能早于开始日期"); + } + if (item.getStartDate().isBefore(periodStartDate) || item.getEndDate().isAfter(periodEndDate)) { + throw invalidParamException("出差分段必须落在报告周期内"); + } + } + } + + private BigDecimal sumReviewWorkHours(List items) { + BigDecimal total = BigDecimal.ZERO; + for (PersonalReportReviewItemReqVO item : defaultList(items)) { + if (item.getWorkHours() != null) { + total = total.add(item.getWorkHours()); + } + } + return total.compareTo(BigDecimal.ZERO) == 0 ? null : total; + } + + private BigDecimal sumProjectCurrentWorkHours(List items) { + BigDecimal total = BigDecimal.ZERO; + for (ProjectReportItemReqVO item : defaultList(items)) { + if (item.getWorkHours() != null) { + total = total.add(item.getWorkHours()); + } + } + return total.compareTo(BigDecimal.ZERO) == 0 ? null : total; + } + + private BigDecimal sumTravelDays(List items) { + BigDecimal total = BigDecimal.ZERO; + for (WeeklyReportTravelSegmentReqVO item : defaultList(items)) { + if (item.getTravelDays() != null) { + total = total.add(item.getTravelDays()); + } + } + return total.compareTo(BigDecimal.ZERO) == 0 ? null : total; + } + + private String buildWeeklyRemark(WeeklyReportDO report) { + return "周报:" + defaultText(report.getReporterName()) + " / " + defaultText(report.getPeriodLabel()); + } + + private String buildMonthlyRemark(MonthlyReportDO report) { + return "月报:" + defaultText(report.getReporterName()) + " / " + defaultText(report.getPeriodLabel()); + } + + private String buildProjectRemark(ProjectReportDO report) { + return "项目半月报:" + defaultText(report.getProjectName()) + " / " + defaultText(report.getPeriodLabel()); + } + + private String normalizeReason(WorkReportStatusActionReqVO reqVO) { + return reqVO == null ? null : normalizeNullableText(reqVO.getReason()); + } + + private String normalizeRequiredText(String value, String message) { + if (!StringUtils.hasText(value)) { + throw invalidParamException(message); + } + return value.trim(); + } + + private String normalizeNullableText(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } + + private String defaultText(String value) { + return StringUtils.hasText(value) ? value.trim() : ""; + } + + private List defaultList(List list) { + return list == null ? Collections.emptyList() : list; + } + + private record CurrentUserProfile(Long userId, String userName, String deptName, String postName, + Long directManagerId, String directManagerName) { + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportStatusService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportStatusService.java new file mode 100644 index 0000000..7d085bf --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportStatusService.java @@ -0,0 +1,10 @@ +package com.njcn.rdms.module.project.service.workreport.common; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusDictRespVO; + +import java.util.List; + +public interface WorkReportStatusService { + + List getStatusDict(); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportStatusServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportStatusServiceImpl.java new file mode 100644 index 0000000..9c4f5d4 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/common/WorkReportStatusServiceImpl.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.service.workreport.common; + +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusDictRespVO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class WorkReportStatusServiceImpl implements WorkReportStatusService { + + @Resource + private WorkReportCommonService workReportCommonService; + + @Override + public List getStatusDict() { + return workReportCommonService.getStatusDict(); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/defaultdraft/WorkReportDefaultDraftService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/defaultdraft/WorkReportDefaultDraftService.java new file mode 100644 index 0000000..be9848e --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/defaultdraft/WorkReportDefaultDraftService.java @@ -0,0 +1,930 @@ +package com.njcn.rdms.module.project.service.workreport.defaultdraft; + +import com.njcn.rdms.framework.common.biz.system.dict.dto.DictDataRespDTO; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.util.object.BeanUtils; +import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; +import com.njcn.rdms.module.project.constant.ProjectObjectConstants; +import com.njcn.rdms.module.project.constant.ProjectTaskConstants; +import com.njcn.rdms.module.project.constant.WorkReportConstants; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportMemberSnapshotRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportDefaultDraftReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportDefaultDraftReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportDefaultDraftReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRespVO; +import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO; +import com.njcn.rdms.module.project.dal.dataobject.personal.PersonalItemDO; +import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO; +import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExecutionDO; +import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskDO; +import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskWorklogDO; +import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO; +import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportMemberSnapshotItem; +import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper; +import com.njcn.rdms.module.project.dal.mysql.personal.PersonalItemMapper; +import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper; +import com.njcn.rdms.module.project.dal.mysql.project.execution.ProjectExecutionMapper; +import com.njcn.rdms.module.project.dal.mysql.project.task.ProjectTaskMapper; +import com.njcn.rdms.module.project.dal.mysql.project.task.TaskWorklogMapper; +import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper; +import com.njcn.rdms.module.project.enums.ErrorCodeConstants; +import com.njcn.rdms.module.system.api.dept.DeptApi; +import com.njcn.rdms.module.system.api.dept.PostApi; +import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO; +import com.njcn.rdms.module.system.api.dept.dto.PostRespDTO; +import com.njcn.rdms.module.system.api.dict.DictDataApi; +import com.njcn.rdms.module.system.api.user.AdminUserApi; +import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; +import com.njcn.rdms.module.system.enums.DictTypeConstants; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +@Service +public class WorkReportDefaultDraftService { + + private static final String WORK_ITEM_MY_ITEMS = "我的事项"; + + @Resource + private ObjectStatusModelMapper objectStatusModelMapper; + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + @Resource + private PostApi postApi; + @Resource + private UserManagementRelationApi userManagementRelationApi; + @Resource + private DictDataApi dictDataApi; + @Resource + private ProjectMapper projectMapper; + @Resource + private UserObjectRoleMapper userObjectRoleMapper; + @Resource + private TaskWorklogMapper taskWorklogMapper; + @Resource + private ProjectTaskMapper projectTaskMapper; + @Resource + private PersonalItemMapper personalItemMapper; + @Resource + private ProjectExecutionMapper projectExecutionMapper; + + public WeeklyReportRespVO previewWeeklyDefaultDraft(WeeklyReportDefaultDraftReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + CurrentUserProfile profile = loadCurrentUserProfile(true); + ObjectStatusModelDO initialStatus = getInitialStatusModel(); + + WeeklyReportRespVO respVO = new WeeklyReportRespVO(); + fillPersonalBase(respVO, profile); + respVO.setPeriodKey(reqVO.getPeriodKey()); + respVO.setPeriodLabel(reqVO.getPeriodLabel()); + respVO.setPeriodStartDate(reqVO.getPeriodStartDate()); + respVO.setPeriodEndDate(reqVO.getPeriodEndDate()); + respVO.setIsBusinessTrip(Boolean.FALSE); + respVO.setTravelSegments(Collections.emptyList()); + respVO.setTotalTravelDays(BigDecimal.ZERO); + applyStatusView(respVO, initialStatus); + + LocalDateRange nextRange = nextWeeklyRange(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + List reviewItems = buildPersonalReviewItems( + reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate(), profile.userId(), true); + List planItems = buildPersonalPlanItems( + nextRange.startDate(), nextRange.endDate(), profile.userId(), true); + respVO.setReviewItems(reviewItems); + respVO.setPlanItems(planItems); + respVO.setTotalWorkHours(sumReviewWorkHours(reviewItems)); + return respVO; + } + + public MonthlyReportRespVO previewMonthlyDefaultDraft(MonthlyReportDefaultDraftReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + CurrentUserProfile profile = loadCurrentUserProfile(true); + ObjectStatusModelDO initialStatus = getInitialStatusModel(); + + MonthlyReportRespVO respVO = new MonthlyReportRespVO(); + fillPersonalBase(respVO, profile); + respVO.setPeriodKey(reqVO.getPeriodKey()); + respVO.setPeriodLabel(reqVO.getPeriodLabel()); + respVO.setPeriodStartDate(reqVO.getPeriodStartDate()); + respVO.setPeriodEndDate(reqVO.getPeriodEndDate()); + applyStatusView(respVO, initialStatus); + + LocalDateRange nextRange = nextMonthlyRange(reqVO.getPeriodEndDate()); + List reviewItems = buildPersonalReviewItems( + reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate(), profile.userId(), false); + List planItems = buildPersonalPlanItems( + nextRange.startDate(), nextRange.endDate(), profile.userId(), false); + respVO.setReviewItems(reviewItems); + respVO.setPlanItems(planItems); + respVO.setTotalWorkHours(sumReviewWorkHours(reviewItems)); + return respVO; + } + + public ProjectReportRespVO previewProjectDefaultDraft(Long projectId, ProjectReportDefaultDraftReqVO reqVO) { + validatePeriod(reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + validateProjectFlag(reqVO.getFlag()); + CurrentUserProfile profile = loadCurrentUserProfile(true); + ProjectDO project = validateProjectExists(projectId); + ObjectStatusModelDO initialStatus = getInitialStatusModel(); + + ProjectReportRespVO respVO = new ProjectReportRespVO(); + respVO.setProjectId(project.getId()); + respVO.setProjectName(project.getProjectName()); + respVO.setProjectOwnerId(profile.userId()); + respVO.setProjectOwnerName(profile.userName()); + respVO.setTechnicalOwnerName(resolveProjectManagerName(project.getManagerUserId())); + respVO.setProjectMemberSnapshot(BeanUtils.toBean(buildProjectMemberSnapshot(projectId), + WorkReportMemberSnapshotRespVO.class)); + respVO.setSupervisorUserId(profile.directManagerId()); + respVO.setSupervisorName(profile.directManagerName()); + respVO.setPeriodKey(reqVO.getPeriodKey()); + respVO.setPeriodLabel(reqVO.getPeriodLabel()); + respVO.setPeriodStartDate(reqVO.getPeriodStartDate()); + respVO.setPeriodEndDate(reqVO.getPeriodEndDate()); + respVO.setFlag(reqVO.getFlag()); + applyStatusView(respVO, initialStatus); + + LocalDateRange nextRange = nextHalfMonthRange(reqVO.getPeriodEndDate()); + List currentItems = buildProjectCurrentItems( + projectId, reqVO.getPeriodStartDate(), reqVO.getPeriodEndDate()); + List nextItems = buildProjectNextItems( + projectId, nextRange.startDate(), nextRange.endDate()); + respVO.setCurrentItems(currentItems); + respVO.setNextItems(nextItems); + respVO.setTotalWorkHours(sumProjectWorkHours(currentItems)); + return respVO; + } + + private List buildPersonalReviewItems(LocalDate periodStartDate, + LocalDate periodEndDate, + Long userId, + boolean weeklyMode) { + List worklogs = taskWorklogMapper.selectListByUserIdAndPeriod(userId, periodStartDate, periodEndDate); + if (worklogs.isEmpty()) { + return Collections.emptyList(); + } + + Set taskIds = worklogs.stream() + .map(TaskWorklogDO::getTaskId) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (taskIds.isEmpty()) { + return Collections.emptyList(); + } + + Map taskMap = projectTaskMapper.selectBatchIds(taskIds).stream() + .collect(Collectors.toMap(ProjectTaskDO::getId, item -> item)); + Map itemMap = personalItemMapper.selectBatchIds(taskIds).stream() + .collect(Collectors.toMap(PersonalItemDO::getId, item -> item)); + + Set projectIds = taskMap.values().stream() + .map(ProjectTaskDO::getProjectId) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + Map projectMap = projectIds.isEmpty() ? Collections.emptyMap() + : projectMapper.selectBatchIds(projectIds).stream() + .collect(Collectors.toMap(ProjectDO::getId, item -> item)); + Map taskItemTypeLabelMap = loadTaskItemTypeLabelMap(); + + Map aggregateMap = new LinkedHashMap<>(); + for (TaskWorklogDO worklog : worklogs) { + if (worklog.getTaskId() == null) { + continue; + } + ProjectTaskDO task = taskMap.get(worklog.getTaskId()); + if (task != null) { + ProjectDO project = projectMap.get(task.getProjectId()); + String workItemTitle = safeText(project == null ? null : project.getProjectName()); + String groupKey = workItemTitle + "||task||" + task.getId(); + ReviewAggregate aggregate = aggregateMap.computeIfAbsent(groupKey, key -> new ReviewAggregate( + workItemTitle, + safeText(task.getTaskTitle()), + safeText(task.getPriority()), + resolveTaskItemTypeLabel(task.getType(), taskItemTypeLabelMap))); + aggregate.merge(worklog); + continue; + } + PersonalItemDO item = itemMap.get(worklog.getTaskId()); + if (item != null) { + String groupKey = WORK_ITEM_MY_ITEMS + "||item||" + item.getId(); + ReviewAggregate aggregate = aggregateMap.computeIfAbsent(groupKey, key -> new ReviewAggregate( + WORK_ITEM_MY_ITEMS, + safeText(item.getTaskTitle()), + null, + resolveTaskItemTypeLabel(item.getType(), taskItemTypeLabelMap))); + aggregate.merge(worklog); + } + } + + List result = new ArrayList<>(); + int itemNumber = 1; + for (ReviewAggregate aggregate : aggregateMap.values()) { + if (isZeroProgress(aggregate.latestProgressRate())) { + continue; + } + PersonalReportReviewItemRespVO item = new PersonalReportReviewItemRespVO(); + item.setItemNumber(itemNumber++); + item.setItemTitle(aggregate.workItemTitle()); + item.setWorkHours(nullToZero(aggregate.totalWorkHours())); + item.setContentText(weeklyMode ? aggregate.buildWeeklyReviewText() : aggregate.buildMonthlyReviewText()); + item.setReflectionText(null); + result.add(item); + } + return result; + } + + private List buildPersonalPlanItems(LocalDate nextStartDate, + LocalDate nextEndDate, + Long userId, + boolean weeklyMode) { + List nextPeriodWorklogs = taskWorklogMapper.selectListByUserIdAndPeriod(userId, nextStartDate, nextEndDate); + Map worklogAggregateMap = aggregateTaskWorklogs(nextPeriodWorklogs); + + Map taskMap = loadPlanTaskMap(userId, nextStartDate, nextEndDate, worklogAggregateMap.keySet()); + Map itemMap = loadPlanItemMap(userId, nextStartDate, nextEndDate, worklogAggregateMap.keySet()); + + Set projectIds = taskMap.values().stream() + .map(ProjectTaskDO::getProjectId) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + Map projectMap = projectIds.isEmpty() ? Collections.emptyMap() + : projectMapper.selectBatchIds(projectIds).stream() + .collect(Collectors.toMap(ProjectDO::getId, item -> item)); + Map taskItemTypeLabelMap = loadTaskItemTypeLabelMap(); + + Map aggregateMap = new LinkedHashMap<>(); + for (ProjectTaskDO task : taskMap.values()) { + TaskWorklogAggregate worklogAggregate = worklogAggregateMap.get(task.getId()); + if (!shouldIncludeTaskInPlan(task, nextStartDate, nextEndDate, worklogAggregate)) { + continue; + } + ProjectDO project = projectMap.get(task.getProjectId()); + String workItemTitle = safeText(project == null ? null : project.getProjectName()); + String categoryCode = safeText(task.getType()); + String categoryLabel = resolveTaskItemTypeLabel(task.getType(), taskItemTypeLabelMap); + String groupKey = workItemTitle + "||" + categoryCode; + PlanAggregate aggregate = aggregateMap.computeIfAbsent(groupKey, + key -> new PlanAggregate(workItemTitle, categoryLabel)); + aggregate.addTaskLine(safeText(task.getTaskTitle()), safeText(task.getPriority()), task.getProgressRate(), + worklogAggregate, weeklyMode); + } + for (PersonalItemDO item : itemMap.values()) { + TaskWorklogAggregate worklogAggregate = worklogAggregateMap.get(item.getId()); + if (!shouldIncludePersonalItemInPlan(item, nextStartDate, nextEndDate, worklogAggregate)) { + continue; + } + String categoryCode = safeText(item.getType()); + String categoryLabel = resolveTaskItemTypeLabel(item.getType(), taskItemTypeLabelMap); + String groupKey = WORK_ITEM_MY_ITEMS + "||" + categoryCode; + PlanAggregate aggregate = aggregateMap.computeIfAbsent(groupKey, + key -> new PlanAggregate(WORK_ITEM_MY_ITEMS, categoryLabel)); + aggregate.addItemLine(safeText(item.getTaskTitle()), item.getProgressRate(), worklogAggregate, weeklyMode); + } + + List result = new ArrayList<>(); + int itemNumber = 1; + for (PlanAggregate aggregate : aggregateMap.values()) { + if (aggregate.lines().isEmpty()) { + continue; + } + PersonalReportPlanItemRespVO item = new PersonalReportPlanItemRespVO(); + item.setItemNumber(itemNumber++); + item.setItemTitle(aggregate.workItemTitle()); + item.setTargetText(aggregate.buildTargetText()); + item.setSupportNeed(null); + result.add(item); + } + return result; + } + + private List buildProjectCurrentItems(Long projectId, + LocalDate periodStartDate, + LocalDate periodEndDate) { + List tasks = projectTaskMapper.selectListByProjectId(projectId); + if (tasks.isEmpty()) { + return Collections.emptyList(); + } + + Set taskIds = tasks.stream() + .map(ProjectTaskDO::getId) + .collect(Collectors.toCollection(LinkedHashSet::new)); + Map taskMap = tasks.stream() + .collect(Collectors.toMap(ProjectTaskDO::getId, item -> item)); + List worklogs = taskWorklogMapper.selectListByTaskIdsAndPeriod(taskIds, periodStartDate, periodEndDate); + if (worklogs.isEmpty()) { + return Collections.emptyList(); + } + + Map executionMap = projectExecutionMapper.selectListByProjectId(projectId).stream() + .collect(Collectors.toMap(ProjectExecutionDO::getId, item -> item)); + Map hoursByExecutionId = new LinkedHashMap<>(); + for (TaskWorklogDO worklog : worklogs) { + ProjectTaskDO task = taskMap.get(worklog.getTaskId()); + if (task == null || task.getExecutionId() == null) { + continue; + } + hoursByExecutionId.merge(task.getExecutionId(), nullToZero(worklog.getDurationHours()), BigDecimal::add); + } + if (hoursByExecutionId.isEmpty()) { + return Collections.emptyList(); + } + + List progressExcludedStatusCodes = loadProgressExcludedTaskStatusCodes(); + List result = new ArrayList<>(); + for (Map.Entry entry : hoursByExecutionId.entrySet()) { + ProjectExecutionDO execution = executionMap.get(entry.getKey()); + if (execution == null) { + continue; + } + ProjectReportItemRespVO item = new ProjectReportItemRespVO(); + item.setItemTitle(safeText(execution.getExecutionName())); + item.setWorkHours(entry.getValue()); + item.setPriorityCode(safeText(execution.getPriority())); + item.setProgressRate(normalizeProgress(projectTaskMapper.selectRootTaskAvgProgressByExecutionId( + projectId, execution.getId(), progressExcludedStatusCodes))); + result.add(item); + } + result.sort(Comparator.comparing(ProjectReportItemRespVO::getItemTitle, Comparator.nullsLast(String::compareTo))); + return result; + } + + private List buildProjectNextItems(Long projectId, + LocalDate nextStartDate, + LocalDate nextEndDate) { + List plannedExecutions = projectExecutionMapper.selectPlannedListByProjectIdAndOverlap( + projectId, nextStartDate, nextEndDate, ProjectObjectConstants.STATUS_CANCELLED); + List zeroProgressExecutions = projectExecutionMapper.selectListByProjectIdAndStatusNot( + projectId, ProjectObjectConstants.STATUS_CANCELLED).stream() + .filter(execution -> isZeroProgress(execution.getProgressRate())) + .collect(Collectors.toList()); + Map executionMap = new LinkedHashMap<>(); + plannedExecutions.forEach(item -> executionMap.put(item.getId(), item)); + zeroProgressExecutions.forEach(item -> executionMap.put(item.getId(), item)); + if (executionMap.isEmpty()) { + return Collections.emptyList(); + } + + List progressExcludedStatusCodes = loadProgressExcludedTaskStatusCodes(); + List result = new ArrayList<>(); + for (ProjectExecutionDO execution : executionMap.values()) { + BigDecimal progress = normalizeProgress(projectTaskMapper.selectRootTaskAvgProgressByExecutionId( + projectId, execution.getId(), progressExcludedStatusCodes)); + if (isCompleted(progress)) { + continue; + } + ProjectReportItemRespVO item = new ProjectReportItemRespVO(); + item.setItemTitle(safeText(execution.getExecutionName())); + item.setWorkHours(BigDecimal.ZERO); + item.setPriorityCode(safeText(execution.getPriority())); + item.setProgressRate(progress); + result.add(item); + } + result.sort(Comparator.comparing(ProjectReportItemRespVO::getItemTitle, Comparator.nullsLast(String::compareTo))); + return result; + } + + private void validateProjectFlag(Integer flag) { + if (!Objects.equals(flag, 1) && !Objects.equals(flag, 2)) { + throw invalidParamException("上半月/下半月标记只能为 1 或 2"); + } + } + + private void validatePeriod(LocalDate startDate, LocalDate endDate) { + if (startDate == null || endDate == null) { + throw invalidParamException("周期开始日期和结束日期不能为空"); + } + if (endDate.isBefore(startDate)) { + throw invalidParamException("周期结束日期不能早于开始日期"); + } + } + + private ProjectDO validateProjectExists(Long projectId) { + ProjectDO project = projectMapper.selectById(projectId); + if (project == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_PROJECT_NOT_EXISTS); + } + return project; + } + + private ObjectStatusModelDO getInitialStatusModel() { + ObjectStatusModelDO statusModel = objectStatusModelMapper + .selectInitialByObjectTypeEnabled(WorkReportConstants.STATUS_OBJECT_TYPE); + if (statusModel == null || !StringUtils.hasText(statusModel.getStatusCode())) { + throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED); + } + return statusModel; + } + + private void applyStatusView(WeeklyReportRespVO respVO, ObjectStatusModelDO statusModel) { + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private void applyStatusView(MonthlyReportRespVO respVO, ObjectStatusModelDO statusModel) { + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private void applyStatusView(ProjectReportRespVO respVO, ObjectStatusModelDO statusModel) { + respVO.setStatusCode(statusModel.getStatusCode()); + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private void fillPersonalBase(WeeklyReportRespVO respVO, CurrentUserProfile profile) { + respVO.setReporterId(profile.userId()); + respVO.setReporterName(profile.userName()); + respVO.setReporterDeptName(profile.deptName()); + respVO.setReporterPostName(profile.postName()); + respVO.setSupervisorUserId(profile.directManagerId()); + respVO.setSupervisorName(profile.directManagerName()); + } + + private void fillPersonalBase(MonthlyReportRespVO respVO, CurrentUserProfile profile) { + respVO.setReporterId(profile.userId()); + respVO.setReporterName(profile.userName()); + respVO.setReporterDeptName(profile.deptName()); + respVO.setReporterPostName(profile.postName()); + respVO.setSupervisorUserId(profile.directManagerId()); + respVO.setSupervisorName(profile.directManagerName()); + } + + private CurrentUserProfile loadCurrentUserProfile(boolean requireManager) { + Long userId = SecurityFrameworkUtils.getLoginUserId(); + AdminUserRespDTO user = loadUser(userId); + String userName = StringUtils.hasText(user.getNickname()) + ? user.getNickname().trim() + : safeText(SecurityFrameworkUtils.getLoginUserNickname()); + + String deptName = null; + if (user.getDeptId() != null) { + CommonResult deptResult = deptApi.getDept(user.getDeptId()); + DeptRespDTO dept = deptResult == null ? null : deptResult.getCheckedData(); + deptName = dept == null ? null : dept.getName(); + } + + String postName = null; + if (user.getPositionId() != null) { + Map postMap = postApi.getPostMap(Collections.singleton(user.getPositionId())); + PostRespDTO post = postMap.get(user.getPositionId()); + postName = post == null ? null : post.getName(); + } + + Long directManagerId = null; + String directManagerName = null; + CommonResult directManagerResult = userManagementRelationApi.getDirectManager(userId); + AdminUserRespDTO directManager = directManagerResult == null ? null : directManagerResult.getCheckedData(); + if (directManager != null) { + directManagerId = directManager.getId(); + directManagerName = safeText(directManager.getNickname()); + } + if (requireManager && directManagerId == null) { + throw exception(ErrorCodeConstants.WORK_REPORT_DIRECT_MANAGER_NOT_EXISTS); + } + return new CurrentUserProfile(userId, safeText(userName), deptName, postName, directManagerId, directManagerName); + } + + private AdminUserRespDTO loadUser(Long userId) { + if (userId == null) { + throw invalidParamException("用户编号不能为空"); + } + adminUserApi.validateUserList(Collections.singleton(userId)).getCheckedData(); + CommonResult result = adminUserApi.getUser(userId); + AdminUserRespDTO user = result == null ? null : result.getCheckedData(); + if (user == null) { + throw invalidParamException("用户不存在:{}", userId); + } + return user; + } + + private String resolveProjectManagerName(Long managerUserId) { + if (managerUserId == null) { + return null; + } + AdminUserRespDTO manager = loadUser(managerUserId); + return safeText(manager.getNickname()); + } + + private List buildProjectMemberSnapshot(Long projectId) { + List members = userObjectRoleMapper.selectListByObject(ProjectObjectConstants.OBJECT_TYPE, projectId); + Set userIds = new LinkedHashSet<>(); + for (UserObjectRoleDO member : members) { + if (member == null || !Objects.equals(member.getStatus(), 0) || member.getUserId() == null) { + continue; + } + userIds.add(member.getUserId()); + } + if (userIds.isEmpty()) { + return Collections.emptyList(); + } + Map userMap = adminUserApi.getUserMap(userIds); + List result = new ArrayList<>(userIds.size()); + for (Long userId : userIds) { + WorkReportMemberSnapshotItem item = new WorkReportMemberSnapshotItem(); + item.setUserId(userId); + AdminUserRespDTO user = userMap.get(userId); + item.setUserName(user == null ? "" : safeText(user.getNickname())); + result.add(item); + } + return result; + } + + private Map loadTaskItemTypeLabelMap() { + CommonResult> result = dictDataApi.getDictDataList(DictTypeConstants.RDMS_TASK_ITEM_TYPE); + List dictDataList = result == null ? null : result.getCheckedData(); + if (dictDataList == null || dictDataList.isEmpty()) { + return Collections.emptyMap(); + } + Map labelMap = new LinkedHashMap<>(); + for (DictDataRespDTO item : dictDataList) { + if (item == null || !StringUtils.hasText(item.getValue())) { + continue; + } + labelMap.put(item.getValue().trim(), safeText(item.getLabel())); + } + return labelMap; + } + + private String resolveTaskItemTypeLabel(String typeCode, Map labelMap) { + String normalizedTypeCode = safeText(typeCode); + if (!StringUtils.hasText(normalizedTypeCode)) { + return normalizedTypeCode; + } + String label = labelMap.get(normalizedTypeCode); + return StringUtils.hasText(label) ? label : normalizedTypeCode; + } + + private List loadProgressExcludedTaskStatusCodes() { + return objectStatusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled(ProjectTaskConstants.OBJECT_TYPE); + } + + private BigDecimal sumReviewWorkHours(List reviewItems) { + if (reviewItems == null || reviewItems.isEmpty()) { + return BigDecimal.ZERO; + } + return reviewItems.stream() + .map(PersonalReportReviewItemRespVO::getWorkHours) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private BigDecimal sumProjectWorkHours(List items) { + if (items == null || items.isEmpty()) { + return BigDecimal.ZERO; + } + return items.stream() + .map(ProjectReportItemRespVO::getWorkHours) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private boolean isZeroProgress(BigDecimal value) { + return value == null || value.compareTo(BigDecimal.ZERO) == 0; + } + + private boolean isCompleted(BigDecimal value) { + return value != null && value.compareTo(BigDecimal.valueOf(100)) >= 0; + } + + private BigDecimal normalizeProgress(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value.stripTrailingZeros(); + } + + private BigDecimal nullToZero(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value; + } + + private Map loadPlanTaskMap(Long userId, LocalDate nextStartDate, LocalDate nextEndDate, + Set worklogTaskIds) { + Map taskMap = new LinkedHashMap<>(); + projectTaskMapper.selectPlannedInvolvedListByUserIdAndOverlap( + userId, nextStartDate, nextEndDate, ProjectObjectConstants.STATUS_CANCELLED) + .forEach(task -> taskMap.put(task.getId(), task)); + projectTaskMapper.selectInvolvedListByUserIdAndStatusNot(userId, ProjectObjectConstants.STATUS_CANCELLED).stream() + .filter(task -> worklogTaskIds.contains(task.getId()) || isZeroProgress(task.getProgressRate())) + .forEach(task -> taskMap.put(task.getId(), task)); + return taskMap; + } + + private Map loadPlanItemMap(Long userId, LocalDate nextStartDate, LocalDate nextEndDate, + Set worklogTaskIds) { + Map itemMap = new LinkedHashMap<>(); + personalItemMapper.selectPlannedListByOwnerIdAndOverlap( + userId, nextStartDate, nextEndDate, ProjectObjectConstants.STATUS_CANCELLED) + .forEach(item -> itemMap.put(item.getId(), item)); + personalItemMapper.selectListByOwnerIdAndStatusNot(userId, ProjectObjectConstants.STATUS_CANCELLED).stream() + .filter(item -> worklogTaskIds.contains(item.getId()) || isZeroProgress(item.getProgressRate())) + .forEach(item -> itemMap.put(item.getId(), item)); + return itemMap; + } + + private Map aggregateTaskWorklogs(List worklogs) { + if (worklogs == null || worklogs.isEmpty()) { + return Collections.emptyMap(); + } + Map aggregateMap = new LinkedHashMap<>(); + for (TaskWorklogDO worklog : worklogs) { + if (worklog.getTaskId() == null) { + continue; + } + aggregateMap.computeIfAbsent(worklog.getTaskId(), key -> new TaskWorklogAggregate()) + .merge(worklog); + } + return aggregateMap; + } + + private boolean shouldIncludeTaskInPlan(ProjectTaskDO task, LocalDate nextStartDate, LocalDate nextEndDate, + TaskWorklogAggregate worklogAggregate) { + if (task == null || isCompleted(task.getProgressRate())) { + return false; + } + if (worklogAggregate != null) { + return true; + } + if (isZeroProgress(task.getProgressRate())) { + return true; + } + return overlapsNextPeriod(task.getPlannedStartDate(), task.getPlannedEndDate(), nextStartDate, nextEndDate); + } + + private boolean shouldIncludePersonalItemInPlan(PersonalItemDO item, LocalDate nextStartDate, LocalDate nextEndDate, + TaskWorklogAggregate worklogAggregate) { + if (item == null || isCompleted(item.getProgressRate())) { + return false; + } + if (worklogAggregate != null) { + return true; + } + if (isZeroProgress(item.getProgressRate())) { + return true; + } + return overlapsNextPeriod(item.getPlannedStartDate(), item.getPlannedEndDate(), nextStartDate, nextEndDate); + } + + private boolean overlapsNextPeriod(LocalDate plannedStartDate, LocalDate plannedEndDate, + LocalDate nextStartDate, LocalDate nextEndDate) { + return plannedStartDate != null + && plannedEndDate != null + && !plannedStartDate.isAfter(nextEndDate) + && !plannedEndDate.isBefore(nextStartDate); + } + + private String safeText(String value) { + return value == null ? "" : value.trim(); + } + + private LocalDateRange nextWeeklyRange(LocalDate currentStartDate, LocalDate currentEndDate) { + return new LocalDateRange(currentStartDate.plusWeeks(1), currentEndDate.plusWeeks(1)); + } + + private LocalDateRange nextMonthlyRange(LocalDate currentEndDate) { + LocalDate nextMonthStart = currentEndDate.withDayOfMonth(1).plusMonths(1); + return new LocalDateRange(nextMonthStart, nextMonthStart.withDayOfMonth(nextMonthStart.lengthOfMonth())); + } + + private LocalDateRange nextHalfMonthRange(LocalDate currentEndDate) { + LocalDate nextDay = currentEndDate.plusDays(1); + if (nextDay.getDayOfMonth() <= 15) { + return new LocalDateRange(nextDay.withDayOfMonth(1), nextDay.withDayOfMonth(15)); + } + return new LocalDateRange(nextDay.withDayOfMonth(16), nextDay.withDayOfMonth(nextDay.lengthOfMonth())); + } + + private record CurrentUserProfile(Long userId, String userName, String deptName, String postName, + Long directManagerId, String directManagerName) { + } + + private record LocalDateRange(LocalDate startDate, LocalDate endDate) { + } + + private static final class ReviewAggregate { + + private final String workItemTitle; + private final String lineTitle; + private final String priority; + private final String typeCode; + private BigDecimal totalWorkHours = BigDecimal.ZERO; + private BigDecimal latestProgressRate = BigDecimal.ZERO; + private LocalDate latestEndDate; + private final List workContents = new ArrayList<>(); + + private ReviewAggregate(String workItemTitle, String lineTitle, String priority, String typeCode) { + this.workItemTitle = workItemTitle; + this.lineTitle = lineTitle; + this.priority = priority; + this.typeCode = typeCode; + } + + private void merge(TaskWorklogDO worklog) { + totalWorkHours = totalWorkHours.add( + worklog.getDurationHours() == null ? BigDecimal.ZERO : worklog.getDurationHours()); + LocalDate endDate = worklog.getEndDate(); + if (endDate != null && (latestEndDate == null || !endDate.isBefore(latestEndDate))) { + latestEndDate = endDate; + latestProgressRate = worklog.getProgressRate() == null ? BigDecimal.ZERO : worklog.getProgressRate(); + } + if (StringUtils.hasText(worklog.getWorkContent())) { + workContents.add(worklog.getWorkContent().trim()); + } + } + + private String buildWeeklyReviewText() { + StringBuilder builder = new StringBuilder(); + if (StringUtils.hasText(typeCode)) { + builder.append(typeCode).append(" - "); + } + builder.append(lineTitle); + appendBracket(builder, priority, latestProgressRate, totalWorkHours); + if (!workContents.isEmpty()) { + builder.append(":").append(String.join(";", workContents)); + } + return builder.toString(); + } + + private String buildMonthlyReviewText() { + StringBuilder builder = new StringBuilder(); + if (StringUtils.hasText(typeCode)) { + builder.append(typeCode).append(" - "); + } + builder.append(lineTitle); + appendBracket(builder, priority, latestProgressRate, totalWorkHours); + return builder.toString(); + } + + private void appendBracket(StringBuilder builder, String priority, + BigDecimal progressRate, BigDecimal hours) { + List parts = new ArrayList<>(); + if (StringUtils.hasText(priority)) { + parts.add(priority); + } + if (progressRate != null) { + parts.add("进度" + progressRate.stripTrailingZeros().toPlainString() + "%"); + } + if (hours != null) { + parts.add(hours.stripTrailingZeros().toPlainString() + "h"); + } + if (!parts.isEmpty()) { + builder.append("(").append(String.join(" / ", parts)).append(")"); + } + } + + private String workItemTitle() { + return workItemTitle; + } + + private BigDecimal totalWorkHours() { + return totalWorkHours; + } + + private BigDecimal latestProgressRate() { + return latestProgressRate; + } + } + + private static final class PlanAggregate { + + private final String workItemTitle; + private final String category; + private final List lines = new ArrayList<>(); + + private PlanAggregate(String workItemTitle, String category) { + this.workItemTitle = workItemTitle; + this.category = category; + } + + private void addTaskLine(String title, String priority, BigDecimal progressRate, + TaskWorklogAggregate worklogAggregate, boolean weeklyMode) { + if (!StringUtils.hasText(title)) { + return; + } + if (worklogAggregate != null && worklogAggregate.hasWorkContent()) { + lines.add(buildWorklogStyleLine(title, priority, progressRate, worklogAggregate, weeklyMode)); + return; + } + lines.add(buildPlannedStyleLine(title, priority, progressRate)); + } + + private void addItemLine(String title, BigDecimal progressRate, + TaskWorklogAggregate worklogAggregate, boolean weeklyMode) { + if (!StringUtils.hasText(title)) { + return; + } + if (worklogAggregate != null && worklogAggregate.hasWorkContent()) { + lines.add(buildWorklogStyleLine(title, null, progressRate, worklogAggregate, weeklyMode)); + return; + } + lines.add(buildPlannedStyleLine(title, null, progressRate)); + } + + private String buildWorklogStyleLine(String title, String priority, BigDecimal progressRate, + TaskWorklogAggregate worklogAggregate, boolean weeklyMode) { + StringBuilder builder = new StringBuilder(); + if (StringUtils.hasText(category)) { + builder.append(category).append(" - "); + } + builder.append(title); + List parts = new ArrayList<>(); + if (StringUtils.hasText(priority)) { + parts.add(priority); + } + if (progressRate != null) { + parts.add("进度" + progressRate.stripTrailingZeros().toPlainString() + "%"); + } + if (worklogAggregate.totalWorkHours() != null) { + parts.add(worklogAggregate.totalWorkHours().stripTrailingZeros().toPlainString() + "h"); + } + if (!parts.isEmpty()) { + builder.append("(").append(String.join(" / ", parts)).append(")"); + } + if (weeklyMode && worklogAggregate.hasWorkContent()) { + builder.append(":").append(String.join(";", worklogAggregate.workContents())); + } + return builder.toString(); + } + + private String buildPlannedStyleLine(String title, String priority, BigDecimal progressRate) { + StringBuilder builder = new StringBuilder(); + if (StringUtils.hasText(category)) { + builder.append(category).append(" - "); + } + builder.append(title); + List parts = new ArrayList<>(); + if (StringUtils.hasText(priority)) { + parts.add(priority); + } + if (progressRate != null) { + parts.add("进度" + progressRate.stripTrailingZeros().toPlainString() + "%"); + } + if (!parts.isEmpty()) { + builder.append("(").append(String.join(" / ", parts)).append(")"); + } + return builder.toString(); + } + + private String buildTargetText() { + return String.join("\n", lines); + } + + private String workItemTitle() { + return workItemTitle; + } + + private List lines() { + return lines; + } + } + + private static final class TaskWorklogAggregate { + + private BigDecimal totalWorkHours = BigDecimal.ZERO; + private final List workContents = new ArrayList<>(); + + private void merge(TaskWorklogDO worklog) { + totalWorkHours = totalWorkHours.add( + worklog.getDurationHours() == null ? BigDecimal.ZERO : worklog.getDurationHours()); + if (StringUtils.hasText(worklog.getWorkContent())) { + workContents.add(worklog.getWorkContent().trim()); + } + } + + private boolean hasWorkContent() { + return !workContents.isEmpty(); + } + + private BigDecimal totalWorkHours() { + return totalWorkHours; + } + + private List workContents() { + return workContents; + } + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/export/WorkReportContentExportService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/export/WorkReportContentExportService.java new file mode 100644 index 0000000..cd44aac --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/export/WorkReportContentExportService.java @@ -0,0 +1,1583 @@ +package com.njcn.rdms.module.project.service.workreport.export; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.njcn.rdms.framework.common.biz.system.dict.dto.DictDataRespDTO; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageParam; +import com.njcn.rdms.framework.common.util.json.JsonUtils; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportPlanItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.PersonalReportReviewItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportMemberSnapshotRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApprovalRecordRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportContentExportReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportContentExportReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportItemRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportContentExportReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.weekly.vo.WeeklyReportTravelSegmentRespVO; +import com.njcn.rdms.module.project.enums.ProjectDictTypeConstants; +import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService; +import com.njcn.rdms.module.system.api.dict.DictDataApi; +import jakarta.annotation.Resource; +import org.apache.poi.xwpf.usermodel.*; +import org.apache.xmlbeans.XmlException; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +@Service +public class WorkReportContentExportService { + + private static final String DOCX_CONTENT_TYPE = + "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + private static final String ZIP_CONTENT_TYPE = "application/zip"; + private static final String WEEKLY_TEMPLATE_PATH = "templates/work-report/weekly-report-template.docx"; + private static final String MONTHLY_TEMPLATE_PATH = "templates/work-report/monthly-report-template.docx"; + private static final String PROJECT_TEMPLATE_PATH = "templates/work-report/project-report-template.docx"; + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"); + + @Resource + private WorkReportCommonService workReportCommonService; + @Resource + private DictDataApi dictDataApi; + + public WorkReportExportFile exportWeekly(WeeklyReportContentExportReqVO reqVO) throws IOException { + List reports = resolveWeeklyReports(reqVO); + return buildFile("个人周报", reports.stream().map(this::buildWeeklyDocument).toList()); + } + + public WorkReportExportFile exportMonthly(MonthlyReportContentExportReqVO reqVO) throws IOException { + List reports = resolveMonthlyReports(reqVO); + List documents = new ArrayList<>(reports.size()); + for (MonthlyReportRespVO report : reports) { + MonthlyReportApprovalRecordRespVO approvalRecord = latestMonthlyApprovalRecord(report.getId()); + documents.add(buildMonthlyDocument(report, approvalRecord)); + } + return buildFile("个人月报", documents); + } + + public WorkReportExportFile exportProject(ProjectReportContentExportReqVO reqVO) throws IOException { + List reports = resolveProjectReports(reqVO); + return buildFile("项目半月报", reports.stream().map(this::buildProjectDocument).toList()); + } + + private List resolveWeeklyReports(WeeklyReportContentExportReqVO reqVO) { + if (Boolean.TRUE.equals(reqVO.getExportAll())) { + reqVO.setPageNo(1); + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + return workReportCommonService.getWeeklyReportPage(reqVO).getList().stream() + .map(item -> workReportCommonService.getWeeklyReport(item.getId())) + .toList(); + } + return distinctIds(reqVO.getIds()).stream() + .map(workReportCommonService::getWeeklyReport) + .toList(); + } + + private List resolveMonthlyReports(MonthlyReportContentExportReqVO reqVO) { + if (Boolean.TRUE.equals(reqVO.getExportAll())) { + reqVO.setPageNo(1); + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + return workReportCommonService.getMonthlyReportPage(reqVO).getList().stream() + .map(item -> workReportCommonService.getMonthlyReport(item.getId())) + .toList(); + } + return distinctIds(reqVO.getIds()).stream() + .map(workReportCommonService::getMonthlyReport) + .toList(); + } + + private List resolveProjectReports(ProjectReportContentExportReqVO reqVO) { + if (Boolean.TRUE.equals(reqVO.getExportAll())) { + reqVO.setPageNo(1); + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + return workReportCommonService.getProjectReportPage(reqVO).getList().stream() + .map(item -> workReportCommonService.getProjectReport(item.getId())) + .toList(); + } + return distinctIds(reqVO.getIds()).stream() + .map(workReportCommonService::getProjectReport) + .toList(); + } + + private List distinctIds(List ids) { + if (ids == null || ids.isEmpty()) { + throw invalidParamException("请选择要导出的报告"); + } + Set result = new LinkedHashSet<>(); + for (Long id : ids) { + if (id != null) { + result.add(id); + } + } + if (result.isEmpty()) { + throw invalidParamException("请选择要导出的报告"); + } + return new ArrayList<>(result); + } + + private MonthlyReportApprovalRecordRespVO latestMonthlyApprovalRecord(Long reportId) { + List records = workReportCommonService.getMonthlyApprovalRecords(reportId); + if (records == null || records.isEmpty()) { + return null; + } + return records.stream() + .max(Comparator.comparing(MonthlyReportApprovalRecordRespVO::getApprovalRound, + Comparator.nullsFirst(Integer::compareTo))) + .orElse(null); + } + + private WorkReportExportFile buildFile(String title, List documents) throws IOException { + if (documents.isEmpty()) { + throw invalidParamException("没有可导出的报告"); + } + if (documents.size() == 1) { + DocumentContent document = documents.get(0); + return new WorkReportExportFile(document.filename(), DOCX_CONTENT_TYPE, document.content()); + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { + for (int i = 0; i < documents.size(); i++) { + DocumentContent document = documents.get(i); + zipOutputStream.putNextEntry(new ZipEntry((i + 1) + "_" + document.filename())); + zipOutputStream.write(document.content()); + zipOutputStream.closeEntry(); + } + } + return new WorkReportExportFile(title + "_" + LocalDate.now() + ".zip", ZIP_CONTENT_TYPE, + outputStream.toByteArray()); + } + + private DocumentContent buildWeeklyDocument(WeeklyReportRespVO report) { + ClassPathResource templateResource = new ClassPathResource(WEEKLY_TEMPLATE_PATH); + if (templateResource.exists()) { + return buildWeeklyTemplateDocument(report, templateResource); + } + return buildWeeklyFallbackDocument(report); + } + + private DocumentContent buildWeeklyTemplateDocument(WeeklyReportRespVO report, ClassPathResource templateResource) { + try (InputStream inputStream = templateResource.getInputStream(); + XWPFDocument document = new XWPFDocument(inputStream)) { + replacePlaceholders(document, buildWeeklyTemplateData(report)); + renderPersonalReviewTable(document, report.getReviewItems()); + renderPersonalPlanTable(document, report.getPlanItems()); + return new DocumentContent(buildFilename("个人周报", report.getReporterName(), report.getPeriodLabel()), + toBytes(document)); + } catch (IOException e) { + throw new IllegalStateException("生成周报模板导出文件失败", e); + } + } + + private DocumentContent buildWeeklyFallbackDocument(WeeklyReportRespVO report) { + try (XWPFDocument document = new XWPFDocument()) { + addTitle(document, "个人周报"); + addParagraph(document, "填报周期:" + formatPeriod(report), false); + addPersonalBaseTable(document, report.getReporterName(), report.getReporterDeptName(), + report.getReporterPostName(), report.getSupervisorName()); + + addSectionTitle(document, "第一部分:当期重点工作回顾"); + addPersonalReviewTable(document, report.getReviewItems(), false); + + if (Boolean.TRUE.equals(report.getIsBusinessTrip())) { + addSectionTitle(document, "出差信息"); + addKeyValueTable(document, List.of( + row("出差天数", formatDecimal(report.getTotalTravelDays())), + row("出差明细", buildTravelText(report.getTravelSegments())))); + } + + addSectionTitle(document, "下周期重点工作计划"); + addPersonalPlanTable(document, report.getPlanItems(), false); + return new DocumentContent(buildFilename("个人周报", report.getReporterName(), report.getPeriodLabel()), + toBytes(document)); + } catch (IOException e) { + throw new IllegalStateException("生成周报导出文件失败", e); + } + } + + private Map buildWeeklyTemplateData(WeeklyReportRespVO report) { + Map data = new HashMap<>(); + data.put("reporterName", text(report.getReporterName())); + data.put("reporterDeptName", text(report.getReporterDeptName())); + data.put("reporterPostName", text(report.getReporterPostName())); + data.put("periodText", formatPeriod(report)); + data.put("periodLabel", text(report.getPeriodLabel())); + data.put("supervisorName", text(report.getSupervisorName())); + data.put("businessTripText", Boolean.TRUE.equals(report.getIsBusinessTrip()) ? "是" : "否"); + data.put("totalTravelDaysText", formatDecimal(report.getTotalTravelDays())); + data.put("totalWorkHoursText", formatDecimal(report.getTotalWorkHours())); + data.put("submitTimeText", formatDateTime(report.getSubmitTime())); + data.put("approvalTimeText", formatDateTime(report.getApprovalTime())); + return data; + } + + private void replacePlaceholders(XWPFDocument document, Map data) { + for (XWPFParagraph paragraph : document.getParagraphs()) { + replaceParagraphPlaceholders(paragraph, data); + } + for (XWPFTable table : document.getTables()) { + for (XWPFTableRow row : table.getRows()) { + for (XWPFTableCell cell : row.getTableCells()) { + for (XWPFParagraph paragraph : cell.getParagraphs()) { + replaceParagraphPlaceholders(paragraph, data); + } + } + } + } + } + + private void replaceParagraphPlaceholders(XWPFParagraph paragraph, Map data) { + List runs = paragraph.getRuns(); + if (runs == null || runs.isEmpty()) { + return; + } + String sourceText = paragraph.getText(); + if (!StringUtils.hasText(sourceText) || !sourceText.contains("{{")) { + return; + } + String replacedText = sourceText; + for (Map.Entry entry : data.entrySet()) { + replacedText = replacedText.replace("{{" + entry.getKey() + "}}", entry.getValue()); + } + XWPFRun firstRun = runs.get(0); + applyRunText(firstRun, replacedText); + for (int i = runs.size() - 1; i >= 1; i--) { + paragraph.removeRun(i); + } + } + + private void renderPersonalReviewTable(XWPFDocument document, List items) { + TemplateRowLocation location = findTemplateRowByPlaceholders(document, List.of( + "{{itemNumber}}", "{{itemTitleWithHours}}", "{{contentText}}", "{{reflectionText}}")); + if (location == null) { + return; + } + List> rows = safeList(items).stream() + .map(item -> placeholderValues(Map.of( + "itemNumber", formatNumber(item.getItemNumber()), + "itemTitleWithHours", appendHours(item.getItemTitle(), item.getWorkHours()), + "contentText", buildStructuredReviewText(item, false), + "reflectionText", text(item.getReflectionText())))) + .toList(); + List renderedRows = renderRowsByTemplateRow(location, rows, List.of( + "itemNumber", "itemTitleWithHours", "contentText", "reflectionText")); + rewriteReviewRows(renderedRows, items, false); + } + + private void renderPersonalPlanTable(XWPFDocument document, List items) { + TemplateRowLocation location = findTemplateRowByPlaceholders(document, List.of( + "{{itemNumber}}", "{{itemTitle}}", "{{targetText}}", "{{supportNeed}}")); + if (location == null) { + return; + } + List> rows = safeList(items).stream() + .map(item -> placeholderValues(Map.of( + "itemNumber", formatNumber(item.getItemNumber()), + "itemTitle", text(item.getItemTitle()), + "targetText", buildStructuredPlanText(item, false), + "supportNeed", text(item.getSupportNeed())))) + .toList(); + List renderedRows = renderRowsByTemplateRow(location, rows, List.of( + "itemNumber", "itemTitle", "targetText", "supportNeed")); + rewritePlanRows(renderedRows, items, false); + } + + private TemplateRowLocation findTemplateRowByPlaceholders(XWPFDocument document, List placeholders) { + for (XWPFTable table : document.getTables()) { + for (int i = 0; i < table.getRows().size(); i++) { + XWPFTableRow row = table.getRow(i); + String rowXml = row.getCtRow().xmlText(); + if (placeholders.stream().allMatch(rowXml::contains)) { + return new TemplateRowLocation(table, i); + } + } + } + return null; + } + + private List renderRowsByTemplateRow(TemplateRowLocation location, + List> dataRows, List placeholderNames) { + XWPFTable table = location.table(); + int templateRowIndex = location.rowIndex(); + int dataStartIndex = templateRowIndex; + int dataEndIndex = findPlaceholderBlockEndRowIndex(table, templateRowIndex, placeholderNames); + String templateRowXml = table.getRow(templateRowIndex).getCtRow().xmlText(); + for (int i = dataEndIndex - 1; i >= dataStartIndex; i--) { + table.removeRow(i); + } + + List> rows = dataRows == null || dataRows.isEmpty() + ? List.of(emptyPlaceholderValues(placeholderNames)) + : dataRows; + int insertIndex = dataStartIndex; + List insertedRows = new ArrayList<>(rows.size()); + for (Map rowData : rows) { + XWPFTableRow row = new XWPFTableRow(parseRenderedRow(templateRowXml, rowData), table); + table.addRow(row, insertIndex++); + insertedRows.add(table.getRow(insertIndex - 1)); + } + return insertedRows; + } + + private Map placeholderValues(Map values) { + Map result = new HashMap<>(); + for (Map.Entry entry : values.entrySet()) { + result.put("{{" + entry.getKey() + "}}", text(entry.getValue())); + } + return result; + } + + private Map emptyPlaceholderValues(List placeholderNames) { + Map result = new HashMap<>(); + for (String placeholderName : placeholderNames) { + result.put("{{" + placeholderName + "}}", ""); + } + return result; + } + + private CTRow parseRenderedRow(String templateRowXml, Map values) { + String renderedXml = templateRowXml; + for (Map.Entry entry : values.entrySet()) { + renderedXml = renderedXml.replace(entry.getKey(), escapeXmlText(entry.getValue())); + } + try { + return CTRow.Factory.parse(renderedXml); + } catch (XmlException e) { + throw new IllegalStateException("渲染周报模板明细行失败", e); + } + } + + private String escapeXmlText(String value) { + return text(value) + .replace("&", "&") + .replace("<", "<") + .replace(">", ">"); + } + + private int findPlaceholderBlockEndRowIndex(XWPFTable table, int templateRowIndex, List placeholderNames) { + List placeholderTokens = placeholderNames.stream() + .map(name -> "{{" + name + "}}") + .toList(); + for (int i = templateRowIndex; i < table.getRows().size(); i++) { + String rowXml = table.getRow(i).getCtRow().xmlText(); + boolean containsPlaceholder = placeholderTokens.stream().anyMatch(rowXml::contains); + if (!containsPlaceholder) { + return i; + } + } + return table.getNumberOfRows(); + } + + private void applyRunText(XWPFRun run, String value) { + String[] lines = text(value).split("\\R", -1); + run.setText("", 0); + for (int i = 0; i < lines.length; i++) { + if (i == 0) { + run.setText(lines[i], 0); + } else { + run.addBreak(); + run.setText(lines[i]); + } + } + } + + private record TemplateRowLocation(XWPFTable table, int rowIndex) { + } + + private DocumentContent buildMonthlyDocument(MonthlyReportRespVO report, + MonthlyReportApprovalRecordRespVO approvalRecord) { + ClassPathResource templateResource = new ClassPathResource(MONTHLY_TEMPLATE_PATH); + if (templateResource.exists()) { + return buildMonthlyTemplateDocument(report, approvalRecord, templateResource); + } + return buildMonthlyFallbackDocument(report, approvalRecord); + } + + private DocumentContent buildMonthlyTemplateDocument(MonthlyReportRespVO report, + MonthlyReportApprovalRecordRespVO approvalRecord, + ClassPathResource templateResource) { + try (InputStream inputStream = templateResource.getInputStream(); + XWPFDocument document = new XWPFDocument(inputStream)) { + replacePlaceholders(document, buildMonthlyTemplateData(report, approvalRecord)); + renderMonthlyReviewTable(document, report.getReviewItems()); + renderMonthlyFeedbackTables(document, approvalRecord); + renderMonthlyPlanTable(document, report.getPlanItems()); + return new DocumentContent(buildFilename("个人月报", report.getReporterName(), report.getPeriodLabel()), + toBytes(document)); + } catch (IOException e) { + throw new IllegalStateException("生成月报模板导出文件失败", e); + } + } + + private DocumentContent buildMonthlyFallbackDocument(MonthlyReportRespVO report, + MonthlyReportApprovalRecordRespVO approvalRecord) { + try (XWPFDocument document = new XWPFDocument()) { + addTitle(document, "灿能电力绩效反馈面谈表"); + addPersonalBaseTable(document, report.getReporterName(), report.getReporterDeptName(), + report.getReporterPostName(), report.getSupervisorName()); + addKeyValueTable(document, List.of( + row("考核周期", formatPeriod(report)), + row("面谈时间", formatDate(approvalRecord == null ? null : approvalRecord.getMeetingDate())))); + + addSectionTitle(document, "第一部分:当期重点工作回顾(个人填写)"); + addPersonalReviewTable(document, report.getReviewItems(), false); + + addSectionTitle(document, "第二部分:当期工作反馈(直接上级/分管领导填写)"); + addFeedbackTable(document, approvalRecord); + + addSectionTitle(document, "第三部分:当期绩效考核结果"); + addKeyValueTable(document, List.of(row("绩效考核结果", + approvalRecord == null ? null : approvalRecord.getPerformanceResult()))); + + addSectionTitle(document, "第四部分:下周期重点工作计划"); + addPersonalPlanTable(document, report.getPlanItems(), false); + + addParagraph(document, "被考核人签名:" + text(approvalRecord == null ? null : approvalRecord.getEmployeeSignName()) + + " 日期:" + formatDate(approvalRecord == null ? null : approvalRecord.getEmployeeSignedDate()), false); + addParagraph(document, "上级签名:" + text(approvalRecord == null ? null : approvalRecord.getSupervisorSignName()) + + " 日期:" + formatDate(approvalRecord == null ? null : approvalRecord.getSupervisorSignedDate()), false); + return new DocumentContent(buildFilename("个人月报", report.getReporterName(), report.getPeriodLabel()), + toBytes(document)); + } catch (IOException e) { + throw new IllegalStateException("生成月报导出文件失败", e); + } + } + + private DocumentContent buildProjectDocument(ProjectReportRespVO report) { + ClassPathResource templateResource = new ClassPathResource(PROJECT_TEMPLATE_PATH); + if (templateResource.exists()) { + return buildProjectTemplateDocument(report, templateResource); + } + return buildProjectFallbackDocument(report); + } + + private DocumentContent buildProjectTemplateDocument(ProjectReportRespVO report, + ClassPathResource templateResource) { + try (InputStream inputStream = templateResource.getInputStream(); + XWPFDocument document = new XWPFDocument(inputStream)) { + replacePlaceholders(document, buildProjectTemplateData(report)); + return new DocumentContent(buildFilename("项目半月报", report.getProjectName(), report.getPeriodLabel()), + toBytes(document)); + } catch (IOException e) { + throw new IllegalStateException("生成项目半月报模板导出文件失败", e); + } + } + + private DocumentContent buildProjectFallbackDocument(ProjectReportRespVO report) { + try (XWPFDocument document = new XWPFDocument()) { + addTitle(document, "研发工作情况简报"); + addParagraph(document, "研发工作情况简报(" + formatPeriod(report) + "):", true); + addSectionTitle(document, "一、" + text(report.getProjectName())); + addKeyValueTable(document, List.of( + row("项目名称", report.getProjectName()), + row("项目负责人", report.getProjectOwnerName()), + row("技术负责人", report.getTechnicalOwnerName()), + row("项目组成员", buildMemberText(report.getProjectMemberSnapshot())), + row("项目状况", report.getProjectStatusDesc()), + row("整体计划进度", report.getProjectProgressPlan()))); + + addSectionTitle(document, "本期工作内容"); + addProjectItemTable(document, report.getCurrentItems(), true); + + addSectionTitle(document, "下期计划工作内容"); + addProjectItemTable(document, report.getNextItems(), false); + + addKeyValueTable(document, List.of( + row("要点描述", report.getProjectKeyPoints()), + row("项目问题", report.getProjectProblems()))); + return new DocumentContent(buildFilename("项目半月报", report.getProjectName(), report.getPeriodLabel()), + toBytes(document)); + } catch (IOException e) { + throw new IllegalStateException("生成项目半月报导出文件失败", e); + } + } + + private Map buildMonthlyTemplateData(MonthlyReportRespVO report, + MonthlyReportApprovalRecordRespVO approvalRecord) { + Map data = new HashMap<>(); + data.put("reporterName", text(report.getReporterName())); + data.put("reporterDeptName", text(report.getReporterDeptName())); + data.put("reporterPostName", text(report.getReporterPostName())); + data.put("periodText", formatPeriod(report)); + data.put("supervisorName", text(report.getSupervisorName())); + data.put("meetingDateText", formatDate(approvalRecord == null ? null : approvalRecord.getMeetingDate())); + data.put("strengthDesc", text(approvalRecord == null ? null : approvalRecord.getStrengthDesc())); + data.put("strengthExample", text(approvalRecord == null ? null : approvalRecord.getStrengthExample())); + data.put("weaknessDesc", text(approvalRecord == null ? null : approvalRecord.getWeaknessDesc())); + data.put("weaknessExample", text(approvalRecord == null ? null : approvalRecord.getWeaknessExample())); + data.put("improvementSuggestion", text(approvalRecord == null ? null : approvalRecord.getImprovementSuggestion())); + data.put("performanceResult", text(approvalRecord == null ? null : approvalRecord.getPerformanceResult())); + data.put("employeeSignName", text(approvalRecord == null ? null : approvalRecord.getEmployeeSignName())); + data.put("employeeSignedDateText", formatDate(approvalRecord == null ? null : approvalRecord.getEmployeeSignedDate())); + data.put("supervisorSignName", text(approvalRecord == null ? null : approvalRecord.getSupervisorSignName())); + data.put("supervisorSignedDateText", formatDate(approvalRecord == null ? null : approvalRecord.getSupervisorSignedDate())); + return data; + } + + private Map buildProjectTemplateData(ProjectReportRespVO report) { + Map data = new HashMap<>(); + data.put("briefDateText", report.getPeriodEndDate() == null ? "" : + report.getPeriodEndDate().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); + data.put("periodText", formatPeriod(report)); + data.put("projectName", text(report.getProjectName())); + data.put("projectOwnerName", text(report.getProjectOwnerName())); + data.put("technicalOwnerName", text(report.getTechnicalOwnerName())); + data.put("projectMembersText", buildMemberText(report.getProjectMemberSnapshot())); + data.put("projectStatusDesc", text(report.getProjectStatusDesc())); + data.put("projectProgressPlan", text(report.getProjectProgressPlan())); + data.put("currentItems", buildProjectItemsText(report.getCurrentItems(), true)); + data.put("nextItems", buildProjectItemsText(report.getNextItems(), false)); + data.put("projectKeyPoints", text(report.getProjectKeyPoints())); + data.put("projectProblems", text(report.getProjectProblems())); + return data; + } + + private void renderMonthlyReviewTable(XWPFDocument document, List items) { + TemplateRowLocation location = findTemplateRowByPlaceholders(document, List.of( + "{{itemNumber}}", "{{itemTitleWithHours}}", "{{contentText}}", "{{reflectionText}}")); + if (location == null) { + return; + } + List> rows = safeList(items).stream() + .map(item -> placeholderValues(Map.of( + "itemNumber", formatNumber(item.getItemNumber()), + "itemTitleWithHours", appendHours(item.getItemTitle(), item.getWorkHours()), + "contentText", buildStructuredReviewText(item, false), + "reflectionText", text(item.getReflectionText())))) + .toList(); + List renderedRows = renderRowsByTemplateRow(location, rows, List.of( + "itemNumber", "itemTitleWithHours", "contentText", "reflectionText")); + rewriteReviewRows(renderedRows, items, false); + } + + private void renderMonthlyPlanTable(XWPFDocument document, List items) { + TemplateRowLocation location = findTemplateRowByPlaceholders(document, List.of( + "{{itemNumber}}", "{{itemTitle}}", "{{targetText}}", "{{supportNeed}}")); + if (location == null) { + return; + } + List> rows = safeList(items).stream() + .map(item -> placeholderValues(Map.of( + "itemNumber", formatNumber(item.getItemNumber()), + "itemTitle", text(item.getItemTitle()), + "targetText", buildStructuredPlanText(item, false), + "supportNeed", text(item.getSupportNeed())))) + .toList(); + List renderedRows = renderRowsByTemplateRow(location, rows, List.of( + "itemNumber", "itemTitle", "targetText", "supportNeed")); + rewritePlanRows(renderedRows, items, false); + } + + private void renderMonthlyFeedbackTables(XWPFDocument document, MonthlyReportApprovalRecordRespVO approvalRecord) { + TemplateRowLocation strengthLocation = findTemplateRowByPlaceholders(document, List.of( + "优势", "{{strengthDesc}}", "{{strengthExample}}", "{{improvementSuggestion}}")); + if (strengthLocation != null) { + renderSingleTemplateRow(strengthLocation, placeholderValues(Map.of( + "strengthDesc", text(approvalRecord == null ? null : approvalRecord.getStrengthDesc()), + "strengthExample", text(approvalRecord == null ? null : approvalRecord.getStrengthExample()), + "improvementSuggestion", text(approvalRecord == null ? null : approvalRecord.getImprovementSuggestion()) + ))); + } + TemplateRowLocation weaknessLocation = findTemplateRowByPlaceholders(document, List.of( + "劣势", "{{weaknessDesc}}", "{{weaknessExample}}")); + if (weaknessLocation != null) { + renderSingleTemplateRow(weaknessLocation, placeholderValues(Map.of( + "weaknessDesc", text(approvalRecord == null ? null : approvalRecord.getWeaknessDesc()), + "weaknessExample", text(approvalRecord == null ? null : approvalRecord.getWeaknessExample()) + ))); + } + } + + private void renderSingleTemplateRow(TemplateRowLocation location, Map values) { + XWPFTableRow row = location.table().getRow(location.rowIndex()); + CTRow renderedRow = parseRenderedRow(row.getCtRow().xmlText(), values); + row.getCtRow().set(renderedRow); + } + + private void rewriteReviewRows(List rows, List items, + boolean includeDetail) { + List safeItems = safeList(items); + for (int i = 0; i < rows.size() && i < safeItems.size(); i++) { + PersonalReportReviewItemRespVO item = safeItems.get(i); + XWPFTableRow row = rows.get(i); + setStructuredReviewCell(row.getCell(2), item, includeDetail); + setCellText(row.getCell(3), text(item.getReflectionText()), false); + } + } + + private void rewritePlanRows(List rows, List items, + boolean includeDetail) { + List safeItems = safeList(items); + for (int i = 0; i < rows.size() && i < safeItems.size(); i++) { + PersonalReportPlanItemRespVO item = safeItems.get(i); + XWPFTableRow row = rows.get(i); + setStructuredPlanCell(row.getCell(2), item, includeDetail); + setCellText(row.getCell(3), text(item.getSupportNeed()), false); + } + } + + private String buildStructuredReviewText(PersonalReportReviewItemRespVO item, boolean includeDetail) { + return buildStructuredText(item == null ? null : item.getContentJson(), + item == null ? null : item.getContentText(), true, includeDetail); + } + + private String buildStructuredPlanText(PersonalReportPlanItemRespVO item, boolean includeDetail) { + return buildStructuredText(item == null ? null : item.getTargetJson(), + item == null ? null : item.getTargetText(), false, includeDetail); + } + + private void setStructuredReviewCell(XWPFTableCell cell, PersonalReportReviewItemRespVO item, + boolean includeDetail) { + setStructuredCellTextForTemplate(cell, + item == null ? null : item.getContentJson(), + item == null ? null : item.getContentText(), + true, + includeDetail); + } + + private void setStructuredPlanCell(XWPFTableCell cell, PersonalReportPlanItemRespVO item, + boolean includeDetail) { + setStructuredCellTextForTemplate(cell, + item == null ? null : item.getTargetJson(), + item == null ? null : item.getTargetText(), + false, + includeDetail); + } + + private void setStructuredCellTextForTemplate(XWPFTableCell cell, Object jsonValue, + String fallbackText, boolean includeHours, + boolean includeDetail) { + List lines = buildStructuredParagraphsForTemplate(jsonValue, fallbackText, includeHours, includeDetail); + unsetNoWrap(cell); + if (lines.isEmpty()) { + setCellTextByTemplateParagraph(cell, normalizeLineBreaks(fallbackText), false); + return; + } + setCellTextByTemplateParagraph(cell, String.join("\n", lines), false); + } + + private List buildStructuredParagraphsForTemplate(Object jsonValue, String fallbackText, + boolean includeHours, boolean includeDetail) { + List sections = parseStructuredSections(jsonValue); + if (sections.isEmpty()) { + sections = parseStructuredSectionsForTemplate(fallbackText); + } + if (sections.isEmpty()) { + return List.of(); + } + Map priorityLabelMap = loadPriorityLabelMap(); + List lines = new ArrayList<>(); + for (StructuredSectionData section : sections) { + String category = normalizeSectionCategory(section.category()); + if (StringUtils.hasText(category)) { + lines.add("# " + category + "\uFF1A"); + } + List tasks = safeList(section.tasks()); + for (int i = 0; i < tasks.size(); i++) { + StructuredTaskData task = tasks.get(i); + String taskLine = (i + 1) + "\u3001 " + formatStructuredTaskForTemplate(task, includeHours, priorityLabelMap); + String detail = includeDetail ? text(task.detail()) : ""; + if (StringUtils.hasText(detail)) { + List detailLines = splitTextLines(detail); + if (detailLines.isEmpty()) { + lines.add(taskLine + "\uFF1A"); + } else { + lines.add(taskLine + "\uFF1A" + detailLines.get(0)); + for (int j = 1; j < detailLines.size(); j++) { + lines.add(detailLines.get(j)); + } + } + } else { + lines.add(taskLine); + } + } + } + while (!lines.isEmpty() && !StringUtils.hasText(lines.get(lines.size() - 1))) { + lines.remove(lines.size() - 1); + } + return lines; + } + + private String formatStructuredTaskForTemplate(StructuredTaskData task, boolean includeHours, + Map priorityLabelMap) { + List metrics = new ArrayList<>(); + if (StringUtils.hasText(task.priority())) { + metrics.add(resolvePriorityLabel(task.priority(), priorityLabelMap)); + } + if (task.progress() != null) { + metrics.add("\u8FDB\u5EA6 " + formatDecimal(task.progress()) + "%"); + } + if (includeHours && task.hours() != null) { + metrics.add(formatDecimal(task.hours()) + "h"); + } + return text(task.title()) + (metrics.isEmpty() ? "" : "\uFF08" + String.join(" / ", metrics) + "\uFF09"); + } + + private List parseStructuredSectionsForTemplate(String textValue) { + if (!StringUtils.hasText(textValue)) { + return List.of(); + } + String[] rawLines = normalizeLineBreaks(text(textValue)).split("\\R"); + Map> sectionMap = new LinkedHashMap<>(); + String currentCategory = null; + for (String rawLine : rawLines) { + String line = text(rawLine); + if (!StringUtils.hasText(line)) { + continue; + } + if (line.startsWith("#")) { + currentCategory = normalizeSectionCategory(line.substring(1)); + if (StringUtils.hasText(currentCategory)) { + sectionMap.putIfAbsent(currentCategory, new ArrayList<>()); + } + continue; + } + if (appendDetailContinuation(sectionMap, currentCategory, line)) { + continue; + } + java.util.regex.Matcher legacyMatcher = java.util.regex.Pattern + .compile("^(.+?)\\s*[--]\\s*(.+)$") + .matcher(line); + if (legacyMatcher.matches()) { + String category = normalizeSectionCategory(legacyMatcher.group(1)); + StructuredTaskData task = parseTemplateStructuredTask(legacyMatcher.group(2)); + if (StringUtils.hasText(category) && task != null) { + sectionMap.computeIfAbsent(category, key -> new ArrayList<>()).add(task); + continue; + } + } + StructuredTaskData task = parseTemplateStructuredTask(line); + if (task == null) { + continue; + } + String category = StringUtils.hasText(currentCategory) ? currentCategory : "工作内容"; + sectionMap.computeIfAbsent(category, key -> new ArrayList<>()).add(task); + } + return sectionMap.entrySet().stream() + .map(entry -> new StructuredSectionData(entry.getKey(), entry.getValue())) + .toList(); + } + + private StructuredTaskData parseTemplateStructuredTask(String line) { + String normalized = text(line) + .replaceFirst("^\\d+[.、.]\\s*", "") + .replaceFirst("[。.!!?]+$", ""); + if (!StringUtils.hasText(normalized)) { + return null; + } + TaskDetailParts parts = splitTaskDetail(normalized); + java.util.regex.Matcher matcher = java.util.regex.Pattern + .compile("^(.*?)(?:[((]([^()()]*)[))])?$") + .matcher(parts.title()); + if (!matcher.matches()) { + return new StructuredTaskData(parts.title(), parts.detail(), null, null, null); + } + String title = text(matcher.group(1)); + if (!StringUtils.hasText(title)) { + return null; + } + StructuredMetricsData metrics = parseTemplateMetrics(text(matcher.group(2))); + return new StructuredTaskData(title, parts.detail(), metrics.priority(), metrics.progress(), metrics.hours()); + } + + private StructuredMetricsData parseTemplateMetrics(String metricsText) { + if (!StringUtils.hasText(metricsText)) { + return new StructuredMetricsData(null, null, null); + } + String[] parts = metricsText.split("[//]"); + String priority = null; + BigDecimal progress = null; + BigDecimal hours = null; + for (String part : parts) { + String trimmed = text(part); + if (!StringUtils.hasText(trimmed)) { + continue; + } + java.util.regex.Matcher priorityMatcher = java.util.regex.Pattern + .compile("(?i)^P?(\\d+)(?:\\.0+)?$") + .matcher(trimmed); + if (priority == null && priorityMatcher.matches()) { + priority = normalizePriority(priorityMatcher.group(1)); + continue; + } + java.util.regex.Matcher progressMatcher = java.util.regex.Pattern + .compile("^(?:进度\\s*)?(\\d+(?:\\.\\d+)?)%$") + .matcher(trimmed); + if (progress == null && progressMatcher.matches()) { + progress = parseDecimal(progressMatcher.group(1)); + continue; + } + java.util.regex.Matcher hoursMatcher = java.util.regex.Pattern + .compile("^(\\d+(?:\\.\\d+)?)h$") + .matcher(trimmed); + if (hours == null && hoursMatcher.matches()) { + hours = parseDecimal(hoursMatcher.group(1)); + } + } + return new StructuredMetricsData(priority, progress, hours); + } + + private String buildStructuredText(Object jsonValue, String fallbackText, boolean includeHours, + boolean includeDetail) { + List sections = parseStructuredSections(jsonValue); + if (sections.isEmpty()) { + sections = parseStructuredSectionsFromText(fallbackText); + } + if (sections.isEmpty()) { + return text(fallbackText); + } + Map priorityLabelMap = loadPriorityLabelMap(); + List lines = new ArrayList<>(); + for (StructuredSectionData section : sections) { + lines.add("# " + normalizeSectionCategory(section.category()) + ":"); + List tasks = safeList(section.tasks()); + for (int i = 0; i < tasks.size(); i++) { + StructuredTaskData task = tasks.get(i); + String taskLine = (i + 1) + "、" + formatStructuredTask(task, includeHours, priorityLabelMap); + String detail = includeDetail ? text(task.detail()) : ""; + if (StringUtils.hasText(detail)) { + List detailLines = splitTextLines(detail); + if (detailLines.isEmpty()) { + lines.add(taskLine + ":"); + } else { + lines.add(taskLine + ":" + detailLines.get(0)); + for (int j = 1; j < detailLines.size(); j++) { + lines.add(detailLines.get(j)); + } + } + } else { + lines.add(taskLine); + } + } + } + return String.join("\n", lines); + } + + private void setStructuredCellText(XWPFTableCell cell, Object jsonValue, String fallbackText, boolean includeHours) { + List lines = buildStructuredLines(jsonValue, fallbackText, includeHours); + if (lines.isEmpty()) { + setCellText(cell, fallbackText, false); + return; + } + setCellParagraphs(cell, lines, false); + } + + private List buildStructuredLines(Object jsonValue, String fallbackText, boolean includeHours) { + List sections = parseStructuredSections(jsonValue); + if (sections.isEmpty()) { + sections = parseStructuredSectionsFromText(fallbackText); + } + if (sections.isEmpty()) { + return List.of(); + } + Map priorityLabelMap = loadPriorityLabelMap(); + List lines = new ArrayList<>(); + for (StructuredSectionData section : sections) { + lines.add("# " + normalizeSectionCategory(section.category()) + ":"); + List tasks = safeList(section.tasks()); + for (int i = 0; i < tasks.size(); i++) { + lines.add((i + 1) + "、" + formatStructuredTask(tasks.get(i), includeHours, priorityLabelMap)); + } + } + return lines; + } + + private List parseStructuredSections(Object jsonValue) { + if (jsonValue == null) { + return List.of(); + } + Object normalized = jsonValue; + if (jsonValue instanceof String textValue && StringUtils.hasText(textValue) && JsonUtils.isJson(textValue)) { + normalized = JsonUtils.parseTree(textValue); + } + if (normalized instanceof Map mapValue) { + Object sectionsValue = mapValue.get("sections"); + if (sectionsValue instanceof List sectionList) { + return convertStructuredSections(sectionList); + } + } + if (normalized instanceof List sectionList) { + return convertStructuredSections(sectionList); + } + try { + Map mapValue = JsonUtils.convertObject(normalized, new TypeReference>() {}); + Object sectionsValue = mapValue == null ? null : mapValue.get("sections"); + if (sectionsValue instanceof List sectionList) { + return convertStructuredSections(sectionList); + } + } catch (IllegalArgumentException ignored) { + return List.of(); + } + return List.of(); + } + + private List convertStructuredSections(List rawSections) { + List result = new ArrayList<>(); + for (Object rawSection : rawSections) { + Map sectionMap; + try { + sectionMap = JsonUtils.convertObject(rawSection, new TypeReference>() {}); + } catch (IllegalArgumentException ignored) { + continue; + } + if (sectionMap == null) { + continue; + } + String category = textOf(firstNonNull(sectionMap.get("category"), sectionMap.get("title"), sectionMap.get("name"))); + Object tasksValue = sectionMap.get("tasks"); + List tasks = new ArrayList<>(); + if (tasksValue instanceof List rawTasks) { + for (Object rawTask : rawTasks) { + Map taskMap; + try { + taskMap = JsonUtils.convertObject(rawTask, new TypeReference>() {}); + } catch (IllegalArgumentException ignored) { + continue; + } + if (taskMap == null) { + continue; + } + String title = textOf(firstNonNull(taskMap.get("title"), taskMap.get("name"))); + if (!StringUtils.hasText(title)) { + continue; + } + tasks.add(new StructuredTaskData( + title, + textOf(firstNonNull(taskMap.get("detail"), taskMap.get("content"))), + textOf(taskMap.get("priority")), + parseDecimal(taskMap.get("progress")), + parseDecimal(taskMap.get("hours")))); + } + } + if (StringUtils.hasText(category) || !tasks.isEmpty()) { + result.add(new StructuredSectionData(StringUtils.hasText(category) ? category : "工作内容", tasks)); + } + } + return result; + } + + private List parseStructuredSectionsFromText(String textValue) { + if (!StringUtils.hasText(textValue)) { + return List.of(); + } + String[] lines = normalizeLineBreaks(text(textValue)).split("\\R"); + Map> sectionMap = new LinkedHashMap<>(); + String currentCategory = null; + for (String line : lines) { + String trimmed = text(line); + if (!StringUtils.hasText(trimmed)) { + continue; + } + if (trimmed.startsWith("#")) { + currentCategory = normalizeSectionCategory(trimmed.replaceFirst("^#\\s*", "")); + sectionMap.putIfAbsent(currentCategory, new ArrayList<>()); + continue; + } + if (appendDetailContinuation(sectionMap, currentCategory, trimmed)) { + continue; + } + StructuredLineData legacyLine = parseLegacyStructuredLine(trimmed); + if (legacyLine != null) { + sectionMap.computeIfAbsent(legacyLine.category(), key -> new ArrayList<>()).add(legacyLine.task()); + continue; + } + StructuredTaskData task = parseSimpleStructuredLine(trimmed); + if (task != null) { + sectionMap.computeIfAbsent(StringUtils.hasText(currentCategory) ? currentCategory : "工作内容", + key -> new ArrayList<>()).add(task); + } + } + return sectionMap.entrySet().stream() + .map(entry -> new StructuredSectionData(normalizeSectionCategory(entry.getKey()), entry.getValue())) + .toList(); + } + + private StructuredLineData parseLegacyStructuredLine(String line) { + java.util.regex.Matcher matcher = java.util.regex.Pattern + .compile("^(.+?)\\s*[--]\\s*(.+)$") + .matcher(line); + if (!matcher.matches()) { + return null; + } + String category = text(matcher.group(1)); + StructuredTaskData task = parseSimpleStructuredLine(matcher.group(2)); + if (!StringUtils.hasText(category) || task == null) { + return null; + } + return new StructuredLineData(category, task); + } + + private StructuredTaskData parseSimpleStructuredLine(String line) { + String normalized = text(line).replaceFirst("^\\d+[..、]\\s*", ""); + if (!StringUtils.hasText(normalized)) { + return null; + } + TaskDetailParts parts = splitTaskDetail(normalized); + java.util.regex.Matcher matcher = java.util.regex.Pattern + .compile("^(.*?)(?:[((]([^()()]*)[))])?[。.!!??]*$") + .matcher(parts.title()); + if (!matcher.matches()) { + return new StructuredTaskData(parts.title(), parts.detail(), null, null, null); + } + String title = text(matcher.group(1)); + if (!StringUtils.hasText(title)) { + return null; + } + StructuredMetricsData metrics = parseMetrics(text(matcher.group(2))); + return new StructuredTaskData(title, parts.detail(), metrics.priority(), metrics.progress(), metrics.hours()); + } + + private StructuredMetricsData parseMetrics(String metricsText) { + if (!StringUtils.hasText(metricsText)) { + return new StructuredMetricsData(null, null, null); + } + String[] parts = metricsText.split("/"); + String priority = null; + BigDecimal progress = null; + BigDecimal hours = null; + for (String part : parts) { + String trimmed = text(part); + if (!StringUtils.hasText(trimmed)) { + continue; + } + if (priority == null && trimmed.matches("(?i)^P?\\d+$")) { + priority = normalizePriority(trimmed); + continue; + } + if (progress == null && trimmed.matches("^\\d+(?:\\.\\d+)?%$")) { + progress = parseDecimal(trimmed.substring(0, trimmed.length() - 1)); + continue; + } + if (progress == null && trimmed.matches("^进度\\s*\\d+(?:\\.\\d+)?%$")) { + progress = parseDecimal(trimmed.replaceFirst("^进度\\s*", "").replace("%", "")); + continue; + } + if (hours == null && trimmed.matches("^\\d+(?:\\.\\d+)?h$")) { + hours = parseDecimal(trimmed.substring(0, trimmed.length() - 1)); + } + } + return new StructuredMetricsData(priority, progress, hours); + } + + private String formatStructuredTask(StructuredTaskData task, boolean includeHours, Map priorityLabelMap) { + List metrics = new ArrayList<>(); + if (StringUtils.hasText(task.priority())) { + metrics.add(resolvePriorityLabel(task.priority(), priorityLabelMap)); + } + if (task.progress() != null) { + metrics.add("\u8FDB\u5EA6 " + formatDecimal(task.progress()) + "%"); + } + if (includeHours && task.hours() != null) { + metrics.add(formatDecimal(task.hours()) + "h"); + } + return text(task.title()) + (metrics.isEmpty() ? "" : "(" + String.join(" / ", metrics) + ")"); + } + + private String normalizePriority(String value) { + String trimmed = text(value); + if (!StringUtils.hasText(trimmed)) { + return ""; + } + if (trimmed.matches("^\\d+\\.0+$")) { + trimmed = trimmed.substring(0, trimmed.indexOf('.')); + } + if (trimmed.matches("(?i)^P\\d+$")) { + return trimmed.toUpperCase(); + } + if (trimmed.matches("^\\d+$")) { + return "P" + trimmed; + } + return trimmed; + } + + private String resolvePriorityLabel(String value, Map priorityLabelMap) { + String normalized = normalizePriority(value); + if (!StringUtils.hasText(normalized)) { + return ""; + } + if (normalized.matches("(?i)^P\\d+$")) { + return normalized.toUpperCase(); + } + String rawValue = text(value); + String label = priorityLabelMap.get(rawValue); + if (!StringUtils.hasText(label) && rawValue.matches("^\\d+\\.0+$")) { + label = priorityLabelMap.get(rawValue.substring(0, rawValue.indexOf('.'))); + } + if (!StringUtils.hasText(label)) { + label = priorityLabelMap.get(normalized.replaceFirst("(?i)^P", "")); + } + if (!StringUtils.hasText(label)) { + label = priorityLabelMap.get(normalized); + } + return StringUtils.hasText(label) ? label : normalized; + } + + private boolean appendDetailContinuation(Map> sectionMap, + String currentCategory, String line) { + if (looksLikeStructuredTaskStart(line)) { + return false; + } + String category = StringUtils.hasText(currentCategory) ? currentCategory : lastCategory(sectionMap); + if (!StringUtils.hasText(category)) { + return false; + } + List tasks = sectionMap.get(category); + if (tasks == null || tasks.isEmpty()) { + return false; + } + int lastIndex = tasks.size() - 1; + StructuredTaskData lastTask = tasks.get(lastIndex); + if (!StringUtils.hasText(lastTask.detail())) { + return false; + } + tasks.set(lastIndex, new StructuredTaskData(lastTask.title(), + lastTask.detail() + "\n" + line, + lastTask.priority(), lastTask.progress(), lastTask.hours())); + return true; + } + + private boolean looksLikeStructuredTaskStart(String line) { + String value = text(line); + return value.startsWith("#") + || value.matches("^\\d+[..、]\\s*.+") + || value.matches("^.+?\\s*[--]\\s*.+"); + } + + private String lastCategory(Map> sectionMap) { + if (sectionMap == null || sectionMap.isEmpty()) { + return null; + } + String result = null; + for (String category : sectionMap.keySet()) { + result = category; + } + return result; + } + + private TaskDetailParts splitTaskDetail(String value) { + String source = text(value); + if (!StringUtils.hasText(source)) { + return new TaskDetailParts("", ""); + } + java.util.regex.Matcher matcher = java.util.regex.Pattern + .compile("^(.*?)(?:[::]\\s*)(.*)$") + .matcher(source); + if (!matcher.matches()) { + return new TaskDetailParts(source, ""); + } + return new TaskDetailParts(text(matcher.group(1)), text(matcher.group(2))); + } + + private List splitTextLines(String value) { + String normalized = normalizeLineBreaks(text(value)); + if (!StringUtils.hasText(normalized)) { + return List.of(); + } + List result = new ArrayList<>(); + for (String line : normalized.split("\\R")) { + String textLine = text(line); + if (StringUtils.hasText(textLine)) { + result.add(textLine); + } + } + return result; + } + + private String normalizeLineBreaks(String value) { + return text(value) + .replace("\\r\\n", "\n") + .replace("\\n", "\n") + .replace("\\r", "\n"); + } + + private Map loadPriorityLabelMap() { + CommonResult> result = dictDataApi.getDictDataList(ProjectDictTypeConstants.REQ_PRIORITY); + List dictDataList = result == null ? null : result.getCheckedData(); + if (dictDataList == null || dictDataList.isEmpty()) { + return Map.of(); + } + Map labelMap = new HashMap<>(); + for (DictDataRespDTO item : dictDataList) { + if (item == null) { + continue; + } + String value = text(item.getValue()); + String label = text(item.getLabel()); + if (StringUtils.hasText(value) && StringUtils.hasText(label)) { + labelMap.put(value, label); + } + } + return labelMap; + } + + private String normalizeSectionCategory(String category) { + return text(category).replaceFirst("[::]+$", ""); + } + + private BigDecimal parseDecimal(Object value) { + if (value == null) { + return null; + } + if (value instanceof BigDecimal decimal) { + return decimal; + } + if (value instanceof Number number) { + return BigDecimal.valueOf(number.doubleValue()); + } + if (value instanceof String textValue && StringUtils.hasText(textValue)) { + try { + return new BigDecimal(textValue.trim()); + } catch (NumberFormatException ignored) { + return null; + } + } + return null; + } + + private Object firstNonNull(Object... values) { + if (values == null) { + return null; + } + for (Object value : values) { + if (value != null) { + return value; + } + } + return null; + } + + private String buildProjectItemsText(List items, boolean includeHours) { + if (items == null || items.isEmpty()) { + return ""; + } + Map priorityLabelMap = loadPriorityLabelMap(); + List lines = new ArrayList<>(items.size()); + int index = 1; + for (ProjectReportItemRespVO item : items) { + String itemTitle = text(item == null ? null : item.getItemTitle()); + if (!StringUtils.hasText(itemTitle)) { + continue; + } + List metrics = new ArrayList<>(); + if (item != null && StringUtils.hasText(item.getPriorityCode())) { + metrics.add(resolvePriorityLabel(item.getPriorityCode(), priorityLabelMap)); + } + if (item != null && item.getProgressRate() != null) { + metrics.add(formatProgress(item.getProgressRate())); + } + if (includeHours && item != null && item.getWorkHours() != null) { + metrics.add(formatDecimal(item.getWorkHours()) + "h"); + } + String suffix = metrics.isEmpty() ? "" : "(" + String.join("/", metrics) + ")"; + lines.add(index++ + "、" + itemTitle + suffix + "。"); + } + return String.join("\n", lines); + } + + private void addTitle(XWPFDocument document, String title) { + XWPFParagraph paragraph = document.createParagraph(); + paragraph.setAlignment(ParagraphAlignment.CENTER); + XWPFRun run = paragraph.createRun(); + run.setBold(true); + run.setFontSize(18); + run.setText(title); + } + + private void addSectionTitle(XWPFDocument document, String title) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setBold(true); + run.setFontSize(12); + run.setText(title); + } + + private void addParagraph(XWPFDocument document, String text, boolean bold) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setBold(bold); + run.setText(text(text)); + } + + private void addPersonalBaseTable(XWPFDocument document, String name, String deptName, + String postName, String managerName) { + addKeyValueTable(document, List.of( + row("姓名", name), + row("部门", deptName), + row("岗位", postName), + row("直接上级", managerName))); + } + + private void addPersonalReviewTable(XWPFDocument document, List items, + boolean includeDetail) { + XWPFTable table = document.createTable(1, 4); + setTableWidth(table); + setRowText(table.getRow(0), List.of("序号", "工作事项", "具体工作内容及成果描述", "工作感悟"), true); + for (PersonalReportReviewItemRespVO item : safeList(items)) { + XWPFTableRow row = table.createRow(); + setCellText(row.getCell(0), formatNumber(item.getItemNumber()), false); + setCellText(row.getCell(1), appendHours(item.getItemTitle(), item.getWorkHours()), false); + setCellText(row.getCell(2), buildStructuredReviewText(item, includeDetail), false); + setCellText(row.getCell(3), item.getReflectionText(), false); + } + } + + private void addPersonalPlanTable(XWPFDocument document, List items, + boolean includeDetail) { + XWPFTable table = document.createTable(1, 4); + setTableWidth(table); + setRowText(table.getRow(0), List.of("序号", "工作事项", "具体目标", "对他人协助的需求"), true); + for (PersonalReportPlanItemRespVO item : safeList(items)) { + XWPFTableRow row = table.createRow(); + setCellText(row.getCell(0), formatNumber(item.getItemNumber()), false); + setCellText(row.getCell(1), item.getItemTitle(), false); + setCellText(row.getCell(2), buildStructuredPlanText(item, includeDetail), false); + setCellText(row.getCell(3), item.getSupportNeed(), false); + } + } + + private void addFeedbackTable(XWPFDocument document, MonthlyReportApprovalRecordRespVO record) { + XWPFTable table = document.createTable(1, 4); + setTableWidth(table); + setRowText(table.getRow(0), List.of("优势/劣势项", "优势/劣势描述", "行为事例", "改进提升建议"), true); + XWPFTableRow strengthRow = table.createRow(); + setRowText(strengthRow, values("优势", + record == null ? null : record.getStrengthDesc(), + record == null ? null : record.getStrengthExample(), + ""), false); + XWPFTableRow weaknessRow = table.createRow(); + setRowText(weaknessRow, values("劣势", + record == null ? null : record.getWeaknessDesc(), + record == null ? null : record.getWeaknessExample(), + record == null ? null : record.getImprovementSuggestion()), false); + } + + private void addProjectItemTable(XWPFDocument document, List items, boolean showHours) { + XWPFTable table = document.createTable(1, showHours ? 4 : 3); + setTableWidth(table); + setRowText(table.getRow(0), showHours + ? List.of("工作内容", "工时", "优先级", "进度") + : List.of("工作内容", "优先级", "进度"), true); + for (ProjectReportItemRespVO item : safeList(items)) { + XWPFTableRow row = table.createRow(); + setRowText(row, showHours + ? values(item.getItemTitle(), formatDecimal(item.getWorkHours()), item.getPriorityCode(), + formatProgress(item.getProgressRate())) + : values(item.getItemTitle(), item.getPriorityCode(), formatProgress(item.getProgressRate())), + false); + } + } + + private void addKeyValueTable(XWPFDocument document, List> rows) { + XWPFTable table = document.createTable(rows.size(), 2); + setTableWidth(table); + for (int i = 0; i < rows.size(); i++) { + setRowText(table.getRow(i), rows.get(i), i == 0 && rows.size() == 1); + } + } + + private void setTableWidth(XWPFTable table) { + table.setWidth("100%"); + } + + private void setRowText(XWPFTableRow row, List values, boolean bold) { + for (int i = 0; i < values.size(); i++) { + setCellText(row.getCell(i), values.get(i), bold); + } + } + + private void setCellText(XWPFTableCell cell, String value, boolean bold) { + while (cell.getParagraphs().size() > 0) { + cell.removeParagraph(0); + } + XWPFParagraph paragraph = cell.addParagraph(); + XWPFRun run = paragraph.createRun(); + run.setBold(bold); + String[] lines = text(value).split("\\R", -1); + for (int i = 0; i < lines.length; i++) { + if (i > 0) { + run.addBreak(); + } + run.setText(lines[i]); + } + } + + private void setCellTextByTemplateParagraph(XWPFTableCell cell, String value, boolean bold) { + unsetNoWrap(cell); + XWPFParagraph paragraph; + if (cell.getParagraphs() == null || cell.getParagraphs().isEmpty()) { + paragraph = cell.addParagraph(); + } else { + paragraph = cell.getParagraphs().get(0); + for (int i = cell.getParagraphs().size() - 1; i >= 1; i--) { + cell.removeParagraph(i); + } + } + List runs = paragraph.getRuns(); + XWPFRun firstRun; + if (runs == null || runs.isEmpty()) { + firstRun = paragraph.createRun(); + } else { + firstRun = runs.get(0); + for (int i = runs.size() - 1; i >= 1; i--) { + paragraph.removeRun(i); + } + } + firstRun.setBold(bold); + applyRunText(firstRun, value); + } + + private void unsetNoWrap(XWPFTableCell cell) { + if (cell == null || cell.getCTTc() == null) { + return; + } + var tcPr = cell.getCTTc().getTcPr(); + if (tcPr != null && tcPr.isSetNoWrap()) { + tcPr.unsetNoWrap(); + } + } + + private void setCellParagraphs(XWPFTableCell cell, List lines, boolean bold) { + while (cell.getParagraphs().size() > 0) { + cell.removeParagraph(0); + } + List safeLines = lines == null || lines.isEmpty() ? List.of("") : lines; + for (String line : safeLines) { + XWPFParagraph paragraph = cell.addParagraph(); + paragraph.setSpacingBefore(0); + paragraph.setSpacingAfter(0); + XWPFRun run = paragraph.createRun(); + run.setBold(bold); + run.setText(text(line)); + } + } + + private byte[] toBytes(XWPFDocument document) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + document.write(outputStream); + return outputStream.toByteArray(); + } + + private String buildTravelText(List travelSegments) { + if (travelSegments == null || travelSegments.isEmpty()) { + return ""; + } + return travelSegments.stream() + .map(item -> formatDate(item.getStartDate()) + "-" + formatDate(item.getEndDate()) + + "," + formatDecimal(item.getTravelDays()) + "天," + text(item.getLocation())) + .collect(java.util.stream.Collectors.joining("\n")); + } + + private String buildMemberText(List members) { + return safeList(members).stream() + .map(WorkReportMemberSnapshotRespVO::getUserName) + .filter(StringUtils::hasText) + .collect(java.util.stream.Collectors.joining("、")); + } + + private String appendHours(String title, BigDecimal hours) { + if (hours == null) { + return text(title); + } + return text(title) + "(" + formatDecimal(hours) + "h)"; + } + + private String formatPeriod(Object report) { + if (report instanceof WeeklyReportRespVO weeklyReport) { + return formatDate(weeklyReport.getPeriodStartDate()) + "-" + formatDate(weeklyReport.getPeriodEndDate()); + } + if (report instanceof MonthlyReportRespVO monthlyReport) { + return formatDate(monthlyReport.getPeriodStartDate()) + "-" + formatDate(monthlyReport.getPeriodEndDate()); + } + if (report instanceof ProjectReportRespVO projectReport) { + return formatDate(projectReport.getPeriodStartDate()) + "-" + formatDate(projectReport.getPeriodEndDate()); + } + return ""; + } + + private String formatDate(LocalDate value) { + return value == null ? "" : DATE_FORMATTER.format(value); + } + + private String formatDateTime(LocalDateTime value) { + return value == null ? "" : DATE_TIME_FORMATTER.format(value); + } + + private String formatNumber(Integer value) { + return value == null ? "" : String.valueOf(value); + } + + private String formatDecimal(BigDecimal value) { + return value == null ? "" : value.stripTrailingZeros().toPlainString(); + } + + private String formatProgress(BigDecimal value) { + return value == null ? "" : value.stripTrailingZeros().toPlainString() + "%"; + } + + private String buildFilename(String reportTypeName, String ownerName, String periodLabel) { + return sanitizeFilename(reportTypeName + "_" + text(ownerName) + "_" + text(periodLabel)) + ".docx"; + } + + private String sanitizeFilename(String filename) { + return filename.replaceAll("[\\\\/:*?\"<>|\\r\\n]+", "_"); + } + + private String text(String value) { + return StringUtils.hasText(value) ? value.trim() : ""; + } + + private String textOf(Object value) { + return value == null ? "" : text(String.valueOf(value)); + } + + private List row(String label, String value) { + return List.of(text(label), text(value)); + } + + private List values(String... values) { + List result = new ArrayList<>(values.length); + for (String value : values) { + result.add(text(value)); + } + return result; + } + + private List safeList(List source) { + return source == null ? List.of() : source; + } + + private record StructuredSectionData(String category, List tasks) { + } + + private record StructuredTaskData(String title, String detail, String priority, BigDecimal progress, BigDecimal hours) { + } + + private record StructuredMetricsData(String priority, BigDecimal progress, BigDecimal hours) { + } + + private record StructuredLineData(String category, StructuredTaskData task) { + } + + private record TaskDetailParts(String title, String detail) { + } + + private record DocumentContent(String filename, byte[] content) { + } +} \ No newline at end of file diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/export/WorkReportExportFile.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/export/WorkReportExportFile.java new file mode 100644 index 0000000..a0c1980 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/export/WorkReportExportFile.java @@ -0,0 +1,4 @@ +package com.njcn.rdms.module.project.service.workreport.export; + +public record WorkReportExportFile(String filename, String contentType, byte[] content) { +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/monthly/MonthlyReportService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/monthly/MonthlyReportService.java new file mode 100644 index 0000000..66c5550 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/monthly/MonthlyReportService.java @@ -0,0 +1,45 @@ +package com.njcn.rdms.module.project.service.workreport.monthly; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApprovalRecordRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApproveReqVO; +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.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; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; + +import java.util.List; + +public interface MonthlyReportService { + + MonthlyReportRespVO initMonthlyReport(); + + MonthlyReportRespVO previewMonthlyDefaultDraft(MonthlyReportDefaultDraftReqVO reqVO); + + Long createMonthlyReport(MonthlyReportSaveReqVO reqVO); + + void updateMonthlyReport(Long id, MonthlyReportSaveReqVO reqVO); + + MonthlyReportRespVO getMonthlyReport(Long id); + + PageResult getMonthlyReportPage(MonthlyReportPageReqVO reqVO); + + PageResult getMonthlyApprovalPage(MonthlyReportPageReqVO reqVO); + + void submitMonthlyReport(Long id); + + void approveMonthlyReport(Long id, MonthlyReportApproveReqVO reqVO); + + void rejectMonthlyReport(Long id, WorkReportStatusActionReqVO reqVO); + + void deleteMonthlyReport(Long id); + + List getMonthlyStatusLogs(Long id); + + List getMonthlyApprovalRecords(Long id); + + List getMonthlyExportList(MonthlyReportPageReqVO reqVO); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/monthly/MonthlyReportServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/monthly/MonthlyReportServiceImpl.java new file mode 100644 index 0000000..d64168f --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/monthly/MonthlyReportServiceImpl.java @@ -0,0 +1,97 @@ +package com.njcn.rdms.module.project.service.workreport.monthly; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApprovalRecordRespVO; +import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportApproveReqVO; +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.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; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; +import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService; +import com.njcn.rdms.module.project.service.workreport.defaultdraft.WorkReportDefaultDraftService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class MonthlyReportServiceImpl implements MonthlyReportService { + + @Resource + private WorkReportCommonService workReportCommonService; + @Resource + private WorkReportDefaultDraftService workReportDefaultDraftService; + + @Override + public MonthlyReportRespVO initMonthlyReport() { + return workReportCommonService.initMonthlyReport(); + } + + @Override + public MonthlyReportRespVO previewMonthlyDefaultDraft(MonthlyReportDefaultDraftReqVO reqVO) { + return workReportDefaultDraftService.previewMonthlyDefaultDraft(reqVO); + } + + @Override + public Long createMonthlyReport(MonthlyReportSaveReqVO reqVO) { + return workReportCommonService.createMonthlyReport(reqVO); + } + + @Override + public void updateMonthlyReport(Long id, MonthlyReportSaveReqVO reqVO) { + workReportCommonService.updateMonthlyReport(id, reqVO); + } + + @Override + public MonthlyReportRespVO getMonthlyReport(Long id) { + return workReportCommonService.getMonthlyReport(id); + } + + @Override + public PageResult getMonthlyReportPage(MonthlyReportPageReqVO reqVO) { + return workReportCommonService.getMonthlyReportPage(reqVO); + } + + @Override + public PageResult getMonthlyApprovalPage(MonthlyReportPageReqVO reqVO) { + return workReportCommonService.getMonthlyApprovalPage(reqVO); + } + + @Override + public void submitMonthlyReport(Long id) { + workReportCommonService.submitMonthlyReport(id); + } + + @Override + public void approveMonthlyReport(Long id, MonthlyReportApproveReqVO reqVO) { + workReportCommonService.approveMonthlyReport(id, reqVO); + } + + @Override + public void rejectMonthlyReport(Long id, WorkReportStatusActionReqVO reqVO) { + workReportCommonService.rejectMonthlyReport(id, reqVO); + } + + @Override + public void deleteMonthlyReport(Long id) { + workReportCommonService.deleteMonthlyReport(id); + } + + @Override + public List getMonthlyStatusLogs(Long id) { + return workReportCommonService.getMonthlyStatusLogs(id); + } + + @Override + public List getMonthlyApprovalRecords(Long id) { + return workReportCommonService.getMonthlyApprovalRecords(id); + } + + @Override + public List getMonthlyExportList(MonthlyReportPageReqVO reqVO) { + return workReportCommonService.getMonthlyExportList(reqVO); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/project/ProjectReportService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/project/ProjectReportService.java new file mode 100644 index 0000000..8a2809c --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/project/ProjectReportService.java @@ -0,0 +1,47 @@ +package com.njcn.rdms.module.project.service.workreport.project; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportExportVO; +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.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; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; + +import java.util.List; + +public interface ProjectReportService { + + List getOwnerProjectOptions(); + + ProjectReportRespVO initProjectReport(Long projectId); + + ProjectReportRespVO previewProjectDefaultDraft(Long projectId, ProjectReportDefaultDraftReqVO reqVO); + + Long createProjectReport(ProjectReportSaveReqVO reqVO); + + void updateProjectReport(Long id, ProjectReportSaveReqVO reqVO); + + ProjectReportRespVO getProjectReport(Long id); + + PageResult getProjectReportPage(ProjectReportPageReqVO reqVO); + + PageResult getProjectApprovalPage(ProjectReportPageReqVO reqVO); + + void submitProjectReport(Long id); + + void approveProjectReport(Long id, WorkReportStatusActionReqVO reqVO); + + void rejectProjectReport(Long id, WorkReportStatusActionReqVO reqVO); + + void deleteProjectReport(Long id); + + List getProjectStatusLogs(Long id); + + List getProjectApprovalRecords(Long id); + + List getProjectExportList(ProjectReportPageReqVO reqVO); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/project/ProjectReportServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/project/ProjectReportServiceImpl.java new file mode 100644 index 0000000..0e87368 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/project/ProjectReportServiceImpl.java @@ -0,0 +1,106 @@ +package com.njcn.rdms.module.project.service.workreport.project; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.controller.admin.workreport.project.vo.ProjectReportExportVO; +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.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; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; +import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService; +import com.njcn.rdms.module.project.service.workreport.defaultdraft.WorkReportDefaultDraftService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class ProjectReportServiceImpl implements ProjectReportService { + + @Resource + private WorkReportCommonService workReportCommonService; + @Resource + private WorkReportDefaultDraftService workReportDefaultDraftService; + + @Override + public List getOwnerProjectOptions() { + return workReportCommonService.getProjectReportOwnerProjectOptions(); + } + + @Override + public ProjectReportRespVO initProjectReport(Long projectId) { + workReportCommonService.validateCurrentUserIsProjectReportProjectOwner(projectId); + return workReportCommonService.initProjectReport(projectId); + } + + @Override + public ProjectReportRespVO previewProjectDefaultDraft(Long projectId, ProjectReportDefaultDraftReqVO reqVO) { + workReportCommonService.validateCurrentUserIsProjectReportProjectOwner(projectId); + return workReportDefaultDraftService.previewProjectDefaultDraft(projectId, reqVO); + } + + @Override + public Long createProjectReport(ProjectReportSaveReqVO reqVO) { + workReportCommonService.validateCurrentUserIsProjectReportProjectOwner(reqVO.getProjectId()); + return workReportCommonService.createProjectReport(reqVO); + } + + @Override + public void updateProjectReport(Long id, ProjectReportSaveReqVO reqVO) { + workReportCommonService.validateCurrentUserIsProjectReportProjectOwner(reqVO.getProjectId()); + workReportCommonService.updateProjectReport(id, reqVO); + } + + @Override + public ProjectReportRespVO getProjectReport(Long id) { + return workReportCommonService.getProjectReport(id); + } + + @Override + public PageResult getProjectReportPage(ProjectReportPageReqVO reqVO) { + return workReportCommonService.getProjectReportPage(reqVO); + } + + @Override + public PageResult getProjectApprovalPage(ProjectReportPageReqVO reqVO) { + return workReportCommonService.getProjectApprovalPage(reqVO); + } + + @Override + public void submitProjectReport(Long id) { + workReportCommonService.submitProjectReport(id); + } + + @Override + public void approveProjectReport(Long id, WorkReportStatusActionReqVO reqVO) { + workReportCommonService.approveProjectReport(id, reqVO); + } + + @Override + public void rejectProjectReport(Long id, WorkReportStatusActionReqVO reqVO) { + workReportCommonService.rejectProjectReport(id, reqVO); + } + + @Override + public void deleteProjectReport(Long id) { + workReportCommonService.deleteProjectReport(id); + } + + @Override + public List getProjectStatusLogs(Long id) { + return workReportCommonService.getProjectStatusLogs(id); + } + + @Override + public List getProjectApprovalRecords(Long id) { + return workReportCommonService.getProjectApprovalRecords(id); + } + + @Override + public List getProjectExportList(ProjectReportPageReqVO reqVO) { + return workReportCommonService.getProjectExportList(reqVO); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/weekly/WeeklyReportService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/weekly/WeeklyReportService.java new file mode 100644 index 0000000..da28fe6 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/weekly/WeeklyReportService.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.project.service.workreport.weekly; + +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.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; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; + +import java.util.List; + +public interface WeeklyReportService { + + WeeklyReportRespVO initWeeklyReport(); + + WeeklyReportRespVO previewWeeklyDefaultDraft(WeeklyReportDefaultDraftReqVO reqVO); + + Long createWeeklyReport(WeeklyReportSaveReqVO reqVO); + + void updateWeeklyReport(Long id, WeeklyReportSaveReqVO reqVO); + + WeeklyReportRespVO getWeeklyReport(Long id); + + PageResult getWeeklyReportPage(WeeklyReportPageReqVO reqVO); + + PageResult getWeeklyApprovalPage(WeeklyReportPageReqVO reqVO); + + void submitWeeklyReport(Long id); + + void approveWeeklyReport(Long id, WorkReportStatusActionReqVO reqVO); + + void rejectWeeklyReport(Long id, WorkReportStatusActionReqVO reqVO); + + void deleteWeeklyReport(Long id); + + List getWeeklyStatusLogs(Long id); + + List getWeeklyApprovalRecords(Long id); + + List getWeeklyExportList(WeeklyReportPageReqVO reqVO); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/weekly/WeeklyReportServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/weekly/WeeklyReportServiceImpl.java new file mode 100644 index 0000000..4e7ce87 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/workreport/weekly/WeeklyReportServiceImpl.java @@ -0,0 +1,96 @@ +package com.njcn.rdms.module.project.service.workreport.weekly; + +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.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; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusLogRespVO; +import com.njcn.rdms.module.project.service.workreport.defaultdraft.WorkReportDefaultDraftService; +import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class WeeklyReportServiceImpl implements WeeklyReportService { + + @Resource + private WorkReportCommonService workReportCommonService; + @Resource + private WorkReportDefaultDraftService workReportDefaultDraftService; + + @Override + public WeeklyReportRespVO initWeeklyReport() { + return workReportCommonService.initWeeklyReport(); + } + + @Override + public WeeklyReportRespVO previewWeeklyDefaultDraft(WeeklyReportDefaultDraftReqVO reqVO) { + return workReportDefaultDraftService.previewWeeklyDefaultDraft(reqVO); + } + + @Override + public Long createWeeklyReport(WeeklyReportSaveReqVO reqVO) { + return workReportCommonService.createWeeklyReport(reqVO); + } + + @Override + public void updateWeeklyReport(Long id, WeeklyReportSaveReqVO reqVO) { + workReportCommonService.updateWeeklyReport(id, reqVO); + } + + @Override + public WeeklyReportRespVO getWeeklyReport(Long id) { + return workReportCommonService.getWeeklyReport(id); + } + + @Override + public PageResult getWeeklyReportPage(WeeklyReportPageReqVO reqVO) { + return workReportCommonService.getWeeklyReportPage(reqVO); + } + + @Override + public PageResult getWeeklyApprovalPage(WeeklyReportPageReqVO reqVO) { + return workReportCommonService.getWeeklyApprovalPage(reqVO); + } + + @Override + public void submitWeeklyReport(Long id) { + workReportCommonService.submitWeeklyReport(id); + } + + @Override + public void approveWeeklyReport(Long id, WorkReportStatusActionReqVO reqVO) { + workReportCommonService.approveWeeklyReport(id, reqVO); + } + + @Override + public void rejectWeeklyReport(Long id, WorkReportStatusActionReqVO reqVO) { + workReportCommonService.rejectWeeklyReport(id, reqVO); + } + + @Override + public void deleteWeeklyReport(Long id) { + workReportCommonService.deleteWeeklyReport(id); + } + + @Override + public List getWeeklyStatusLogs(Long id) { + return workReportCommonService.getWeeklyStatusLogs(id); + } + + @Override + public List getWeeklyApprovalRecords(Long id) { + return workReportCommonService.getWeeklyApprovalRecords(id); + } + + @Override + public List getWeeklyExportList(WeeklyReportPageReqVO reqVO) { + return workReportCommonService.getWeeklyExportList(reqVO); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/monthly-report-template.docx b/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/monthly-report-template.docx new file mode 100644 index 0000000..e6e8834 Binary files /dev/null and b/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/monthly-report-template.docx differ diff --git a/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/project-report-template.docx b/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/project-report-template.docx new file mode 100644 index 0000000..503aea2 Binary files /dev/null and b/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/project-report-template.docx differ diff --git a/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/weekly-report-template.docx b/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/weekly-report-template.docx new file mode 100644 index 0000000..97bb06c Binary files /dev/null and b/rdms-project/rdms-project-boot/src/main/resources/templates/work-report/weekly-report-template.docx differ