2026-09-02 43e62967f06fa9be3b84548106ab55a7266b9cf1
feat(hrm): 新增员工合同导入功能

- 在 ErrorCodeConstants.java 中添加员工合同导入相关的错误码定义
- 在 HrmEmployeeContractController.java 中实现导入模板下载和导入接口
- 在 HrmEmployeeContractMapper.java 中添加按员工、合同类型、开始日期查询合同的方法
- 在 HrmEmployeeContractService.java 中定义批量导入员工合同的服务接口
- 在 HrmEmployeeContractServiceImpl.java 中实现员工合同导入的核心逻辑,包括员工匹配、数据校验和批量处理
- 移除项目中的 playwright 测试配置文件
已删除80个文件
已添加4个文件
已修改5个文件
6508 ■■■■■ 文件已修改
.playwright-mcp/img.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-05-51-287Z.yml 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-06-20-697Z.yml 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-06-46-253Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-08-30-493Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-08-59-408Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-09-42-959Z.yml 428 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-09-59-810Z.yml 416 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-11-18-147Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-13-43-647Z.yml 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-14-06-572Z.yml 394 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-14-13-732Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-14-34-792Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-21T07-57-24-131Z.yml 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-40-35-404Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-42-11-724Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-42-36-804Z.yml 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-43-35-225Z.yml 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-47-24-223Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-47-38-983Z.yml 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-47-57-125Z.yml 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-51-00-932Z.yml 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-56-04-077Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T06-56-14-335Z.yml 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T07-10-45-745Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T07-12-31-548Z.yml 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T07-14-24-445Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T07-14-44-587Z.yml 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-07-29T07-14-58-476Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-03T09-16-38-621Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-03T09-20-27-740Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-03T09-20-36-226Z.yml 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-03T09-22-30-620Z.yml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-03T09-22-42-679Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-03T09-22-54-018Z.yml 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-04T07-44-03-300Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-04T07-44-19-942Z.yml 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-04T07-44-38-346Z.yml 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-36-48-990Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-36-56-155Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-41-09-497Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-41-21-485Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-51-48-188Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-54-21-100Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-58-12-325Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T07-58-23-056Z.yml 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T08-54-54-463Z.yml 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T08-59-40-566Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-17-10-326Z.yml 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-17-21-418Z.yml 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-18-45-630Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-18-52-745Z.yml 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-20-49-932Z.png 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-25-58-846Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-27-33-763Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-27-39-409Z.yml 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-29-05-292Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-29-11-606Z.yml 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-33-00-314Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-39-43-280Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-39-54-145Z.yml 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-09-408Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-13-937Z.yml 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-19-588Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-24-098Z.yml 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-28-333Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-33-342Z.yml 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-42-217Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-40-49-786Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-53-15-917Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-53-22-647Z.yml 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-53-30-790Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-05T09-53-35-271Z.yml 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-12T08-14-30-909Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-12T08-14-46-176Z.yml 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-12T08-15-00-361Z.yml 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-12T08-16-45-289Z.yml 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-14T05-18-36-135Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-14T05-18-50-100Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-14T05-19-08-995Z.yml 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_employee_contract_import_menu.sql 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeContractImportExcelVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeContractImportReqVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeContractImportRespVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeContractService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeContractServiceImpl.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/img.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-05-51-287Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-21T07-06-20-697Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-21T07-06-46-253Z.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-08-30-493Z.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-08-59-408Z.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-09-42-959Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-21T07-09-59-810Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-21T07-11-18-147Z.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-13-43-647Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-21T07-14-06-572Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-21T07-14-13-732Z.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-14-34-792Z.png
Binary files differ
.playwright-mcp/page-2026-07-21T07-57-24-131Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-40-35-404Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-42-11-724Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-42-36-804Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-43-35-225Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-47-24-223Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-47-38-983Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-47-57-125Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-51-00-932Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-56-04-077Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T06-56-14-335Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T07-10-45-745Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T07-12-31-548Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T07-14-24-445Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T07-14-44-587Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-07-29T07-14-58-476Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-03T09-16-38-621Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-03T09-20-27-740Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-03T09-20-36-226Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-03T09-22-30-620Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-03T09-22-42-679Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-03T09-22-54-018Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-04T07-44-03-300Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-04T07-44-19-942Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-04T07-44-38-346Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T07-36-48-990Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T07-36-56-155Z.png
Binary files differ
.playwright-mcp/page-2026-08-05T07-41-09-497Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T07-41-21-485Z.png
Binary files differ
.playwright-mcp/page-2026-08-05T07-51-48-188Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T07-54-21-100Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T07-58-12-325Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T07-58-23-056Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T08-54-54-463Z.yml
.playwright-mcp/page-2026-08-05T08-59-40-566Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-17-10-326Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-17-21-418Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-18-45-630Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-18-52-745Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-20-49-932Z.png
Binary files differ
.playwright-mcp/page-2026-08-05T09-25-58-846Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-27-33-763Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-27-39-409Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-29-05-292Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-29-11-606Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-33-00-314Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-39-43-280Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-39-54-145Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-09-408Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-13-937Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-19-588Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-24-098Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-28-333Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-33-342Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-42-217Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-40-49-786Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-53-15-917Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-53-22-647Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-53-30-790Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-05T09-53-35-271Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-12T08-14-30-909Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-12T08-14-46-176Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-12T08-15-00-361Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-12T08-16-45-289Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-14T05-18-36-135Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-14T05-18-50-100Z.yml
ÎļþÒÑɾ³ý
.playwright-mcp/page-2026-08-14T05-19-08-995Z.yml
ÎļþÒÑɾ³ý
sql/mysql/hrm_employee_contract_import_menu.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
-- å‘˜å·¥åˆåŒ - å¯¼å…¥/导入模板 æŒ‰é’®æƒé™
-- çˆ¶èœå•:员工合同(parent_id = 1063105)
-- å‚照:合同新增按钮 id=1063107(type=3, permission=hrm:employee-contract:create)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
                         component_name, status, visible, keep_alive, always_show,
                         creator, create_time, updater, update_time, deleted)
