feat(绩效第二阶段、签名管理): 开发绩效第二阶段、下签功能、用户签名管理。

This commit is contained in:
dk
2026-07-13 22:09:39 +08:00
parent f77007c4b7
commit 2830b55ffb
53 changed files with 1797 additions and 14 deletions

View File

@@ -261,6 +261,15 @@ public interface ErrorCodeConstants {
ErrorCode WORK_REPORT_PERIOD_DUPLICATE = new ErrorCode(1_008_010_013, "当前周期的工作报告已存在,请勿重复创建"); 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_APPROVAL_RECORD_NOT_EXISTS = new ErrorCode(1_008_010_014, "工作报告审核记录不存在");
ErrorCode WORK_REPORT_PROJECT_OWNER_ONLY = new ErrorCode(1_008_010_015, "仅项目负责人可创建或维护该项目的项目半月报"); ErrorCode WORK_REPORT_PROJECT_OWNER_ONLY = new ErrorCode(1_008_010_015, "仅项目负责人可创建或维护该项目的项目半月报");
ErrorCode WORK_REPORT_SIGN_OFF_NOT_ALLOWED = new ErrorCode(1_008_010_016, "当前月报未处于待员工确认状态");
ErrorCode WORK_REPORT_SIGN_OFF_BATCH_LOCKED = new ErrorCode(1_008_010_017, "该月份已下签,禁止重复操作");
ErrorCode WORK_REPORT_MONTHLY_SIGN_OFF_NOT_EXISTS = new ErrorCode(1_008_010_018, "月报下签实例不存在");
ErrorCode WORK_REPORT_MONTHLY_SIGN_OFF_CONFIRM_ONLY_REPORTER = new ErrorCode(1_008_010_019, "仅月报填报人可确认下签");
ErrorCode WORK_REPORT_MONTHLY_SIGN_OFF_APPROVAL_RECORD_NOT_EXISTS = new ErrorCode(1_008_010_020, "月报缺少审批通过记录,无法完成下签确认");
ErrorCode WORK_REPORT_MONTHLY_SIGN_OFF_CANDIDATE_EMPTY = new ErrorCode(1_008_010_021, "没有可下签的月报数据");
ErrorCode SIGN_OFF_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_010_022, "当前下签状态为「{}」,不支持「{}」操作");
ErrorCode SIGN_OFF_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_010_023, "「{}」操作必须填写原因");
ErrorCode SIGN_OFF_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_010_024, "下签状态定义不存在或已停用");
// ========== 绩效管理 1_008_011_xxx ========== // ========== 绩效管理 1_008_011_xxx ==========
ErrorCode PERFORMANCE_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_011_001, "绩效模板不存在"); ErrorCode PERFORMANCE_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_011_001, "绩效模板不存在");
@@ -282,4 +291,12 @@ public interface ErrorCodeConstants {
ErrorCode PERFORMANCE_SHEET_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_011_017, "绩效表状态定义不存在或已停用"); ErrorCode PERFORMANCE_SHEET_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_011_017, "绩效表状态定义不存在或已停用");
ErrorCode PERFORMANCE_SHEET_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_011_018, "「{}」操作必须填写原因"); ErrorCode PERFORMANCE_SHEET_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_011_018, "「{}」操作必须填写原因");
ErrorCode PERFORMANCE_EMPLOYEE_DEPT_INVALID = new ErrorCode(1_008_011_019, "被考核员工部门信息不完整"); ErrorCode PERFORMANCE_EMPLOYEE_DEPT_INVALID = new ErrorCode(1_008_011_019, "被考核员工部门信息不完整");
ErrorCode PERFORMANCE_SHEET_SIGN_OFF_NOT_ALLOWED = new ErrorCode(1_008_011_020, "当前绩效表未处于待员工确认状态");
ErrorCode PERFORMANCE_SHEET_SIGN_OFF_BATCH_LOCKED = new ErrorCode(1_008_011_021, "该月份已下签,禁止重复操作");
ErrorCode PERFORMANCE_SHEET_SIGN_OFF_NOT_EXISTS = new ErrorCode(1_008_011_022, "绩效下签实例不存在");
ErrorCode PERFORMANCE_SHEET_IMPORT_ZIP_EMPTY = new ErrorCode(1_008_011_023, "导入压缩包不能为空");
ErrorCode PERFORMANCE_SHEET_IMPORT_TARGET_EMPTY = new ErrorCode(1_008_011_024, "没有匹配到可导入的绩效表");
ErrorCode PERFORMANCE_SHEET_SIGN_OFF_CANDIDATE_EMPTY = new ErrorCode(1_008_011_025, "没有可下签的绩效表数据");
ErrorCode PERFORMANCE_SHEET_MONTHLY_REPORT_NOT_EXISTS = new ErrorCode(1_008_011_026, "未找到对应月报,无法同步绩效结果");
ErrorCode PERFORMANCE_SHEET_MONTHLY_APPROVAL_RECORD_NOT_EXISTS = new ErrorCode(1_008_011_027, "未找到对应月报审批记录,无法同步绩效结果");
} }

View File

@@ -36,4 +36,6 @@ public final class PerformanceConstants {
public static final String PERMISSION_SHEET_REJECT = "project:performance-sheet:reject"; public static final String PERMISSION_SHEET_REJECT = "project:performance-sheet:reject";
public static final String PERMISSION_SHEET_EXPORT = "project:performance-sheet:export"; public static final String PERMISSION_SHEET_EXPORT = "project:performance-sheet:export";
public static final String PERMISSION_TEAM_DASHBOARD = "project:performance-sheet:team-dashboard"; public static final String PERMISSION_TEAM_DASHBOARD = "project:performance-sheet:team-dashboard";
public static final String PERMISSION_SHEET_IMPORT = "project:performance-sheet:import";
public static final String PERMISSION_SHEET_SIGN_OFF = "project:performance-sheet:sign-off";
} }

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.constant;
/**
* 员工下签常量。
*/
public final class SignOffConstants {
private SignOffConstants() {
}
public static final String STATUS_OBJECT_TYPE = "employee_sign_off";
public static final String STATUS_NOT_STARTED = "not_started";
public static final String STATUS_PENDING_EMPLOYEE_CONFIRM = "pending_employee_confirm";
public static final String STATUS_EMPLOYEE_CONFIRMED = "employee_confirmed";
public static final String STATUS_EMPLOYEE_REJECTED = "employee_rejected";
public static final String ACTION_ISSUE = "issue";
public static final String ACTION_CONFIRM = "confirm";
public static final String ACTION_REJECT = "reject";
}

View File

@@ -39,4 +39,6 @@ public final class WorkReportConstants {
public static final String PERMISSION_EXPORT = "project:work-report:export"; public static final String PERMISSION_EXPORT = "project:work-report:export";
public static final String PERMISSION_PROJECT_OWNER = "project:work-report:project-owner"; public static final String PERMISSION_PROJECT_OWNER = "project:work-report:project-owner";
public static final String PERMISSION_TEAM_DASHBOARD = "project:work-report:team-dashboard"; public static final String PERMISSION_TEAM_DASHBOARD = "project:work-report:team-dashboard";
public static final String PERMISSION_SIGN_OFF_QUERY = "project:work-report:sign-off-query";
public static final String PERMISSION_SIGN_OFF_CONFIRM = "project:work-report:sign-off-confirm";
} }

View File

@@ -8,10 +8,13 @@ import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceM
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetBatchDownloadReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetBatchDownloadReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetCreateReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetCreateReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetExcelUpdateReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetExcelUpdateReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetImportZipReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetImportZipRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPrefillRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPrefillRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetResponseRecordRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetResponseRecordRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetSignOffReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusActionReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusActionReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusDictRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusDictRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusLogRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusLogRespVO;
@@ -124,6 +127,21 @@ public class PerformanceSheetController {
return success(true); return success(true);
} }
@PostMapping("/import-zip")
@Operation(summary = "导入绩效压缩包")
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_IMPORT + "')")
public CommonResult<PerformanceSheetImportZipRespVO> importZip(@Valid PerformanceSheetImportZipReqVO reqVO)
throws Exception {
return success(performanceSheetService.importZip(reqVO));
}
@PostMapping("/sign-off")
@Operation(summary = "一键下签")
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_SIGN_OFF + "')")
public CommonResult<Long> signOff(@Valid @RequestBody PerformanceSheetSignOffReqVO reqVO) {
return success(performanceSheetService.signOff(reqVO));
}
@GetMapping("/{id}/download") @GetMapping("/{id}/download")
@Operation(summary = "下载单条绩效表") @Operation(summary = "下载单条绩效表")
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_EXPORT + "')") @PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_EXPORT + "')")

View File

@@ -0,0 +1,25 @@
package com.njcn.rdms.module.project.controller.admin.performance.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.web.multipart.MultipartFile;
import java.util.List;
@Schema(description = "管理后台 - 绩效压缩包导入 Request VO")
@Data
public class PerformanceSheetImportZipReqVO {
@Schema(description = "zip 文件", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "导入文件不能为空")
private MultipartFile file;
@Schema(description = "绩效月份,格式 yyyy-MM", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06")
@NotBlank(message = "绩效月份不能为空")
private String periodMonth;
@Schema(description = "限定员工 ID 列表")
private List<Long> employeeIds;
}

View File

@@ -0,0 +1,23 @@
package com.njcn.rdms.module.project.controller.admin.performance.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 绩效压缩包导入结果 Response VO")
@Data
public class PerformanceSheetImportZipRespVO {
private Integer workbookCount;
private Integer sheetCount;
private Integer successCount;
private Integer skippedCount;
private Integer failedCount;
private List<String> messages;
}

View File

@@ -38,4 +38,7 @@ public class PerformanceSheetPageReqVO extends PageParam {
@Schema(description = "状态编码", example = "sent") @Schema(description = "状态编码", example = "sent")
private String statusCode; private String statusCode;
@Schema(description = "下签状态编码", example = "pending_employee_confirm")
private String signOffStatus;
} }

View File

@@ -25,6 +25,8 @@ public class PerformanceSheetRespVO {
private Integer fileVersion; private Integer fileVersion;
private String statusCode; private String statusCode;
private String statusName; private String statusName;
private String signOffStatus;
private String signOffStatusName;
private BigDecimal actualScoreTotal; private BigDecimal actualScoreTotal;
private BigDecimal baseScoreTotal; private BigDecimal baseScoreTotal;
private BigDecimal extraScoreTotal; private BigDecimal extraScoreTotal;

View File

@@ -0,0 +1,24 @@
package com.njcn.rdms.module.project.controller.admin.performance.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 绩效一键下签 Request VO")
@Data
public class PerformanceSheetSignOffReqVO {
@Schema(description = "绩效月份,格式 yyyy-MM", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06")
@NotBlank(message = "绩效月份不能为空")
private String periodMonth;
@Schema(description = "员工 ID 列表;为空则默认全部下属")
private List<Long> employeeIds;
@Schema(description = "备注")
@Size(max = 500, message = "备注长度不能超过 500 个字符")
private String remark;
}

View File

@@ -17,6 +17,8 @@ import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.Month
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSignOffConfirmReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSignOffDetailRespVO;
import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService; import com.njcn.rdms.module.project.service.workreport.export.WorkReportContentExportService;
import com.njcn.rdms.module.project.service.workreport.monthly.MonthlyReportService; import com.njcn.rdms.module.project.service.workreport.monthly.MonthlyReportService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -111,6 +113,20 @@ public class MonthlyReportController {
return success(monthlyReportService.getMonthlyApprovalPage(reqVO)); return success(monthlyReportService.getMonthlyApprovalPage(reqVO));
} }
@GetMapping("/sign-off-page")
@Operation(summary = "获取待我确认的月报分页")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_SIGN_OFF_QUERY + "')")
public CommonResult<PageResult<MonthlyReportRespVO>> getMonthlySignOffPage(@Valid MonthlyReportPageReqVO reqVO) {
return success(monthlyReportService.getMonthlySignOffPage(reqVO));
}
@GetMapping("/{id}/sign-off-detail")
@Operation(summary = "获取月报下签详情")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_SIGN_OFF_QUERY + "')")
public CommonResult<MonthlyReportSignOffDetailRespVO> getMonthlySignOffDetail(@PathVariable("id") Long id) {
return success(monthlyReportService.getMonthlySignOffDetail(id));
}
@PostMapping("/{id}/submit") @PostMapping("/{id}/submit")
@Operation(summary = "提交月报") @Operation(summary = "提交月报")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')") @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')")
@@ -137,6 +153,15 @@ public class MonthlyReportController {
return success(true); return success(true);
} }
@PostMapping("/{id}/confirm-sign-off")
@Operation(summary = "确认月报下签")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_SIGN_OFF_CONFIRM + "')")
public CommonResult<Boolean> confirmMonthlySignOff(@PathVariable("id") Long id,
@Valid @RequestBody MonthlyReportSignOffConfirmReqVO reqVO) {
monthlyReportService.confirmMonthlySignOff(id, reqVO);
return success(true);
}
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
@Operation(summary = "删除月报") @Operation(summary = "删除月报")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_DELETE + "')") @PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_DELETE + "')")

