feat(我的绩效): 开发我的绩效功能。
This commit is contained in:
@@ -259,4 +259,25 @@ 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, "仅项目负责人可创建或维护该项目的项目半月报");
|
||||||
|
|
||||||
|
// ========== 绩效管理 1_008_011_xxx ==========
|
||||||
|
ErrorCode PERFORMANCE_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_011_001, "绩效模板不存在");
|
||||||
|
ErrorCode PERFORMANCE_TEMPLATE_CURRENT_NOT_EXISTS = new ErrorCode(1_008_011_002, "当前没有已启用的绩效模板");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_NOT_EXISTS = new ErrorCode(1_008_011_003, "绩效表不存在");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_DUPLICATE = new ErrorCode(1_008_011_004, "该员工当前月份已存在绩效表");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_011_005, "当前绩效表状态不允许编辑");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_STATUS_NOT_ALLOW_DELETE = new ErrorCode(1_008_011_006, "仅待发送状态绩效表允许删除");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_011_007, "当前绩效表为「{}」状态,不支持「{}」操作");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_011_008, "绩效表已被其他人更新,请刷新后重试");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_MANAGER_ONLY = new ErrorCode(1_008_011_009, "仅直属上级可维护该员工绩效表");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_EMPLOYEE_ONLY = new ErrorCode(1_008_011_010, "仅被考核员工可执行该操作");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_READ_FORBIDDEN = new ErrorCode(1_008_011_011, "无权查看该绩效表");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_FILE_REQUIRED = new ErrorCode(1_008_011_012, "请先保存绩效 Excel 后再发送");
|
||||||
|
ErrorCode PERFORMANCE_SHEET_REJECT_REASON_REQUIRED = new ErrorCode(1_008_011_013, "退回绩效表必须填写原因");
|
||||||
|
ErrorCode PERFORMANCE_EMPLOYEE_INVALID = new ErrorCode(1_008_011_014, "被考核员工不是有效系统用户");
|
||||||
|
ErrorCode PERFORMANCE_DIRECT_MANAGER_NOT_EXISTS = new ErrorCode(1_008_011_015, "被考核员工不存在生效中的直属上级");
|
||||||
|
ErrorCode PERFORMANCE_FILE_NOT_EXISTS = new ErrorCode(1_008_011_016, "绩效 Excel 文件不存在");
|
||||||
|
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, "被考核员工部门信息不完整");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.njcn.rdms.module.project.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绩效管理常量。
|
||||||
|
*/
|
||||||
|
public final class PerformanceConstants {
|
||||||
|
|
||||||
|
private PerformanceConstants() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String STATUS_OBJECT_TYPE = "performance_sheet";
|
||||||
|
|
||||||
|
public static final String STATUS_DRAFT = "draft";
|
||||||
|
public static final String STATUS_SENT = "sent";
|
||||||
|
public static final String STATUS_CONFIRMED = "confirmed";
|
||||||
|
public static final String STATUS_REJECTED = "rejected";
|
||||||
|
|
||||||
|
public static final String ACTION_SEND = "send";
|
||||||
|
public static final String ACTION_RESEND = "resend";
|
||||||
|
public static final String ACTION_CONFIRM = "confirm";
|
||||||
|
public static final String ACTION_REJECT = "reject";
|
||||||
|
|
||||||
|
public static final String REMIND_TYPE_PENDING_CONFIRM = "pending_confirm";
|
||||||
|
public static final String REMIND_TYPE_PENDING_SEND = "pending_send";
|
||||||
|
|
||||||
|
public static final String ORG_TYPE_DIRECTION = "direction";
|
||||||
|
public static final String ORG_TYPE_FUNCTION = "function";
|
||||||
|
|
||||||
|
public static final String PERMISSION_TEMPLATE_QUERY = "project:performance-template:query";
|
||||||
|
public static final String PERMISSION_TEMPLATE_UPDATE = "project:performance-template:update";
|
||||||
|
public static final String PERMISSION_SHEET_QUERY = "project:performance-sheet:query";
|
||||||
|
public static final String PERMISSION_SHEET_CREATE = "project:performance-sheet:create";
|
||||||
|
public static final String PERMISSION_SHEET_UPDATE = "project:performance-sheet:update";
|
||||||
|
public static final String PERMISSION_SHEET_DELETE = "project:performance-sheet:delete";
|
||||||
|
public static final String PERMISSION_SHEET_CONFIRM = "project:performance-sheet:confirm";
|
||||||
|
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";
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog;
|
||||||
|
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||||
|
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||||
|
import com.njcn.rdms.module.project.constant.PerformanceConstants;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceMonthlyResultRespVO;
|
||||||
|
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.PerformanceSheetPageReqVO;
|
||||||
|
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.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.service.performance.PerformanceDownloadFile;
|
||||||
|
import com.njcn.rdms.module.project.service.performance.PerformanceSheetService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||||
|
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
@Tag(name = "管理后台 - 绩效表")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/project/performance-sheets")
|
||||||
|
@Validated
|
||||||
|
public class PerformanceSheetController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PerformanceSheetService performanceSheetService;
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "获取绩效表分页")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<PageResult<PerformanceSheetRespVO>> page(@Valid PerformanceSheetPageReqVO reqVO) {
|
||||||
|
return success(performanceSheetService.getSheetPage(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@Operation(summary = "获取绩效表详情")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<PerformanceSheetRespVO> get(@PathVariable("id") Long id) {
|
||||||
|
return success(performanceSheetService.getSheet(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@Operation(summary = "创建绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_CREATE + "')")
|
||||||
|
public CommonResult<Long> create(@Valid @RequestBody PerformanceSheetCreateReqVO reqVO) {
|
||||||
|
return success(performanceSheetService.createSheet(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}/excel")
|
||||||
|
@Operation(summary = "保存绩效 Excel")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_UPDATE + "')")
|
||||||
|
public CommonResult<Boolean> updateExcel(@PathVariable("id") Long id,
|
||||||
|
@Valid @RequestBody PerformanceSheetExcelUpdateReqVO reqVO) {
|
||||||
|
performanceSheetService.updateExcel(id, reqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@Operation(summary = "删除绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_DELETE + "')")
|
||||||
|
public CommonResult<Boolean> delete(@PathVariable("id") Long id) {
|
||||||
|
performanceSheetService.deleteSheet(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/send")
|
||||||
|
@Operation(summary = "发送绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_UPDATE + "')")
|
||||||
|
public CommonResult<Boolean> send(@PathVariable("id") Long id) {
|
||||||
|
performanceSheetService.send(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/resend")
|
||||||
|
@Operation(summary = "重新发送绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_UPDATE + "')")
|
||||||
|
public CommonResult<Boolean> resend(@PathVariable("id") Long id) {
|
||||||
|
performanceSheetService.resend(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/confirm")
|
||||||
|
@Operation(summary = "员工确认绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_CONFIRM + "')")
|
||||||
|
public CommonResult<Boolean> confirm(@PathVariable("id") Long id,
|
||||||
|
@Valid @RequestBody(required = false) PerformanceSheetStatusActionReqVO reqVO) {
|
||||||
|
performanceSheetService.confirm(id, reqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/reject")
|
||||||
|
@Operation(summary = "员工退回绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_REJECT + "')")
|
||||||
|
public CommonResult<Boolean> reject(@PathVariable("id") Long id,
|
||||||
|
@Valid @RequestBody PerformanceSheetStatusActionReqVO reqVO) {
|
||||||
|
performanceSheetService.reject(id, reqVO);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/download")
|
||||||
|
@Operation(summary = "下载单条绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_EXPORT + "')")
|
||||||
|
@ApiAccessLog(operateType = EXPORT)
|
||||||
|
public void download(@PathVariable("id") Long id, HttpServletResponse response) throws IOException {
|
||||||
|
writeDownload(response, performanceSheetService.download(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/batch-download")
|
||||||
|
@Operation(summary = "批量下载绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_EXPORT + "')")
|
||||||
|
@ApiAccessLog(operateType = EXPORT)
|
||||||
|
public void batchDownload(@Valid @RequestBody PerformanceSheetBatchDownloadReqVO reqVO,
|
||||||
|
HttpServletResponse response) throws IOException {
|
||||||
|
writeDownload(response, performanceSheetService.batchDownload(reqVO.getIds()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/export")
|
||||||
|
@Operation(summary = "按筛选条件导出绩效表")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_EXPORT + "')")
|
||||||
|
@ApiAccessLog(operateType = EXPORT)
|
||||||
|
public void export(@Valid PerformanceSheetPageReqVO reqVO, HttpServletResponse response) throws IOException {
|
||||||
|
writeDownload(response, performanceSheetService.exportByCondition(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/status-logs")
|
||||||
|
@Operation(summary = "获取绩效表状态日志")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<List<PerformanceSheetStatusLogRespVO>> statusLogs(@PathVariable("id") Long id) {
|
||||||
|
return success(performanceSheetService.getStatusLogs(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}/response-records")
|
||||||
|
@Operation(summary = "获取绩效表员工反馈历史")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<List<PerformanceSheetResponseRecordRespVO>> responseRecords(@PathVariable("id") Long id) {
|
||||||
|
return success(performanceSheetService.getResponseRecords(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/monthly-result")
|
||||||
|
@Operation(summary = "获取月报审批绩效结果")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<PerformanceMonthlyResultRespVO> monthlyResult(@RequestParam("employeeId") Long employeeId,
|
||||||
|
@RequestParam("periodMonth") String periodMonth) {
|
||||||
|
return success(performanceSheetService.getMonthlyResult(employeeId, periodMonth));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/status-dict")
|
||||||
|
@Operation(summary = "获取绩效表状态字典")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<List<PerformanceSheetStatusDictRespVO>> statusDict() {
|
||||||
|
return success(performanceSheetService.getStatusDict());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/status-transitions")
|
||||||
|
@Operation(summary = "获取绩效表状态动作字典")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_SHEET_QUERY + "')")
|
||||||
|
public CommonResult<List<PerformanceSheetStatusTransitionRespVO>> statusTransitions() {
|
||||||
|
return success(performanceSheetService.getStatusTransitions());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeDownload(HttpServletResponse response, PerformanceDownloadFile file) throws IOException {
|
||||||
|
response.setContentType(file.contentType());
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"
|
||||||
|
+ URLEncoder.encode(file.filename(), StandardCharsets.UTF_8).replace("+", "%20"));
|
||||||
|
response.setContentLength(file.content().length);
|
||||||
|
response.getOutputStream().write(file.content());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||||
|
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||||
|
import com.njcn.rdms.module.project.constant.PerformanceConstants;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplatePageReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplateRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplateUploadReqVO;
|
||||||
|
import com.njcn.rdms.module.project.service.performance.PerformanceTemplateService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
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.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
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.RestController;
|
||||||
|
|
||||||
|
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
@Tag(name = "管理后台 - 绩效模板")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/project/performance-templates")
|
||||||
|
@Validated
|
||||||
|
public class PerformanceTemplateController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PerformanceTemplateService performanceTemplateService;
|
||||||
|
|
||||||
|
@GetMapping("/current")
|
||||||
|
@Operation(summary = "获取当前生效绩效模板")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_TEMPLATE_QUERY + "')")
|
||||||
|
public CommonResult<PerformanceTemplateRespVO> current() {
|
||||||
|
return success(performanceTemplateService.getCurrentTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/page")
|
||||||
|
@Operation(summary = "获取绩效模板分页")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_TEMPLATE_QUERY + "')")
|
||||||
|
public CommonResult<PageResult<PerformanceTemplateRespVO>> page(@Valid PerformanceTemplatePageReqVO reqVO) {
|
||||||
|
return success(performanceTemplateService.getTemplatePage(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/upload")
|
||||||
|
@Operation(summary = "保存绩效模板上传记录")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_TEMPLATE_UPDATE + "')")
|
||||||
|
public CommonResult<Long> upload(@Valid @RequestBody PerformanceTemplateUploadReqVO reqVO) {
|
||||||
|
return success(performanceTemplateService.uploadTemplate(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/activate")
|
||||||
|
@Operation(summary = "启用绩效模板")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_TEMPLATE_UPDATE + "')")
|
||||||
|
public CommonResult<Boolean> activate(@PathVariable("id") Long id) {
|
||||||
|
performanceTemplateService.activateTemplate(id);
|
||||||
|
return success(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.team;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||||
|
import com.njcn.rdms.module.project.constant.PerformanceConstants;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryRespVO;
|
||||||
|
import com.njcn.rdms.module.project.service.performance.team.TeamPerformanceService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
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.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.RestController;
|
||||||
|
|
||||||
|
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
|
@Tag(name = "管理后台 - 团队绩效")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/project/performance-sheets/team")
|
||||||
|
@Validated
|
||||||
|
public class TeamPerformanceController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TeamPerformanceService teamPerformanceService;
|
||||||
|
|
||||||
|
@GetMapping("/summary")
|
||||||
|
@Operation(summary = "获取团队绩效统计")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_TEAM_DASHBOARD + "')")
|
||||||
|
public CommonResult<TeamPerformanceSummaryRespVO> getSummary(@Valid TeamPerformanceSummaryReqVO reqVO) {
|
||||||
|
return success(teamPerformanceService.getSummary(reqVO));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/remind")
|
||||||
|
@Operation(summary = "催办团队绩效")
|
||||||
|
@PreAuthorize("@ss.hasPermission('" + PerformanceConstants.PERMISSION_TEAM_DASHBOARD + "')")
|
||||||
|
public CommonResult<TeamPerformanceRemindRespVO> remind(@Valid @RequestBody TeamPerformanceRemindReqVO reqVO) {
|
||||||
|
return success(teamPerformanceService.remind(reqVO));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.team.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 团队绩效催办 Request VO")
|
||||||
|
@Data
|
||||||
|
public class TeamPerformanceRemindReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份起始,格式 yyyy-MM;与 periodMonthEnd 构成区间筛选,两者均为空时默认当前月", example = "2026-03")
|
||||||
|
private String periodMonthStart;
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份结束,格式 yyyy-MM;与 periodMonthStart 构成区间筛选,两者均为空时默认当前月", example = "2026-05")
|
||||||
|
private String periodMonthEnd;
|
||||||
|
|
||||||
|
@Schema(description = "催办类型:pending_confirm / pending_send", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "催办类型不能为空")
|
||||||
|
private String remindType;
|
||||||
|
|
||||||
|
@Schema(description = "目标员工 ID 列表;为空表示全部待办")
|
||||||
|
private List<Long> userIds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.team.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 团队绩效催办 Response VO")
|
||||||
|
@Data
|
||||||
|
public class TeamPerformanceRemindRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "实际催办人数", example = "3")
|
||||||
|
private Integer remindedCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.team.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 团队绩效统计 Request VO")
|
||||||
|
@Data
|
||||||
|
public class TeamPerformanceSummaryReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份起始,格式 yyyy-MM;与 periodMonthEnd 构成区间筛选,两者均为空时默认当前月", example = "2026-03")
|
||||||
|
private String periodMonthStart;
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份结束,格式 yyyy-MM;与 periodMonthStart 构成区间筛选,两者均为空时默认当前月", example = "2026-05")
|
||||||
|
private String periodMonthEnd;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.team.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 团队绩效统计 Response VO")
|
||||||
|
@Data
|
||||||
|
public class TeamPerformanceSummaryRespVO {
|
||||||
|
|
||||||
|
private String periodMonthStart;
|
||||||
|
private String periodMonthEnd;
|
||||||
|
private Integer totalSheetCount;
|
||||||
|
private Integer pendingSendCount;
|
||||||
|
private Integer pendingConfirmCount;
|
||||||
|
private BigDecimal confirmedRate;
|
||||||
|
private List<PendingSendUser> pendingSendUsers;
|
||||||
|
private List<PendingConfirmUser> pendingConfirmUsers;
|
||||||
|
private List<DeptOrgAverage> deptOrgAverages;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class PendingSendUser {
|
||||||
|
private Long userId;
|
||||||
|
private String userNickname;
|
||||||
|
private Long managerUserId;
|
||||||
|
private String managerName;
|
||||||
|
private Long sheetId;
|
||||||
|
private String statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class PendingConfirmUser {
|
||||||
|
private Long userId;
|
||||||
|
private String userNickname;
|
||||||
|
private Long sheetId;
|
||||||
|
private LocalDateTime sentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DeptOrgAverage {
|
||||||
|
private Long deptId;
|
||||||
|
private String deptName;
|
||||||
|
private String deptOrgType;
|
||||||
|
private BigDecimal averageScore;
|
||||||
|
private Integer confirmedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 月报审批绩效结果 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceMonthlyResultRespVO {
|
||||||
|
|
||||||
|
private Long sheetId;
|
||||||
|
private String periodMonth;
|
||||||
|
private Long employeeId;
|
||||||
|
private BigDecimal actualScoreTotal;
|
||||||
|
private BigDecimal baseScoreTotal;
|
||||||
|
private BigDecimal extraScoreTotal;
|
||||||
|
private String statusCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效分数单元格映射 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceScoreCellMappingRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "实际得分总计单元格", example = "J10")
|
||||||
|
private String actualScoreTotalCell;
|
||||||
|
|
||||||
|
@Schema(description = "基础得分总计单元格", example = "K10")
|
||||||
|
private String baseScoreTotalCell;
|
||||||
|
|
||||||
|
@Schema(description = "附加得分总计单元格", example = "L10")
|
||||||
|
private String extraScoreTotalCell;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 批量下载绩效表 Request VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetBatchDownloadReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "绩效表 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "绩效表 ID 不能为空")
|
||||||
|
private List<Long> ids;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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 jakarta.validation.constraints.Pattern;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 创建绩效表 Request VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetCreateReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份,格式 yyyy-MM", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06")
|
||||||
|
@NotBlank(message = "绩效月份不能为空")
|
||||||
|
@Pattern(regexp = "^\\d{4}-\\d{2}$", message = "绩效月份格式必须为 yyyy-MM")
|
||||||
|
private String periodMonth;
|
||||||
|
|
||||||
|
@Schema(description = "被考核员工 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1002")
|
||||||
|
@NotNull(message = "被考核员工不能为空")
|
||||||
|
private Long employeeId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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 jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 保存绩效 Excel Request VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetExcelUpdateReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "当前文件 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2042074259501088770")
|
||||||
|
@NotNull(message = "文件 ID 不能为空")
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
@Schema(description = "当前文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06月-绩效表_张三.xlsx")
|
||||||
|
@NotBlank(message = "文件名不能为空")
|
||||||
|
@Size(max = 255, message = "文件名长度不能超过255个字符")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@Schema(description = "当前文件版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||||
|
@NotNull(message = "文件版本不能为空")
|
||||||
|
private Integer fileVersion;
|
||||||
|
|
||||||
|
@Schema(description = "实际得分总计", requiredMode = Schema.RequiredMode.REQUIRED, example = "13.00")
|
||||||
|
@NotNull(message = "实际得分总计不能为空")
|
||||||
|
private BigDecimal actualScoreTotal;
|
||||||
|
|
||||||
|
@Schema(description = "基础得分总计", requiredMode = Schema.RequiredMode.REQUIRED, example = "6.00")
|
||||||
|
@NotNull(message = "基础得分总计不能为空")
|
||||||
|
private BigDecimal baseScoreTotal;
|
||||||
|
|
||||||
|
@Schema(description = "附加得分总计", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.00")
|
||||||
|
@NotNull(message = "附加得分总计不能为空")
|
||||||
|
private BigDecimal extraScoreTotal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效表分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PerformanceSheetPageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "团队视角下的员工 ID 列表")
|
||||||
|
private List<Long> employeeIds;
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份起始,格式 yyyy-MM;与 periodMonthEnd 构成区间筛选", example = "2026-03")
|
||||||
|
private String periodMonthStart;
|
||||||
|
|
||||||
|
@Schema(description = "绩效月份结束,格式 yyyy-MM;与 periodMonthStart 构成区间筛选", example = "2026-05")
|
||||||
|
private String periodMonthEnd;
|
||||||
|
|
||||||
|
@Schema(description = "员工姓名", example = "张三")
|
||||||
|
private String employeeName;
|
||||||
|
|
||||||
|
@Schema(description = "员工部门 ID", example = "100")
|
||||||
|
private Long employeeDeptId;
|
||||||
|
|
||||||
|
@Schema(description = "员工部门名称", example = "产品方向")
|
||||||
|
private String employeeDeptName;
|
||||||
|
|
||||||
|
@Schema(description = "直属上级 ID", example = "9001")
|
||||||
|
private Long managerId;
|
||||||
|
|
||||||
|
@Schema(description = "直属上级姓名", example = "王经理")
|
||||||
|
private String managerName;
|
||||||
|
|
||||||
|
@Schema(description = "状态编码", example = "sent")
|
||||||
|
private String statusCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效表 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetRespVO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String periodMonth;
|
||||||
|
private Long employeeId;
|
||||||
|
private String employeeName;
|
||||||
|
private Long employeeDeptId;
|
||||||
|
private String employeeDeptName;
|
||||||
|
private String deptOrgType;
|
||||||
|
private Long managerId;
|
||||||
|
private String managerName;
|
||||||
|
private Long templateId;
|
||||||
|
private Long fileId;
|
||||||
|
private String fileName;
|
||||||
|
private Integer fileVersion;
|
||||||
|
private String statusCode;
|
||||||
|
private String statusName;
|
||||||
|
private BigDecimal actualScoreTotal;
|
||||||
|
private BigDecimal baseScoreTotal;
|
||||||
|
private BigDecimal extraScoreTotal;
|
||||||
|
private LocalDateTime sentTime;
|
||||||
|
private LocalDateTime confirmedTime;
|
||||||
|
private LocalDateTime rejectedTime;
|
||||||
|
private String lastStatusReason;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -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.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效表员工反馈历史 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetResponseRecordRespVO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long sheetId;
|
||||||
|
private Long statusLogId;
|
||||||
|
private Integer roundNo;
|
||||||
|
private String actionType;
|
||||||
|
private String fromStatus;
|
||||||
|
private String toStatus;
|
||||||
|
private String opinion;
|
||||||
|
private Long responderUserId;
|
||||||
|
private String responderName;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效表状态动作 Request VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetStatusActionReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "原因/意见", example = "请重新确认分数")
|
||||||
|
@Size(max = 1000, message = "原因长度不能超过1000个字符")
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理后台 - 绩效表状态字典 Response VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - 绩效表状态字典 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetStatusDictRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "状态编码", example = "draft")
|
||||||
|
private String statusCode;
|
||||||
|
|
||||||
|
@Schema(description = "状态名称", example = "待发送")
|
||||||
|
private String statusName;
|
||||||
|
|
||||||
|
@Schema(description = "排序值", example = "10")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
@Schema(description = "是否初始状态", example = "true")
|
||||||
|
private Boolean initialFlag;
|
||||||
|
|
||||||
|
@Schema(description = "是否终态", example = "false")
|
||||||
|
private Boolean terminalFlag;
|
||||||
|
|
||||||
|
@Schema(description = "是否允许编辑", example = "true")
|
||||||
|
private Boolean allowEdit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效表状态日志 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetStatusLogRespVO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long sheetId;
|
||||||
|
private String actionType;
|
||||||
|
private String fromStatus;
|
||||||
|
private String toStatus;
|
||||||
|
private String reason;
|
||||||
|
private Long operatorUserId;
|
||||||
|
private String operatorName;
|
||||||
|
private String periodMonthSnapshot;
|
||||||
|
private String employeeNameSnapshot;
|
||||||
|
private String remark;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理后台 - 绩效表状态动作字典 Response VO。
|
||||||
|
*/
|
||||||
|
@Schema(description = "管理后台 - 绩效表状态动作字典 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceSheetStatusTransitionRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "动作编码", example = "send")
|
||||||
|
private String actionCode;
|
||||||
|
|
||||||
|
@Schema(description = "动作名称", example = "发送")
|
||||||
|
private String actionName;
|
||||||
|
|
||||||
|
@Schema(description = "起始状态编码", example = "draft")
|
||||||
|
private String fromStatusCode;
|
||||||
|
|
||||||
|
@Schema(description = "目标状态编码", example = "sent")
|
||||||
|
private String toStatusCode;
|
||||||
|
|
||||||
|
@Schema(description = "是否必须填写原因", example = "false")
|
||||||
|
private Boolean needReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.PageParam;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效模板分页 Request VO")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PerformanceTemplatePageReqVO extends PageParam {
|
||||||
|
|
||||||
|
@Schema(description = "模板名称", example = "2026绩效模板")
|
||||||
|
private String templateName;
|
||||||
|
|
||||||
|
@Schema(description = "是否当前生效", example = "true")
|
||||||
|
private Boolean activeFlag;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.njcn.rdms.module.project.controller.admin.performance.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效模板 Response VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceTemplateRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "模板 ID", example = "1024")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "模板名称", example = "2026绩效模板")
|
||||||
|
private String templateName;
|
||||||
|
|
||||||
|
@Schema(description = "文件 ID", example = "1024")
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
@Schema(description = "原文件名", example = "绩效模板.xlsx")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@Schema(description = "版本号", example = "1")
|
||||||
|
private Integer versionNo;
|
||||||
|
|
||||||
|
@Schema(description = "是否当前生效", example = "true")
|
||||||
|
private Boolean activeFlag;
|
||||||
|
|
||||||
|
@Schema(description = "上传人 ID", example = "1001")
|
||||||
|
private Long uploadUserId;
|
||||||
|
|
||||||
|
@Schema(description = "上传人名称", example = "张三")
|
||||||
|
private String uploadUserName;
|
||||||
|
|
||||||
|
@Schema(description = "上传时间")
|
||||||
|
private LocalDateTime uploadTime;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Schema(description = "分数单元格映射")
|
||||||
|
private PerformanceScoreCellMappingRespVO scoreCellMapping;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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 jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - 绩效模板上传 Request VO")
|
||||||
|
@Data
|
||||||
|
public class PerformanceTemplateUploadReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026绩效模板")
|
||||||
|
@NotBlank(message = "模板名称不能为空")
|
||||||
|
@Size(max = 100, message = "模板名称长度不能超过100个字符")
|
||||||
|
private String templateName;
|
||||||
|
|
||||||
|
@Schema(description = "文件 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2042074259501088770")
|
||||||
|
@NotNull(message = "文件 ID 不能为空")
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
@Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "绩效模板.xlsx")
|
||||||
|
@NotBlank(message = "文件名不能为空")
|
||||||
|
@Size(max = 255, message = "文件名长度不能超过255个字符")
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
@Schema(description = "是否上传后立即启用", example = "true")
|
||||||
|
private Boolean activeFlag;
|
||||||
|
|
||||||
|
@Schema(description = "备注", example = "2026年度模板")
|
||||||
|
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 员工绩效表。
|
||||||
|
*/
|
||||||
|
@TableName("rdms_performance_sheet")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PerformanceSheetDO extends BaseDO {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String periodMonth;
|
||||||
|
|
||||||
|
private Long employeeId;
|
||||||
|
|
||||||
|
private String employeeName;
|
||||||
|
|
||||||
|
private Long employeeDeptId;
|
||||||
|
|
||||||
|
private String employeeDeptName;
|
||||||
|
|
||||||
|
private String deptOrgType;
|
||||||
|
|
||||||
|
private Long managerId;
|
||||||
|
|
||||||
|
private String managerName;
|
||||||
|
|
||||||
|
private Long templateId;
|
||||||
|
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
private Integer fileVersion;
|
||||||
|
|
||||||
|
private String statusCode;
|
||||||
|
|
||||||
|
private BigDecimal actualScoreTotal;
|
||||||
|
|
||||||
|
private BigDecimal baseScoreTotal;
|
||||||
|
|
||||||
|
private BigDecimal extraScoreTotal;
|
||||||
|
|
||||||
|
private LocalDateTime sentTime;
|
||||||
|
|
||||||
|
private LocalDateTime confirmedTime;
|
||||||
|
|
||||||
|
private LocalDateTime rejectedTime;
|
||||||
|
|
||||||
|
private String lastStatusReason;
|
||||||
|
}
|
||||||
@@ -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_response_record")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PerformanceSheetResponseRecordDO extends BaseDO {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long sheetId;
|
||||||
|
|
||||||
|
private Long statusLogId;
|
||||||
|
|
||||||
|
private Integer roundNo;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private String fromStatus;
|
||||||
|
|
||||||
|
private String toStatus;
|
||||||
|
|
||||||
|
private String opinion;
|
||||||
|
|
||||||
|
private Long responderUserId;
|
||||||
|
|
||||||
|
private String responderName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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_status_log")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PerformanceSheetStatusLogDO extends BaseDO {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long sheetId;
|
||||||
|
|
||||||
|
private String actionType;
|
||||||
|
|
||||||
|
private String fromStatus;
|
||||||
|
|
||||||
|
private String toStatus;
|
||||||
|
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
private Long operatorUserId;
|
||||||
|
|
||||||
|
private String operatorName;
|
||||||
|
|
||||||
|
private String periodMonthSnapshot;
|
||||||
|
|
||||||
|
private String employeeNameSnapshot;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绩效模板。
|
||||||
|
*/
|
||||||
|
@TableName("rdms_performance_template")
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PerformanceTemplateDO extends BaseDO {
|
||||||
|
|
||||||
|
@TableId
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String templateName;
|
||||||
|
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
private String fileName;
|
||||||
|
|
||||||
|
private Integer versionNo;
|
||||||
|
|
||||||
|
private Boolean activeFlag;
|
||||||
|
|
||||||
|
private Long uploadUserId;
|
||||||
|
|
||||||
|
private String uploadUserName;
|
||||||
|
|
||||||
|
private LocalDateTime uploadTime;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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.module.project.controller.admin.performance.vo.PerformanceSheetPageReqVO;
|
||||||
|
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PerformanceSheetMapper extends BaseMapperX<PerformanceSheetDO> {
|
||||||
|
|
||||||
|
default PerformanceSheetDO selectByEmployeeIdAndPeriodMonth(Long employeeId, String periodMonth) {
|
||||||
|
return selectOne(new LambdaQueryWrapperX<PerformanceSheetDO>()
|
||||||
|
.eq(PerformanceSheetDO::getEmployeeId, employeeId)
|
||||||
|
.eq(PerformanceSheetDO::getPeriodMonth, periodMonth));
|
||||||
|
}
|
||||||
|
|
||||||
|
default PageResult<PerformanceSheetDO> selectEmployeePage(Long employeeId, PerformanceSheetPageReqVO reqVO) {
|
||||||
|
LambdaQueryWrapperX<PerformanceSheetDO> wrapper = buildPageQuery(reqVO)
|
||||||
|
.eq(PerformanceSheetDO::getEmployeeId, employeeId)
|
||||||
|
.orderByDesc(PerformanceSheetDO::getPeriodMonth)
|
||||||
|
.orderByDesc(PerformanceSheetDO::getId);
|
||||||
|
return selectPage(reqVO, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
default PageResult<PerformanceSheetDO> selectEmployeePage(Collection<Long> employeeIds,
|
||||||
|
PerformanceSheetPageReqVO reqVO) {
|
||||||
|
if (employeeIds == null || employeeIds.isEmpty()) {
|
||||||
|
return new PageResult<>(List.of(), 0L);
|
||||||
|
}
|
||||||
|
LambdaQueryWrapperX<PerformanceSheetDO> wrapper = buildPageQuery(reqVO)
|
||||||
|
.in(PerformanceSheetDO::getEmployeeId, employeeIds)
|
||||||
|
.orderByDesc(PerformanceSheetDO::getPeriodMonth)
|
||||||
|
.orderByDesc(PerformanceSheetDO::getId);
|
||||||
|
return selectPage(reqVO, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<PerformanceSheetDO> selectListByEmployeeIdsAndPeriodMonth(Collection<Long> employeeIds,
|
||||||
|
String periodMonth) {
|
||||||
|
if (employeeIds == null || employeeIds.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return selectList(new LambdaQueryWrapperX<PerformanceSheetDO>()
|
||||||
|
.in(PerformanceSheetDO::getEmployeeId, employeeIds)
|
||||||
|
.eq(PerformanceSheetDO::getPeriodMonth, periodMonth)
|
||||||
|
.orderByAsc(PerformanceSheetDO::getEmployeeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按员工 ID 列表与月份区间查询绩效表。
|
||||||
|
*/
|
||||||
|
default List<PerformanceSheetDO> selectListByEmployeeIdsAndPeriodMonthRange(Collection<Long> employeeIds,
|
||||||
|
String periodMonthStart,
|
||||||
|
String periodMonthEnd) {
|
||||||
|
if (employeeIds == null || employeeIds.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return selectList(new LambdaQueryWrapperX<PerformanceSheetDO>()
|
||||||
|
.in(PerformanceSheetDO::getEmployeeId, employeeIds)
|
||||||
|
.geIfPresent(PerformanceSheetDO::getPeriodMonth, periodMonthStart)
|
||||||
|
.leIfPresent(PerformanceSheetDO::getPeriodMonth, periodMonthEnd)
|
||||||
|
.orderByAsc(PerformanceSheetDO::getEmployeeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<PerformanceSheetDO> selectExportList(PerformanceSheetPageReqVO reqVO, Collection<Long> employeeIds) {
|
||||||
|
if (employeeIds == null || employeeIds.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return selectList(buildPageQuery(reqVO)
|
||||||
|
.in(PerformanceSheetDO::getEmployeeId, employeeIds)
|
||||||
|
.orderByDesc(PerformanceSheetDO::getPeriodMonth)
|
||||||
|
.orderByDesc(PerformanceSheetDO::getId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default int updateExcelByIdAndVersion(PerformanceSheetDO update, Long id, Integer fileVersion) {
|
||||||
|
return update(update, new LambdaQueryWrapperX<PerformanceSheetDO>()
|
||||||
|
.eq(PerformanceSheetDO::getId, id)
|
||||||
|
.eq(PerformanceSheetDO::getFileVersion, fileVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
default int updateStatusByIdAndStatus(PerformanceSheetDO update, Long id, String fromStatus) {
|
||||||
|
return update(update, new LambdaQueryWrapperX<PerformanceSheetDO>()
|
||||||
|
.eq(PerformanceSheetDO::getId, id)
|
||||||
|
.eq(PerformanceSheetDO::getStatusCode, fromStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
private LambdaQueryWrapperX<PerformanceSheetDO> buildPageQuery(PerformanceSheetPageReqVO reqVO) {
|
||||||
|
return new LambdaQueryWrapperX<PerformanceSheetDO>()
|
||||||
|
.geIfPresent(PerformanceSheetDO::getPeriodMonth, reqVO.getPeriodMonthStart())
|
||||||
|
.leIfPresent(PerformanceSheetDO::getPeriodMonth, reqVO.getPeriodMonthEnd())
|
||||||
|
.likeIfPresent(PerformanceSheetDO::getEmployeeName, reqVO.getEmployeeName())
|
||||||
|
.eqIfPresent(PerformanceSheetDO::getEmployeeDeptId, reqVO.getEmployeeDeptId())
|
||||||
|
.likeIfPresent(PerformanceSheetDO::getEmployeeDeptName, reqVO.getEmployeeDeptName())
|
||||||
|
.eqIfPresent(PerformanceSheetDO::getManagerId, reqVO.getManagerId())
|
||||||
|
.likeIfPresent(PerformanceSheetDO::getManagerName, reqVO.getManagerName())
|
||||||
|
.eqIfPresent(PerformanceSheetDO::getStatusCode, reqVO.getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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.PerformanceSheetResponseRecordDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PerformanceSheetResponseRecordMapper extends BaseMapperX<PerformanceSheetResponseRecordDO> {
|
||||||
|
|
||||||
|
default List<PerformanceSheetResponseRecordDO> selectListBySheetId(Long sheetId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<PerformanceSheetResponseRecordDO>()
|
||||||
|
.eq(PerformanceSheetResponseRecordDO::getSheetId, sheetId)
|
||||||
|
.orderByDesc(PerformanceSheetResponseRecordDO::getCreateTime)
|
||||||
|
.orderByDesc(PerformanceSheetResponseRecordDO::getId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default int countBySheetId(Long sheetId) {
|
||||||
|
return Math.toIntExact(selectCount(new LambdaQueryWrapperX<PerformanceSheetResponseRecordDO>()
|
||||||
|
.eq(PerformanceSheetResponseRecordDO::getSheetId, sheetId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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.PerformanceSheetStatusLogDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PerformanceSheetStatusLogMapper extends BaseMapperX<PerformanceSheetStatusLogDO> {
|
||||||
|
|
||||||
|
default List<PerformanceSheetStatusLogDO> selectListBySheetId(Long sheetId) {
|
||||||
|
return selectList(new LambdaQueryWrapperX<PerformanceSheetStatusLogDO>()
|
||||||
|
.eq(PerformanceSheetStatusLogDO::getSheetId, sheetId)
|
||||||
|
.orderByDesc(PerformanceSheetStatusLogDO::getCreateTime)
|
||||||
|
.orderByDesc(PerformanceSheetStatusLogDO::getId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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.module.project.controller.admin.performance.vo.PerformanceTemplatePageReqVO;
|
||||||
|
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceTemplateDO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PerformanceTemplateMapper extends BaseMapperX<PerformanceTemplateDO> {
|
||||||
|
|
||||||
|
default PerformanceTemplateDO selectCurrent() {
|
||||||
|
return selectOne(new LambdaQueryWrapperX<PerformanceTemplateDO>()
|
||||||
|
.eq(PerformanceTemplateDO::getActiveFlag, true)
|
||||||
|
.orderByDesc(PerformanceTemplateDO::getVersionNo)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
default PageResult<PerformanceTemplateDO> selectPage(PerformanceTemplatePageReqVO reqVO) {
|
||||||
|
return selectPage(reqVO, new LambdaQueryWrapperX<PerformanceTemplateDO>()
|
||||||
|
.likeIfPresent(PerformanceTemplateDO::getTemplateName, reqVO.getTemplateName())
|
||||||
|
.eqIfPresent(PerformanceTemplateDO::getActiveFlag, reqVO.getActiveFlag())
|
||||||
|
.orderByDesc(PerformanceTemplateDO::getVersionNo)
|
||||||
|
.orderByDesc(PerformanceTemplateDO::getId));
|
||||||
|
}
|
||||||
|
|
||||||
|
default Integer selectMaxVersionNo() {
|
||||||
|
PerformanceTemplateDO template = selectOne(new LambdaQueryWrapperX<PerformanceTemplateDO>()
|
||||||
|
.select(PerformanceTemplateDO::getVersionNo)
|
||||||
|
.orderByDesc(PerformanceTemplateDO::getVersionNo)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
return template == null || template.getVersionNo() == null ? 0 : template.getVersionNo();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,4 +43,16 @@ public class NotifyTemplateCodeConstants {
|
|||||||
/** 产品需求创建:通知处理人 */
|
/** 产品需求创建:通知处理人 */
|
||||||
public static final String PRODUCT_REQUIREMENT_ASSIGNED = "product_requirement_assigned";
|
public static final String PRODUCT_REQUIREMENT_ASSIGNED = "product_requirement_assigned";
|
||||||
|
|
||||||
|
/** 绩效表发送:通知员工确认绩效表 */
|
||||||
|
public static final String PERFORMANCE_SENT = "performance_sent";
|
||||||
|
|
||||||
|
/** 绩效待确认催办:主管催办员工确认绩效表 */
|
||||||
|
public static final String PERFORMANCE_PENDING_CONFIRM_REMIND = "performance_pending_confirm_remind";
|
||||||
|
|
||||||
|
/** 绩效待发送催办:提醒直属上级发送或重新发送绩效表 */
|
||||||
|
public static final String PERFORMANCE_PENDING_SEND_REMIND = "performance_pending_send_remind";
|
||||||
|
|
||||||
|
/** 绩效表退回:通知直属上级处理员工退回的绩效表 */
|
||||||
|
public static final String PERFORMANCE_REJECTED = "performance_rejected";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance;
|
||||||
|
|
||||||
|
public record PerformanceDownloadFile(String filename, String contentType, byte[] content) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绩效配置。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "rdms.performance")
|
||||||
|
@Data
|
||||||
|
public class PerformanceProperties {
|
||||||
|
|
||||||
|
private ScoreCellMapping scoreCellMapping = new ScoreCellMapping();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ScoreCellMapping {
|
||||||
|
|
||||||
|
private String actualScoreTotal = "J10";
|
||||||
|
|
||||||
|
private String baseScoreTotal = "K10";
|
||||||
|
|
||||||
|
private String extraScoreTotal = "L10";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance;
|
||||||
|
|
||||||
|
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.PerformanceSheetPageReqVO;
|
||||||
|
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.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 java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface PerformanceSheetService {
|
||||||
|
|
||||||
|
PageResult<PerformanceSheetRespVO> getSheetPage(PerformanceSheetPageReqVO reqVO);
|
||||||
|
|
||||||
|
PerformanceSheetRespVO getSheet(Long id);
|
||||||
|
|
||||||
|
Long createSheet(PerformanceSheetCreateReqVO reqVO);
|
||||||
|
|
||||||
|
void updateExcel(Long id, PerformanceSheetExcelUpdateReqVO reqVO);
|
||||||
|
|
||||||
|
void deleteSheet(Long id);
|
||||||
|
|
||||||
|
void send(Long id);
|
||||||
|
|
||||||
|
void resend(Long id);
|
||||||
|
|
||||||
|
void confirm(Long id, PerformanceSheetStatusActionReqVO reqVO);
|
||||||
|
|
||||||
|
void reject(Long id, PerformanceSheetStatusActionReqVO reqVO);
|
||||||
|
|
||||||
|
PerformanceDownloadFile download(Long id);
|
||||||
|
|
||||||
|
PerformanceDownloadFile batchDownload(Collection<Long> ids);
|
||||||
|
|
||||||
|
PerformanceDownloadFile exportByCondition(PerformanceSheetPageReqVO reqVO);
|
||||||
|
|
||||||
|
List<PerformanceSheetStatusLogRespVO> getStatusLogs(Long id);
|
||||||
|
|
||||||
|
List<PerformanceSheetResponseRecordRespVO> getResponseRecords(Long id);
|
||||||
|
|
||||||
|
PerformanceMonthlyResultRespVO getMonthlyResult(Long employeeId, String periodMonth);
|
||||||
|
|
||||||
|
List<PerformanceSheetStatusDictRespVO> getStatusDict();
|
||||||
|
|
||||||
|
List<PerformanceSheetStatusTransitionRespVO> getStatusTransitions();
|
||||||
|
}
|
||||||
@@ -0,0 +1,634 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||||
|
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.PerformanceConstants;
|
||||||
|
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.PerformanceSheetPageReqVO;
|
||||||
|
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.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.PerformanceSheetStatusLogDO;
|
||||||
|
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceTemplateDO;
|
||||||
|
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.PerformanceSheetStatusLogMapper;
|
||||||
|
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceTemplateMapper;
|
||||||
|
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.framework.notify.NotifySendEvent;
|
||||||
|
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
|
||||||
|
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.dto.DeptRespDTO;
|
||||||
|
import com.njcn.rdms.module.system.api.file.FileApi;
|
||||||
|
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||||
|
import com.njcn.rdms.module.system.api.user.UserManagementRelationApi;
|
||||||
|
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.YearMonth;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PerformanceSheetServiceImpl implements PerformanceSheetService {
|
||||||
|
|
||||||
|
private static final String EXCEL_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
private static final String ZIP_CONTENT_TYPE = "application/zip";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PerformanceSheetMapper performanceSheetMapper;
|
||||||
|
@Resource
|
||||||
|
private PerformanceTemplateMapper performanceTemplateMapper;
|
||||||
|
@Resource
|
||||||
|
private PerformanceSheetStatusLogMapper performanceSheetStatusLogMapper;
|
||||||
|
@Resource
|
||||||
|
private PerformanceSheetResponseRecordMapper performanceSheetResponseRecordMapper;
|
||||||
|
@Resource
|
||||||
|
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||||
|
@Resource
|
||||||
|
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
|
||||||
|
@Resource
|
||||||
|
private StatusActionTextResolver statusActionTextResolver;
|
||||||
|
@Resource
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
@Resource
|
||||||
|
private DeptApi deptApi;
|
||||||
|
@Resource
|
||||||
|
private UserManagementRelationApi userManagementRelationApi;
|
||||||
|
@Resource
|
||||||
|
private FileApi fileApi;
|
||||||
|
@Resource
|
||||||
|
private TeamDashboardAccessService teamDashboardAccessService;
|
||||||
|
@Resource
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<PerformanceSheetRespVO> getSheetPage(PerformanceSheetPageReqVO reqVO) {
|
||||||
|
PageResult<PerformanceSheetDO> pageResult;
|
||||||
|
if (reqVO.getEmployeeIds() != null) {
|
||||||
|
List<Long> employeeIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(
|
||||||
|
reqVO.getEmployeeIds(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
|
pageResult = performanceSheetMapper.selectEmployeePage(employeeIds, reqVO);
|
||||||
|
} else {
|
||||||
|
pageResult = performanceSheetMapper.selectEmployeePage(SecurityFrameworkUtils.getLoginUserId(), reqVO);
|
||||||
|
}
|
||||||
|
return new PageResult<>(pageResult.getList().stream().map(this::toRespVO).toList(), pageResult.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PerformanceSheetRespVO getSheet(Long id) {
|
||||||
|
PerformanceSheetDO sheet = validateReadableSheet(id);
|
||||||
|
return toRespVO(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long createSheet(PerformanceSheetCreateReqVO reqVO) {
|
||||||
|
String periodMonth = normalizePeriodMonth(reqVO.getPeriodMonth());
|
||||||
|
PerformanceTemplateDO template = performanceTemplateMapper.selectCurrent();
|
||||||
|
if (template == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_TEMPLATE_CURRENT_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
if (performanceSheetMapper.selectByEmployeeIdAndPeriodMonth(reqVO.getEmployeeId(), periodMonth) != null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_DUPLICATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminUserRespDTO employee = validateActiveUser(reqVO.getEmployeeId());
|
||||||
|
AdminUserRespDTO manager = loadDirectManager(employee.getId());
|
||||||
|
validateManagerOnly(manager.getId());
|
||||||
|
DeptRespDTO dept = validateEmployeeDept(employee.getDeptId());
|
||||||
|
|
||||||
|
PerformanceSheetDO sheet = new PerformanceSheetDO();
|
||||||
|
sheet.setPeriodMonth(periodMonth);
|
||||||
|
sheet.setEmployeeId(employee.getId());
|
||||||
|
sheet.setEmployeeName(defaultText(employee.getNickname()));
|
||||||
|
sheet.setEmployeeDeptId(employee.getDeptId());
|
||||||
|
sheet.setEmployeeDeptName(dept.getName().trim());
|
||||||
|
sheet.setDeptOrgType(dept.getOrgType().trim());
|
||||||
|
sheet.setManagerId(manager.getId());
|
||||||
|
sheet.setManagerName(defaultText(manager.getNickname()));
|
||||||
|
sheet.setTemplateId(template.getId());
|
||||||
|
sheet.setFileName(buildPerformanceFileName(periodMonth, employee.getNickname()));
|
||||||
|
sheet.setFileVersion(0);
|
||||||
|
sheet.setStatusCode(getInitialStatusCode());
|
||||||
|
performanceSheetMapper.insert(sheet);
|
||||||
|
return sheet.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateExcel(Long id, PerformanceSheetExcelUpdateReqVO reqVO) {
|
||||||
|
PerformanceSheetDO current = validateSheetExists(id);
|
||||||
|
validateManagerOnly(current.getManagerId());
|
||||||
|
validateEditable(current);
|
||||||
|
|
||||||
|
PerformanceSheetDO update = new PerformanceSheetDO();
|
||||||
|
update.setFileId(reqVO.getFileId());
|
||||||
|
update.setFileName(reqVO.getFileName().trim());
|
||||||
|
update.setFileVersion(reqVO.getFileVersion() + 1);
|
||||||
|
update.setActualScoreTotal(normalizeScore(reqVO.getActualScoreTotal()));
|
||||||
|
update.setBaseScoreTotal(normalizeScore(reqVO.getBaseScoreTotal()));
|
||||||
|
update.setExtraScoreTotal(normalizeScore(reqVO.getExtraScoreTotal()));
|
||||||
|
int updateCount = performanceSheetMapper.updateExcelByIdAndVersion(update, id, reqVO.getFileVersion());
|
||||||
|
if (updateCount != 1) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void deleteSheet(Long id) {
|
||||||
|
PerformanceSheetDO sheet = validateSheetExists(id);
|
||||||
|
validateManagerOnly(sheet.getManagerId());
|
||||||
|
if (!PerformanceConstants.STATUS_DRAFT.equals(sheet.getStatusCode())) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_NOT_ALLOW_DELETE);
|
||||||
|
}
|
||||||
|
performanceSheetMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void send(Long id) {
|
||||||
|
processManagerStatusAction(id, PerformanceConstants.ACTION_SEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void resend(Long id) {
|
||||||
|
processManagerStatusAction(id, PerformanceConstants.ACTION_RESEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void confirm(Long id, PerformanceSheetStatusActionReqVO reqVO) {
|
||||||
|
processEmployeeResponse(id, PerformanceConstants.ACTION_CONFIRM,
|
||||||
|
normalizeNullableText(reqVO == null ? null : reqVO.getReason()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void reject(Long id, PerformanceSheetStatusActionReqVO reqVO) {
|
||||||
|
String reason = normalizeNullableText(reqVO == null ? null : reqVO.getReason());
|
||||||
|
if (!StringUtils.hasText(reason)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_REJECT_REASON_REQUIRED);
|
||||||
|
}
|
||||||
|
PerformanceSheetDO sheet = processEmployeeResponse(id, PerformanceConstants.ACTION_REJECT, reason);
|
||||||
|
publishPerformanceNotice(List.of(sheet.getManagerId()), NotifyTemplateCodeConstants.PERFORMANCE_REJECTED, sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PerformanceDownloadFile download(Long id) {
|
||||||
|
PerformanceSheetDO sheet = validateReadableSheet(id);
|
||||||
|
return new PerformanceDownloadFile(defaultDownloadName(sheet), EXCEL_CONTENT_TYPE, readFileContent(sheet));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PerformanceDownloadFile batchDownload(Collection<Long> ids) {
|
||||||
|
if (ids == null || ids.isEmpty()) {
|
||||||
|
throw invalidParamException("绩效表 ID 不能为空");
|
||||||
|
}
|
||||||
|
List<PerformanceSheetDO> sheets = new ArrayList<>();
|
||||||
|
for (Long id : ids) {
|
||||||
|
sheets.add(validateReadableSheet(id));
|
||||||
|
}
|
||||||
|
return zipSheets("绩效表_" + LocalDate.now() + ".zip", sheets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PerformanceDownloadFile exportByCondition(PerformanceSheetPageReqVO reqVO) {
|
||||||
|
List<Long> employeeIds;
|
||||||
|
if (reqVO.getEmployeeIds() != null) {
|
||||||
|
employeeIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(
|
||||||
|
reqVO.getEmployeeIds(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
|
} else {
|
||||||
|
employeeIds = List.of(SecurityFrameworkUtils.getLoginUserId());
|
||||||
|
}
|
||||||
|
List<PerformanceSheetDO> sheets = performanceSheetMapper.selectExportList(reqVO, employeeIds);
|
||||||
|
return zipSheets("绩效表_" + LocalDate.now() + ".zip", sheets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PerformanceSheetStatusLogRespVO> getStatusLogs(Long id) {
|
||||||
|
validateReadableSheet(id);
|
||||||
|
return BeanUtils.toBean(performanceSheetStatusLogMapper.selectListBySheetId(id),
|
||||||
|
PerformanceSheetStatusLogRespVO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PerformanceSheetResponseRecordRespVO> getResponseRecords(Long id) {
|
||||||
|
validateReadableSheet(id);
|
||||||
|
return BeanUtils.toBean(performanceSheetResponseRecordMapper.selectListBySheetId(id),
|
||||||
|
PerformanceSheetResponseRecordRespVO.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PerformanceMonthlyResultRespVO getMonthlyResult(Long employeeId, String periodMonth) {
|
||||||
|
String normalizedMonth = normalizePeriodMonth(periodMonth);
|
||||||
|
PerformanceSheetDO sheet = performanceSheetMapper.selectByEmployeeIdAndPeriodMonth(employeeId, normalizedMonth);
|
||||||
|
PerformanceMonthlyResultRespVO respVO = new PerformanceMonthlyResultRespVO();
|
||||||
|
respVO.setEmployeeId(employeeId);
|
||||||
|
respVO.setPeriodMonth(normalizedMonth);
|
||||||
|
if (sheet == null) {
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
validateReadable(sheet);
|
||||||
|
respVO.setSheetId(sheet.getId());
|
||||||
|
respVO.setActualScoreTotal(sheet.getActualScoreTotal());
|
||||||
|
respVO.setBaseScoreTotal(sheet.getBaseScoreTotal());
|
||||||
|
respVO.setExtraScoreTotal(sheet.getExtraScoreTotal());
|
||||||
|
respVO.setStatusCode(sheet.getStatusCode());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PerformanceSheetStatusDictRespVO> getStatusDict() {
|
||||||
|
return objectStatusModelMapper.selectListByObjectTypeEnabled(PerformanceConstants.STATUS_OBJECT_TYPE)
|
||||||
|
.stream()
|
||||||
|
.map(this::buildStatusDictRespVO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PerformanceSheetStatusTransitionRespVO> getStatusTransitions() {
|
||||||
|
List<String> statusCodes = objectStatusModelMapper
|
||||||
|
.selectListByObjectTypeEnabled(PerformanceConstants.STATUS_OBJECT_TYPE)
|
||||||
|
.stream()
|
||||||
|
.map(ObjectStatusModelDO::getStatusCode)
|
||||||
|
.toList();
|
||||||
|
if (statusCodes.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return objectStatusTransitionMapper
|
||||||
|
.selectListByObjectTypeAndFromStatuses(PerformanceConstants.STATUS_OBJECT_TYPE, statusCodes)
|
||||||
|
.stream()
|
||||||
|
.map(this::buildStatusTransitionRespVO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processManagerStatusAction(Long id, String actionType) {
|
||||||
|
PerformanceSheetDO current = validateSheetExists(id);
|
||||||
|
validateManagerOnly(current.getManagerId());
|
||||||
|
ObjectStatusTransitionDO transition = validateTransition(current.getStatusCode(), actionType, null);
|
||||||
|
if (current.getFileId() == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_FILE_REQUIRED);
|
||||||
|
}
|
||||||
|
PerformanceSheetDO update = new PerformanceSheetDO();
|
||||||
|
update.setStatusCode(transition.getToStatusCode());
|
||||||
|
update.setSentTime(LocalDateTime.now());
|
||||||
|
int updateCount = performanceSheetMapper.updateStatusByIdAndStatus(update, id, current.getStatusCode());
|
||||||
|
if (updateCount != 1) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
|
||||||
|
}
|
||||||
|
PerformanceSheetDO after = merge(current, update);
|
||||||
|
writeStatusLog(after, actionType, current.getStatusCode(), transition.getToStatusCode(), null);
|
||||||
|
publishPerformanceNotice(List.of(after.getEmployeeId()), NotifyTemplateCodeConstants.PERFORMANCE_SENT, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetDO processEmployeeResponse(Long id, String actionType, String reason) {
|
||||||
|
PerformanceSheetDO current = validateSheetExists(id);
|
||||||
|
validateEmployeeOnly(current.getEmployeeId());
|
||||||
|
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());
|
||||||
|
} else if (PerformanceConstants.STATUS_REJECTED.equals(transition.getToStatusCode())) {
|
||||||
|
update.setRejectedTime(LocalDateTime.now());
|
||||||
|
update.setLastStatusReason(reason);
|
||||||
|
}
|
||||||
|
int updateCount = performanceSheetMapper.updateStatusByIdAndStatus(update, id, current.getStatusCode());
|
||||||
|
if (updateCount != 1) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_CONCURRENT_MODIFIED);
|
||||||
|
}
|
||||||
|
PerformanceSheetDO after = merge(current, update);
|
||||||
|
PerformanceSheetStatusLogDO statusLog = writeStatusLog(after, actionType, current.getStatusCode(),
|
||||||
|
transition.getToStatusCode(), reason);
|
||||||
|
writeResponseRecord(after, statusLog, actionType, current.getStatusCode(), transition.getToStatusCode(), reason);
|
||||||
|
return after;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetStatusLogDO writeStatusLog(PerformanceSheetDO sheet, String actionType,
|
||||||
|
String fromStatus, String toStatus, String reason) {
|
||||||
|
PerformanceSheetStatusLogDO statusLog = new PerformanceSheetStatusLogDO();
|
||||||
|
statusLog.setSheetId(sheet.getId());
|
||||||
|
statusLog.setActionType(actionType);
|
||||||
|
statusLog.setFromStatus(fromStatus);
|
||||||
|
statusLog.setToStatus(toStatus);
|
||||||
|
statusLog.setReason(defaultText(reason));
|
||||||
|
statusLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||||
|
statusLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||||
|
statusLog.setPeriodMonthSnapshot(sheet.getPeriodMonth());
|
||||||
|
statusLog.setEmployeeNameSnapshot(sheet.getEmployeeName());
|
||||||
|
statusLog.setRemark(buildRemark(sheet));
|
||||||
|
performanceSheetStatusLogMapper.insert(statusLog);
|
||||||
|
return statusLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeResponseRecord(PerformanceSheetDO sheet, PerformanceSheetStatusLogDO statusLog,
|
||||||
|
String actionType, String fromStatus, String toStatus, String opinion) {
|
||||||
|
PerformanceSheetResponseRecordDO record = new PerformanceSheetResponseRecordDO();
|
||||||
|
record.setSheetId(sheet.getId());
|
||||||
|
record.setStatusLogId(statusLog.getId());
|
||||||
|
record.setRoundNo(performanceSheetResponseRecordMapper.countBySheetId(sheet.getId()) + 1);
|
||||||
|
record.setActionType(actionType);
|
||||||
|
record.setFromStatus(fromStatus);
|
||||||
|
record.setToStatus(toStatus);
|
||||||
|
record.setOpinion(defaultText(opinion));
|
||||||
|
record.setResponderUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||||
|
record.setResponderName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||||
|
performanceSheetResponseRecordMapper.insert(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetDO validateSheetExists(Long id) {
|
||||||
|
PerformanceSheetDO sheet = id == null ? null : performanceSheetMapper.selectById(id);
|
||||||
|
if (sheet == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetDO validateReadableSheet(Long id) {
|
||||||
|
PerformanceSheetDO sheet = validateSheetExists(id);
|
||||||
|
validateReadable(sheet);
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateReadable(PerformanceSheetDO sheet) {
|
||||||
|
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||||
|
if (!Objects.equals(loginUserId, sheet.getEmployeeId())
|
||||||
|
&& !Objects.equals(loginUserId, sheet.getManagerId())
|
||||||
|
&& !teamDashboardAccessService.canReadSubordinateUser(
|
||||||
|
sheet.getEmployeeId(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_READ_FORBIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEditable(PerformanceSheetDO sheet) {
|
||||||
|
ObjectStatusModelDO statusModel = objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled(
|
||||||
|
PerformanceConstants.STATUS_OBJECT_TYPE, sheet.getStatusCode());
|
||||||
|
if (statusModel == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||||
|
}
|
||||||
|
if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_NOT_ALLOW_EDIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateManagerOnly(Long managerId) {
|
||||||
|
if (!Objects.equals(SecurityFrameworkUtils.getLoginUserId(), managerId)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_MANAGER_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEmployeeOnly(Long employeeId) {
|
||||||
|
if (!Objects.equals(SecurityFrameworkUtils.getLoginUserId(), employeeId)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_EMPLOYEE_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminUserRespDTO validateActiveUser(Long userId) {
|
||||||
|
CommonResult<AdminUserRespDTO> result = adminUserApi.getUser(userId);
|
||||||
|
AdminUserRespDTO user = result == null ? null : result.getCheckedData();
|
||||||
|
if (user == null || user.getId() == null || !Objects.equals(user.getStatus(), 0)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_EMPLOYEE_INVALID);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminUserRespDTO loadDirectManager(Long employeeId) {
|
||||||
|
CommonResult<AdminUserRespDTO> result = userManagementRelationApi.getDirectManager(employeeId);
|
||||||
|
AdminUserRespDTO manager = result == null ? null : result.getCheckedData();
|
||||||
|
if (manager == null || manager.getId() == null || !Objects.equals(manager.getStatus(), 0)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_DIRECT_MANAGER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeptRespDTO validateEmployeeDept(Long deptId) {
|
||||||
|
if (deptId == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_EMPLOYEE_DEPT_INVALID);
|
||||||
|
}
|
||||||
|
CommonResult<DeptRespDTO> result = deptApi.getDept(deptId);
|
||||||
|
DeptRespDTO dept = result == null ? null : result.getCheckedData();
|
||||||
|
if (dept == null || dept.getId() == null
|
||||||
|
|| !StringUtils.hasText(dept.getName())
|
||||||
|
|| !StringUtils.hasText(dept.getOrgType())) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_EMPLOYEE_DEPT_INVALID);
|
||||||
|
}
|
||||||
|
return dept;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetRespVO toRespVO(PerformanceSheetDO sheet) {
|
||||||
|
PerformanceSheetRespVO respVO = BeanUtils.toBean(sheet, PerformanceSheetRespVO.class);
|
||||||
|
respVO.setStatusName(statusName(sheet.getStatusCode()));
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceDownloadFile zipSheets(String filename, List<PerformanceSheetDO> sheets) {
|
||||||
|
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||||
|
Map<String, Integer> filenameCounts = new HashMap<>();
|
||||||
|
for (PerformanceSheetDO sheet : sheets) {
|
||||||
|
byte[] content = readFileContent(sheet);
|
||||||
|
String entryName = uniqueFilename(defaultDownloadName(sheet), filenameCounts);
|
||||||
|
zipOutputStream.putNextEntry(new ZipEntry(entryName));
|
||||||
|
zipOutputStream.write(content);
|
||||||
|
zipOutputStream.closeEntry();
|
||||||
|
}
|
||||||
|
zipOutputStream.finish();
|
||||||
|
return new PerformanceDownloadFile(filename, ZIP_CONTENT_TYPE, outputStream.toByteArray());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalStateException("打包绩效表失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readFileContent(PerformanceSheetDO sheet) {
|
||||||
|
if (sheet.getFileId() == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_FILE_REQUIRED);
|
||||||
|
}
|
||||||
|
CommonResult<byte[]> result = fileApi.getFileContent(sheet.getFileId());
|
||||||
|
byte[] content = result == null ? null : result.getCheckedData();
|
||||||
|
if (content == null || content.length == 0) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_FILE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String uniqueFilename(String filename, Map<String, Integer> filenameCounts) {
|
||||||
|
Integer count = filenameCounts.merge(filename, 1, Integer::sum);
|
||||||
|
if (count == 1) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
int dotIndex = filename.lastIndexOf('.');
|
||||||
|
if (dotIndex <= 0) {
|
||||||
|
return filename + "_" + count;
|
||||||
|
}
|
||||||
|
return filename.substring(0, dotIndex) + "_" + count + filename.substring(dotIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetDO merge(PerformanceSheetDO source, PerformanceSheetDO update) {
|
||||||
|
PerformanceSheetDO merged = BeanUtils.toBean(source, PerformanceSheetDO.class);
|
||||||
|
if (update.getStatusCode() != null) {
|
||||||
|
merged.setStatusCode(update.getStatusCode());
|
||||||
|
}
|
||||||
|
if (update.getSentTime() != null) {
|
||||||
|
merged.setSentTime(update.getSentTime());
|
||||||
|
}
|
||||||
|
if (update.getConfirmedTime() != null) {
|
||||||
|
merged.setConfirmedTime(update.getConfirmedTime());
|
||||||
|
}
|
||||||
|
if (update.getRejectedTime() != null) {
|
||||||
|
merged.setRejectedTime(update.getRejectedTime());
|
||||||
|
}
|
||||||
|
if (update.getLastStatusReason() != null) {
|
||||||
|
merged.setLastStatusReason(update.getLastStatusReason());
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishPerformanceNotice(Collection<Long> recipients, String templateCode, PerformanceSheetDO sheet) {
|
||||||
|
if (recipients == null || recipients.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, Object> params = new LinkedHashMap<>();
|
||||||
|
params.put("periodMonth", sheet.getPeriodMonth());
|
||||||
|
params.put("employeeName", sheet.getEmployeeName());
|
||||||
|
params.put("managerName", sheet.getManagerName());
|
||||||
|
params.put("sheetId", sheet.getId());
|
||||||
|
params.put("statusName", statusName(sheet.getStatusCode()));
|
||||||
|
params.put("reason", sheet.getLastStatusReason());
|
||||||
|
applicationEventPublisher.publishEvent(NotifySendEvent.of(new ArrayList<>(new LinkedHashSet<>(recipients)),
|
||||||
|
templateCode, params, NotifyMessageLevelConstants.REMIND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObjectStatusTransitionDO validateTransition(String fromStatus, String actionCode, String reason) {
|
||||||
|
ObjectStatusTransitionDO transition = objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
|
||||||
|
PerformanceConstants.STATUS_OBJECT_TYPE, fromStatus, actionCode);
|
||||||
|
if (transition == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_ACTION_NOT_ALLOWED,
|
||||||
|
statusActionTextResolver.statusName(PerformanceConstants.STATUS_OBJECT_TYPE, fromStatus),
|
||||||
|
statusActionTextResolver.actionName(PerformanceConstants.STATUS_OBJECT_TYPE, actionCode));
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_ACTION_REASON_REQUIRED,
|
||||||
|
statusActionTextResolver.actionName(PerformanceConstants.STATUS_OBJECT_TYPE, actionCode));
|
||||||
|
}
|
||||||
|
ObjectStatusModelDO toModel = objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled(
|
||||||
|
PerformanceConstants.STATUS_OBJECT_TYPE, transition.getToStatusCode());
|
||||||
|
if (toModel == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||||
|
}
|
||||||
|
return transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getInitialStatusCode() {
|
||||||
|
ObjectStatusModelDO statusModel = objectStatusModelMapper.selectInitialByObjectTypeEnabled(
|
||||||
|
PerformanceConstants.STATUS_OBJECT_TYPE);
|
||||||
|
if (statusModel == null || !StringUtils.hasText(statusModel.getStatusCode())) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_SHEET_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||||
|
}
|
||||||
|
return statusModel.getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizePeriodMonth(String periodMonth) {
|
||||||
|
if (!StringUtils.hasText(periodMonth)) {
|
||||||
|
throw invalidParamException("绩效月份不能为空");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return YearMonth.parse(periodMonth.trim()).toString();
|
||||||
|
} catch (DateTimeParseException ex) {
|
||||||
|
throw invalidParamException("绩效月份格式必须为 yyyy-MM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal normalizeScore(BigDecimal value) {
|
||||||
|
return value.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildPerformanceFileName(String periodMonth, String employeeName) {
|
||||||
|
return periodMonth + "月-绩效表_" + defaultText(employeeName) + ".xlsx";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultDownloadName(PerformanceSheetDO sheet) {
|
||||||
|
return StringUtils.hasText(sheet.getFileName())
|
||||||
|
? sheet.getFileName()
|
||||||
|
: buildPerformanceFileName(sheet.getPeriodMonth(), sheet.getEmployeeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildRemark(PerformanceSheetDO sheet) {
|
||||||
|
return sheet.getPeriodMonth() + " " + defaultText(sheet.getEmployeeName()) + " 绩效表";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String statusName(String statusCode) {
|
||||||
|
return statusActionTextResolver.statusName(PerformanceConstants.STATUS_OBJECT_TYPE, statusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetStatusDictRespVO buildStatusDictRespVO(ObjectStatusModelDO statusModel) {
|
||||||
|
PerformanceSheetStatusDictRespVO respVO = new PerformanceSheetStatusDictRespVO();
|
||||||
|
respVO.setStatusCode(statusModel.getStatusCode());
|
||||||
|
respVO.setStatusName(statusModel.getStatusName());
|
||||||
|
respVO.setSort(statusModel.getSort());
|
||||||
|
respVO.setInitialFlag(statusModel.getInitialFlag());
|
||||||
|
respVO.setTerminalFlag(statusModel.getTerminalFlag());
|
||||||
|
respVO.setAllowEdit(statusModel.getAllowEdit());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetStatusTransitionRespVO buildStatusTransitionRespVO(ObjectStatusTransitionDO transition) {
|
||||||
|
PerformanceSheetStatusTransitionRespVO respVO = new PerformanceSheetStatusTransitionRespVO();
|
||||||
|
respVO.setActionCode(transition.getActionCode());
|
||||||
|
respVO.setActionName(transition.getActionName());
|
||||||
|
respVO.setFromStatusCode(transition.getFromStatusCode());
|
||||||
|
respVO.setToStatusCode(transition.getToStatusCode());
|
||||||
|
respVO.setNeedReason(transition.getNeedReason());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeNullableText(String value) {
|
||||||
|
if (!StringUtils.hasText(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultText(String value) {
|
||||||
|
return StringUtils.hasText(value) ? value.trim() : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplatePageReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplateRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplateUploadReqVO;
|
||||||
|
|
||||||
|
public interface PerformanceTemplateService {
|
||||||
|
|
||||||
|
PerformanceTemplateRespVO getCurrentTemplate();
|
||||||
|
|
||||||
|
PageResult<PerformanceTemplateRespVO> getTemplatePage(PerformanceTemplatePageReqVO reqVO);
|
||||||
|
|
||||||
|
Long uploadTemplate(PerformanceTemplateUploadReqVO reqVO);
|
||||||
|
|
||||||
|
void activateTemplate(Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||||
|
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||||
|
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||||
|
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceScoreCellMappingRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplatePageReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplateRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.vo.PerformanceTemplateUploadReqVO;
|
||||||
|
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceTemplateDO;
|
||||||
|
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceTemplateMapper;
|
||||||
|
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PerformanceTemplateServiceImpl implements PerformanceTemplateService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PerformanceTemplateMapper performanceTemplateMapper;
|
||||||
|
@Resource
|
||||||
|
private PerformanceProperties performanceProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PerformanceTemplateRespVO getCurrentTemplate() {
|
||||||
|
PerformanceTemplateDO template = performanceTemplateMapper.selectCurrent();
|
||||||
|
if (template == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_TEMPLATE_CURRENT_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return toRespVO(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<PerformanceTemplateRespVO> getTemplatePage(PerformanceTemplatePageReqVO reqVO) {
|
||||||
|
PageResult<PerformanceTemplateDO> pageResult = performanceTemplateMapper.selectPage(reqVO);
|
||||||
|
return new PageResult<>(pageResult.getList().stream()
|
||||||
|
.map(this::toRespVO)
|
||||||
|
.toList(), pageResult.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long uploadTemplate(PerformanceTemplateUploadReqVO reqVO) {
|
||||||
|
if (Boolean.TRUE.equals(reqVO.getActiveFlag())) {
|
||||||
|
deactivateAllTemplates();
|
||||||
|
}
|
||||||
|
PerformanceTemplateDO template = new PerformanceTemplateDO();
|
||||||
|
template.setTemplateName(reqVO.getTemplateName().trim());
|
||||||
|
template.setFileId(reqVO.getFileId());
|
||||||
|
template.setFileName(reqVO.getFileName().trim());
|
||||||
|
template.setVersionNo(performanceTemplateMapper.selectMaxVersionNo() + 1);
|
||||||
|
template.setActiveFlag(Boolean.TRUE.equals(reqVO.getActiveFlag()));
|
||||||
|
template.setUploadUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||||
|
template.setUploadUserName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||||
|
template.setUploadTime(LocalDateTime.now());
|
||||||
|
template.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||||
|
performanceTemplateMapper.insert(template);
|
||||||
|
return template.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void activateTemplate(Long id) {
|
||||||
|
PerformanceTemplateDO template = validateTemplateExists(id);
|
||||||
|
deactivateAllTemplates();
|
||||||
|
PerformanceTemplateDO update = new PerformanceTemplateDO();
|
||||||
|
update.setId(template.getId());
|
||||||
|
update.setActiveFlag(true);
|
||||||
|
performanceTemplateMapper.updateById(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceTemplateDO validateTemplateExists(Long id) {
|
||||||
|
PerformanceTemplateDO template = id == null ? null : performanceTemplateMapper.selectById(id);
|
||||||
|
if (template == null) {
|
||||||
|
throw exception(ErrorCodeConstants.PERFORMANCE_TEMPLATE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deactivateAllTemplates() {
|
||||||
|
PerformanceTemplateDO update = new PerformanceTemplateDO();
|
||||||
|
update.setActiveFlag(false);
|
||||||
|
performanceTemplateMapper.update(update, new LambdaQueryWrapperX<PerformanceTemplateDO>()
|
||||||
|
.eq(PerformanceTemplateDO::getActiveFlag, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceTemplateRespVO toRespVO(PerformanceTemplateDO template) {
|
||||||
|
PerformanceTemplateRespVO respVO = BeanUtils.toBean(template, PerformanceTemplateRespVO.class);
|
||||||
|
respVO.setScoreCellMapping(scoreCellMapping());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceScoreCellMappingRespVO scoreCellMapping() {
|
||||||
|
PerformanceProperties.ScoreCellMapping mapping = performanceProperties.getScoreCellMapping();
|
||||||
|
PerformanceScoreCellMappingRespVO respVO = new PerformanceScoreCellMappingRespVO();
|
||||||
|
respVO.setActualScoreTotalCell(mapping.getActualScoreTotal());
|
||||||
|
respVO.setBaseScoreTotalCell(mapping.getBaseScoreTotal());
|
||||||
|
respVO.setExtraScoreTotalCell(mapping.getExtraScoreTotal());
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeNullableText(String value) {
|
||||||
|
if (!StringUtils.hasText(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultText(String value) {
|
||||||
|
return StringUtils.hasText(value) ? value.trim() : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance.team;
|
||||||
|
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryRespVO;
|
||||||
|
|
||||||
|
public interface TeamPerformanceService {
|
||||||
|
|
||||||
|
TeamPerformanceSummaryRespVO getSummary(TeamPerformanceSummaryReqVO reqVO);
|
||||||
|
|
||||||
|
TeamPerformanceRemindRespVO remind(TeamPerformanceRemindReqVO reqVO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,357 @@
|
|||||||
|
package com.njcn.rdms.module.project.service.performance.team;
|
||||||
|
|
||||||
|
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||||
|
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
|
import com.njcn.rdms.module.project.constant.PerformanceConstants;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindRespVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryReqVO;
|
||||||
|
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryRespVO;
|
||||||
|
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO;
|
||||||
|
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetMapper;
|
||||||
|
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
|
||||||
|
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
|
||||||
|
import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService;
|
||||||
|
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||||
|
import com.njcn.rdms.module.system.api.user.UserManagementRelationApi;
|
||||||
|
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
|
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.YearMonth;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TeamPerformanceServiceImpl implements TeamPerformanceService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TeamDashboardAccessService teamDashboardAccessService;
|
||||||
|
@Resource
|
||||||
|
private PerformanceSheetMapper performanceSheetMapper;
|
||||||
|
@Resource
|
||||||
|
private AdminUserApi adminUserApi;
|
||||||
|
@Resource
|
||||||
|
private UserManagementRelationApi userManagementRelationApi;
|
||||||
|
@Resource
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TeamPerformanceSummaryRespVO getSummary(TeamPerformanceSummaryReqVO reqVO) {
|
||||||
|
teamDashboardAccessService.validateTeamDashboardPermission(PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
|
String[] range = normalizePeriodMonthRange(
|
||||||
|
reqVO == null ? null : reqVO.getPeriodMonthStart(),
|
||||||
|
reqVO == null ? null : reqVO.getPeriodMonthEnd());
|
||||||
|
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
||||||
|
List<PerformanceSheetDO> sheets = performanceSheetMapper
|
||||||
|
.selectListByEmployeeIdsAndPeriodMonthRange(subordinateIds, range[0], range[1]);
|
||||||
|
Map<Long, List<PerformanceSheetDO>> sheetsByEmployee = sheets.stream()
|
||||||
|
.collect(Collectors.groupingBy(PerformanceSheetDO::getEmployeeId));
|
||||||
|
Map<Long, AdminUserRespDTO> userMap = loadUserMap(subordinateIds);
|
||||||
|
|
||||||
|
TeamPerformanceSummaryRespVO respVO = new TeamPerformanceSummaryRespVO();
|
||||||
|
respVO.setPeriodMonthStart(range[0]);
|
||||||
|
respVO.setPeriodMonthEnd(range[1]);
|
||||||
|
respVO.setTotalSheetCount(sheets.size());
|
||||||
|
respVO.setPendingSendUsers(buildPendingSendUsers(subordinateIds, sheetsByEmployee, userMap));
|
||||||
|
respVO.setPendingConfirmUsers(buildPendingConfirmUsers(sheets, userMap));
|
||||||
|
respVO.setPendingSendCount(respVO.getPendingSendUsers().size());
|
||||||
|
respVO.setPendingConfirmCount(respVO.getPendingConfirmUsers().size());
|
||||||
|
respVO.setConfirmedRate(calculateConfirmedRate(sheets));
|
||||||
|
respVO.setDeptOrgAverages(buildDeptOrgAverages(sheets));
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public TeamPerformanceRemindRespVO remind(TeamPerformanceRemindReqVO reqVO) {
|
||||||
|
teamDashboardAccessService.validateTeamDashboardPermission(PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
|
String[] range = normalizePeriodMonthRange(reqVO.getPeriodMonthStart(), reqVO.getPeriodMonthEnd());
|
||||||
|
List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
|
||||||
|
List<Long> targetEmployeeIds = reqVO.getUserIds() == null
|
||||||
|
? subordinateIds
|
||||||
|
: teamDashboardAccessService.resolveRequestedSubordinateUserIds(
|
||||||
|
reqVO.getUserIds(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
|
||||||
|
List<PerformanceSheetDO> sheets = performanceSheetMapper
|
||||||
|
.selectListByEmployeeIdsAndPeriodMonthRange(targetEmployeeIds, range[0], range[1]);
|
||||||
|
Map<Long, List<PerformanceSheetDO>> sheetsByEmployee = sheets.stream()
|
||||||
|
.collect(Collectors.groupingBy(PerformanceSheetDO::getEmployeeId));
|
||||||
|
Map<Long, AdminUserRespDTO> userMap = loadUserMap(targetEmployeeIds);
|
||||||
|
|
||||||
|
int remindedCount;
|
||||||
|
if (PerformanceConstants.REMIND_TYPE_PENDING_CONFIRM.equals(reqVO.getRemindType())) {
|
||||||
|
remindedCount = remindPendingConfirm(sheets);
|
||||||
|
} else if (PerformanceConstants.REMIND_TYPE_PENDING_SEND.equals(reqVO.getRemindType())) {
|
||||||
|
remindedCount = remindPendingSend(targetEmployeeIds, sheetsByEmployee, userMap, range[0]);
|
||||||
|
} else {
|
||||||
|
throw invalidParamException("催办类型不合法");
|
||||||
|
}
|
||||||
|
TeamPerformanceRemindRespVO respVO = new TeamPerformanceRemindRespVO();
|
||||||
|
respVO.setRemindedCount(remindedCount);
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TeamPerformanceSummaryRespVO.PendingSendUser> buildPendingSendUsers(
|
||||||
|
List<Long> subordinateIds, Map<Long, List<PerformanceSheetDO>> sheetsByEmployee,
|
||||||
|
Map<Long, AdminUserRespDTO> userMap) {
|
||||||
|
List<TeamPerformanceSummaryRespVO.PendingSendUser> result = new ArrayList<>();
|
||||||
|
for (Long subordinateId : subordinateIds) {
|
||||||
|
List<PerformanceSheetDO> employeeSheets = sheetsByEmployee.getOrDefault(subordinateId, List.of());
|
||||||
|
if (!hasPendingSend(employeeSheets)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AdminUserRespDTO user = userMap.get(subordinateId);
|
||||||
|
PerformanceSheetDO firstPending = employeeSheets.isEmpty() ? null : employeeSheets.get(0);
|
||||||
|
AdminUserRespDTO manager = firstPending == null ? loadDirectManagerQuietly(subordinateId) : null;
|
||||||
|
TeamPerformanceSummaryRespVO.PendingSendUser item = new TeamPerformanceSummaryRespVO.PendingSendUser();
|
||||||
|
item.setUserId(subordinateId);
|
||||||
|
item.setUserNickname(user == null ? "" : defaultText(user.getNickname()));
|
||||||
|
item.setManagerUserId(
|
||||||
|
firstPending == null ? (manager == null ? null : manager.getId()) : firstPending.getManagerId());
|
||||||
|
item.setManagerName(firstPending == null ? (manager == null ? "" : defaultText(manager.getNickname()))
|
||||||
|
: firstPending.getManagerName());
|
||||||
|
item.setSheetId(firstPending == null ? null : firstPending.getId());
|
||||||
|
item.setStatusCode(firstPending == null ? null : firstPending.getStatusCode());
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TeamPerformanceSummaryRespVO.PendingConfirmUser> buildPendingConfirmUsers(
|
||||||
|
List<PerformanceSheetDO> sheets, Map<Long, AdminUserRespDTO> userMap) {
|
||||||
|
return sheets.stream()
|
||||||
|
.filter(sheet -> PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode()))
|
||||||
|
.map(sheet -> {
|
||||||
|
AdminUserRespDTO user = userMap.get(sheet.getEmployeeId());
|
||||||
|
TeamPerformanceSummaryRespVO.PendingConfirmUser item = new TeamPerformanceSummaryRespVO.PendingConfirmUser();
|
||||||
|
item.setUserId(sheet.getEmployeeId());
|
||||||
|
item.setUserNickname(
|
||||||
|
user == null ? defaultText(sheet.getEmployeeName()) : defaultText(user.getNickname()));
|
||||||
|
item.setSheetId(sheet.getId());
|
||||||
|
item.setSentTime(sheet.getSentTime());
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TeamPerformanceSummaryRespVO.DeptOrgAverage> buildDeptOrgAverages(List<PerformanceSheetDO> sheets) {
|
||||||
|
Map<String, DeptAverageAccumulator> averages = new LinkedHashMap<>();
|
||||||
|
for (PerformanceSheetDO sheet : sheets) {
|
||||||
|
if (!PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode())
|
||||||
|
|| sheet.getActualScoreTotal() == null
|
||||||
|
|| sheet.getEmployeeDeptId() == null
|
||||||
|
|| (!PerformanceConstants.ORG_TYPE_DIRECTION.equals(sheet.getDeptOrgType())
|
||||||
|
&& !PerformanceConstants.ORG_TYPE_FUNCTION.equals(sheet.getDeptOrgType()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = sheet.getEmployeeDeptId() + ":" + sheet.getDeptOrgType();
|
||||||
|
averages.computeIfAbsent(key, ignored -> new DeptAverageAccumulator(
|
||||||
|
sheet.getEmployeeDeptId(), sheet.getEmployeeDeptName(), sheet.getDeptOrgType()))
|
||||||
|
.add(sheet.getActualScoreTotal());
|
||||||
|
}
|
||||||
|
return averages.values().stream()
|
||||||
|
.map(DeptAverageAccumulator::toRespVO)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigDecimal calculateConfirmedRate(List<PerformanceSheetDO> sheets) {
|
||||||
|
if (sheets.isEmpty()) {
|
||||||
|
return BigDecimal.ZERO.setScale(2);
|
||||||
|
}
|
||||||
|
long confirmed = sheets.stream()
|
||||||
|
.filter(sheet -> PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode()))
|
||||||
|
.count();
|
||||||
|
return BigDecimal.valueOf(confirmed * 100)
|
||||||
|
.divide(BigDecimal.valueOf(sheets.size()), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int remindPendingConfirm(List<PerformanceSheetDO> sheets) {
|
||||||
|
int count = 0;
|
||||||
|
for (PerformanceSheetDO sheet : sheets) {
|
||||||
|
if (!PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
publishNotice(List.of(sheet.getEmployeeId()),
|
||||||
|
NotifyTemplateCodeConstants.PERFORMANCE_PENDING_CONFIRM_REMIND, sheet, sheet.getEmployeeName());
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int remindPendingSend(List<Long> targetEmployeeIds, Map<Long, List<PerformanceSheetDO>> sheetsByEmployee,
|
||||||
|
Map<Long, AdminUserRespDTO> userMap, String defaultPeriodMonth) {
|
||||||
|
int count = 0;
|
||||||
|
for (Long employeeId : targetEmployeeIds) {
|
||||||
|
List<PerformanceSheetDO> employeeSheets = sheetsByEmployee.getOrDefault(employeeId, List.of());
|
||||||
|
if (!hasPendingSend(employeeSheets)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
PerformanceSheetDO firstPending = employeeSheets.isEmpty() ? null : employeeSheets.get(0);
|
||||||
|
AdminUserRespDTO manager = firstPending == null ? loadDirectManagerQuietly(employeeId) : null;
|
||||||
|
Long managerId = firstPending == null ? (manager == null ? null : manager.getId())
|
||||||
|
: firstPending.getManagerId();
|
||||||
|
if (managerId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String employeeName = firstPending == null
|
||||||
|
? defaultText(userMap.get(employeeId) == null ? null : userMap.get(employeeId).getNickname())
|
||||||
|
: firstPending.getEmployeeName();
|
||||||
|
PerformanceSheetDO noticeSheet = firstPending == null
|
||||||
|
? buildVirtualSheet(defaultPeriodMonth, employeeId, employeeName, manager)
|
||||||
|
: firstPending;
|
||||||
|
publishNotice(List.of(managerId),
|
||||||
|
NotifyTemplateCodeConstants.PERFORMANCE_PENDING_SEND_REMIND, noticeSheet, employeeName);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PerformanceSheetDO buildVirtualSheet(String periodMonth, Long employeeId, String employeeName,
|
||||||
|
AdminUserRespDTO manager) {
|
||||||
|
PerformanceSheetDO sheet = new PerformanceSheetDO();
|
||||||
|
sheet.setPeriodMonth(periodMonth);
|
||||||
|
sheet.setEmployeeId(employeeId);
|
||||||
|
sheet.setEmployeeName(employeeName);
|
||||||
|
sheet.setManagerId(manager == null ? null : manager.getId());
|
||||||
|
sheet.setManagerName(manager == null ? "" : defaultText(manager.getNickname()));
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishNotice(Collection<Long> recipients, String templateCode, PerformanceSheetDO sheet,
|
||||||
|
String employeeName) {
|
||||||
|
if (recipients == null || recipients.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("periodMonth", sheet.getPeriodMonth());
|
||||||
|
params.put("employeeName", employeeName);
|
||||||
|
params.put("managerName", sheet.getManagerName());
|
||||||
|
params.put("sheetId", sheet.getId());
|
||||||
|
params.put("statusName", statusName(sheet.getStatusCode()));
|
||||||
|
applicationEventPublisher.publishEvent(NotifySendEvent.of(new ArrayList<>(new LinkedHashSet<>(recipients)),
|
||||||
|
templateCode, params, NotifyMessageLevelConstants.REMIND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, AdminUserRespDTO> loadUserMap(Collection<Long> userIds) {
|
||||||
|
if (userIds == null || userIds.isEmpty()) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserList(new LinkedHashSet<>(userIds));
|
||||||
|
List<AdminUserRespDTO> users = result == null || result.getCheckedData() == null
|
||||||
|
? List.of()
|
||||||
|
: result.getCheckedData();
|
||||||
|
return users.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(user -> user.getId() != null)
|
||||||
|
.collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminUserRespDTO loadDirectManagerQuietly(Long employeeId) {
|
||||||
|
try {
|
||||||
|
CommonResult<AdminUserRespDTO> result = userManagementRelationApi.getDirectManager(employeeId);
|
||||||
|
return result == null ? null : result.getCheckedData();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验并归一化月份区间,两者均为空时默认当前月。
|
||||||
|
*/
|
||||||
|
private String[] normalizePeriodMonthRange(String start, String end) {
|
||||||
|
String normalizedStart = StringUtils.hasText(start) ? normalizeSinglePeriodMonth(start) : null;
|
||||||
|
String normalizedEnd = StringUtils.hasText(end) ? normalizeSinglePeriodMonth(end) : null;
|
||||||
|
if (normalizedStart == null && normalizedEnd == null) {
|
||||||
|
String currentMonth = YearMonth.now().toString();
|
||||||
|
return new String[] { currentMonth, currentMonth };
|
||||||
|
}
|
||||||
|
if (normalizedStart != null && normalizedEnd != null && normalizedStart.compareTo(normalizedEnd) > 0) {
|
||||||
|
throw invalidParamException("绩效月份起始不能晚于结束月份");
|
||||||
|
}
|
||||||
|
return new String[] { normalizedStart, normalizedEnd };
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeSinglePeriodMonth(String periodMonth) {
|
||||||
|
try {
|
||||||
|
return YearMonth.parse(periodMonth.trim()).toString();
|
||||||
|
} catch (DateTimeParseException ex) {
|
||||||
|
throw invalidParamException("绩效月份格式必须为 yyyy-MM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断员工在区间内的绩效表是否存在“待发送”状态(无记录 / 含 draft / 含 rejected)。
|
||||||
|
*/
|
||||||
|
private boolean hasPendingSend(List<PerformanceSheetDO> sheets) {
|
||||||
|
if (sheets == null || sheets.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return sheets.stream().anyMatch(sheet -> PerformanceConstants.STATUS_DRAFT.equals(sheet.getStatusCode())
|
||||||
|
|| PerformanceConstants.STATUS_REJECTED.equals(sheet.getStatusCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String statusName(String statusCode) {
|
||||||
|
if (statusCode == null) {
|
||||||
|
return "未创建";
|
||||||
|
}
|
||||||
|
return switch (statusCode) {
|
||||||
|
case PerformanceConstants.STATUS_DRAFT -> "待发送";
|
||||||
|
case PerformanceConstants.STATUS_SENT -> "待确认";
|
||||||
|
case PerformanceConstants.STATUS_CONFIRMED -> "已确认";
|
||||||
|
case PerformanceConstants.STATUS_REJECTED -> "已退回";
|
||||||
|
default -> statusCode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultText(String text) {
|
||||||
|
return StringUtils.hasText(text) ? text.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeptAverageAccumulator {
|
||||||
|
|
||||||
|
private final Long deptId;
|
||||||
|
private final String deptName;
|
||||||
|
private final String deptOrgType;
|
||||||
|
private BigDecimal total = BigDecimal.ZERO;
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
DeptAverageAccumulator(Long deptId, String deptName, String deptOrgType) {
|
||||||
|
this.deptId = deptId;
|
||||||
|
this.deptName = deptName;
|
||||||
|
this.deptOrgType = deptOrgType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(BigDecimal score) {
|
||||||
|
total = total.add(score);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
TeamPerformanceSummaryRespVO.DeptOrgAverage toRespVO() {
|
||||||
|
TeamPerformanceSummaryRespVO.DeptOrgAverage respVO = new TeamPerformanceSummaryRespVO.DeptOrgAverage();
|
||||||
|
respVO.setDeptId(deptId);
|
||||||
|
respVO.setDeptName(deptName);
|
||||||
|
respVO.setDeptOrgType(deptOrgType);
|
||||||
|
respVO.setConfirmedCount(count);
|
||||||
|
respVO.setAverageScore(count == 0
|
||||||
|
? BigDecimal.ZERO.setScale(2)
|
||||||
|
: total.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
|
||||||
|
return respVO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,5 +133,10 @@ rdms:
|
|||||||
cron: "0 0 12 1-31 * ?"
|
cron: "0 0 12 1-31 * ?"
|
||||||
scope:
|
scope:
|
||||||
dept-ids: [100]
|
dept-ids: [100]
|
||||||
|
performance:
|
||||||
|
score-cell-mapping:
|
||||||
|
actual-score-total: J10
|
||||||
|
base-score-total: K10
|
||||||
|
extra-score-total: L10
|
||||||
|
|
||||||
debug: false
|
debug: false
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ public class DeptRespDTO {
|
|||||||
@Schema(description = "父部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
@Schema(description = "父部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
private Long parentId;
|
private Long parentId;
|
||||||
|
|
||||||
|
@Schema(description = "组织节点类型", example = "direction")
|
||||||
|
private String orgType;
|
||||||
|
|
||||||
|
@Schema(description = "组织编码", example = "rd")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
@Schema(description = "组织物化路径", example = "/100/101")
|
||||||
|
private String path;
|
||||||
|
|
||||||
@Schema(description = "部门状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
@Schema(description = "部门状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
|||||||
@@ -21,4 +21,14 @@ public interface FileApi {
|
|||||||
@Parameter(name = "url", description = "文件 URL", required = true)
|
@Parameter(name = "url", description = "文件 URL", required = true)
|
||||||
CommonResult<FileRespDTO> getFileByUrl(@RequestParam("url") String url);
|
CommonResult<FileRespDTO> getFileByUrl(@RequestParam("url") String url);
|
||||||
|
|
||||||
|
@GetMapping(PREFIX + "/get")
|
||||||
|
@Operation(summary = "通过文件 ID 查询文件")
|
||||||
|
@Parameter(name = "id", description = "文件 ID", required = true)
|
||||||
|
CommonResult<FileRespDTO> getFile(@RequestParam("id") Long id);
|
||||||
|
|
||||||
|
@GetMapping(PREFIX + "/content")
|
||||||
|
@Operation(summary = "通过文件 ID 读取文件内容")
|
||||||
|
@Parameter(name = "id", description = "文件 ID", required = true)
|
||||||
|
CommonResult<byte[]> getFileContent(@RequestParam("id") Long id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ public class FileRespDTO {
|
|||||||
*/
|
*/
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件配置编号。
|
||||||
|
*/
|
||||||
|
private Long configId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件名称。
|
* 文件名称。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public enum DeptOrgTypeEnum {
|
|||||||
COMPANY("company"),
|
COMPANY("company"),
|
||||||
DEPT("dept"),
|
DEPT("dept"),
|
||||||
DIRECTION("direction"),
|
DIRECTION("direction"),
|
||||||
|
FUNCTION("function"),
|
||||||
TEAM("team");
|
TEAM("team");
|
||||||
|
|
||||||
private final String type;
|
private final String type;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO;
|
|||||||
import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO;
|
import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO;
|
||||||
import com.njcn.rdms.module.system.service.dept.DeptService;
|
import com.njcn.rdms.module.system.service.dept.DeptService;
|
||||||
import io.swagger.v3.oas.annotations.Hidden;
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|||||||
@@ -26,4 +26,23 @@ public class FileApiImpl implements FileApi {
|
|||||||
return success(BeanUtils.toBean(file, FileRespDTO.class));
|
return success(BeanUtils.toBean(file, FileRespDTO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<FileRespDTO> getFile(Long id) {
|
||||||
|
FileDO file = fileService.getFile(id);
|
||||||
|
return success(BeanUtils.toBean(file, FileRespDTO.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonResult<byte[]> getFileContent(Long id) {
|
||||||
|
try {
|
||||||
|
FileDO file = fileService.getFile(id);
|
||||||
|
if (file == null) {
|
||||||
|
return success(null);
|
||||||
|
}
|
||||||
|
return success(fileService.getFileContent(file.getConfigId(), file.getPath()));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalStateException("读取文件内容失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class FileController {
|
|||||||
@GetMapping("/download")
|
@GetMapping("/download")
|
||||||
@Operation(summary = "下载文件")
|
@Operation(summary = "下载文件")
|
||||||
@Parameter(name = "id", description = "编号", required = true)
|
@Parameter(name = "id", description = "编号", required = true)
|
||||||
@PreAuthorize("@ss.hasPermission('system:file:query')")
|
//@PreAuthorize("@ss.hasPermission('system:file:query')")
|
||||||
public void downloadFile(@RequestParam("id") Long id, HttpServletResponse response) throws Exception {
|
public void downloadFile(@RequestParam("id") Long id, HttpServletResponse response) throws Exception {
|
||||||
FileDO file = fileService.getFile(id);
|
FileDO file = fileService.getFile(id);
|
||||||
byte[] content = fileService.getFileContent(file.getConfigId(), file.getPath());
|
byte[] content = fileService.getFileContent(file.getConfigId(), file.getPath());
|
||||||
|
|||||||
Reference in New Issue
Block a user