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_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_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 ==========
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_ACTION_REASON_REQUIRED = new ErrorCode(1_008_011_018, "「{}」操作必须填写原因");
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_EXPORT = "project:performance-sheet:export";
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_PROJECT_OWNER = "project:work-report:project-owner";
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.PerformanceSheetCreateReqVO;
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.PerformanceSheetPrefillRespVO;
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.PerformanceSheetSignOffReqVO;
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.PerformanceSheetStatusLogRespVO;
@@ -124,6 +127,21 @@ public class PerformanceSheetController {
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")
@Operation(summary = "下载单条绩效表")
@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")
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 String statusCode;
private String statusName;
private String signOffStatus;
private String signOffStatusName;
private BigDecimal actualScoreTotal;
private BigDecimal baseScoreTotal;
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.MonthlyReportRespVO;
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.monthly.MonthlyReportService;
import io.swagger.v3.oas.annotations.Operation;
@@ -111,6 +113,20 @@ public class MonthlyReportController {
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")
@Operation(summary = "提交月报")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_UPDATE + "')")
@@ -137,6 +153,15 @@ public class MonthlyReportController {
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}")
@Operation(summary = "删除月报")
@PreAuthorize("@ss.hasPermission('" + WorkReportConstants.PERMISSION_DELETE + "')")

View File

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

View File

@@ -14,4 +14,7 @@ public class MonthlyReportPageReqVO extends WorkReportBasePageReqVO {
@Schema(description = "团队视角下的填报人用户编号列表")
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 String statusCode;
private String statusName;
private String signOffStatus;
private String signOffStatusName;
private Boolean allowEdit;
private Boolean terminal;
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 signOffStatus;
private BigDecimal actualScoreTotal;
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)
private String employeeSignName;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long employeeSignatureFileId;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDate employeeSignedDate;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String supervisorSignName;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long supervisorSignatureFileId;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDate supervisorSignedDate;

View File

@@ -47,6 +47,8 @@ public class MonthlyReportDO extends BaseDO {
private String statusCode;
private String signOffStatus;
@TableField(updateStrategy = FieldStrategy.ALWAYS)
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;
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.mapper.BaseMapperX;
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.dal.dataobject.performance.PerformanceSheetDO;
@@ -102,6 +102,14 @@ public interface PerformanceSheetMapper extends BaseMapperX<PerformanceSheetDO>
.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) {
return new LambdaQueryWrapperX<PerformanceSheetDO>()
.geIfPresent(PerformanceSheetDO::getPeriodMonth, reqVO.getPeriodMonthStart())
@@ -111,6 +119,7 @@ public interface PerformanceSheetMapper extends BaseMapperX<PerformanceSheetDO>
.likeIfPresent(PerformanceSheetDO::getEmployeeDeptName, reqVO.getEmployeeDeptName())
.eqIfPresent(PerformanceSheetDO::getManagerId, reqVO.getManagerId())
.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>()
.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));
}
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,
Long reporterId) {
return update(update, new LambdaQueryWrapperX<MonthlyReportDO>()
@@ -177,6 +185,7 @@ public interface MonthlyReportMapper extends BaseMapperX<MonthlyReportDO> {
private LambdaQueryWrapperX<MonthlyReportDO> buildPageQuery(MonthlyReportPageReqVO reqVO) {
LambdaQueryWrapperX<MonthlyReportDO> wrapper = new LambdaQueryWrapperX<MonthlyReportDO>()
.eqIfPresent(MonthlyReportDO::getStatusCode, reqVO.getStatusCode())
.eqIfPresent(MonthlyReportDO::getSignOffStatus, reqVO.getSignOffStatus())
.betweenIfPresent(MonthlyReportDO::getPeriodStartDate, reqVO.getPeriodStartDate())
.betweenIfPresent(MonthlyReportDO::getSubmitTime, reqVO.getSubmitTime());
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.user.AdminUserApi;
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.context.annotation.Configuration;
@@ -22,6 +23,6 @@ import org.springframework.context.annotation.Configuration;
{AdminUserApi.class, ObjectPermissionApi.class, DictDataApi.class, FileApi.class,
PermissionApi.class, OrgLeaderApi.class, UserVisibilityConfigApi.class,
DeptApi.class, PostApi.class, UserManagementRelationApi.class,
NotifyMessageSendApi.class})
NotifyMessageSendApi.class, UserSignatureApi.class})
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.PerformanceSheetCreateReqVO;
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.PerformanceSheetPrefillRespVO;
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.PerformanceSheetSignOffReqVO;
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.PerformanceSheetStatusLogRespVO;
@@ -36,6 +39,10 @@ public interface PerformanceSheetService {
void reject(Long id, PerformanceSheetStatusActionReqVO reqVO);
PerformanceSheetImportZipRespVO importZip(PerformanceSheetImportZipReqVO reqVO) throws Exception;
Long signOff(PerformanceSheetSignOffReqVO reqVO);
PerformanceDownloadFile download(Long id);
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.module.project.constant.PerformanceExcelCellConstants;
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.PerformanceSheetCreateReqVO;
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.PerformanceSheetPrefillRespVO;
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.PerformanceSheetSignOffReqVO;
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.PerformanceSheetStatusLogRespVO;
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.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.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.ObjectStatusTransitionDO;
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.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.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.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.framework.notify.NotifySendEvent;
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.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.dto.DeptRespDTO;
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.UserManagementRelationApi;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
@@ -95,6 +115,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
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.invalidParamException;
@@ -116,12 +138,22 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
@Resource
private PerformanceSheetResponseRecordMapper performanceSheetResponseRecordMapper;
@Resource
private PerformanceSheetSignOffMapper performanceSheetSignOffMapper;
@Resource
private PerformanceSheetSignOffRecordMapper performanceSheetSignOffRecordMapper;
@Resource
private EmployeeSignOffBatchMapper employeeSignOffBatchMapper;
@Resource
private MonthlyReportApprovalRecordMapper monthlyReportApprovalRecordMapper;
@Resource
private ObjectStatusModelMapper objectStatusModelMapper;
@Resource
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
@Resource
private StatusActionTextResolver statusActionTextResolver;
@Resource
private EmployeeSignOffStatusService employeeSignOffStatusService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@@ -130,9 +162,13 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
@Resource
private FileApi fileApi;
@Resource
private WorkReportCommonService workReportCommonService;
@Resource
private TeamDashboardAccessService teamDashboardAccessService;
@Resource
private ApplicationEventPublisher applicationEventPublisher;
@Resource
private TransactionTemplate transactionTemplate;
@Override
public PageResult<PerformanceSheetRespVO> getSheetPage(PerformanceSheetPageReqVO reqVO) {
@@ -183,6 +219,7 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
sheet.setFileName(buildPerformanceFileName(periodMonth, employee.getNickname()));
sheet.setFileVersion(0);
sheet.setStatusCode(getInitialStatusCode());
sheet.setSignOffStatus(SignOffConstants.STATUS_NOT_STARTED);
performanceSheetMapper.insert(sheet);
return sheet.getId();
}
@@ -248,6 +285,158 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
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
public PerformanceDownloadFile download(Long id) {
PerformanceSheetDO sheet = validateReadableSheet(id);
@@ -393,23 +582,38 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private PerformanceSheetDO processEmployeeResponse(Long id, String actionType, String reason) {
PerformanceSheetDO current = validateSheetExists(id);
validateEmployeeOnly(current.getEmployeeId());
PerformanceEmployeeResponseContext responseContext = resolvePerformanceEmployeeResponseContext(current);
ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionType, reason);
PerformanceSheetDO update = new PerformanceSheetDO();
update.setStatusCode(transition.getToStatusCode());
if (PerformanceConstants.STATUS_CONFIRMED.equals(transition.getToStatusCode())) {
update.setConfirmedTime(LocalDateTime.now());
if (responseContext.signOffFlow()) {
update.setSignOffStatus(SignOffConstants.STATUS_EMPLOYEE_CONFIRMED);
}
} else if (PerformanceConstants.STATUS_REJECTED.equals(transition.getToStatusCode())) {
update.setRejectedTime(LocalDateTime.now());
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) {
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
}
if (responseContext.signOffFlow()) {
updatePerformanceSignOffAfterEmployeeAction(current, responseContext.signOff(), actionType, reason);
}
PerformanceSheetDO after = merge(current, update);
PerformanceSheetStatusLogDO statusLog = writeStatusLog(after, 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;
}
@@ -527,6 +731,11 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private PerformanceSheetRespVO toRespVO(PerformanceSheetDO sheet) {
PerformanceSheetRespVO respVO = BeanUtils.toBean(sheet, PerformanceSheetRespVO.class);
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;
}
@@ -1095,17 +1304,22 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
Sheet sourceSheet = workbook.getSheetAt(0);
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
DataFormatter formatter = new DataFormatter();
Map<String, String> cellValues = new LinkedHashMap<>();
for (String cellAddress : cellAddresses) {
String value = readCellDisplayValue(sourceSheet, evaluator, formatter, cellAddress);
if (value != null) {
cellValues.put(cellAddress, value);
}
}
return cellValues;
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<>();
for (String cellAddress : cellAddresses) {
String value = readCellDisplayValue(sheet, evaluator, formatter, cellAddress);
if (value != null) {
cellValues.put(cellAddress, value);
}
}
return cellValues;
}
private String readCellDisplayValue(Sheet sheet,
FormulaEvaluator evaluator,
DataFormatter formatter,
@@ -1123,6 +1337,348 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
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) {
PerformanceSheetDO merged = BeanUtils.toBean(source, PerformanceSheetDO.class);
if (update.getStatusCode() != null) {
@@ -1140,6 +1696,9 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
if (update.getLastStatusReason() != null) {
merged.setLastStatusReason(update.getLastStatusReason());
}
if (update.getSignOffStatus() != null) {
merged.setSignOffStatus(update.getSignOffStatus());
}
return merged;
}
@@ -1258,4 +1817,13 @@ public class PerformanceSheetServiceImpl implements PerformanceSheetService {
private final Map<Font, Font> fontCache = new IdentityHashMap<>();
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.security.core.util.SecurityFrameworkUtils;
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.framework.notify.NotifySendEvent;
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.monthly.MonthlyReportApprovalRecordDO;
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.ProjectReportCurrentItemDO;
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.monthly.MonthlyReportApprovalRecordMapper;
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.ProjectReportCurrentItemMapper;
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.WeeklyReportTravelSegmentMapper;
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.team.TeamDashboardAccessService;
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.user.UserSignatureApi;
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.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.UserManagementRelationApi;
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 jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher;
@@ -109,6 +117,10 @@ public class WorkReportCommonService {
@Resource
private MonthlyReportApprovalRecordMapper monthlyReportApprovalRecordMapper;
@Resource
private MonthlyReportSignOffMapper monthlyReportSignOffMapper;
@Resource
private MonthlyReportSignOffRecordMapper monthlyReportSignOffRecordMapper;
@Resource
private ProjectReportApprovalRecordMapper projectReportApprovalRecordMapper;
@Resource
private WorkReportStatusLogMapper workReportStatusLogMapper;
@@ -121,6 +133,8 @@ public class WorkReportCommonService {
@Resource
private StatusActionTextResolver statusActionTextResolver;
@Resource
private EmployeeSignOffStatusService employeeSignOffStatusService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@@ -129,6 +143,8 @@ public class WorkReportCommonService {
@Resource
private UserManagementRelationApi userManagementRelationApi;
@Resource
private UserSignatureApi userSignatureApi;
@Resource
private ObjectPermissionApi objectPermissionApi;
@Resource
private ProjectMapper projectMapper;
@@ -368,6 +384,7 @@ public class WorkReportCommonService {
MonthlyReportDO report = new MonthlyReportDO();
applyMonthlySaveFields(report, reqVO, profile);
report.setStatusCode(getInitialStatusCode());
report.setSignOffStatus(SignOffConstants.STATUS_NOT_STARTED);
monthlyReportMapper.insert(report);
replaceMonthlyChildren(report.getId(), reqVO);
MonthlyReportDO latest = monthlyReportMapper.selectById(report.getId());
@@ -439,6 +456,29 @@ public class WorkReportCommonService {
.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)
public void submitMonthlyReport(Long id) {
MonthlyReportDO current = validateMonthlyReportExists(id);
@@ -478,6 +518,55 @@ public class WorkReportCommonService {
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)
public void deleteMonthlyReport(Long id) {
MonthlyReportDO current = validateMonthlyReportExists(id);
@@ -513,6 +602,89 @@ public class WorkReportCommonService {
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) {
CurrentUserProfile profile = loadCurrentUserProfile(true);
ProjectDO project = validateProjectExists(projectId);
@@ -810,6 +982,14 @@ public class WorkReportCommonService {
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) {
ProjectReportDO report = validateProjectReportExists(id);
validateReadable(report.getProjectOwnerId(), report.getSupervisorUserId());
@@ -1438,6 +1618,7 @@ public class WorkReportCommonService {
record.setConclusion(statusLog.getToStatus());
record.setOpinion(normalizeReason(reqVO));
if (WorkReportConstants.ACTION_APPROVE.equals(actionCode)) {
UserSignatureRespDTO userSignature = getUserSignature(SecurityFrameworkUtils.getLoginUserId());
record.setMeetingDate(reqVO.getMeetingDate());
record.setStrengthDesc(normalizeNullableText(reqVO.getStrengthDesc()));
record.setStrengthExample(normalizeNullableText(reqVO.getStrengthExample()));
@@ -1446,15 +1627,32 @@ public class WorkReportCommonService {
record.setImprovementSuggestion(normalizeNullableText(reqVO.getImprovementSuggestion()));
record.setPerformanceResult(normalizeNullableText(reqVO.getPerformanceResult()));
record.setEmployeeSignName(normalizeNullableText(reqVO.getEmployeeSignName()));
record.setEmployeeSignatureFileId(null);
record.setEmployeeSignedDate(reqVO.getEmployeeSignedDate());
record.setSupervisorSignName(normalizeNullableText(reqVO.getSupervisorSignName()));
record.setSupervisorSignedDate(reqVO.getSupervisorSignedDate());
record.setSupervisorSignName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
record.setSupervisorSignatureFileId(userSignature == null ? null : userSignature.getFileId());
record.setSupervisorSignedDate(LocalDate.now());
}
record.setAuditorUserId(SecurityFrameworkUtils.getLoginUserId());
record.setAuditorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
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) {
ProjectReportApprovalRecordDO record = new ProjectReportApprovalRecordDO();
record.setProjectReportId(report.getId());
@@ -1513,6 +1711,11 @@ public class WorkReportCommonService {
private MonthlyReportRespVO toMonthlyRespVO(MonthlyReportDO report, boolean withChildren) {
MonthlyReportRespVO respVO = BeanUtils.toBean(report, MonthlyReportRespVO.class);
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) {
respVO.setReviewItems(BeanUtils.toBean(
personalReportReviewItemMapper.selectListByReport(WorkReportConstants.REPORT_TYPE_MONTHLY, report.getId()),
@@ -1704,6 +1907,19 @@ public class WorkReportCommonService {
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) {
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.MonthlyReportRespVO;
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.WorkReportStatusLogRespVO;
@@ -32,12 +34,18 @@ public interface MonthlyReportService {
PageResult<MonthlyReportRespVO> getMonthlyApprovalPage(MonthlyReportPageReqVO reqVO);
PageResult<MonthlyReportRespVO> getMonthlySignOffPage(MonthlyReportPageReqVO reqVO);
MonthlyReportSignOffDetailRespVO getMonthlySignOffDetail(Long id);
void submitMonthlyReport(Long id);
void approveMonthlyReport(Long id, MonthlyReportApproveReqVO reqVO);
void rejectMonthlyReport(Long id, WorkReportStatusActionReqVO reqVO);
void confirmMonthlySignOff(Long id, MonthlyReportSignOffConfirmReqVO reqVO);
void deleteMonthlyReport(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.MonthlyReportRespVO;
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.WorkReportStatusLogRespVO;
import com.njcn.rdms.module.project.service.workreport.common.WorkReportCommonService;
@@ -71,6 +73,16 @@ public class MonthlyReportServiceImpl implements MonthlyReportService {
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
public void submitMonthlyReport(Long id) {
workReportCommonService.submitMonthlyReport(id);
@@ -86,6 +98,11 @@ public class MonthlyReportServiceImpl implements MonthlyReportService {
workReportCommonService.rejectMonthlyReport(id, reqVO);
}
@Override
public void confirmMonthlySignOff(Long id, MonthlyReportSignOffConfirmReqVO reqVO) {
workReportCommonService.confirmMonthlySignOff(id, reqVO);
}
@Override
public void deleteMonthlyReport(Long id) {
workReportCommonService.deleteMonthlyReport(id);