View File

@@ -36,10 +36,14 @@ public class MonthlyReportApprovalRecordRespVO {
private String employeeSignName; private String employeeSignName;
private Long employeeSignatureFileId;
private LocalDate employeeSignedDate; private LocalDate employeeSignedDate;
private String supervisorSignName; private String supervisorSignName;
private Long supervisorSignatureFileId;
private LocalDate supervisorSignedDate; private LocalDate supervisorSignedDate;
private Long auditorUserId; private Long auditorUserId;

View File

@@ -14,4 +14,7 @@ public class MonthlyReportPageReqVO extends WorkReportBasePageReqVO {
@Schema(description = "团队视角下的填报人用户编号列表") @Schema(description = "团队视角下的填报人用户编号列表")
private List<Long> reporterIds; private List<Long> reporterIds;
@Schema(description = "下签状态编码", example = "pending_employee_confirm")
private String signOffStatus;
} }

View File

@@ -27,6 +27,8 @@ public class MonthlyReportRespVO {
private LocalDate periodEndDate; private LocalDate periodEndDate;
private String statusCode; private String statusCode;
private String statusName; private String statusName;
private String signOffStatus;
private String signOffStatusName;
private Boolean allowEdit; private Boolean allowEdit;
private Boolean terminal; private Boolean terminal;
private BigDecimal totalWorkHours; private BigDecimal totalWorkHours;

View File

@@ -0,0 +1,24 @@
package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo;
import io.swagger.v3.oas.annotations.media.Schema;
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 MonthlyReportSignOffConfirmReqVO {
@Schema(description = "面谈日期", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "面谈日期不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate meetingDate;
@Schema(description = "员工签字日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate employeeSignedDate;
}

View File

@@ -0,0 +1,13 @@
package com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 月报下签详情 Response VO")
@Data
public class MonthlyReportSignOffDetailRespVO {
private MonthlyReportRespVO report;
private MonthlyReportApprovalRecordRespVO latestApprovalRecord;
}

View File

@@ -46,6 +46,8 @@ public class PerformanceSheetDO extends BaseDO {
private String statusCode; private String statusCode;
private String signOffStatus;
private BigDecimal actualScoreTotal; private BigDecimal actualScoreTotal;
private BigDecimal baseScoreTotal; private BigDecimal baseScoreTotal;

View File

@@ -0,0 +1,47 @@
package com.njcn.rdms.module.project.dal.dataobject.performance;
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.LocalDateTime;
/**
* 绩效下签当前实例。
*/
@TableName("rdms_performance_sheet_sign_off")
@Data
@EqualsAndHashCode(callSuper = true)
public class PerformanceSheetSignOffDO extends BaseDO {
@TableId
private Long id;
private Long performanceSheetId;
private Long batchNo;
private String statusCode;
private Long issueUserId;
private String issueUserName;
private LocalDateTime issueTime;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long confirmUserId;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String confirmUserName;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDateTime confirmTime;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String lastRejectReason;
}

View File

@@ -0,0 +1,37 @@
package com.njcn.rdms.module.project.dal.dataobject.performance;
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_performance_sheet_sign_off_record")
@Data
@EqualsAndHashCode(callSuper = true)
public class PerformanceSheetSignOffRecordDO extends BaseDO {
@TableId
private Long id;
private Long signOffId;
private Long performanceSheetId;
private Long batchNo;
private String actionType;
private String fromSignOffStatus;
private String toSignOffStatus;
private String reason;
private Long operatorUserId;
private String operatorName;
}

View File

@@ -0,0 +1,33 @@
package com.njcn.rdms.module.project.dal.dataobject.signoff;
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_employee_sign_off_batch")
@Data
@EqualsAndHashCode(callSuper = true)
public class EmployeeSignOffBatchDO extends BaseDO {
@TableId
private Long id;
private String periodMonth;
private Long deptId;
private String deptName;
private Long operatorUserId;
private String operatorName;
private Integer lockedFlag;
private String remark;
}

View File

@@ -56,12 +56,18 @@ public class MonthlyReportApprovalRecordDO extends BaseDO {
@TableField(updateStrategy = FieldStrategy.ALWAYS) @TableField(updateStrategy = FieldStrategy.ALWAYS)
private String employeeSignName; private String employeeSignName;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long employeeSignatureFileId;
@TableField(updateStrategy = FieldStrategy.ALWAYS) @TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDate employeeSignedDate; private LocalDate employeeSignedDate;
@TableField(updateStrategy = FieldStrategy.ALWAYS) @TableField(updateStrategy = FieldStrategy.ALWAYS)
private String supervisorSignName; private String supervisorSignName;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long supervisorSignatureFileId;
@TableField(updateStrategy = FieldStrategy.ALWAYS) @TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDate supervisorSignedDate; private LocalDate supervisorSignedDate;

View File

@@ -47,6 +47,8 @@ public class MonthlyReportDO extends BaseDO {
private String statusCode; private String statusCode;
private String signOffStatus;
@TableField(updateStrategy = FieldStrategy.ALWAYS) @TableField(updateStrategy = FieldStrategy.ALWAYS)
private BigDecimal totalWorkHours; private BigDecimal totalWorkHours;

View File

@@ -0,0 +1,44 @@
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.LocalDateTime;
/**
* 月报下签当前实例。
*/
@TableName("rdms_work_report_monthly_sign_off")
@Data
@EqualsAndHashCode(callSuper = true)
public class MonthlyReportSignOffDO extends BaseDO {
@TableId
private Long id;
private Long monthlyReportId;
private Long batchNo;
private String statusCode;
private Long issueUserId;
private String issueUserName;
private LocalDateTime issueTime;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long confirmUserId;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String confirmUserName;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDateTime confirmTime;
}

View File

@@ -0,0 +1,37 @@
package com.njcn.rdms.module.project.dal.dataobject.workreport.monthly;
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_monthly_sign_off_record")
@Data
@EqualsAndHashCode(callSuper = true)
public class MonthlyReportSignOffRecordDO extends BaseDO {
@TableId
private Long id;
private Long signOffId;
private Long monthlyReportId;
private Long approvalRecordId;
private Long batchNo;
private String actionType;
private String fromSignOffStatus;
private String toSignOffStatus;
private Long operatorUserId;
private String operatorName;
}

View File

@@ -1,8 +1,8 @@
package com.njcn.rdms.module.project.dal.mysql.performance; package com.njcn.rdms.module.project.dal.mysql.performance;
import com.njcn.rdms.framework.common.pojo.PageResult; 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.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.module.project.constant.PerformanceConstants; import com.njcn.rdms.module.project.constant.PerformanceConstants;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO; import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO;
@@ -102,6 +102,14 @@ public interface PerformanceSheetMapper extends BaseMapperX<PerformanceSheetDO>
.eq(PerformanceSheetDO::getStatusCode, fromStatus)); .eq(PerformanceSheetDO::getStatusCode, fromStatus));
} }
default int updateByIdAndStatusAndSignOffStatus(PerformanceSheetDO update, Long id, String fromStatus,
String fromSignOffStatus) {
return update(update, new LambdaQueryWrapperX<PerformanceSheetDO>()
.eq(PerformanceSheetDO::getId, id)
.eq(PerformanceSheetDO::getStatusCode, fromStatus)
.eq(PerformanceSheetDO::getSignOffStatus, fromSignOffStatus));
}
private LambdaQueryWrapperX<PerformanceSheetDO> buildPageQuery(PerformanceSheetPageReqVO reqVO) { private LambdaQueryWrapperX<PerformanceSheetDO> buildPageQuery(PerformanceSheetPageReqVO reqVO) {
return new LambdaQueryWrapperX<PerformanceSheetDO>() return new LambdaQueryWrapperX<PerformanceSheetDO>()
.geIfPresent(PerformanceSheetDO::getPeriodMonth, reqVO.getPeriodMonthStart()) .geIfPresent(PerformanceSheetDO::getPeriodMonth, reqVO.getPeriodMonthStart())
@@ -111,6 +119,7 @@ public interface PerformanceSheetMapper extends BaseMapperX<PerformanceSheetDO>
.likeIfPresent(PerformanceSheetDO::getEmployeeDeptName, reqVO.getEmployeeDeptName()) .likeIfPresent(PerformanceSheetDO::getEmployeeDeptName, reqVO.getEmployeeDeptName())
.eqIfPresent(PerformanceSheetDO::getManagerId, reqVO.getManagerId()) .eqIfPresent(PerformanceSheetDO::getManagerId, reqVO.getManagerId())
.likeIfPresent(PerformanceSheetDO::getManagerName, reqVO.getManagerName()) .likeIfPresent(PerformanceSheetDO::getManagerName, reqVO.getManagerName())
.eqIfPresent(PerformanceSheetDO::getStatusCode, reqVO.getStatusCode()); .eqIfPresent(PerformanceSheetDO::getStatusCode, reqVO.getStatusCode())
.eqIfPresent(PerformanceSheetDO::getSignOffStatus, reqVO.getSignOffStatus());
} }
} }

View File

@@ -0,0 +1,22 @@
package com.njcn.rdms.module.project.dal.mysql.performance;
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.performance.PerformanceSheetSignOffDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PerformanceSheetSignOffMapper extends BaseMapperX<PerformanceSheetSignOffDO> {
default PerformanceSheetSignOffDO selectByPerformanceSheetId(Long performanceSheetId) {
return selectOne(new LambdaQueryWrapperX<PerformanceSheetSignOffDO>()
.eq(PerformanceSheetSignOffDO::getPerformanceSheetId, performanceSheetId));
}
default int updateByPerformanceSheetIdAndStatus(PerformanceSheetSignOffDO update, Long performanceSheetId,
String fromStatus) {
return update(update, new LambdaQueryWrapperX<PerformanceSheetSignOffDO>()
.eq(PerformanceSheetSignOffDO::getPerformanceSheetId, performanceSheetId)
.eq(PerformanceSheetSignOffDO::getStatusCode, fromStatus));
}
}

View File

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

View File

@@ -0,0 +1,16 @@
package com.njcn.rdms.module.project.dal.mysql.signoff;
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.signoff.EmployeeSignOffBatchDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmployeeSignOffBatchMapper extends BaseMapperX<EmployeeSignOffBatchDO> {
default EmployeeSignOffBatchDO selectByPeriodMonthAndDeptId(String periodMonth, Long deptId) {
return selectOne(new LambdaQueryWrapperX<EmployeeSignOffBatchDO>()
.eq(EmployeeSignOffBatchDO::getPeriodMonth, periodMonth)
.eq(EmployeeSignOffBatchDO::getDeptId, deptId));
}
}

View File

@@ -26,4 +26,13 @@ public interface MonthlyReportApprovalRecordMapper extends BaseMapperX<MonthlyRe
return delete(new LambdaQueryWrapperX<MonthlyReportApprovalRecordDO>() return delete(new LambdaQueryWrapperX<MonthlyReportApprovalRecordDO>()
.eq(MonthlyReportApprovalRecordDO::getMonthlyReportId, monthlyReportId)); .eq(MonthlyReportApprovalRecordDO::getMonthlyReportId, monthlyReportId));
} }
default MonthlyReportApprovalRecordDO selectLatestApprovedByMonthlyReportId(Long monthlyReportId) {
return selectOne(new LambdaQueryWrapperX<MonthlyReportApprovalRecordDO>()
.eq(MonthlyReportApprovalRecordDO::getMonthlyReportId, monthlyReportId)
.eq(MonthlyReportApprovalRecordDO::getConclusion, "approved")
.orderByDesc(MonthlyReportApprovalRecordDO::getApprovalRound)
.orderByDesc(MonthlyReportApprovalRecordDO::getId)
.last("LIMIT 1"));
}
} }

View File