VALUES (1063300, '合同导入', 'hrm:employee-contract:import', 3, 5, 1063105, '', '#', '', NULL,
        0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
-- æŽˆäºˆè¶…级管理员角色(role_id=1)该按钮权限(幂等)
INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
SELECT 1, 1063300, '1', NOW(), '1', NOW(), 0 FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_role_menu WHERE role_id = 1 AND menu_id = 1063300);
yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -26,6 +26,11 @@
    ErrorCode EMPLOYEE_CONTRACT_END_DATE_REQUIRE_FOR_FIXED = new ErrorCode(1_030_000_013, "固定期限合同必须填写结束日期");
    ErrorCode EMPLOYEE_CONTRACT_DATE_INVALID = new ErrorCode(1_030_000_014, "合同日期不合法,开始日期不能晚于结束日期");
    ErrorCode EMPLOYEE_CONTRACT_DATE_OVERLAP = new ErrorCode(1_030_000_015, "该员工在所选时间段已存在未解除/未终止的合同,请先解除/终止后再签订");
    ErrorCode EMPLOYEE_CONTRACT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_030_000_016, "导入员工合同数据不能为空");
    ErrorCode EMPLOYEE_CONTRACT_IMPORT_FIELD_REQUIRED = new ErrorCode(1_030_000_017, "导入字段【{}】不能为空");
    ErrorCode EMPLOYEE_CONTRACT_IMPORT_EMPLOYEE_NAME_NOT_EXISTS = new ErrorCode(1_030_000_018, "员工姓名【{}】不存在,请先维护员工档案");
    ErrorCode EMPLOYEE_CONTRACT_IMPORT_EMPLOYEE_NAME_MULTIPLE = new ErrorCode(1_030_000_019, "员工姓名【{}】对应多个员工档案,无法自动匹配,请先在页面手工为该员工新增合同");
    ErrorCode EMPLOYEE_CONTRACT_IMPORT_CONTRACT_EXISTS = new ErrorCode(1_030_000_020, "该员工【{}】已存在相同类型、相同开始日期的合同,如需覆盖请勾选“已存在时更新”");
    // ========== è¯·å‡ç”³è¯· 1-030-001-000 ==========
    ErrorCode LEAVE_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_001_000, "请假申请不存在");
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeContractController.java
@@ -2,6 +2,10 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportExcelVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractSaveReqVO;
@@ -11,10 +15,17 @@
import io.swagger.v3.oas.annotations.Parameter;
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.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -81,4 +92,30 @@
        return success(contractService.getExpiringCount());
    }
    @GetMapping("/import-template")
    @Operation(summary = "下载员工合同导入模板")
    @PreAuthorize("@ss.hasPermission('hrm:employee-contract:import')")
    public void importTemplate(HttpServletResponse response) throws IOException {
        // è¾“出示例数据,方便使用者参考。合同类型/期限类型按字典中文填写
        List<HrmEmployeeContractImportExcelVO> list = Arrays.asList(
                HrmEmployeeContractImportExcelVO.builder().name("张三").contractType(1).contractTermType(1)
                        .signCompany("南通XX科技有限公司").signDate(LocalDate.of(2026, 1, 1))
                        .startDate(LocalDate.of(2026, 1, 1)).endDate(LocalDate.of(2028, 12, 31))
                        .probationStartDate(LocalDate.of(2026, 1, 1)).probationEndDate(LocalDate.of(2026, 6, 30))
                        .probationSalary(new BigDecimal("8000.00")).regularSalary(new BigDecimal("10000.00"))
                        .remark("示例:请按实际修改后导入").build()
        );
        ExcelUtils.write(response, "员工合同导入模板.xls", "员工合同", HrmEmployeeContractImportExcelVO.class, list);
    }
    @PostMapping("/import")
    @Operation(summary = "导入员工合同", description = "按员工姓名 + åˆåŒç±»åž‹ + åˆåŒå¼€å§‹æ—¥æœŸåˆ¤å®šï¼›å·²å­˜åœ¨ä¸”开启“已存在时更新”则覆盖更新该行")
    @PreAuthorize("@ss.hasPermission('hrm:employee-contract:import')")
    public CommonResult<HrmEmployeeContractImportRespVO> importExcel(@Valid HrmEmployeeContractImportReqVO importReqVO)
            throws Exception {
        List<HrmEmployeeContractImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(),
                HrmEmployeeContractImportExcelVO.class);
        return success(contractService.importContractList(list, importReqVO));
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeContractImportExcelVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
 * å‘˜å·¥åˆåŒ Excel å¯¼å…¥ VO
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class HrmEmployeeContractImportExcelVO {
    @ExcelProperty("员工姓名")
    private String name;
    @ExcelProperty(value = "合同类型", converter = DictConvert.class)
    @DictFormat("hrm_contract_type")
    private Integer contractType;
    @ExcelProperty(value = "合同期限类型", converter = DictConvert.class)
    @DictFormat("hrm_contract_term_type")
    private Integer contractTermType;
    @ExcelProperty("签约主体(公司全称)")
    private String signCompany;
    @ExcelProperty("签订日期")
    private LocalDate signDate;
    @ExcelProperty("合同开始日期")
    private LocalDate startDate;
    @ExcelProperty("合同结束日期")
    private LocalDate endDate;
    @ExcelProperty("试用期开始日期")
    private LocalDate probationStartDate;
    @ExcelProperty("试用期结束日期")
    private LocalDate probationEndDate;
    @ExcelProperty("试用期工资")
    private BigDecimal probationSalary;
    @ExcelProperty("转正工资")
    private BigDecimal regularSalary;
    @ExcelProperty("备注")
    private String remark;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeContractImportReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Schema(description = "管理后台 - å‘˜å·¥åˆåŒå¯¼å…¥ Request VO")
@Data
public class HrmEmployeeContractImportReqVO {
    @Schema(description = "Excel æ–‡ä»¶", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "Excel æ–‡ä»¶ä¸èƒ½ä¸ºç©º")
    private MultipartFile file;
    @Schema(description = "是否覆盖更新(匹配到同员工相同合同类型与开始日期的合同则覆盖更新),默认 false", example = "true")
    private Boolean updateSupport = false;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeContractImportRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.hrm.controller.admin.employee.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - å‘˜å·¥åˆåŒå¯¼å…¥ Response VO")
@Data
@Builder
public class HrmEmployeeContractImportRespVO {
    @Schema(description = "新增成功的员工名数组", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<String> createNames;
    @Schema(description = "覆盖更新成功的员工名数组", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<String> updateNames;
    @Schema(description = "导入失败的员工名集合,key ä¸ºå‘˜å·¥åï¼Œvalue ä¸ºå¤±è´¥åŽŸå› ", requiredMode = Schema.RequiredMode.REQUIRED)
    private Map<String, String> failureNames;
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeContractMapper.java
@@ -165,6 +165,24 @@
    }
    /**
     * æŸ¥è¯¢å‘˜å·¥åœ¨æŸåˆåŒç±»åž‹ã€æŸå¼€å§‹æ—¥æœŸä¸‹å·²å­˜åœ¨çš„合同(用于导入时判定覆盖更新),取最新一条
     *
     * @param employeeId   å‘˜å·¥ID
     * @param contractType åˆåŒç±»åž‹
     * @param startDate    åˆåŒå¼€å§‹æ—¥æœŸ
     * @return å·²å­˜åœ¨çš„合同;不存在返回 null
     */
    default HrmEmployeeContractDO selectByEmployeeTypeStartDate(Long employeeId, Integer contractType,
                                                                LocalDate startDate) {
        return selectOne(new LambdaQueryWrapperX<HrmEmployeeContractDO>()
                .eq(HrmEmployeeContractDO::getEmployeeId, employeeId)
                .eq(HrmEmployeeContractDO::getContractType, contractType)
                .eq(HrmEmployeeContractDO::getStartDate, startDate)
                .orderByDesc(HrmEmployeeContractDO::getId)
                .last("LIMIT 1"));
    }
    /**
     * æ¸…除该员工全部合同的"当前合同"标记
     *
     * @param employeeId å‘˜å·¥ID
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeContractService.java
@@ -1,10 +1,15 @@
package cn.iocoder.yudao.module.hrm.service.employee;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportExcelVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractTerminateReqVO;
import java.util.List;
/**
 * å‘˜å·¥åˆåŒ Service æŽ¥å£
@@ -65,4 +70,16 @@
     */
    Long getExpiringCount();
    /**
     * æ‰¹é‡å¯¼å…¥å‘˜å·¥åˆåŒ
     * <p>
     * åˆ¤å®šè§„则:按「员工姓名 + åˆåŒç±»åž‹ + åˆåŒå¼€å§‹æ—¥æœŸã€åˆ¤æ–­æ˜¯å¦å­˜åœ¨ï¼›å­˜åœ¨ä¸”开启 updateSupport æ—¶è¦†ç›–更新该行(保留合同编号),否则新增。
     *
     * @param importContracts å¯¼å…¥çš„合同 Excel è¡Œ
     * @param importReqVO     å¯¼å…¥è¯·æ±‚(含是否覆盖更新开关)
     * @return å¯¼å…¥ç»“果(新增/更新成功列表、失败明细)
     */
    HrmEmployeeContractImportRespVO importContractList(List<HrmEmployeeContractImportExcelVO> importContracts,
                                                       HrmEmployeeContractImportReqVO importReqVO);
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeContractServiceImpl.java
@@ -2,26 +2,34 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
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.module.hrm.controller.admin.employee.vo.HrmEmployeeContractPageReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractSaveReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportExcelVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportReqVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractImportRespVO;
import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeContractTerminateReqVO;
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 cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -50,11 +58,16 @@
    @Resource
    private HrmEmployeeService employeeService;
    @Resource
    private HrmEmployeeMapper employeeMapper;
    @Resource
    private DeptApi deptApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private HrmNoRedisDAO noRedisDAO;
    @Resource
    @Lazy // è‡ªæ³¨å…¥éœ€å»¶è¿ŸåŠ è½½ï¼Œå¯¼å…¥æ—¶é€æ¡èµ°äº‹åŠ¡è¾¹ç•Œ
    private HrmEmployeeContractService self;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -318,4 +331,101 @@
        }
    }
    @Override
    public HrmEmployeeContractImportRespVO importContractList(List<HrmEmployeeContractImportExcelVO> importContracts,
                                                              HrmEmployeeContractImportReqVO importReqVO) {
        // 1. è¿‡æ»¤å‘˜å·¥å§“名为空的行
        importContracts = importContracts.stream()
                .filter(item -> item.getName() != null && StrUtil.isNotBlank(item.getName().trim()))
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(importContracts)) {
            throw exception(EMPLOYEE_CONTRACT_IMPORT_LIST_IS_EMPTY);
        }
        // 2. é¢„加载员工姓名 -> å‘˜å·¥æ¡£æ¡ˆ æ˜ å°„(含已离职,便于补录历史合同)
        Map<String, List<HrmEmployeeDO>> employeeNameMap = employeeMapper.selectList().stream()
                .filter(emp -> StrUtil.isNotBlank(emp.getName()))
                .collect(Collectors.groupingBy(emp -> emp.getName().trim(), LinkedHashMap::new, Collectors.toList()));
        // 3. é€æ¡å¤„理:匹配到则覆盖更新,否则新建
        HrmEmployeeContractImportRespVO respVO = HrmEmployeeContractImportRespVO.builder()
                .createNames(new ArrayList<>())
                .updateNames(new ArrayList<>())
                .failureNames(new LinkedHashMap<>())
                .build();
        for (HrmEmployeeContractImportExcelVO importContract : importContracts) {
            String name = importContract.getName().trim();
            try {
                // 3.1 å‘˜å·¥å§“名转员工 ID
                Long employeeId = parseEmployeeId(name, employeeNameMap);
                // 3.2 ç»„装保存请求(必填字段校验)
                HrmEmployeeContractSaveReqVO saveReqVO = buildImportSaveReqVO(importContract, employeeId);
                // 3.3 æŒ‰ã€Œå‘˜å·¥ + åˆåŒç±»åž‹ + åˆåŒå¼€å§‹æ—¥æœŸã€åˆ¤å®šå·²å­˜åœ¨
                HrmEmployeeContractDO exist = contractMapper.selectByEmployeeTypeStartDate(employeeId,
                        saveReqVO.getContractType(), saveReqVO.getStartDate());
                if (exist == null) {
                    self.createContract(saveReqVO);
                    respVO.getCreateNames().add(name);
                } else {
                    if (!Boolean.TRUE.equals(importReqVO.getUpdateSupport())) {
                        respVO.getFailureNames().put(name,
                                StrUtil.format(EMPLOYEE_CONTRACT_IMPORT_CONTRACT_EXISTS.getMsg(),
                                        saveReqVO.getStartDate().toString()));
                        continue;
                    }
                    saveReqVO.setId(exist.getId());
                    self.updateContract(saveReqVO);
                    respVO.getUpdateNames().add(name);
                }
            } catch (ServiceException ex) {
                respVO.getFailureNames().put(name, ex.getMessage());
            } catch (Exception ex) {
                respVO.getFailureNames().put(name, ex.getMessage());
            }
        }
        return respVO;
    }
    /**
     * å‘˜å·¥å§“名解析为员工 ID,重名时提示需手工处理
     */
    private Long parseEmployeeId(String name, Map<String, List<HrmEmployeeDO>> employeeNameMap) {
        List<HrmEmployeeDO> employees = employeeNameMap.get(name);
        if (CollUtil.isEmpty(employees)) {
            throw exception(EMPLOYEE_CONTRACT_IMPORT_EMPLOYEE_NAME_NOT_EXISTS, name);
        }
        if (employees.size() > 1) {
            throw exception(EMPLOYEE_CONTRACT_IMPORT_EMPLOYEE_NAME_MULTIPLE, name);
        }
        return employees.get(0).getId();
    }
    /**
     * ç»„装导入合同保存请求并做必填字段校验(日期逻辑校验交由 create/update å¤„理)
     */
    private HrmEmployeeContractSaveReqVO buildImportSaveReqVO(HrmEmployeeContractImportExcelVO importContract,
                                                              Long employeeId) {
        if (importContract.getContractType() == null) {
            throw exception(EMPLOYEE_CONTRACT_IMPORT_FIELD_REQUIRED, "合同类型");
        }
        if (importContract.getContractTermType() == null) {
            throw exception(EMPLOYEE_CONTRACT_IMPORT_FIELD_REQUIRED, "合同期限类型");
        }
        if (importContract.getStartDate() == null) {
            throw exception(EMPLOYEE_CONTRACT_IMPORT_FIELD_REQUIRED, "合同开始日期");
        }
        HrmEmployeeContractSaveReqVO reqVO = new HrmEmployeeContractSaveReqVO();
        reqVO.setEmployeeId(employeeId);
        reqVO.setContractType(importContract.getContractType());
        reqVO.setContractTermType(importContract.getContractTermType());
        reqVO.setSignCompany(importContract.getSignCompany());
        reqVO.setSignDate(importContract.getSignDate());
        reqVO.setStartDate(importContract.getStartDate());
        reqVO.setEndDate(importContract.getEndDate());
        reqVO.setProbationStartDate(importContract.getProbationStartDate());
        reqVO.setProbationEndDate(importContract.getProbationEndDate());
        reqVO.setProbationSalary(importContract.getProbationSalary());
        reqVO.setRegularSalary(importContract.getRegularSalary());
        reqVO.setRemark(importContract.getRemark());
        return reqVO;
    }
}