From 89071e0c09188d75c3d537f82204ae19713ac7fe Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 12 十月 2026 10:09:33 +0800
Subject: [PATCH] feat(hrm): 合同生成与模板下载、员工转正、导入导出字段对齐
---
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ByteArrayMultipartFile.java | 71 +++++++
yudao-module-hrm/src/main/resources/template/contract/original/service.doc | 0
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java | 18 ++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java | 6
yudao-module-hrm/src/main/resources/template/contract/contract_service.docx | 0
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java | 64 +++++++
yudao-module-hrm/src/main/resources/template/contract/original/labor_simple.docx | 0
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java | 3
yudao-module-hrm/src/main/resources/template/contract/contract_labor_simple.docx | 0
yudao-module-hrm/pom.xml | 7
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentServiceImpl.java | 204 ++++++++++++++++++++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java | 17 +
yudao-module-hrm/src/main/resources/template/contract/original/labor_standard.docx | 0
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java | 84 +++++++++
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java | 15 +
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentService.java | 26 ++
yudao-module-hrm/src/main/resources/template/contract/contract_labor_standard.docx | 0
17 files changed, 512 insertions(+), 3 deletions(-)
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
index 275c031..2b2a428 100644
--- a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -125,6 +125,9 @@
ErrorCode SALARY_STRUCTURE_IN_USE = new ErrorCode(1_030_005_026, "钖叕缁撴瀯姝e湪琚憳宸ヤ娇鐢紝鏃犳硶鍒犻櫎");
ErrorCode OVERTIME_WAGE_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_027, "宀椾綅鍔犵彮宸ヨ祫閰嶇疆涓嶅瓨鍦�");
ErrorCode OVERTIME_WAGE_CONFIG_POST_EXISTS = new ErrorCode(1_030_005_028, "璇ュ矖浣嶅凡閰嶇疆鍔犵彮宸ヨ祫锛岃鍕块噸澶嶆坊鍔�");
+ ErrorCode CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT = new ErrorCode(1_030_005_029, "涓嶆敮鎸佺殑鍚堝悓妯℃澘绫诲瀷");
+ ErrorCode CONTRACT_TEMPLATE_LOAD_FAILED = new ErrorCode(1_030_005_030, "鍚堝悓妯℃澘鍔犺浇澶辫触");
+ ErrorCode EMPLOYEE_REGULARIZE_NEED_LABOR_CONTRACT = new ErrorCode(1_030_005_031, "璇ュ憳宸ュ綋鍓嶅悎鍚屼笉鏄姵鍔ㄥ悎鍚岋紝鏃犳硶杞");
// ========== 鐝绠$悊 1-030-009-000 ==========
ErrorCode SHIFT_NOT_EXISTS = new ErrorCode(1_030_009_000, "鐝涓嶅瓨鍦�");
diff --git a/yudao-module-hrm/pom.xml b/yudao-module-hrm/pom.xml
index df07d9c..0f56438 100644
--- a/yudao-module-hrm/pom.xml
+++ b/yudao-module-hrm/pom.xml
@@ -70,6 +70,13 @@
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
+ <!-- Word 鍚堝悓妯℃澘娓叉煋锛坧oi-tl锛� -->
+ <dependency>
+ <groupId>com.deepoove</groupId>
+ <artifactId>poi-tl</artifactId>
+ <version>1.12.2</version>
+ </dependency>
+
<!-- Test 娴嬭瘯鐩稿叧 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
index 0530349..ebc01fe 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
@@ -11,6 +11,11 @@
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractTerminateReqVO;
import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeContractService;
+import cn.iocoder.yudao.module.hrm.service.employee.contract.ContractDocumentService;
+import cn.iocoder.yudao.module.hrm.service.employee.contract.ByteArrayMultipartFile;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
+import cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService;
+import org.springframework.web.multipart.MultipartFile;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -22,12 +27,19 @@
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
+import java.io.InputStream;
import java.math.BigDecimal;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_TEMPLATE_LOAD_FAILED;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT;
@Tag(name = "绠$悊鍚庡彴 - HRM 鍛樺伐鍚堝悓")
@RestController
@@ -37,6 +49,12 @@
@Resource
private HrmEmployeeContractService contractService;
+ @Resource
+ private ContractDocumentService contractDocumentService;
+ @Resource
+ private SystemStorageBlobService storageBlobService;
+ @Resource
+ private StorageAttachmentApi storageAttachmentApi;
@PostMapping("/create")
@Operation(summary = "鍒涘缓鍛樺伐鍚堝悓")
@@ -92,6 +110,72 @@
return success(contractService.getExpiringCount());
}
+ @PostMapping("/generate")
+ @Operation(summary = "鐢熸垚鍚堝悓骞跺洖瀛樹负鍚堝悓闄勪欢")
+ @Parameter(name = "id", description = "鍚堝悓ID", required = true)
+ @Parameter(name = "templateType", description = "妯℃澘绫诲瀷锛�1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee-contract:query')")
+ public CommonResult<Long> generateContract(@RequestParam("id") Long id,
+ @RequestParam("templateType") Integer templateType) {
+ byte[] bytes = contractDocumentService.generateDocx(id, templateType);
+ String fileName = contractDocumentService.buildFileName(id, templateType);
+ MultipartFile file = new ByteArrayMultipartFile("file", fileName,
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document", bytes);
+ Long blobId = storageBlobService.upload(Collections.singletonList(file), false).get(0).getId();
+ storageAttachmentApi.bindAttachments("file", "hrm_employee_contract", id, Collections.singletonList(blobId));
+ return success(blobId);
+ }
+
+ @GetMapping("/generate-docx")
+ @Operation(summary = "鐢熸垚骞朵笅杞藉悎鍚屾枃妗o紙docx锛�")
+ @Parameter(name = "id", description = "鍚堝悓ID", required = true)
+ @Parameter(name = "templateType", description = "妯℃澘绫诲瀷锛�1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee-contract:query')")
+ public void generateDocx(@RequestParam("id") Long id,
+ @RequestParam("templateType") Integer templateType,
+ HttpServletResponse response) throws IOException {
+ byte[] bytes = contractDocumentService.generateDocx(id, templateType);
+ String fileName = URLEncoder.encode(contractDocumentService.buildFileName(id, templateType), StandardCharsets.UTF_8);
+ response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+ response.setHeader("Content-Disposition", "attachment; filename=" + fileName + "; filename*=UTF-8''" + fileName);
+ response.getOutputStream().write(bytes);
+ }
+
+ @GetMapping("/download-template")
+ @Operation(summary = "涓嬭浇鍚堝悓绌虹櫧妯℃澘", description = "1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓")
+ @Parameter(name = "type", description = "妯℃澘绫诲瀷锛�1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee-contract:query')")
+ public void downloadTemplate(@RequestParam("type") Integer type, HttpServletResponse response) throws IOException {
+ String path;
+ String fileName;
+ String contentType;
+ if (type == 1) {
+ path = "template/contract/original/labor_simple.docx";
+ fileName = "鍔冲姩鍚堝悓(绠�鐗�).docx";
+ contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+ } else if (type == 2) {
+ path = "template/contract/original/labor_standard.docx";
+ fileName = "鍔冲姩鍚堝悓(鏍囧噯).docx";
+ contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+ } else if (type == 3) {
+ path = "template/contract/original/service.doc";
+ fileName = "鍔冲姟鍚堝悓.doc";
+ contentType = "application/msword";
+ } else {
+ throw exception(CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT);
+ }
+ try (InputStream in = getClass().getClassLoader().getResourceAsStream(path)) {
+ if (in == null) {
+ throw exception(CONTRACT_TEMPLATE_LOAD_FAILED);
+ }
+ byte[] bytes = in.readAllBytes();
+ String enc = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
+ response.setContentType(contentType);
+ response.setHeader("Content-Disposition", "attachment; filename=" + enc + "; filename*=UTF-8''" + enc);
+ response.getOutputStream().write(bytes);
+ }
+ }
+
@GetMapping("/import-template")
@Operation(summary = "涓嬭浇鍛樺伐鍚堝悓瀵煎叆妯℃澘")
@PreAuthorize("@ss.hasPermission('hrm:employee-contract:import')")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
index 4d4829a..0e07916 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
@@ -222,4 +222,22 @@
return success(employeeService.importEmployeeList(list, importReqVO));
}
+ @PutMapping("/regular")
+ @Operation(summary = "鍛樺伐杞", description = "浠呫�庡姵鍔ㄥ悎鍚屻�忓憳宸ュ彲杞锛涘姵鍔″悎鍚屼笉杞銆傝浆姝e彧鏀圭姸鎬�+杞鏃ユ湡锛屼笉鍚皟宀�")
+ @Parameter(name = "id", description = "鍛樺伐ID", required = true)
+ @PreAuthorize("@ss.hasPermission('hrm:employee:update')")
+ public CommonResult<Boolean> regularizeEmployee(@RequestParam("id") Long id) {
+ employeeService.regularizeEmployee(id);
+ return success(true);
+ }
+
+ @GetMapping("/regularize-reminders")
+ @Operation(summary = "鑾峰緱寰呰浆姝f彁閱掑垪琛�", description = "璇曠敤涓�佷笖褰撳墠涓哄姵鍔ㄥ悎鍚屻�佽瘯鐢ㄦ湡鍦� days 澶╁唴鍒版湡鐨勫憳宸�")
+ @PreAuthorize("@ss.hasPermission('hrm:employee:query')")
+ public CommonResult<List<HrmEmployeeRespVO>> getRegularizeReminders(
+ @RequestParam(value = "days", required = false, defaultValue = "7") Integer days) {
+ List<HrmEmployeeDO> list = employeeService.getRegularizeReminderList(days);
+ return success(BeanUtils.toBean(list, HrmEmployeeRespVO.class));
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java
index 977f0c1..20f930f 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeImportExcelVO.java
@@ -8,6 +8,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
+import java.math.BigDecimal;
import java.time.LocalDate;
/**
@@ -45,6 +46,10 @@
@DictFormat("hrm_employee_status")
private Integer employeeStatus;
+ @ExcelProperty(value = "鍛樺伐绫诲埆", converter = DictConvert.class)
+ @DictFormat("hrm_employee_category")
+ private Integer employeeCategory;
+
@ExcelProperty("韬唤璇佸彿")
private String idCard;
@@ -72,4 +77,16 @@
@ExcelProperty("澶囨敞")
private String remark;
+ @ExcelProperty("鍩烘湰宸ヨ祫")
+ private BigDecimal baseSalary;
+
+ @ExcelProperty("寮�鎴烽摱琛�")
+ private String bankName;
+
+ @ExcelProperty("閾惰璐﹀彿")
+ private String bankAccount;
+
+ @ExcelProperty("灞呬綇鍦板潃")
+ private String address;
+
}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
index 383b2b5..687c923 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
@@ -46,7 +46,7 @@
@Schema(description = "鎬у埆")
@ExcelProperty(value = "鎬у埆", converter = DictConvert.class)
- @DictFormat("system_user_sex")
+ @DictFormat("hrm_gender")
private Integer gender;
@Schema(description = "鍑虹敓鏃ユ湡")
@@ -54,7 +54,7 @@
private LocalDate birthday;
@Schema(description = "鎵嬫満鍙风爜")
- @ExcelProperty("鎵嬫満鍙风爜")
+ @ExcelProperty("鎵嬫満鍙�")
private String phone;
@Schema(description = "閭")
@@ -126,7 +126,7 @@
private List<Long> roleIds;
@Schema(description = "瑙掕壊鍚嶇О鍒楄〃")
- @ExcelProperty("瑙掕壊")
+ @ExcelProperty("鐢ㄦ埛瑙掕壊")
private String roleNames;
@Schema(description = "寮�鎴烽摱琛�")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
index 90424ba..9fff42d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
@@ -115,4 +115,19 @@
HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> importEmployees,
HrmEmployeeImportReqVO importReqVO);
+ /**
+ * 鍛樺伐杞锛氫粎銆屽姵鍔ㄥ悎鍚屻�嶅彲杞锛堝姵鍔″悎鍚屼笉鍙級锛岀姸鎬� 璇曠敤 -> 鍦ㄨ亴锛岃褰曡浆姝f棩鏈�
+ *
+ * @param id 鍛樺伐ID
+ */
+ void regularizeEmployee(Long id);
+
+ /**
+ * 鑾峰緱寰呰浆姝f彁閱掑垪琛細璇曠敤涓�佷笖褰撳墠涓哄姵鍔ㄥ悎鍚屻�佽瘯鐢ㄦ湡鍦� days 澶╁唴鍒版湡鐨勫憳宸�
+ *
+ * @param days 鎻愬墠澶╂暟
+ * @return 鍛樺伐鍒楄〃
+ */
+ List<HrmEmployeeDO> getRegularizeReminderList(Integer days);
+
}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index 92a709e..3864fd3 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEducationSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeEmergencyContactSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeImportExcelVO;
@@ -16,10 +17,12 @@
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSimpleRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeWorkHistorySaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeContractDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEducationDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeEmergencyContactDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeWorkHistoryDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeContractMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
@@ -42,6 +45,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -68,6 +72,8 @@
@Resource
private HrmEmployeeMapper employeeMapper;
+ @Resource
+ private HrmEmployeeContractMapper contractMapper;
@Resource
private HrmEmployeeEducationMapper educationMapper;
@Resource
@@ -475,6 +481,11 @@
reqVO.setEducation(importEmployee.getEducation());
reqVO.setPoliticalStatus(importEmployee.getPoliticalStatus());
reqVO.setRemark(importEmployee.getRemark());
+ reqVO.setEmployeeCategory(importEmployee.getEmployeeCategory());
+ reqVO.setBaseSalary(importEmployee.getBaseSalary());
+ reqVO.setBankName(importEmployee.getBankName());
+ reqVO.setBankAccount(importEmployee.getBankAccount());
+ reqVO.setAddress(importEmployee.getAddress());
return reqVO;
}
@@ -530,4 +541,57 @@
return roleIds;
}
+ // ========== 杞 ==========
+
+ @Override
+ public void regularizeEmployee(Long id) {
+ validateEmployee(id);
+ HrmEmployeeContractDO contract = findCurrentContract(id);
+ // 浠呫�屽姵鍔ㄥ悎鍚屻�嶅彲杞锛涘姵鍔″悎鍚屼笉杞
+ if (contract == null || !Integer.valueOf(1).equals(contract.getContractType())) {
+ throw exception(EMPLOYEE_REGULARIZE_NEED_LABOR_CONTRACT);
+ }
+ HrmEmployeeDO updateObj = new HrmEmployeeDO();
+ updateObj.setId(id);
+ updateObj.setEmployeeStatus(1); // 1-鍦ㄨ亴
+ updateObj.setRegularDate(LocalDate.now());
+ employeeMapper.updateById(updateObj);
+ }
+
+ @Override
+ public List<HrmEmployeeDO> getRegularizeReminderList(Integer days) {
+ int aheadDays = days == null ? 7 : days;
+ LocalDate limit = LocalDate.now().plusDays(aheadDays);
+ // 璇曠敤涓殑鍛樺伐
+ List<HrmEmployeeDO> trials = employeeMapper.selectList(
+ new LambdaQueryWrapperX<HrmEmployeeDO>().eq(HrmEmployeeDO::getEmployeeStatus, 2));
+ List<HrmEmployeeDO> result = new ArrayList<>();
+ for (HrmEmployeeDO employee : trials) {
+ HrmEmployeeContractDO contract = findCurrentContract(employee.getId());
+ // 浠呭姵鍔ㄥ悎鍚屻�佷笖璇曠敤鏈熷嵆灏嗗埌鏈�
+ if (contract == null || !Integer.valueOf(1).equals(contract.getContractType())) {
+ continue;
+ }
+ LocalDate probationEnd = contract.getProbationEndDate();
+ if (probationEnd != null && !probationEnd.isAfter(limit)) {
+ result.add(employee);
+ }
+ }
+ return result;
+ }
+
+ /** 鏌ヨ鍛樺伐褰撳墠鐢熸晥鍚堝悓 */
+ private HrmEmployeeContractDO findCurrentContract(Long employeeId) {
+ List<HrmEmployeeContractDO> list = contractMapper.selectListByEmployee(employeeId);
+ if (CollUtil.isEmpty(list)) {
+ return null;
+ }
+ for (HrmEmployeeContractDO contract : list) {
+ if (Boolean.TRUE.equals(contract.getIsCurrent())) {
+ return contract;
+ }
+ }
+ return null;
+ }
+
}
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ByteArrayMultipartFile.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ByteArrayMultipartFile.java
new file mode 100644
index 0000000..4fc2c2e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ByteArrayMultipartFile.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.hrm.service.employee.contract;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 瀛楄妭鏁扮粍鐗� MultipartFile锛氭妸鍐呭瓨涓殑瀛楄妭鍖呰鎴� MultipartFile锛�
+ * 渚夸簬澶嶇敤 system 鐨� SystemStorageBlobService.upload(...) 瀛樺偍鏂囦欢骞舵嬁鍒� blobId銆�
+ */
+public class ByteArrayMultipartFile implements MultipartFile {
+
+ private final String name;
+ private final String originalFilename;
+ private final String contentType;
+ private final byte[] content;
+
+ public ByteArrayMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
+ this.name = name;
+ this.originalFilename = originalFilename;
+ this.contentType = contentType;
+ this.content = content == null ? new byte[0] : content;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getOriginalFilename() {
+ return originalFilename;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return content.length == 0;
+ }
+
+ @Override
+ public long getSize() {
+ return content.length;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return content;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(content);
+ }
+
+ @Override
+ public void transferTo(File dest) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(dest)) {
+ fos.write(content);
+ }
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentService.java
new file mode 100644
index 0000000..ae7a69a
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.hrm.service.employee.contract;
+
+/**
+ * 鍛樺伐鍚堝悓鏂囨。鐢熸垚 Service
+ *
+ * 鎸夊悎鍚� + 鍛樺伐鏁版嵁锛屽鐢� Word 妯℃澘鐢熸垚鍔冲姩鍚堝悓 / 鍔冲姟鍚堝悓 docx銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface ContractDocumentService {
+
+ /**
+ * 鐢熸垚鍚堝悓 docx
+ *
+ * @param contractId 鍚堝悓缂栧彿
+ * @param templateType 妯℃澘绫诲瀷锛�1-鍔冲姩鍚堝悓(绠�鐗�) 2-鍔冲姩鍚堝悓(鏍囧噯) 3-鍔冲姟鍚堝悓
+ * @return docx 瀛楄妭
+ */
+ byte[] generateDocx(Long contractId, Integer templateType);
+
+ /**
+ * 鐢熸垚鏂囦欢鍚�
+ */
+ String buildFileName(Long contractId, Integer templateType);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentServiceImpl.java
new file mode 100644
index 0000000..dbb8373
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/contract/ContractDocumentServiceImpl.java
@@ -0,0 +1,204 @@
+package cn.iocoder.yudao.module.hrm.service.employee.contract;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeContractDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeContractMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import com.deepoove.poi.XWPFTemplate;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_TEMPLATE_LOAD_FAILED;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT;
+
+/**
+ * 鍛樺伐鍚堝悓鏂囨。鐢熸垚 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Slf4j
+@Service
+public class ContractDocumentServiceImpl implements ContractDocumentService {
+
+ /** 鍔冲姩鍚堝悓锛堢畝鐗埪风ぞ淇濇姌鐜帮級 */
+ public static final int TYPE_LABOR_SIMPLE = 1;
+ /** 鍔冲姩鍚堝悓锛堟爣鍑喡峰畼鏂瑰埗寮忥級 */
+ public static final int TYPE_LABOR_STANDARD = 2;
+ /** 鍔冲姟鍚堝悓 */
+ public static final int TYPE_SERVICE = 3;
+
+ private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy骞碝M鏈坉d鏃�");
+
+ private static final Map<Integer, String> TEMPLATE_PATHS = Map.of(
+ TYPE_LABOR_SIMPLE, "template/contract/contract_labor_simple.docx",
+ TYPE_LABOR_STANDARD, "template/contract/contract_labor_standard.docx",
+ TYPE_SERVICE, "template/contract/contract_service.docx");
+
+ private static final Map<Integer, String> TEMPLATE_NAMES = Map.of(
+ TYPE_LABOR_SIMPLE, "鍔冲姩鍚堝悓(绠�鐗�)",
+ TYPE_LABOR_STANDARD, "鍔冲姩鍚堝悓(鏍囧噯)",
+ TYPE_SERVICE, "鍔冲姟鍚堝悓");
+
+ @Resource
+ private HrmEmployeeContractMapper contractMapper;
+ @Resource
+ private HrmEmployeeMapper employeeMapper;
+
+ @Override
+ public byte[] generateDocx(Long contractId, Integer templateType) {
+ HrmEmployeeContractDO contract = contractMapper.selectById(contractId);
+ if (contract == null) {
+ throw exception(CONTRACT_TEMPLATE_LOAD_FAILED);
+ }
+ HrmEmployeeDO employee = contract.getEmployeeId() == null ? null
+ : employeeMapper.selectById(contract.getEmployeeId());
+ Map<String, Object> vars = buildVariables(contract, employee);
+
+ String path = TEMPLATE_PATHS.get(templateType);
+ if (path == null) {
+ throw exception(CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT);
+ }
+ try (InputStream in = getClass().getClassLoader().getResourceAsStream(path)) {
+ if (in == null) {
+ throw exception(CONTRACT_TEMPLATE_LOAD_FAILED);
+ }
+ XWPFTemplate template = XWPFTemplate.compile(in).render(vars);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ template.write(out);
+ template.close();
+ return out.toByteArray();
+ } catch (Exception e) {
+ log.error("[generateDocx][鍚堝悓({}) 妯℃澘({}) 鐢熸垚澶辫触]", contractId, templateType, e);
+ throw exception(CONTRACT_TEMPLATE_LOAD_FAILED);
+ }
+ }
+
+ @Override
+ public String buildFileName(Long contractId, Integer templateType) {
+ HrmEmployeeContractDO contract = contractMapper.selectById(contractId);
+ String no = contract != null && contract.getContractNo() != null ? contract.getContractNo() : String.valueOf(contractId);
+ String name = TEMPLATE_NAMES.getOrDefault(templateType, "鍚堝悓");
+ return no + "-" + name + ".docx";
+ }
+
+ private Map<String, Object> buildVariables(HrmEmployeeContractDO contract, HrmEmployeeDO employee) {
+ Map<String, Object> vars = new HashMap<>();
+ // 鐢叉柟锛堝叕鍙革級鈥斺�� 鐩墠鍙栫绾︿富浣擄紝鍏朵綑鐣欑┖鍚庣画鍙帴鍏ュ叕鍙搁厤缃�
+ vars.put("companyName", nvl(contract.getSignCompany()));
+ vars.put("companyAddress", "");
+ vars.put("legalPerson", "");
+ vars.put("companyPhone", "");
+ vars.put("creditCode", "");
+ // 涔欐柟锛堝憳宸ワ級
+ vars.put("employeeName", employee != null ? nvl(employee.getName()) : "");
+ vars.put("gender", employee != null ? genderText(employee.getGender()) : "");
+ vars.put("age", employee != null ? nvl(employee.getAge()) : "");
+ vars.put("idCard", employee != null ? nvl(employee.getIdCard()) : "");
+ vars.put("householdAddress", employee != null ? nvl(employee.getAddress()) : "");
+ vars.put("address", employee != null ? nvl(employee.getAddress()) : "");
+ vars.put("phone", employee != null ? nvl(employee.getPhone()) : "");
+ vars.put("emergencyPhone", "");
+ // 鍚堝悓
+ vars.put("contractNo", nvl(contract.getContractNo()));
+ vars.put("postName", "");
+ vars.put("postDuty", "");
+ vars.put("workPlace", "");
+ vars.put("contractStart", formatDate(contract.getStartDate()));
+ vars.put("contractEnd", formatDate(contract.getEndDate()));
+ vars.put("contractMonths", monthsBetween(contract.getStartDate(), contract.getEndDate()));
+ vars.put("probationStart", formatDate(contract.getProbationStartDate()));
+ vars.put("probationEnd", formatDate(contract.getProbationEndDate()));
+ vars.put("probationMonths", monthsBetween(contract.getProbationStartDate(), contract.getProbationEndDate()));
+ vars.put("probationDays", daysBetween(contract.getProbationStartDate(), contract.getProbationEndDate()));
+ vars.put("birthday", employee != null ? formatDate(employee.getBirthday()) : "");
+ // 骞存湀鏃ワ紙渚涖�宊__骞確__鏈坃__鏃ャ�嶉�愭牸濉啓锛�
+ vars.put("contractStartY", y(contract.getStartDate()));
+ vars.put("contractStartM", mo(contract.getStartDate()));
+ vars.put("contractStartD", d(contract.getStartDate()));
+ vars.put("contractEndY", y(contract.getEndDate()));
+ vars.put("contractEndM", mo(contract.getEndDate()));
+ vars.put("contractEndD", d(contract.getEndDate()));
+ vars.put("probationStartY", y(contract.getProbationStartDate()));
+ vars.put("probationStartM", mo(contract.getProbationStartDate()));
+ vars.put("probationStartD", d(contract.getProbationStartDate()));
+ vars.put("probationEndY", y(contract.getProbationEndDate()));
+ vars.put("probationEndM", mo(contract.getProbationEndDate()));
+ vars.put("probationEndD", d(contract.getProbationEndDate()));
+ vars.put("probationSalary", nvl(contract.getProbationSalary()));
+ vars.put("regularSalary", nvl(contract.getRegularSalary()));
+ vars.put("baseSalary", nvl(contract.getRegularSalary()));
+ vars.put("performanceSalary", "");
+ vars.put("socialSecuritySalary", "");
+ vars.put("signDate", formatDate(contract.getSignDate()));
+ // 鍔冲姟鍚堝悓閲岀殑鍙�夐」
+ vars.put("payType", "涓�");
+ vars.put("payDay", "");
+ vars.put("workVolume", "");
+ vars.put("workAmount", "");
+ vars.put("piecePrice", "");
+ vars.put("noticeCompensation", "");
+ vars.put("copies", "涓�");
+ vars.put("eachCopies", "涓�");
+ return vars;
+ }
+
+ private static String nvl(Object o) {
+ return o == null ? "" : o.toString();
+ }
+
+ private static String formatDate(LocalDate d) {
+ return d == null ? "" : d.format(DATE_FMT);
+ }
+
+ private static String monthsBetween(LocalDate start, LocalDate end) {
+ if (start == null || end == null) {
+ return "";
+ }
+ long months = ChronoUnit.MONTHS.between(start, end);
+ return String.valueOf(Math.max(months, 0));
+ }
+
+ private static String daysBetween(LocalDate start, LocalDate end) {
+ if (start == null || end == null) {
+ return "";
+ }
+ long days = ChronoUnit.DAYS.between(start, end);
+ return String.valueOf(Math.max(days, 0));
+ }
+
+ private static String y(LocalDate date) {
+ return date == null ? "" : String.format("%04d", date.getYear());
+ }
+
+ private static String mo(LocalDate date) {
+ return date == null ? "" : String.format("%02d", date.getMonthValue());
+ }
+
+ private static String d(LocalDate date) {
+ return date == null ? "" : String.format("%02d", date.getDayOfMonth());
+ }
+
+ private static String genderText(Integer sex) {
+ if (sex == null) {
+ return "";
+ }
+ return sex == 1 ? "鐢�" : sex == 2 ? "濂�" : "";
+ }
+
+ // 鍏煎 BigDecimal 鐩存帴 toString
+ private static String nvl(BigDecimal v) {
+ return v == null ? "" : v.toPlainString();
+ }
+
+}
diff --git a/yudao-module-hrm/src/main/resources/template/contract/contract_labor_simple.docx b/yudao-module-hrm/src/main/resources/template/contract/contract_labor_simple.docx
new file mode 100644
index 0000000..dec68c1
--- /dev/null
+++ b/yudao-module-hrm/src/main/resources/template/contract/contract_labor_simple.docx
Binary files differ
diff --git a/yudao-module-hrm/src/main/resources/template/contract/contract_labor_standard.docx b/yudao-module-hrm/src/main/resources/template/contract/contract_labor_standard.docx
new file mode 100644
index 0000000..3dc11f4
--- /dev/null
+++ b/yudao-module-hrm/src/main/resources/template/contract/contract_labor_standard.docx
Binary files differ
diff --git a/yudao-module-hrm/src/main/resources/template/contract/contract_service.docx b/yudao-module-hrm/src/main/resources/template/contract/contract_service.docx
new file mode 100644
index 0000000..1973d91
--- /dev/null
+++ b/yudao-module-hrm/src/main/resources/template/contract/contract_service.docx
Binary files differ
diff --git a/yudao-module-hrm/src/main/resources/template/contract/original/labor_simple.docx b/yudao-module-hrm/src/main/resources/template/contract/original/labor_simple.docx
new file mode 100644
index 0000000..b74e9f4
--- /dev/null
+++ b/yudao-module-hrm/src/main/resources/template/contract/original/labor_simple.docx
Binary files differ
diff --git a/yudao-module-hrm/src/main/resources/template/contract/original/labor_standard.docx b/yudao-module-hrm/src/main/resources/template/contract/original/labor_standard.docx
new file mode 100644
index 0000000..3ac5a24
--- /dev/null
+++ b/yudao-module-hrm/src/main/resources/template/contract/original/labor_standard.docx
Binary files differ
diff --git a/yudao-module-hrm/src/main/resources/template/contract/original/service.doc b/yudao-module-hrm/src/main/resources/template/contract/original/service.doc
new file mode 100644
index 0000000..b1e1073
--- /dev/null
+++ b/yudao-module-hrm/src/main/resources/template/contract/original/service.doc
Binary files differ
--
Gitblit v1.9.3