@@ -147,6 +147,14 @@ public interface MonthlyReportMapper extends BaseMapperX<MonthlyReportDO> {
.eq(MonthlyReportDO::getStatusCode, fromStatus)); .eq(MonthlyReportDO::getStatusCode, fromStatus));
} }
default int updateByIdAndStatusAndSignOffStatus(MonthlyReportDO update, Long id, String fromStatus,
String fromSignOffStatus) {
return update(update, new LambdaQueryWrapperX<MonthlyReportDO>()
.eq(MonthlyReportDO::getId, id)
.eq(MonthlyReportDO::getStatusCode, fromStatus)
.eq(MonthlyReportDO::getSignOffStatus, fromSignOffStatus));
}
default int updateByIdAndStatusesAndReporterId(MonthlyReportDO update, Long id, Collection<String> statuses, default int updateByIdAndStatusesAndReporterId(MonthlyReportDO update, Long id, Collection<String> statuses,
Long reporterId) { Long reporterId) {
return update(update, new LambdaQueryWrapperX<MonthlyReportDO>() return update(update, new LambdaQueryWrapperX<MonthlyReportDO>()
@@ -177,6 +185,7 @@ public interface MonthlyReportMapper extends BaseMapperX<MonthlyReportDO> {
private LambdaQueryWrapperX<MonthlyReportDO> buildPageQuery(MonthlyReportPageReqVO reqVO) { private LambdaQueryWrapperX<MonthlyReportDO> buildPageQuery(MonthlyReportPageReqVO reqVO) {
LambdaQueryWrapperX<MonthlyReportDO> wrapper = new LambdaQueryWrapperX<MonthlyReportDO>() LambdaQueryWrapperX<MonthlyReportDO> wrapper = new LambdaQueryWrapperX<MonthlyReportDO>()
.eqIfPresent(MonthlyReportDO::getStatusCode, reqVO.getStatusCode()) .eqIfPresent(MonthlyReportDO::getStatusCode, reqVO.getStatusCode())
.eqIfPresent(MonthlyReportDO::getSignOffStatus, reqVO.getSignOffStatus())
.betweenIfPresent(MonthlyReportDO::getPeriodStartDate, reqVO.getPeriodStartDate()) .betweenIfPresent(MonthlyReportDO::getPeriodStartDate, reqVO.getPeriodStartDate())
.betweenIfPresent(MonthlyReportDO::getSubmitTime, reqVO.getSubmitTime()); .betweenIfPresent(MonthlyReportDO::getSubmitTime, reqVO.getSubmitTime());
if (StringUtils.hasText(reqVO.getKeyword())) { if (StringUtils.hasText(reqVO.getKeyword())) {

View File

@@ -0,0 +1,22 @@
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.MonthlyReportSignOffDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MonthlyReportSignOffMapper extends BaseMapperX<MonthlyReportSignOffDO> {
default MonthlyReportSignOffDO selectByMonthlyReportId(Long monthlyReportId) {
return selectOne(new LambdaQueryWrapperX<MonthlyReportSignOffDO>()
.eq(MonthlyReportSignOffDO::getMonthlyReportId, monthlyReportId));
}
default int updateByMonthlyReportIdAndStatus(MonthlyReportSignOffDO update, Long monthlyReportId,
String fromStatus) {
return update(update, new LambdaQueryWrapperX<MonthlyReportSignOffDO>()
.eq(MonthlyReportSignOffDO::getMonthlyReportId, monthlyReportId)
.eq(MonthlyReportSignOffDO::getStatusCode, fromStatus));
}
}

View File

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

View File

@@ -11,6 +11,7 @@ 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.permission.UserVisibilityConfigApi;
import com.njcn.rdms.module.system.api.user.AdminUserApi; import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; import com.njcn.rdms.module.system.api.user.UserManagementRelationApi;
import com.njcn.rdms.module.system.api.user.UserSignatureApi;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -22,6 +23,6 @@ import org.springframework.context.annotation.Configuration;
{AdminUserApi.class, ObjectPermissionApi.class, DictDataApi.class, FileApi.class, {AdminUserApi.class, ObjectPermissionApi.class, DictDataApi.class, FileApi.class,
PermissionApi.class, OrgLeaderApi.class, UserVisibilityConfigApi.class, PermissionApi.class, OrgLeaderApi.class, UserVisibilityConfigApi.class,
DeptApi.class, PostApi.class, UserManagementRelationApi.class, DeptApi.class, PostApi.class, UserManagementRelationApi.class,
NotifyMessageSendApi.class}) NotifyMessageSendApi.class, UserSignatureApi.class})
public class RpcConfiguration { public class RpcConfiguration {
} }

View File

@@ -4,10 +4,13 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceMonthlyResultRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceMonthlyResultRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetCreateReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetCreateReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetExcelUpdateReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetExcelUpdateReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetImportZipReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetImportZipRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPrefillRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPrefillRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetResponseRecordRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetResponseRecordRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetSignOffReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusActionReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusActionReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusDictRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusDictRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusLogRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusLogRespVO;
@@ -36,6 +39,10 @@ public interface PerformanceSheetService {
void reject(Long id, PerformanceSheetStatusActionReqVO reqVO); void reject(Long id, PerformanceSheetStatusActionReqVO reqVO);
PerformanceSheetImportZipRespVO importZip(PerformanceSheetImportZipReqVO reqVO) throws Exception;
Long signOff(PerformanceSheetSignOffReqVO reqVO);
PerformanceDownloadFile download(Long id); PerformanceDownloadFile download(Long id);
PerformanceDownloadFile batchDownload(Collection<Long> ids); PerformanceDownloadFile batchDownload(Collection<Long> ids);

View File

@@ -6,37 +6,52 @@ import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.PerformanceExcelCellConstants; import com.njcn.rdms.module.project.constant.PerformanceExcelCellConstants;
import com.njcn.rdms.module.project.constant.PerformanceConstants; import com.njcn.rdms.module.project.constant.PerformanceConstants;
import com.njcn.rdms.module.project.constant.SignOffConstants;
import com.njcn.rdms.module.project.constant.WorkReportConstants;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceMonthlyResultRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceMonthlyResultRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetCreateReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetCreateReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetExcelUpdateReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetExcelUpdateReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetImportZipReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetImportZipRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPrefillRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetPrefillRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetResponseRecordRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetResponseRecordRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetSignOffReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusActionReqVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusActionReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusDictRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusDictRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusLogRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusLogRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusTransitionRespVO; import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceSheetStatusTransitionRespVO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO; import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetResponseRecordDO; import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetResponseRecordDO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetSignOffDO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetSignOffRecordDO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetStatusLogDO; import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetStatusLogDO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceTemplateDO; import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceTemplateDO;
import com.njcn.rdms.module.project.dal.dataobject.signoff.EmployeeSignOffBatchDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO; 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.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetMapper; import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetMapper;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetResponseRecordMapper; import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetResponseRecordMapper;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetSignOffMapper;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetSignOffRecordMapper;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetStatusLogMapper; import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceTemplateMapper; import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceTemplateMapper;
import com.njcn.rdms.module.project.dal.mysql.signoff.EmployeeSignOffBatchMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper; 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.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportApprovalRecordMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants; import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent; import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants; import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
import com.njcn.rdms.module.project.service.signoff.EmployeeSignOffStatusService;
import com.njcn.rdms.module.project.service.status.StatusActionTextResolver; import com.njcn.rdms.module.project.service.status.StatusActionTextResolver;
import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService; import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService;
import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService;
import com.njcn.rdms.module.system.api.dept.DeptApi; import com.njcn.rdms.module.system.api.dept.DeptApi;
import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO; import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO;
import com.njcn.rdms.module.system.api.file.FileApi; import com.njcn.rdms.module.system.api.file.FileApi;
import com.njcn.rdms.module.system.api.file.dto.FileContentCreateReqDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi; import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; import com.njcn.rdms.module.system.api.user.UserManagementRelationApi;
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
@@ -71,13 +86,18 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.YearMonth; import java.time.YearMonth;
@@ -95,6 +115,8 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
@@ -116,12 +138,22 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
@Resource @Resource
private PerformanceSheetResponseRecordMapper performanceSheetResponseRecordMapper; private PerformanceSheetResponseRecordMapper performanceSheetResponseRecordMapper;
@Resource @Resource
private PerformanceSheetSignOffMapper performanceSheetSignOffMapper;
@Resource
private PerformanceSheetSignOffRecordMapper performanceSheetSignOffRecordMapper;
@Resource
private EmployeeSignOffBatchMapper employeeSignOffBatchMapper;
@Resource
private MonthlyReportApprovalRecordMapper monthlyReportApprovalRecordMapper;
@Resource
private ObjectStatusModelMapper objectStatusModelMapper; private ObjectStatusModelMapper objectStatusModelMapper;
@Resource @Resource
private ObjectStatusTransitionMapper objectStatusTransitionMapper; private ObjectStatusTransitionMapper objectStatusTransitionMapper;
@Resource @Resource
private StatusActionTextResolver statusActionTextResolver; private StatusActionTextResolver statusActionTextResolver;
@Resource @Resource
private EmployeeSignOffStatusService employeeSignOffStatusService;
@Resource
private AdminUserApi adminUserApi; private AdminUserApi adminUserApi;
@Resource @Resource
private DeptApi deptApi; private DeptApi deptApi;
@@ -130,9 +162,13 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
@Resource @Resource
private FileApi fileApi; private FileApi fileApi;
@Resource @Resource
private WorkReportCommonService workReportCommonService;
@Resource
private TeamDashboardAccessService teamDashboardAccessService; private TeamDashboardAccessService teamDashboardAccessService;
@Resource @Resource
private ApplicationEventPublisher applicationEventPublisher; private ApplicationEventPublisher applicationEventPublisher;
@Resource
private TransactionTemplate transactionTemplate;
@Override @Override
public PageResult<PerformanceSheetRespVO> getSheetPage(PerformanceSheetPageReqVO reqVO) { public PageResult<PerformanceSheetRespVO> getSheetPage(PerformanceSheetPageReqVO reqVO) {
@@ -183,6 +219,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
sheet.setFileName(buildPerformanceFileName(periodMonth, employee.getNickname())); sheet.setFileName(buildPerformanceFileName(periodMonth, employee.getNickname()));
sheet.setFileVersion(0); sheet.setFileVersion(0);
sheet.setStatusCode(getInitialStatusCode()); sheet.setStatusCode(getInitialStatusCode());
sheet.setSignOffStatus(SignOffConstants.STATUS_NOT_STARTED);
performanceSheetMapper.insert(sheet); performanceSheetMapper.insert(sheet);
return sheet.getId(); return sheet.getId();
} }
@@ -248,6 +285,158 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
publishPerformanceNotice(List.of(sheet.getManagerId()), NotifyTemplateCodeConstants.PERFORMANCE_REJECTED, sheet); publishPerformanceNotice(List.of(sheet.getManagerId()), NotifyTemplateCodeConstants.PERFORMANCE_REJECTED, sheet);
} }
@Override
public PerformanceSheetImportZipRespVO importZip(PerformanceSheetImportZipReqVO reqVO) throws Exception {
MultipartFile file = reqVO.getFile();
if (file == null || file.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_IMPORT_ZIP_EMPTY);
}
String periodMonth = normalizePeriodMonth(reqVO.getPeriodMonth());
CurrentOperatorProfile operatorProfile = loadCurrentOperatorProfile();
validateBatchUnlocked(periodMonth, operatorProfile.deptId());
List<Long> candidateEmployeeIds = resolveRequestedEmployeeIds(reqVO.getEmployeeIds(),
PerformanceConstants.PERMISSION_SHEET_IMPORT);
if (candidateEmployeeIds.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_IMPORT_TARGET_EMPTY);
}
Map<String, List<PerformanceSheetDO>> sheetsByEmployeeName = performanceSheetMapper
.selectListByEmployeeIdsAndPeriodMonth(candidateEmployeeIds, periodMonth)
.stream()
.collect(java.util.stream.Collectors.groupingBy(PerformanceSheetDO::getEmployeeName,
LinkedHashMap::new, java.util.stream.Collectors.toList()));
if (sheetsByEmployeeName.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_IMPORT_TARGET_EMPTY);
}
List<String> messages = new ArrayList<>();
int workbookCount = 0;
int sheetCount = 0;
int successCount = 0;
int skippedCount = 0;
int failedCount = 0;
List<ImportedWorkbookEntry> workbookEntries = readZipWorkbookEntries(file.getBytes());
for (ImportedWorkbookEntry workbookEntry : workbookEntries) {
if (shouldIgnoreImportedWorkbook(workbookEntry.displayName())) {
skippedCount++;
messages.add("跳过工作簿:" + workbookEntry.displayName() + ",原因:兼容忽略 0_绩效表汇总");
log.info("导入绩效压缩包时跳过工作簿workbookName={}, reason={}",
workbookEntry.displayName(), "兼容忽略 0_绩效表汇总");
continue;
}
workbookCount++;
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(workbookEntry.content());
Workbook workbook = WorkbookFactory.create(inputStream)) {
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
DataFormatter formatter = new DataFormatter();
for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
Sheet importedSheet = workbook.getSheetAt(sheetIndex);
sheetCount++;
try {
importOneSheet(periodMonth, workbookEntry.displayName(), importedSheet, evaluator, formatter,
sheetsByEmployeeName);
successCount++;
} catch (Exception ex) {
failedCount++;
String message = "导入失败:" + workbookEntry.displayName() + " / " + importedSheet.getSheetName()
+ ",原因:" + ex.getMessage();
messages.add(message);
log.warn("导入绩效 sheet 失败workbookName={}, sheetName={}, reason={}",
workbookEntry.displayName(), importedSheet.getSheetName(), ex.getMessage(), ex);
}
}
}
}
PerformanceSheetImportZipRespVO respVO = new PerformanceSheetImportZipRespVO();
respVO.setWorkbookCount(workbookCount);
respVO.setSheetCount(sheetCount);
respVO.setSuccessCount(successCount);
respVO.setSkippedCount(skippedCount);
respVO.setFailedCount(failedCount);
respVO.setMessages(messages);
return respVO;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long signOff(PerformanceSheetSignOffReqVO reqVO) {
String periodMonth = normalizePeriodMonth(reqVO.getPeriodMonth());
CurrentOperatorProfile operatorProfile = loadCurrentOperatorProfile();
validateBatchUnlocked(periodMonth, operatorProfile.deptId());
boolean explicitSelection = reqVO.getEmployeeIds() != null && !reqVO.getEmployeeIds().isEmpty();
List<Long> requestedEmployeeIds = resolveRequestedEmployeeIds(reqVO.getEmployeeIds(),
PerformanceConstants.PERMISSION_SHEET_SIGN_OFF);
if (requestedEmployeeIds.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_CANDIDATE_EMPTY);
}
List<PerformanceSheetDO> performanceSheets = performanceSheetMapper
.selectListByEmployeeIdsAndPeriodMonth(requestedEmployeeIds, periodMonth);
List<PerformanceSheetDO> eligiblePerformanceSheets = performanceSheets.stream()
.filter(this::isEligibleForIssue)
.toList();
Map<Long, PerformanceSheetDO> performanceByEmployeeId = eligiblePerformanceSheets.stream()
.collect(java.util.stream.Collectors.toMap(PerformanceSheetDO::getEmployeeId, item -> item,
(left, right) -> left, LinkedHashMap::new));
List<com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO> monthlyReports =
workReportCommonService.listMonthlyReportsForSignOff(requestedEmployeeIds, periodMonth);
List<com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO> eligibleMonthlyReports =
monthlyReports.stream()
.filter(this::isEligibleMonthlyReportForIssue)
.filter(report -> hasLatestApprovedRecord(report.getId()))
.toList();
Map<Long, com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO> monthlyByEmployeeId =
eligibleMonthlyReports.stream()
.collect(java.util.stream.Collectors.toMap(
com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO::getReporterId,
item -> item, (left, right) -> left, LinkedHashMap::new));
List<Long> targetEmployeeIds = new ArrayList<>();
if (explicitSelection) {
for (Long employeeId : requestedEmployeeIds) {
if (!performanceByEmployeeId.containsKey(employeeId) || !monthlyByEmployeeId.containsKey(employeeId)) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_CANDIDATE_EMPTY);
}
targetEmployeeIds.add(employeeId);
}
} else {
for (Long employeeId : requestedEmployeeIds) {
if (performanceByEmployeeId.containsKey(employeeId) && monthlyByEmployeeId.containsKey(employeeId)) {
targetEmployeeIds.add(employeeId);
}
}
if (targetEmployeeIds.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_CANDIDATE_EMPTY);
}
}
List<PerformanceSheetDO> targetSheets = targetEmployeeIds.stream()
.map(performanceByEmployeeId::get)
.filter(Objects::nonNull)
.toList();
List<com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO> targetReports =
targetEmployeeIds.stream()
.map(monthlyByEmployeeId::get)
.filter(Objects::nonNull)
.toList();
EmployeeSignOffBatchDO batch = new EmployeeSignOffBatchDO();
batch.setPeriodMonth(periodMonth);
batch.setDeptId(operatorProfile.deptId());
batch.setDeptName(operatorProfile.deptName());
batch.setOperatorUserId(operatorProfile.userId());
batch.setOperatorName(operatorProfile.userName());
batch.setLockedFlag(1);
batch.setRemark(normalizeNullableText(reqVO.getRemark()));
employeeSignOffBatchMapper.insert(batch);
issuePerformanceSignOff(targetSheets, batch.getId());
workReportCommonService.issueMonthlySignOff(targetReports, batch.getId());
return batch.getId();
}
@Override @Override
public PerformanceDownloadFile download(Long id) { public PerformanceDownloadFile download(Long id) {
PerformanceSheetDO sheet = validateReadableSheet(id); PerformanceSheetDO sheet = validateReadableSheet(id);
@@ -393,23 +582,38 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private PerformanceSheetDO processEmployeeResponse(Long id, String actionType, String reason) { private PerformanceSheetDO processEmployeeResponse(Long id, String actionType, String reason) {
PerformanceSheetDO current = validateSheetExists(id); PerformanceSheetDO current = validateSheetExists(id);
validateEmployeeOnly(current.getEmployeeId()); validateEmployeeOnly(current.getEmployeeId());
PerformanceEmployeeResponseContext responseContext = resolvePerformanceEmployeeResponseContext(current);
ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionType, reason); ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionType, reason);
PerformanceSheetDO update = new PerformanceSheetDO(); PerformanceSheetDO update = new PerformanceSheetDO();
update.setStatusCode(transition.getToStatusCode()); update.setStatusCode(transition.getToStatusCode());
if (PerformanceConstants.STATUS_CONFIRMED.equals(transition.getToStatusCode())) { if (PerformanceConstants.STATUS_CONFIRMED.equals(transition.getToStatusCode())) {
update.setConfirmedTime(LocalDateTime.now()); update.setConfirmedTime(LocalDateTime.now());
if (responseContext.signOffFlow()) {
update.setSignOffStatus(SignOffConstants.STATUS_EMPLOYEE_CONFIRMED);
}
} else if (PerformanceConstants.STATUS_REJECTED.equals(transition.getToStatusCode())) { } else if (PerformanceConstants.STATUS_REJECTED.equals(transition.getToStatusCode())) {
update.setRejectedTime(LocalDateTime.now()); update.setRejectedTime(LocalDateTime.now());
update.setLastStatusReason(reason); update.setLastStatusReason(reason);
if (responseContext.signOffFlow()) {
update.setSignOffStatus(SignOffConstants.STATUS_EMPLOYEE_REJECTED);
} }
int updateCount = performanceSheetMapper.updateStatusByIdAndStatus(update, id, current.getStatusCode()); }
int updateCount = performanceSheetMapper.updateByIdAndStatusAndSignOffStatus(update, id, current.getStatusCode(),
current.getSignOffStatus());
if (updateCount != 1) { if (updateCount != 1) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED); throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
} }
if (responseContext.signOffFlow()) {
updatePerformanceSignOffAfterEmployeeAction(current, responseContext.signOff(), actionType, reason);
}
PerformanceSheetDO after = merge(current, update); PerformanceSheetDO after = merge(current, update);
PerformanceSheetStatusLogDO statusLog = writeStatusLog(after, actionType, current.getStatusCode(), PerformanceSheetStatusLogDO statusLog = writeStatusLog(after, actionType, current.getStatusCode(),
transition.getToStatusCode(), reason); transition.getToStatusCode(), reason);
writeResponseRecord(after, statusLog, actionType, current.getStatusCode(), transition.getToStatusCode(), reason); writeResponseRecord(after, statusLog, actionType, current.getStatusCode(), transition.getToStatusCode(), reason);
if (PerformanceConstants.ACTION_CONFIRM.equals(actionType)) {
workReportCommonService.syncPerformanceResult(after.getEmployeeId(), after.getPeriodMonth(),
formatScoreForMonthlyResult(after.getActualScoreTotal()));
}
return after; return after;
} }
@@ -527,6 +731,11 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private PerformanceSheetRespVO toRespVO(PerformanceSheetDO sheet) { private PerformanceSheetRespVO toRespVO(PerformanceSheetDO sheet) {
PerformanceSheetRespVO respVO = BeanUtils.toBean(sheet, PerformanceSheetRespVO.class); PerformanceSheetRespVO respVO = BeanUtils.toBean(sheet, PerformanceSheetRespVO.class);
respVO.setStatusName(statusName(sheet.getStatusCode())); respVO.setStatusName(statusName(sheet.getStatusCode()));
String signOffStatus = StringUtils.hasText(sheet.getSignOffStatus())
? sheet.getSignOffStatus()
: SignOffConstants.STATUS_NOT_STARTED;
respVO.setSignOffStatus(signOffStatus);
respVO.setSignOffStatusName(employeeSignOffStatusService.statusName(signOffStatus));
return respVO; return respVO;
} }
@@ -1095,16 +1304,21 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
Sheet sourceSheet = workbook.getSheetAt(0); Sheet sourceSheet = workbook.getSheetAt(0);
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
DataFormatter formatter = new DataFormatter(); DataFormatter formatter = new DataFormatter();
return readSelectedCellValues(sourceSheet, evaluator, formatter, cellAddresses);
}
}
private Map<String, String> readSelectedCellValues(Sheet sheet, FormulaEvaluator evaluator,
DataFormatter formatter, List<String> cellAddresses) {
Map<String, String> cellValues = new LinkedHashMap<>(); Map<String, String> cellValues = new LinkedHashMap<>();
for (String cellAddress : cellAddresses) { for (String cellAddress : cellAddresses) {
String value = readCellDisplayValue(sourceSheet, evaluator, formatter, cellAddress); String value = readCellDisplayValue(sheet, evaluator, formatter, cellAddress);
if (value != null) { if (value != null) {
cellValues.put(cellAddress, value); cellValues.put(cellAddress, value);
} }
} }
return cellValues; return cellValues;
} }
}
private String readCellDisplayValue(Sheet sheet, private String readCellDisplayValue(Sheet sheet,
FormulaEvaluator evaluator, FormulaEvaluator evaluator,
@@ -1123,6 +1337,348 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
return StringUtils.hasText(value) ? value.trim() : null; return StringUtils.hasText(value) ? value.trim() : null;
} }
private void importOneSheet(String periodMonth, String workbookName, Sheet importedSheet,
FormulaEvaluator evaluator, DataFormatter formatter,
Map<String, List<PerformanceSheetDO>> sheetsByEmployeeName) {
transactionTemplate.executeWithoutResult(status -> {
String sheetName = importedSheet.getSheetName();
String employeeName = parseEmployeeNameFromSheetName(periodMonth, sheetName);
List<PerformanceSheetDO> matchedSheets = sheetsByEmployeeName.getOrDefault(employeeName, List.of());
if (matchedSheets.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_IMPORT_TARGET_EMPTY);
}
if (matchedSheets.size() > 1) {
throw invalidParamException("存在重名员工,无法唯一匹配:" + employeeName);
}
PerformanceSheetDO currentSheet = matchedSheets.get(0);
Map<String, String> importedValues = readSelectedCellValues(importedSheet, evaluator, formatter,
PerformanceExcelCellConstants.MERGED_EXPORT_CELL_ADDRESSES);
if (importedValues.isEmpty()) {
throw invalidParamException("未读取到可回写的绩效单元格");
}
applyImportedSheetValues(currentSheet, workbookName, sheetName, importedValues);
});
}
private void applyImportedSheetValues(PerformanceSheetDO currentSheet, String workbookName, String sheetName,
Map<String, String> importedValues) {
byte[] sourceContent = readFileContent(currentSheet);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(sourceContent);
Workbook workbook = WorkbookFactory.create(inputStream);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
if (workbook.getNumberOfSheets() <= 0) {
throw invalidParamException("原始绩效文件缺少 sheet 页");
}
Sheet targetSheet = workbook.getSheetAt(0);
writeMergedExportCellValues(targetSheet, importedValues);
workbook.setForceFormulaRecalculation(true);
FormulaEvaluator targetEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
targetEvaluator.evaluateAll();
DataFormatter formatter = new DataFormatter();
BigDecimal actualScoreTotal = parseScore(readCellDisplayValue(targetSheet, targetEvaluator, formatter,
PerformanceExcelCellConstants.ACTUAL_SCORE_TOTAL_CELL));
BigDecimal baseScoreTotal = parseScore(readCellDisplayValue(targetSheet, targetEvaluator, formatter,
PerformanceExcelCellConstants.BASE_SCORE_TOTAL_CELL));
BigDecimal extraScoreTotal = parseScore(readCellDisplayValue(targetSheet, targetEvaluator, formatter,
PerformanceExcelCellConstants.EXTRA_SCORE_TOTAL_CELL));
workbook.write(outputStream);
FileContentCreateReqDTO fileCreateReqDTO = new FileContentCreateReqDTO();
fileCreateReqDTO.setContent(outputStream.toByteArray());
fileCreateReqDTO.setName(defaultDownloadName(currentSheet));
fileCreateReqDTO.setDirectory("rdms/performance");
fileCreateReqDTO.setType(resolveExcelContentType(defaultDownloadName(currentSheet)));
CommonResult<Long> createFileResult = fileApi.createFileByContent(fileCreateReqDTO);
Long newFileId = createFileResult == null ? null : createFileResult.getCheckedData();
if (newFileId == null) {
throw new IllegalStateException("上传更新后的绩效文件失败");
}
PerformanceSheetDO update = new PerformanceSheetDO();
update.setFileId(newFileId);
update.setFileName(defaultDownloadName(currentSheet));
update.setFileVersion(currentSheet.getFileVersion() == null ? 1 : currentSheet.getFileVersion() + 1);
update.setActualScoreTotal(actualScoreTotal);
update.setBaseScoreTotal(baseScoreTotal);
update.setExtraScoreTotal(extraScoreTotal);
int updateCount = performanceSheetMapper.updateExcelByIdAndVersion(update, currentSheet.getId(),
currentSheet.getFileVersion());
if (updateCount != 1) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
}
workReportCommonService.syncPerformanceResult(currentSheet.getEmployeeId(), currentSheet.getPeriodMonth(),
formatScoreForMonthlyResult(actualScoreTotal));
log.info("导入绩效 sheet 成功workbookName={}, sheetName={}, sheetId={}, employeeId={}",
workbookName, sheetName, currentSheet.getId(), currentSheet.getEmployeeId());
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new IllegalStateException("回写绩效 Excel 失败", ex);
}
}
private List<ImportedWorkbookEntry> readZipWorkbookEntries(byte[] zipBytes) throws IOException {
List<Charset> charsets = List.of(StandardCharsets.UTF_8, Charset.forName("GBK"));
Exception lastException = null;
for (Charset charset : charsets) {
try {
return readZipWorkbookEntries(zipBytes, charset);
} catch (Exception ex) {
lastException = ex;
}
}
if (lastException instanceof IOException ioException) {
throw ioException;
}
throw new IOException("读取 zip 文件失败", lastException);
}
private List<ImportedWorkbookEntry> readZipWorkbookEntries(byte[] zipBytes, Charset charset) throws IOException {
List<ImportedWorkbookEntry> workbookEntries = new ArrayList<>();
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(zipBytes);
ZipInputStream zipInputStream = new ZipInputStream(inputStream, charset)) {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
String displayName = extractEntryDisplayName(entry.getName());
if (!isExcelEntry(displayName)) {
continue;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
zipInputStream.transferTo(outputStream);
workbookEntries.add(new ImportedWorkbookEntry(displayName, outputStream.toByteArray()));
}
}
return workbookEntries;
}
private String extractEntryDisplayName(String entryName) {
if (!StringUtils.hasText(entryName)) {
return "";
}
String normalized = entryName.replace("\\", "/");
int index = normalized.lastIndexOf('/');
return index >= 0 ? normalized.substring(index + 1) : normalized;
}
private boolean isExcelEntry(String fileName) {
if (!StringUtils.hasText(fileName)) {
return false;
}
String lowerCase = fileName.toLowerCase();
return lowerCase.endsWith(".xlsx") || lowerCase.endsWith(".xlsm") || lowerCase.endsWith(".xls");
}
private boolean shouldIgnoreImportedWorkbook(String workbookName) {
return StringUtils.hasText(workbookName) && workbookName.startsWith("0_绩效表汇总");
}
private String parseEmployeeNameFromSheetName(String periodMonth, String sheetName) {
if (!StringUtils.hasText(sheetName)) {
throw invalidParamException("sheet 名不能为空");
}
String normalizedName = sheetName.trim();
int splitIndex = normalizedName.indexOf('_');
if (splitIndex <= 0 || splitIndex >= normalizedName.length() - 1) {
throw invalidParamException("sheet 名不符合“月份_姓名”规则" + normalizedName);
}
String monthPart = normalizedName.substring(0, splitIndex).trim();
if (!periodMonth.equals(monthPart)) {
throw invalidParamException("sheet 月份与导入月份不一致:" + normalizedName);
}
String employeeName = normalizedName.substring(splitIndex + 1).trim();
if (!StringUtils.hasText(employeeName)) {
throw invalidParamException("sheet 名未包含员工姓名:" + normalizedName);
}
return employeeName;
}
private PerformanceEmployeeResponseContext resolvePerformanceEmployeeResponseContext(PerformanceSheetDO sheet) {
if (!PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode())) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_NOT_ALLOWED);
}
if (SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM.equals(sheet.getSignOffStatus())) {
PerformanceSheetSignOffDO signOff = validatePerformanceSignOffExists(sheet.getId());
if (!SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM.equals(signOff.getStatusCode())) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_NOT_ALLOWED);
}
return new PerformanceEmployeeResponseContext(signOff, true);
}
if (SignOffConstants.STATUS_EMPLOYEE_REJECTED.equals(sheet.getSignOffStatus())) {
PerformanceSheetSignOffDO signOff = validatePerformanceSignOffExists(sheet.getId());
if (!SignOffConstants.STATUS_EMPLOYEE_REJECTED.equals(signOff.getStatusCode())) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_NOT_ALLOWED);
}
return new PerformanceEmployeeResponseContext(signOff, false);
}
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_NOT_ALLOWED);
}
private PerformanceSheetSignOffDO validatePerformanceSignOffExists(Long sheetId) {
PerformanceSheetSignOffDO signOff = performanceSheetSignOffMapper.selectByPerformanceSheetId(sheetId);
if (signOff == null) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_NOT_EXISTS);
}
return signOff;
}
private void updatePerformanceSignOffAfterEmployeeAction(PerformanceSheetDO current, PerformanceSheetSignOffDO signOff,
String actionType, String reason) {
PerformanceSheetSignOffDO signOffUpdate = new PerformanceSheetSignOffDO();
signOffUpdate.setStatusCode(PerformanceConstants.ACTION_CONFIRM.equals(actionType)
? SignOffConstants.STATUS_EMPLOYEE_CONFIRMED
: SignOffConstants.STATUS_EMPLOYEE_REJECTED);
signOffUpdate.setConfirmUserId(SecurityFrameworkUtils.getLoginUserId());
signOffUpdate.setConfirmUserName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
signOffUpdate.setConfirmTime(LocalDateTime.now());
signOffUpdate.setLastRejectReason(PerformanceConstants.ACTION_REJECT.equals(actionType) ? reason : null);
int signOffUpdateCount = performanceSheetSignOffMapper.updateByPerformanceSheetIdAndStatus(signOffUpdate,
current.getId(), signOff.getStatusCode());
if (signOffUpdateCount != 1) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
}
writePerformanceSignOffRecord(signOff.getId(), current.getId(), signOff.getBatchNo(), actionType,
signOff.getStatusCode(), signOffUpdate.getStatusCode(), reason);
}
private void issuePerformanceSignOff(List<PerformanceSheetDO> sheets, Long batchNo) {
if (sheets == null || sheets.isEmpty()) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_CANDIDATE_EMPTY);
}
LocalDateTime issueTime = LocalDateTime.now();
Long operatorUserId = SecurityFrameworkUtils.getLoginUserId();
String operatorName = defaultText(SecurityFrameworkUtils.getLoginUserNickname());
for (PerformanceSheetDO sheet : sheets) {
PerformanceSheetDO update = new PerformanceSheetDO();
update.setSignOffStatus(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
int updateCount = performanceSheetMapper.updateByIdAndStatusAndSignOffStatus(update, sheet.getId(),
sheet.getStatusCode(), sheet.getSignOffStatus());
if (updateCount != 1) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
}
PerformanceSheetSignOffDO currentSignOff = performanceSheetSignOffMapper.selectByPerformanceSheetId(sheet.getId());
if (currentSignOff == null) {
PerformanceSheetSignOffDO signOff = new PerformanceSheetSignOffDO();
signOff.setPerformanceSheetId(sheet.getId());
signOff.setBatchNo(batchNo);
signOff.setStatusCode(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
signOff.setIssueUserId(operatorUserId);
signOff.setIssueUserName(operatorName);
signOff.setIssueTime(issueTime);
performanceSheetSignOffMapper.insert(signOff);
writePerformanceSignOffRecord(signOff.getId(), sheet.getId(), batchNo, SignOffConstants.ACTION_ISSUE,
SignOffConstants.STATUS_NOT_STARTED, SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM, null);
} else {
employeeSignOffStatusService.validateTransition(currentSignOff.getStatusCode(), SignOffConstants.ACTION_ISSUE, null);
PerformanceSheetSignOffDO signOffUpdate = new PerformanceSheetSignOffDO();
signOffUpdate.setStatusCode(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
signOffUpdate.setBatchNo(batchNo);
signOffUpdate.setIssueUserId(operatorUserId);
signOffUpdate.setIssueUserName(operatorName);
signOffUpdate.setIssueTime(issueTime);
signOffUpdate.setConfirmUserId(null);
signOffUpdate.setConfirmUserName(null);
signOffUpdate.setConfirmTime(null);
signOffUpdate.setLastRejectReason(null);
int signOffUpdateCount = performanceSheetSignOffMapper.updateByPerformanceSheetIdAndStatus(signOffUpdate,
sheet.getId(), currentSignOff.getStatusCode());
if (signOffUpdateCount != 1) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
}
writePerformanceSignOffRecord(currentSignOff.getId(), sheet.getId(), batchNo,
SignOffConstants.ACTION_ISSUE, currentSignOff.getStatusCode(),
SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM, null);
}
}
}
private void writePerformanceSignOffRecord(Long signOffId, Long sheetId, Long batchNo, String actionType,
String fromStatus, String toStatus, String reason) {
PerformanceSheetSignOffRecordDO record = new PerformanceSheetSignOffRecordDO();
record.setSignOffId(signOffId);
record.setPerformanceSheetId(sheetId);
record.setBatchNo(batchNo);
record.setActionType(actionType);
record.setFromSignOffStatus(fromStatus);
record.setToSignOffStatus(toStatus);
record.setReason(reason);
record.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
record.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
performanceSheetSignOffRecordMapper.insert(record);
}
private void validateBatchUnlocked(String periodMonth, Long deptId) {
if (employeeSignOffBatchMapper.selectByPeriodMonthAndDeptId(periodMonth, deptId) != null) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_SIGN_OFF_BATCH_LOCKED);
}
}
private List<Long> resolveRequestedEmployeeIds(Collection<Long> employeeIds, String permission) {
return teamDashboardAccessService.resolveRequestedSubordinateUserIds(employeeIds, permission);
}
private boolean isEligibleForIssue(PerformanceSheetDO sheet) {
return sheet != null
&& PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode())
&& isIssueAllowed(sheet.getSignOffStatus())
&& sheet.getFileId() != null;
}
private boolean isEligibleMonthlyReportForIssue(
com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO report) {
return report != null
&& WorkReportConstants.STATUS_APPROVED.equals(report.getStatusCode())
&& isIssueAllowed(report.getSignOffStatus());
}
private boolean hasLatestApprovedRecord(Long monthlyReportId) {
return monthlyReportApprovalRecordMapper.selectLatestApprovedByMonthlyReportId(monthlyReportId) != null;
}
private boolean isIssueAllowed(String signOffStatus) {
return !StringUtils.hasText(signOffStatus)
|| SignOffConstants.STATUS_NOT_STARTED.equals(signOffStatus);
}
private BigDecimal parseScore(String value) {
if (!StringUtils.hasText(value)) {
return null;
}
try {
return normalizeScore(new BigDecimal(value.trim().replace(",", "")));
} catch (NumberFormatException ex) {
return null;
}
}
private String formatScoreForMonthlyResult(BigDecimal score) {
return score == null ? null : score.stripTrailingZeros().toPlainString();
}
private String resolveExcelContentType(String fileName) {
return StringUtils.hasText(fileName) && fileName.toLowerCase().endsWith(".xls")
? "application/vnd.ms-excel"
: EXCEL_CONTENT_TYPE;
}
private CurrentOperatorProfile loadCurrentOperatorProfile() {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
CommonResult<AdminUserRespDTO> userResult = adminUserApi.getUser(loginUserId);
AdminUserRespDTO user = userResult == null ? null : userResult.getCheckedData();
if (user == null || user.getDeptId() == null) {
throw invalidParamException("当前登录用户部门信息不完整");
}
CommonResult<DeptRespDTO> deptResult = deptApi.getDept(user.getDeptId());
DeptRespDTO dept = deptResult == null ? null : deptResult.getCheckedData();
if (dept == null || !StringUtils.hasText(dept.getName())) {
throw invalidParamException("当前登录用户部门信息不完整");
}
return new CurrentOperatorProfile(loginUserId, defaultText(SecurityFrameworkUtils.getLoginUserNickname()),
dept.getId(), dept.getName().trim());
}
private PerformanceSheetDO merge(PerformanceSheetDO source, PerformanceSheetDO update) { private PerformanceSheetDO merge(PerformanceSheetDO source, PerformanceSheetDO update) {
PerformanceSheetDO merged = BeanUtils.toBean(source, PerformanceSheetDO.class); PerformanceSheetDO merged = BeanUtils.toBean(source, PerformanceSheetDO.class);
if (update.getStatusCode() != null) { if (update.getStatusCode() != null) {
@@ -1140,6 +1696,9 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
if (update.getLastStatusReason() != null) { if (update.getLastStatusReason() != null) {
merged.setLastStatusReason(update.getLastStatusReason()); merged.setLastStatusReason(update.getLastStatusReason());
} }
if (update.getSignOffStatus() != null) {
merged.setSignOffStatus(update.getSignOffStatus());
}
return merged; return merged;
} }
@@ -1258,4 +1817,13 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private final Map<Font, Font> fontCache = new IdentityHashMap<>(); private final Map<Font, Font> fontCache = new IdentityHashMap<>();
private final Map<String, Short> dataFormatCache = new HashMap<>(); private final Map<String, Short> dataFormatCache = new HashMap<>();
} }
private record ImportedWorkbookEntry(String displayName, byte[] content) {
}
private record PerformanceEmployeeResponseContext(PerformanceSheetSignOffDO signOff, boolean signOffFlow) {
}
private record CurrentOperatorProfile(Long userId, String userName, Long deptId, String deptName) {
}
} }

View File

@@ -0,0 +1,15 @@
package com.njcn.rdms.module.project.service.signoff;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
public interface EmployeeSignOffStatusService {
String getInitialStatusCode();
String statusName(String statusCode);
ObjectStatusTransitionDO validateTransition(String fromStatus, String actionCode, String reason);
ObjectStatusModelDO validateStatusModel(String statusCode);
}

View File

@@ -0,0 +1,66 @@
package com.njcn.rdms.module.project.service.signoff;
import com.njcn.rdms.module.project.constant.SignOffConstants;
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.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.service.status.StatusActionTextResolver;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
@Service
public class EmployeeSignOffStatusServiceImpl implements EmployeeSignOffStatusService {
@Resource
private ObjectStatusModelMapper objectStatusModelMapper;
@Resource
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
@Resource
private StatusActionTextResolver statusActionTextResolver;
@Override
public String getInitialStatusCode() {
ObjectStatusModelDO model = objectStatusModelMapper.selectInitialByObjectTypeEnabled(SignOffConstants.STATUS_OBJECT_TYPE);
if (model == null || !StringUtils.hasText(model.getStatusCode())) {
throw exception(ErrorCodeConstants.SIGN_OFF_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
}
return model.getStatusCode();
}
@Override
public String statusName(String statusCode) {
return statusActionTextResolver.statusName(SignOffConstants.STATUS_OBJECT_TYPE, statusCode);
}
@Override
public ObjectStatusTransitionDO validateTransition(String fromStatus, String actionCode, String reason) {
ObjectStatusTransitionDO transition = objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
SignOffConstants.STATUS_OBJECT_TYPE, fromStatus, actionCode);
if (transition == null) {
throw exception(ErrorCodeConstants.SIGN_OFF_STATUS_ACTION_NOT_ALLOWED,
statusActionTextResolver.statusName(SignOffConstants.STATUS_OBJECT_TYPE, fromStatus),
statusActionTextResolver.actionName(SignOffConstants.STATUS_OBJECT_TYPE, actionCode));
}
if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) {
throw exception(ErrorCodeConstants.SIGN_OFF_STATUS_ACTION_REASON_REQUIRED,
statusActionTextResolver.actionName(SignOffConstants.STATUS_OBJECT_TYPE, actionCode));
}
validateStatusModel(transition.getToStatusCode());
return transition;
}
@Override
public ObjectStatusModelDO validateStatusModel(String statusCode) {
ObjectStatusModelDO model = objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled(
SignOffConstants.STATUS_OBJECT_TYPE, statusCode);
if (model == null) {
throw exception(ErrorCodeConstants.SIGN_OFF_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
}
return model;
}
}

View File

@@ -6,6 +6,7 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.ProjectObjectConstants; import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
import com.njcn.rdms.module.project.constant.SignOffConstants;
import com.njcn.rdms.module.project.constant.WorkReportConstants; import com.njcn.rdms.module.project.constant.WorkReportConstants;
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent; import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants; import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
@@ -24,6 +25,8 @@ import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportM
import com.njcn.rdms.module.project.dal.dataobject.workreport.common.WorkReportStatusLogDO; 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.MonthlyReportApprovalRecordDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO; import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportSignOffDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportSignOffRecordDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportApprovalRecordDO; 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.ProjectReportCurrentItemDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportDO; import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportDO;
@@ -41,6 +44,8 @@ import com.njcn.rdms.module.project.dal.mysql.workreport.common.PersonalReportRe
import com.njcn.rdms.module.project.dal.mysql.workreport.common.WorkReportStatusLogMapper; 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.MonthlyReportApprovalRecordMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportMapper; import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportSignOffMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportSignOffRecordMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportApprovalRecordMapper; 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.ProjectReportCurrentItemMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMapper; import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMapper;
@@ -49,10 +54,12 @@ import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportAppr
import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapper; 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.dal.mysql.workreport.weekly.WeeklyReportTravelSegmentMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants; import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.service.signoff.EmployeeSignOffStatusService;
import com.njcn.rdms.module.project.service.status.StatusActionTextResolver; import com.njcn.rdms.module.project.service.status.StatusActionTextResolver;
import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService; import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService;
import com.njcn.rdms.module.system.api.dept.DeptApi; import com.njcn.rdms.module.system.api.dept.DeptApi;
import com.njcn.rdms.module.system.api.dept.PostApi; import com.njcn.rdms.module.system.api.dept.PostApi;
import com.njcn.rdms.module.system.api.user.UserSignatureApi;
import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO; 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.dept.dto.PostRespDTO;
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi; import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
@@ -60,6 +67,7 @@ import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
import com.njcn.rdms.module.system.api.user.AdminUserApi; import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.UserManagementRelationApi; import com.njcn.rdms.module.system.api.user.UserManagementRelationApi;
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
import com.njcn.rdms.module.system.api.user.dto.UserSignatureRespDTO;
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants; import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@@ -109,6 +117,10 @@ public class WorkReportCommonService {
@Resource @Resource
private MonthlyReportApprovalRecordMapper monthlyReportApprovalRecordMapper; private MonthlyReportApprovalRecordMapper monthlyReportApprovalRecordMapper;
@Resource @Resource
private MonthlyReportSignOffMapper monthlyReportSignOffMapper;
@Resource
private MonthlyReportSignOffRecordMapper monthlyReportSignOffRecordMapper;
@Resource
private ProjectReportApprovalRecordMapper projectReportApprovalRecordMapper; private ProjectReportApprovalRecordMapper projectReportApprovalRecordMapper;
@Resource @Resource
private WorkReportStatusLogMapper workReportStatusLogMapper; private WorkReportStatusLogMapper workReportStatusLogMapper;
@@ -121,6 +133,8 @@ public class WorkReportCommonService {
@Resource @Resource
private StatusActionTextResolver statusActionTextResolver; private StatusActionTextResolver statusActionTextResolver;
@Resource @Resource
private EmployeeSignOffStatusService employeeSignOffStatusService;
@Resource
private AdminUserApi adminUserApi; private AdminUserApi adminUserApi;
@Resource @Resource
private DeptApi deptApi; private DeptApi deptApi;
@@ -129,6 +143,8 @@ public class WorkReportCommonService {
@Resource @Resource
private UserManagementRelationApi userManagementRelationApi; private UserManagementRelationApi userManagementRelationApi;
@Resource @Resource
private UserSignatureApi userSignatureApi;
@Resource
private ObjectPermissionApi objectPermissionApi; private ObjectPermissionApi objectPermissionApi;
@Resource @Resource
private ProjectMapper projectMapper; private ProjectMapper projectMapper;
@@ -368,6 +384,7 @@ public class WorkReportCommonService {
MonthlyReportDO report = new MonthlyReportDO(); MonthlyReportDO report = new MonthlyReportDO();
applyMonthlySaveFields(report, reqVO, profile); applyMonthlySaveFields(report, reqVO, profile);
report.setStatusCode(getInitialStatusCode()); report.setStatusCode(getInitialStatusCode());
report.setSignOffStatus(SignOffConstants.STATUS_NOT_STARTED);
monthlyReportMapper.insert(report); monthlyReportMapper.insert(report);
replaceMonthlyChildren(report.getId(), reqVO); replaceMonthlyChildren(report.getId(), reqVO);
MonthlyReportDO latest = monthlyReportMapper.selectById(report.getId()); MonthlyReportDO latest = monthlyReportMapper.selectById(report.getId());
@@ -439,6 +456,29 @@ public class WorkReportCommonService {
.collect(Collectors.toList()), pageResult.getTotal()); .collect(Collectors.toList()), pageResult.getTotal());
} }
public PageResult<MonthlyReportRespVO> getMonthlySignOffPage(MonthlyReportPageReqVO reqVO) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
reqVO.setStatusCode(WorkReportConstants.STATUS_APPROVED);
reqVO.setSignOffStatus(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
PageResult<MonthlyReportDO> pageResult = monthlyReportMapper.selectReporterPage(loginUserId, reqVO,
List.of(WorkReportConstants.STATUS_APPROVED));
return new PageResult<>(pageResult.getList().stream()
.map(report -> toMonthlyRespVO(report, false))
.collect(Collectors.toList()), pageResult.getTotal());
}
public MonthlyReportSignOffDetailRespVO getMonthlySignOffDetail(Long id) {
MonthlyReportDO report = validateMonthlyReportExists(id);
validateReporter(SecurityFrameworkUtils.getLoginUserId(), report.getReporterId());
MonthlyReportSignOffDetailRespVO respVO = new MonthlyReportSignOffDetailRespVO();
respVO.setReport(toMonthlyRespVO(report, true));
MonthlyReportApprovalRecordDO latestApprovalRecord = monthlyReportApprovalRecordMapper
.selectLatestApprovedByMonthlyReportId(id);
respVO.setLatestApprovalRecord(latestApprovalRecord == null ? null
: BeanUtils.toBean(latestApprovalRecord, MonthlyReportApprovalRecordRespVO.class));
return respVO;
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void submitMonthlyReport(Long id) { public void submitMonthlyReport(Long id) {
MonthlyReportDO current = validateMonthlyReportExists(id); MonthlyReportDO current = validateMonthlyReportExists(id);
@@ -478,6 +518,55 @@ public class WorkReportCommonService {
processMonthlyApproval(id, WorkReportConstants.ACTION_REJECT, rejectReqVO); processMonthlyApproval(id, WorkReportConstants.ACTION_REJECT, rejectReqVO);
} }
@Transactional(rollbackFor = Exception.class)
public void confirmMonthlySignOff(Long id, MonthlyReportSignOffConfirmReqVO reqVO) {
MonthlyReportDO current = validateMonthlyReportExists(id);
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
validateReporter(loginUserId, current.getReporterId());
if (!WorkReportConstants.STATUS_APPROVED.equals(current.getStatusCode())
|| !SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM.equals(current.getSignOffStatus())) {
throw exception(ErrorCodeConstants.WORK_REPORT_SIGN_OFF_NOT_ALLOWED);
}
MonthlyReportSignOffDO signOff = validateMonthlySignOffExists(current.getId());
employeeSignOffStatusService.validateTransition(signOff.getStatusCode(), SignOffConstants.ACTION_CONFIRM, null);
MonthlyReportApprovalRecordDO approvalRecord = monthlyReportApprovalRecordMapper
.selectLatestApprovedByMonthlyReportId(current.getId());
if (approvalRecord == null) {
throw exception(ErrorCodeConstants.WORK_REPORT_MONTHLY_SIGN_OFF_APPROVAL_RECORD_NOT_EXISTS);
}
UserSignatureRespDTO userSignature = getUserSignature(loginUserId);
LocalDate signedDate = reqVO.getEmployeeSignedDate() != null ? reqVO.getEmployeeSignedDate() : LocalDate.now();
MonthlyReportApprovalRecordDO approvalUpdate = new MonthlyReportApprovalRecordDO();
approvalUpdate.setId(approvalRecord.getId());
approvalUpdate.setMeetingDate(reqVO.getMeetingDate());
approvalUpdate.setEmployeeSignName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
approvalUpdate.setEmployeeSignatureFileId(userSignature == null ? null : userSignature.getFileId());
approvalUpdate.setEmployeeSignedDate(signedDate);
monthlyReportApprovalRecordMapper.updateById(approvalUpdate);
MonthlyReportDO reportUpdate = new MonthlyReportDO();
reportUpdate.setSignOffStatus(SignOffConstants.STATUS_EMPLOYEE_CONFIRMED);
int reportUpdateCount = monthlyReportMapper.updateByIdAndStatusAndSignOffStatus(reportUpdate, current.getId(),
current.getStatusCode(), current.getSignOffStatus());
if (reportUpdateCount != 1) {
throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED);
}
MonthlyReportSignOffDO signOffUpdate = new MonthlyReportSignOffDO();
signOffUpdate.setStatusCode(SignOffConstants.STATUS_EMPLOYEE_CONFIRMED);
signOffUpdate.setConfirmUserId(loginUserId);
signOffUpdate.setConfirmUserName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
signOffUpdate.setConfirmTime(LocalDateTime.now());
int signOffUpdateCount = monthlyReportSignOffMapper.updateByMonthlyReportIdAndStatus(signOffUpdate,
current.getId(), signOff.getStatusCode());
if (signOffUpdateCount != 1) {
throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED);
}
writeMonthlySignOffRecord(signOff.getId(), current.getId(), approvalRecord.getId(), signOff.getBatchNo(),
SignOffConstants.ACTION_CONFIRM, signOff.getStatusCode(), SignOffConstants.STATUS_EMPLOYEE_CONFIRMED);
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void deleteMonthlyReport(Long id) { public void deleteMonthlyReport(Long id) {
MonthlyReportDO current = validateMonthlyReportExists(id); MonthlyReportDO current = validateMonthlyReportExists(id);
@@ -513,6 +602,89 @@ public class WorkReportCommonService {
return BeanUtils.toBean(getMonthlyReportPage(reqVO).getList(), MonthlyReportExportVO.class); return BeanUtils.toBean(getMonthlyReportPage(reqVO).getList(), MonthlyReportExportVO.class);
} }
public List<MonthlyReportDO> listMonthlyReportsForSignOff(Collection<Long> reporterIds, String periodMonth) {
return monthlyReportMapper.selectListByReporterIdsAndPeriodKey(reporterIds, periodMonth,
List.of(WorkReportConstants.STATUS_APPROVED));
}
public void issueMonthlySignOff(List<MonthlyReportDO> reports, Long batchNo) {
if (reports == null || reports.isEmpty()) {
throw exception(ErrorCodeConstants.WORK_REPORT_MONTHLY_SIGN_OFF_CANDIDATE_EMPTY);
}
Long operatorUserId = SecurityFrameworkUtils.getLoginUserId();
String operatorName = defaultText(SecurityFrameworkUtils.getLoginUserNickname());
LocalDateTime issueTime = LocalDateTime.now();
for (MonthlyReportDO report : reports) {
if (!WorkReportConstants.STATUS_APPROVED.equals(report.getStatusCode())
|| !isIssueAllowed(report.getSignOffStatus())) {
throw exception(ErrorCodeConstants.WORK_REPORT_SIGN_OFF_NOT_ALLOWED);
}
MonthlyReportApprovalRecordDO approvalRecord = monthlyReportApprovalRecordMapper
.selectLatestApprovedByMonthlyReportId(report.getId());
if (approvalRecord == null) {
throw exception(ErrorCodeConstants.WORK_REPORT_MONTHLY_SIGN_OFF_APPROVAL_RECORD_NOT_EXISTS);
}
MonthlyReportDO reportUpdate = new MonthlyReportDO();
reportUpdate.setSignOffStatus(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
int reportUpdateCount = monthlyReportMapper.updateByIdAndStatusAndSignOffStatus(reportUpdate, report.getId(),
report.getStatusCode(), report.getSignOffStatus());
if (reportUpdateCount != 1) {
throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED);
}
MonthlyReportSignOffDO currentSignOff = monthlyReportSignOffMapper.selectByMonthlyReportId(report.getId());
if (currentSignOff == null) {
MonthlyReportSignOffDO signOff = new MonthlyReportSignOffDO();
signOff.setMonthlyReportId(report.getId());
signOff.setBatchNo(batchNo);
signOff.setStatusCode(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
signOff.setIssueUserId(operatorUserId);
signOff.setIssueUserName(operatorName);
signOff.setIssueTime(issueTime);
monthlyReportSignOffMapper.insert(signOff);
writeMonthlySignOffRecord(signOff.getId(), report.getId(), approvalRecord.getId(), batchNo,
SignOffConstants.ACTION_ISSUE, SignOffConstants.STATUS_NOT_STARTED,
SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
} else {
employeeSignOffStatusService.validateTransition(currentSignOff.getStatusCode(), SignOffConstants.ACTION_ISSUE, null);
MonthlyReportSignOffDO signOffUpdate = new MonthlyReportSignOffDO();
signOffUpdate.setStatusCode(SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
signOffUpdate.setBatchNo(batchNo);
signOffUpdate.setIssueUserId(operatorUserId);
signOffUpdate.setIssueUserName(operatorName);
signOffUpdate.setIssueTime(issueTime);
signOffUpdate.setConfirmUserId(null);
signOffUpdate.setConfirmUserName(null);
signOffUpdate.setConfirmTime(null);
int signOffUpdateCount = monthlyReportSignOffMapper.updateByMonthlyReportIdAndStatus(signOffUpdate,
report.getId(), currentSignOff.getStatusCode());
if (signOffUpdateCount != 1) {
throw exception(ErrorCodeConstants.WORK_REPORT_STATUS_CONCURRENT_MODIFIED);
}
writeMonthlySignOffRecord(currentSignOff.getId(), report.getId(), approvalRecord.getId(), batchNo,
SignOffConstants.ACTION_ISSUE, currentSignOff.getStatusCode(),
SignOffConstants.STATUS_PENDING_EMPLOYEE_CONFIRM);
}
}
}
public void syncPerformanceResult(Long reporterId, String periodMonth, String performanceResult) {
MonthlyReportDO report = monthlyReportMapper.selectByReporterIdAndPeriodKey(reporterId, periodMonth);
if (report == null) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_MONTHLY_REPORT_NOT_EXISTS);
}
MonthlyReportApprovalRecordDO approvalRecord = monthlyReportApprovalRecordMapper
.selectLatestApprovedByMonthlyReportId(report.getId());
if (approvalRecord == null) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_MONTHLY_APPROVAL_RECORD_NOT_EXISTS);
}
MonthlyReportApprovalRecordDO update = new MonthlyReportApprovalRecordDO();
update.setId(approvalRecord.getId());
update.setPerformanceResult(performanceResult);
monthlyReportApprovalRecordMapper.updateById(update);
}
public ProjectReportRespVO initProjectReport(Long projectId) { public ProjectReportRespVO initProjectReport(Long projectId) {
CurrentUserProfile profile = loadCurrentUserProfile(true); CurrentUserProfile profile = loadCurrentUserProfile(true);
ProjectDO project = validateProjectExists(projectId); ProjectDO project = validateProjectExists(projectId);
@@ -810,6 +982,14 @@ public class WorkReportCommonService {
return report; return report;
} }
private MonthlyReportSignOffDO validateMonthlySignOffExists(Long monthlyReportId) {
MonthlyReportSignOffDO signOff = monthlyReportSignOffMapper.selectByMonthlyReportId(monthlyReportId);
if (signOff == null) {
throw exception(ErrorCodeConstants.WORK_REPORT_MONTHLY_SIGN_OFF_NOT_EXISTS);
}
return signOff;
}
private ProjectReportDO validateReadableProjectReport(Long id) { private ProjectReportDO validateReadableProjectReport(Long id) {
ProjectReportDO report = validateProjectReportExists(id); ProjectReportDO report = validateProjectReportExists(id);
validateReadable(report.getProjectOwnerId(), report.getSupervisorUserId()); validateReadable(report.getProjectOwnerId(), report.getSupervisorUserId());
@@ -1438,6 +1618,7 @@ public class WorkReportCommonService {
record.setConclusion(statusLog.getToStatus()); record.setConclusion(statusLog.getToStatus());
record.setOpinion(normalizeReason(reqVO)); record.setOpinion(normalizeReason(reqVO));
if (WorkReportConstants.ACTION_APPROVE.equals(actionCode)) { if (WorkReportConstants.ACTION_APPROVE.equals(actionCode)) {
UserSignatureRespDTO userSignature = getUserSignature(SecurityFrameworkUtils.getLoginUserId());
record.setMeetingDate(reqVO.getMeetingDate()); record.setMeetingDate(reqVO.getMeetingDate());
record.setStrengthDesc(normalizeNullableText(reqVO.getStrengthDesc())); record.setStrengthDesc(normalizeNullableText(reqVO.getStrengthDesc()));
record.setStrengthExample(normalizeNullableText(reqVO.getStrengthExample())); record.setStrengthExample(normalizeNullableText(reqVO.getStrengthExample()));
@@ -1446,15 +1627,32 @@ public class WorkReportCommonService {
record.setImprovementSuggestion(normalizeNullableText(reqVO.getImprovementSuggestion())); record.setImprovementSuggestion(normalizeNullableText(reqVO.getImprovementSuggestion()));
record.setPerformanceResult(normalizeNullableText(reqVO.getPerformanceResult())); record.setPerformanceResult(normalizeNullableText(reqVO.getPerformanceResult()));
record.setEmployeeSignName(normalizeNullableText(reqVO.getEmployeeSignName())); record.setEmployeeSignName(normalizeNullableText(reqVO.getEmployeeSignName()));
record.setEmployeeSignatureFileId(null);
record.setEmployeeSignedDate(reqVO.getEmployeeSignedDate()); record.setEmployeeSignedDate(reqVO.getEmployeeSignedDate());
record.setSupervisorSignName(normalizeNullableText(reqVO.getSupervisorSignName())); record.setSupervisorSignName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
record.setSupervisorSignedDate(reqVO.getSupervisorSignedDate()); record.setSupervisorSignatureFileId(userSignature == null ? null : userSignature.getFileId());
record.setSupervisorSignedDate(LocalDate.now());
} }
record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId()); record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId());
record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
monthlyReportApprovalRecordMapper.insert(record); monthlyReportApprovalRecordMapper.insert(record);
} }
private void writeMonthlySignOffRecord(Long signOffId, Long monthlyReportId, Long approvalRecordId, Long batchNo,
String actionType, String fromStatus, String toStatus) {
MonthlyReportSignOffRecordDO record = new MonthlyReportSignOffRecordDO();
record.setSignOffId(signOffId);
record.setMonthlyReportId(monthlyReportId);
record.setApprovalRecordId(approvalRecordId);
record.setBatchNo(batchNo);
record.setActionType(actionType);
record.setFromSignOffStatus(fromStatus);
record.setToSignOffStatus(toStatus);
record.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
record.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
monthlyReportSignOffRecordMapper.insert(record);
}
private void writeProjectApprovalRecord(ProjectReportDO report, WorkReportStatusLogDO statusLog, String reason) { private void writeProjectApprovalRecord(ProjectReportDO report, WorkReportStatusLogDO statusLog, String reason) {
ProjectReportApprovalRecordDO record = new ProjectReportApprovalRecordDO(); ProjectReportApprovalRecordDO record = new ProjectReportApprovalRecordDO();
record.setProjectReportId(report.getId()); record.setProjectReportId(report.getId());
@@ -1513,6 +1711,11 @@ public class WorkReportCommonService {
private MonthlyReportRespVO toMonthlyRespVO(MonthlyReportDO report, boolean withChildren) { private MonthlyReportRespVO toMonthlyRespVO(MonthlyReportDO report, boolean withChildren) {
MonthlyReportRespVO respVO = BeanUtils.toBean(report, MonthlyReportRespVO.class); MonthlyReportRespVO respVO = BeanUtils.toBean(report, MonthlyReportRespVO.class);
applyStatusView(respVO, getStatusModel(report.getStatusCode())); applyStatusView(respVO, getStatusModel(report.getStatusCode()));
String signOffStatus = StringUtils.hasText(report.getSignOffStatus())
? report.getSignOffStatus()
: SignOffConstants.STATUS_NOT_STARTED;
respVO.setSignOffStatus(signOffStatus);
respVO.setSignOffStatusName(employeeSignOffStatusService.statusName(signOffStatus));
if (withChildren) { if (withChildren) {
respVO.setReviewItems(BeanUtils.toBean( respVO.setReviewItems(BeanUtils.toBean(
personalReportReviewItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, report.getId()), personalReportReviewItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, report.getId()),
@@ -1704,6 +1907,19 @@ public class WorkReportCommonService {
return StringUtils.hasText(value) ? value.trim() : ""; return StringUtils.hasText(value) ? value.trim() : "";
} }
private boolean isIssueAllowed(String signOffStatus) {
return !StringUtils.hasText(signOffStatus)
|| SignOffConstants.STATUS_NOT_STARTED.equals(signOffStatus);
}
private UserSignatureRespDTO getUserSignature(Long userId) {
if (userId == null) {
return null;
}
CommonResult<UserSignatureRespDTO> result = userSignatureApi.getByUserId(userId);
return result == null ? null : result.getCheckedData();
}
private <T> List<T> defaultList(List<T> list) { private <T> List<T> defaultList(List<T> list) {
return list == null ? Collections.emptyList() : list; return list == null ? Collections.emptyList() : list;
} }

View File

@@ -9,6 +9,8 @@ import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.Month
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSignOffConfirmReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSignOffDetailRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; 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.common.vo.WorkReportStatusLogRespVO;
@@ -32,12 +34,18 @@ public interface MonthlyReportService {
PageResult<MonthlyReportRespVO> getMonthlyApprovalPage(MonthlyReportPageReqVO reqVO); PageResult<MonthlyReportRespVO> getMonthlyApprovalPage(MonthlyReportPageReqVO reqVO);
PageResult<MonthlyReportRespVO> getMonthlySignOffPage(MonthlyReportPageReqVO reqVO);
MonthlyReportSignOffDetailRespVO getMonthlySignOffDetail(Long id);
void submitMonthlyReport(Long id); void submitMonthlyReport(Long id);
void approveMonthlyReport(Long id, MonthlyReportApproveReqVO reqVO); void approveMonthlyReport(Long id, MonthlyReportApproveReqVO reqVO);
void rejectMonthlyReport(Long id, WorkReportStatusActionReqVO reqVO); void rejectMonthlyReport(Long id, WorkReportStatusActionReqVO reqVO);
void confirmMonthlySignOff(Long id, MonthlyReportSignOffConfirmReqVO reqVO);
void deleteMonthlyReport(Long id); void deleteMonthlyReport(Long id);
List<WorkReportStatusLogRespVO> getMonthlyStatusLogs(Long id); List<WorkReportStatusLogRespVO> getMonthlyStatusLogs(Long id);

View File

@@ -9,6 +9,8 @@ import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.Month
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRefreshDraftReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO; import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSignOffConfirmReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.monthly.vo.MonthlyReportSignOffDetailRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.common.vo.WorkReportStatusActionReqVO; 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.common.vo.WorkReportStatusLogRespVO;
import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService; import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService;
@@ -71,6 +73,16 @@ public class MonthlyReportServiceImpl implements MonthlyReportService {
return workReportCommonService.getMonthlyApprovalPage(reqVO); return workReportCommonService.getMonthlyApprovalPage(reqVO);
} }
@Override
public PageResult<MonthlyReportRespVO> getMonthlySignOffPage(MonthlyReportPageReqVO reqVO) {
return workReportCommonService.getMonthlySignOffPage(reqVO);
}
@Override
public MonthlyReportSignOffDetailRespVO getMonthlySignOffDetail(Long id) {
return workReportCommonService.getMonthlySignOffDetail(id);
}
@Override @Override
public void submitMonthlyReport(Long id) { public void submitMonthlyReport(Long id) {
workReportCommonService.submitMonthlyReport(id); workReportCommonService.submitMonthlyReport(id);
@@ -86,6 +98,11 @@ public class MonthlyReportServiceImpl implements MonthlyReportService {
workReportCommonService.rejectMonthlyReport(id, reqVO); workReportCommonService.rejectMonthlyReport(id, reqVO);
} }
@Override
public void confirmMonthlySignOff(Long id, MonthlyReportSignOffConfirmReqVO reqVO) {
workReportCommonService.confirmMonthlySignOff(id, reqVO);
}
@Override @Override
public void deleteMonthlyReport(Long id) { public void deleteMonthlyReport(Long id) {
workReportCommonService.deleteMonthlyReport(id); workReportCommonService.deleteMonthlyReport(id);

View File

@@ -1,6 +1,7 @@
package com.njcn.rdms.module.system.api.file; package com.njcn.rdms.module.system.api.file;
import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.module.system.api.file.dto.FileContentCreateReqDTO;
import com.njcn.rdms.module.system.api.file.dto.FileRespDTO; import com.njcn.rdms.module.system.api.file.dto.FileRespDTO;
import com.njcn.rdms.module.system.enums.ApiConstants; import com.njcn.rdms.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -8,6 +9,8 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME) @FeignClient(name = ApiConstants.NAME)
@@ -31,4 +34,8 @@ public interface FileApi {
@Parameter(name = "id", description = "文件 ID", required = true) @Parameter(name = "id", description = "文件 ID", required = true)
CommonResult<byte[]> getFileContent(@RequestParam("id") Long id); CommonResult<byte[]> getFileContent(@RequestParam("id") Long id);
@PostMapping(PREFIX + "/create-by-content")
@Operation(summary = "通过文件字节创建文件")
CommonResult<Long> createFileByContent(@RequestBody FileContentCreateReqDTO reqDTO);
} }

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.system.api.file.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "RPC 服务 - 文件字节创建 Request DTO")
@Data
public class FileContentCreateReqDTO {
@Schema(description = "文件内容", requiredMode = Schema.RequiredMode.REQUIRED)
private byte[] content;
@Schema(description = "文件名", example = "2026-06_张三.xlsx")
private String name;
@Schema(description = "目录", example = "rdms/performance")
private String directory;
@Schema(description = "MIME 类型", example = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
private String type;
}

View File

@@ -0,0 +1,23 @@
package com.njcn.rdms.module.system.api.user;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.module.system.api.user.dto.UserSignatureRespDTO;
import com.njcn.rdms.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 用户电子签名")
public interface UserSignatureApi {
String PREFIX = ApiConstants.PREFIX + "/user-signature";
@GetMapping(PREFIX + "/get")
@Operation(summary = "根据用户 ID 查询电子签名")
@Parameter(name = "userId", description = "用户 ID", required = true)
CommonResult<UserSignatureRespDTO> getByUserId(@RequestParam("userId") Long userId);
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.system.api.user.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "RPC 服务 - 用户电子签名 Response DTO")
@Data
public class UserSignatureRespDTO {
@Schema(description = "主键", example = "1")
private Long id;
@Schema(description = "用户 ID", example = "1024")
private Long userId;
@Schema(description = "文件 ID", example = "2048")
private Long fileId;
@Schema(description = "文件名", example = "zhangsan-sign.png")
private String fileName;
@Schema(description = "状态", example = "0")
private Integer status;
@Schema(description = "备注")
private String remark;
}

View File

@@ -2,6 +2,7 @@ package com.njcn.rdms.module.system.api.file;
import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.system.api.file.dto.FileContentCreateReqDTO;
import com.njcn.rdms.module.system.api.file.dto.FileRespDTO; import com.njcn.rdms.module.system.api.file.dto.FileRespDTO;
import com.njcn.rdms.module.system.dal.dataobject.file.FileDO; import com.njcn.rdms.module.system.dal.dataobject.file.FileDO;
import com.njcn.rdms.module.system.service.file.FileService; import com.njcn.rdms.module.system.service.file.FileService;
@@ -45,4 +46,10 @@ public class FileApiImpl implements FileApi {
} }
} }
@Override
public CommonResult<Long> createFileByContent(FileContentCreateReqDTO reqDTO) {
FileDO file = fileService.createFile(reqDTO.getContent(), reqDTO.getName(), reqDTO.getDirectory(), reqDTO.getType());
return success(file.getId());
}
} }

View File

@@ -0,0 +1,28 @@
package com.njcn.rdms.module.system.api.user;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.system.api.user.dto.UserSignatureRespDTO;
import com.njcn.rdms.module.system.dal.dataobject.user.UserSignatureDO;
import com.njcn.rdms.module.system.service.user.UserSignatureService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@RestController
@Validated
@Hidden
public class UserSignatureApiImpl implements UserSignatureApi {
@Resource
private UserSignatureService userSignatureService;
@Override
public CommonResult<UserSignatureRespDTO> getByUserId(Long userId) {
UserSignatureDO signature = userSignatureService.getByUserId(userId);
return success(BeanUtils.toBean(signature, UserSignatureRespDTO.class));
}
}

View File

@@ -0,0 +1,70 @@
package com.njcn.rdms.module.system.controller.admin.user;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.system.controller.admin.user.vo.signature.UserSignatureRespVO;
import com.njcn.rdms.module.system.controller.admin.user.vo.signature.UserSignatureSaveReqVO;
import com.njcn.rdms.module.system.dal.dataobject.user.UserSignatureDO;
import com.njcn.rdms.module.system.service.user.UserSignatureService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
import static com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 用户电子签名")
@RestController
@RequestMapping("/system/user-signatures")
@Validated
public class UserSignatureController {
@Resource
private UserSignatureService userSignatureService;
@GetMapping("/get")
@Operation(summary = "获取用户电子签名")
@Parameter(name = "userId", description = "用户 ID", required = true)
@PreAuthorize("@ss.hasPermission('system:user-signature:query')")
public CommonResult<UserSignatureRespVO> get(@RequestParam("userId") Long userId) {
return success(toRespVO(userSignatureService.getByUserId(userId)));
}
@PostMapping("/save")
@Operation(summary = "保存用户电子签名")
@PreAuthorize("@ss.hasPermission('system:user-signature:update')")
public CommonResult<Boolean> save(@Valid @RequestBody UserSignatureSaveReqVO reqVO) {
userSignatureService.save(reqVO.getUserId(), reqVO.getFileId(), reqVO.getFileName(), reqVO.getStatus(), reqVO.getRemark());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除用户电子签名")
@Parameter(name = "userId", description = "用户 ID", required = true)
@PreAuthorize("@ss.hasPermission('system:user-signature:update')")
public CommonResult<Boolean> delete(@RequestParam("userId") Long userId) {
userSignatureService.deleteByUserId(userId);
return success(true);
}
@GetMapping("/my")
@Operation(summary = "获取当前登录用户电子签名")
public CommonResult<UserSignatureRespVO> my() {
return success(toRespVO(userSignatureService.getByUserId(getLoginUserId())));
}
private UserSignatureRespVO toRespVO(UserSignatureDO signature) {
return BeanUtils.toBean(signature, UserSignatureRespVO.class);
}
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.system.controller.admin.user.vo.signature;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 用户电子签名 Response VO")
@Data
public class UserSignatureRespVO {
private Long id;
private Long userId;
private Long fileId;
private String fileName;
private Integer status;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,30 @@
package com.njcn.rdms.module.system.controller.admin.user.vo.signature;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 用户电子签名保存 Request VO")
@Data
public class UserSignatureSaveReqVO {
@Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "用户 ID 不能为空")
private Long userId;
@Schema(description = "电子签文件 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
@NotNull(message = "电子签文件 ID 不能为空")
private Long fileId;
@Schema(description = "文件名", example = "zhangsan-sign.png")
@Size(max = 255, message = "文件名长度不能超过 255 个字符")
private String fileName;
@Schema(description = "状态", example = "0")
private Integer status;
@Schema(description = "备注")
@Size(max = 255, message = "备注长度不能超过 255 个字符")
private String remark;
}

View File

@@ -0,0 +1,29 @@
package com.njcn.rdms.module.system.dal.dataobject.user;
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("system_user_signature")
@Data
@EqualsAndHashCode(callSuper = true)
public class UserSignatureDO extends BaseDO {
@TableId
private Long id;
private Long userId;
private Long fileId;
private String fileName;
private Integer status;
private String remark;
}

View File

@@ -0,0 +1,20 @@
package com.njcn.rdms.module.system.dal.mysql.user;
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.module.system.dal.dataobject.user.UserSignatureDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserSignatureMapper extends BaseMapperX<UserSignatureDO> {
default UserSignatureDO selectByUserId(Long userId) {
return selectOne(new LambdaQueryWrapperX<UserSignatureDO>()
.eq(UserSignatureDO::getUserId, userId));
}
default int deleteByUserId(Long userId) {
return delete(new LambdaQueryWrapperX<UserSignatureDO>()
.eq(UserSignatureDO::getUserId, userId));
}
}

View File

@@ -0,0 +1,12 @@
package com.njcn.rdms.module.system.service.user;
import com.njcn.rdms.module.system.dal.dataobject.user.UserSignatureDO;
public interface UserSignatureService {
UserSignatureDO getByUserId(Long userId);
void save(Long userId, Long fileId, String fileName, Integer status, String remark);
void deleteByUserId(Long userId);
}

View File

@@ -0,0 +1,63 @@
package com.njcn.rdms.module.system.service.user;
import com.njcn.rdms.framework.common.enums.CommonStatusEnum;
import com.njcn.rdms.module.system.dal.dataobject.file.FileDO;
import com.njcn.rdms.module.system.dal.dataobject.user.UserSignatureDO;
import com.njcn.rdms.module.system.dal.mysql.user.UserSignatureMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.singleton;
@Service
public class UserSignatureServiceImpl implements UserSignatureService {
@Resource
private UserSignatureMapper userSignatureMapper;
@Resource
private AdminUserService adminUserService;
@Resource
private com.njcn.rdms.module.system.service.file.FileService fileService;
@Override
public UserSignatureDO getByUserId(Long userId) {
if (userId == null) {
return null;
}
return userSignatureMapper.selectByUserId(userId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void save(Long userId, Long fileId, String fileName, Integer status, String remark) {
adminUserService.validateUserList(singleton(userId));
FileDO file = fileService.getFile(fileId);
UserSignatureDO existing = userSignatureMapper.selectByUserId(userId);
UserSignatureDO target = new UserSignatureDO();
if (existing != null) {
target.setId(existing.getId());
}
target.setUserId(userId);
target.setFileId(fileId);
target.setFileName(StringUtils.hasText(fileName) ? fileName.trim() : file.getName());
target.setStatus(status == null ? CommonStatusEnum.ENABLE.getStatus() : status);
target.setRemark(StringUtils.hasText(remark) ? remark.trim() : null);
if (existing == null) {
userSignatureMapper.insert(target);
} else {
userSignatureMapper.updateById(target);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteByUserId(Long userId) {
if (userId == null) {
return;
}
userSignatureMapper.deleteByUserId(userId);
}
}