liu
11 小时以前 c3b0503f8e2ecf955d0e8e4ca9294e6cd5199c0b
HRM: 员工台账岗位支持多选并同步系统用户岗位

- 员工岗位由单选 postId 改为多选 postIds(JSON 存储,兼容 roleIds 模式)
- 新增 hrm_employee.post_ids 列及数据迁移脚本
- 编辑员工时同步更新关联系统用户岗位/部门/昵称/角色/邮箱/备注
- 系统模块新增 AdminUserApi.updateUser 跨模块接口(AdminUserUpdateReqDTO)
- 员工导入模板"岗位名称"列支持多个岗位(逗号分隔)
- 加班申请等关联页面岗位展示支持多岗位

Co-Authored-By: Claude <noreply@anthropic.com>
已添加4个文件
已修改9个文件
345 ■■■■ 文件已修改
docs/hrm_employee_import.md 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/hrm_employee_post_multiselect.md 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_employee_post_multi.sql 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/hrm_employee_post_multiselect_deploy.sql 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java 75 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserUpdateReqDTO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/hrm_employee_import.md
@@ -10,7 +10,7 @@
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. **下载模板** -> å‰ç«¯è°ƒç”¨ `GET /hrm/employee/import-template` ä¸‹è½½ Excel æ¨¡æ¿ï¼Œæ¨¡æ¿åŒ…含示例行供参考。
2. **填写模板** -> ç”¨æˆ·æŒ‰æ¨¡æ¿å¡«å†™å‘˜å·¥ä¿¡æ¯ï¼Œå…¶ä¸­"部门名称/岗位名称/用户角色"三列填**名称**(用户角色多个用中文逗号分隔),由后端自动转换为 ID。
2. **填写模板** -> ç”¨æˆ·æŒ‰æ¨¡æ¿å¡«å†™å‘˜å·¥ä¿¡æ¯ï¼Œå…¶ä¸­"部门名称/岗位名称/用户角色"三列填**名称**(岗位名称、用户角色多个用中文或英文逗号分隔),由后端自动转换为 ID。
3. **上传导入** -> å‰ç«¯è°ƒç”¨ `POST /hrm/employee/import`(multipart è¡¨å•,file + updateSupport)上传文件。
4. **展示结果** -> åŽç«¯è¿”回导入结果(创建成功名单 / æ›´æ–°æˆåŠŸåå• / å¤±è´¥åå•及原因),前端逐条展示。
@@ -30,6 +30,7 @@
- æ€§åˆ«ï¼šå¡«"男"/"女"(字典 `hrm_gender`,值 1/2)
- å‘˜å·¥çŠ¶æ€ï¼šå¡«"在职"/"试用"/"离职"(字典 `hrm_employee_status`,值 1/2/3)
- å²—位名称:填岗位名称,**支持多个**,多个用英文逗号(`,`)或中文逗号(`,`)分隔(如"部门经理,项目经理")
- ç”¨æˆ·è§’色:填角色名称,多个用**中文逗号**分隔(如"普通管理员,普通员工")
### å¯¼å…¥å‘˜å·¥
@@ -75,7 +76,7 @@
| æ€§åˆ« | æ¨¡æ¿åˆ— | å¿…填,默认男 |
| æ‰‹æœºå· | æ¨¡æ¿åˆ— | å¿…填,将作为系统登录账号 |
| éƒ¨é—¨åç§° | æ¨¡æ¿åˆ— | å¿…填,填名称,后端转 ID |
| å²—位名称 | æ¨¡æ¿åˆ— | å¿…填,填名称,后端转 ID |
| å²—位名称 | æ¨¡æ¿åˆ— | å¿…填,填名称,支持多个(用逗号分隔),后端转 ID |
| ç”¨æˆ·è§’色 | æ¨¡æ¿åˆ— | é€‰å¡«ï¼Œå¡«åç§°ï¼Œå¤šä¸ªç”¨ä¸­æ–‡é€—号分隔 |
| å…¥èŒæ—¥æœŸ | æ¨¡æ¿åˆ— | å¿…填,格式 YYYY-MM-DD |
| å‘˜å·¥çŠ¶æ€ | æ¨¡æ¿åˆ— | å¿…填,默认在职 |
docs/hrm_employee_post_multiselect.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
# å‘˜å·¥å°è´¦å²—位多选 - å‰ç«¯è”调方案
## èƒŒæ™¯
人力资源 > å‘˜å·¥ç®¡ç†ï¼ˆå‘˜å·¥å°è´¦ï¼‰çš„"岗位"由单选改为**多选**,与系统"用户管理"的多岗位能力保持一致。存储上采用 JSON æ•°ç»„(`post_ids` åˆ—),同"角色"字段的既有模式。
## æ¶‰åŠé¡µé¢
- äººåŠ›èµ„æº > å‘˜å·¥ç®¡ç† åˆ—表页(`src/views/hrm/employee/index.vue`)
- å‘˜å·¥æ–°å¢ž/编辑表单(`src/views/hrm/employee/modules/form.vue`、`data.ts`)
- å‘˜å·¥è¯¦æƒ…(`src/views/hrm/employee/modules/detail.vue`)
- å‘˜å·¥å¯¼å…¥å¼¹çª—(含下载模板、上传文件)
- å‘˜å·¥å¯¼å‡º Excel
## API å˜æ›´è¯´æ˜Ž
| æ–¹æ³• | è·¯å¾„ | å˜æ›´ |
|------|------|------|
| POST | `/hrm/employee/create` | è¯·æ±‚字段 `postId` â†’ `postIds`(数组) |
| PUT | `/hrm/employee/update` | è¯·æ±‚字段 `postId` â†’ `postIds`(数组) |
| GET | `/hrm/employee/get` | å“åº”字段 `postId/postName` â†’ `postIds/postNames` |
| GET | `/hrm/employee/page` | å“åº”字段 `postId/postName` â†’ `postIds/postNames` |
| GET | `/hrm/employee/export-excel` | "岗位名称"列支持多值(逗号拼接) |
| GET | `/hrm/employee/import-template` | "岗位名称"列示例改为多岗位 |
| POST | `/hrm/employee/import` | "岗位名称"列支持填写多个岗位名 |
### 1. æ–°å¢žå‘˜å·¥ `POST /hrm/employee/create`
**请求参数(字段变更):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| postIds | List\<Long\> | æ˜¯ | å²—位ID数组,至少一个(原 `postId` å•值字段已移除) |
```json
{
  "name": "张三",
  "phone": "13800138000",
  "deptId": 103,
  "postIds": [2, 4],
  "roleIds": [1],
  "hireDate": "2026-08-01",
  "employeeStatus": 1,
  "gender": 1
}
```
### 2. æ›´æ–°å‘˜å·¥ `PUT /hrm/employee/update`
同 create,请求体中的 `postIds` ä¸ºå‘˜å·¥æœ€ç»ˆçš„岗位集合(全量覆盖,非增量)。
### 3. å‘˜å·¥è¯¦æƒ… / åˆ†é¡µ `GET /hrm/employee/get`、`GET /hrm/employee/page`
**响应字段变更:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| postIds | List\<Long\> | å²—位ID数组(原 `postId` å·²ç§»é™¤ï¼‰ |
| postNames | String | å²—位名称,多个用英文逗号拼接,如"部门经理,项目经理"(原 `postName` å·²ç§»é™¤ï¼‰ |
```json
{
  "code": 0,
  "data": {
    "id": 6,
    "name": "张三",
    "deptName": "研发部",
    "postIds": [2, 4],
    "postNames": "部门经理,项目经理",
    "roleNames": "普通管理员"
  }
}
```
### 4. å¯¼å‡º Excel `GET /hrm/employee/export-excel`
- "岗位名称"列展示多个岗位名,英文逗号拼接(与分页返回的 `postNames` ä¸€è‡´ï¼‰ã€‚
### 5. å¯¼å…¥æ¨¡æ¿ / å¯¼å…¥ `GET /hrm/employee/import-template`、`POST /hrm/employee/import`
- æ¨¡æ¿"岗位名称"列由单个岗位名改为支持**多个岗位名**,多个用**英文逗号(`,`)或中文逗号(`,`)**分隔。
- æ¨¡æ¿ç¤ºä¾‹è¡Œï¼š`部门经理,项目经理`。
- ä»»ä¸€å²—位名不存在,该行导入失败,原因"岗位【xx】不存在"。
- å²—位名称列不允许为空(岗位必填)。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| postIds | è¡¨å•(新增/编辑)、详情 | è¡¨å•岗位改为多选下拉框,编辑回显勾选多个 |
| postNames | åˆ—表、详情、导出 | å±•示为逗号拼接的多个岗位名 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| è¡¨å•保存 | `postIds` å¿…填,至少一个岗位 |
| ç¼–辑保存 | `postIds` å…¨é‡è¦†ç›–该员工岗位 |
| å¯¼å…¥ | "岗位名称"列多个名称用逗号分隔,任一不存在则整行失败 |
| å·²æœ‰æ•°æ® | å­˜é‡å‘˜å·¥çš„单岗位数据已迁移为 `postIds`,老数据不丢失 |
## æ³¨æ„äº‹é¡¹
- **前端需同步修改**:新增/编辑表单的岗位字段从单选改为多选下拉,提交字段名从 `postId` æ”¹ä¸º `postIds`;列表/详情展示字段从 `postName` æ”¹ä¸º `postNames`。
- **编辑员工时同步更新**关联系统登录用户(用户管理页)的岗位/部门/昵称/角色/邮箱/备注,两边保持一致。
- å²—位下拉数据仍从系统岗位接口获取(与之前一致)。
- å²—位名称解析为精确匹配,不支持模糊。
sql/mysql/hrm_employee_post_multi.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
-- =============================================
-- HRM å‘˜å·¥è¡¨ï¼šå²—位多选改造 SQL
-- 1. æ·»åŠ  post_ids å­—段(JSON æ ¼å¼ï¼Œä¸Ž role_ids å­˜å‚¨æ–¹å¼ä¸€è‡´ï¼‰
-- 2. è¿ç§»å·²æœ‰ post_id æ•°æ®åˆ° post_ids
-- 3. ä¿ç•™ post_id å­—段(兼容旧脚本/外部引用,已废弃不再写入)
-- =============================================
-- 1. æ·»åŠ å²—ä½ID列表字段(JSON格式存储)
ALTER TABLE `hrm_employee` ADD COLUMN `post_ids` varchar(500) DEFAULT NULL COMMENT '岗位ID列表(JSON格式)' AFTER `post_id`;
-- 2. è¿ç§»å·²æœ‰ post_id æ•°æ®åˆ° post_ids
UPDATE `hrm_employee` SET `post_ids` = CONCAT('[', `post_id`, ']') WHERE `post_id` IS NOT NULL AND (`post_ids` IS NULL OR `post_ids` = '');
-- æ‰§è¡Œå®Œæˆæç¤º
SELECT 'HRM å‘˜å·¥è¡¨å²—位多选改造完成!' AS result;
sql/mysql/hrm_employee_post_multiselect_deploy.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
-- =============================================
-- HRM å‘˜å·¥å°è´¦å²—位多选 - æ•°æ®åº“变更汇总脚本
-- é€‚用:test / prod ç­‰éœ€è¦æ‰‹å·¥æ‰§è¡Œçš„环境
-- åŒ…含:
--   1. hrm_employee è¡¨æ–°å¢žå²—位ID列表(post_ids)列,并迁移旧 post_id æ•°æ®
--   2. hrm_salary_structure è¡¨æ–°å¢žä¼‘息日/法定节假日加班工资倍率列(修复员工编辑页薪酬结构下拉报错)
-- æ³¨æ„ï¼šMySQL 8 çš„ ALTER TABLE ADD COLUMN ä¸æ”¯æŒ IF NOT EXISTS;
--       è‹¥æŸåˆ—已存在,对应 ALTER è¯­å¥ä¼šæŠ¥"Duplicate column",忽略该条即可继续。
-- =============================================
-- ---------- 1. hrm_employee:新增岗位ID列表(JSON),并迁移旧 post_id ----------
ALTER TABLE `hrm_employee` ADD COLUMN `post_ids` varchar(500) DEFAULT NULL COMMENT '岗位ID列表(JSON格式)' AFTER `post_id`;
UPDATE `hrm_employee` SET `post_ids` = CONCAT('[', `post_id`, ']') WHERE `post_id` IS NOT NULL AND (`post_ids` IS NULL OR `post_ids` = '');
-- ---------- 2. hrm_salary_structure:新增休息日/法定节假日加班倍率 ----------
ALTER TABLE `hrm_salary_structure` ADD COLUMN `rest_day_overtime_pay_ratio` decimal(4,2) DEFAULT 2.00 COMMENT '休息日加班工资倍率' AFTER `overtime_pay_ratio`;
ALTER TABLE `hrm_salary_structure` ADD COLUMN `legal_holiday_overtime_pay_ratio` decimal(4,2) DEFAULT 3.00 COMMENT '法定节假日加班工资倍率' AFTER `rest_day_overtime_pay_ratio`;
-- ---------- å®Œæˆæç¤º ----------
SELECT 'HRM å‘˜å·¥å°è´¦å²—位多选数据库变更完成!' AS result;
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/HrmEmployeeController.java
@@ -115,10 +115,12 @@
        Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(deptIds) ? Collections.emptyMap() : deptApi.getDeptMap(deptIds);
        // 2. æ‰¹é‡èŽ·å–å²—ä½ä¿¡æ¯
        Set<Long> postIds = employeeList.stream()
                .map(HrmEmployeeDO::getPostId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Set<Long> postIds = new HashSet<>();
        for (HrmEmployeeDO employee : employeeList) {
            if (StrUtil.isNotBlank(employee.getPostIds())) {
                postIds.addAll(JSONUtil.toList(employee.getPostIds(), Long.class));
            }
        }
        Map<Long, PostRespDTO> postMap = CollUtil.isEmpty(postIds) ? Collections.emptyMap() : postApi.getPostMap(postIds);
        // 3. æ”¶é›†æ‰€æœ‰è§’色ID
@@ -143,9 +145,16 @@
                respVO.setDeptName(deptMap.get(employee.getDeptId()).getName());
            }
            // å¡«å……岗位名称
            if (employee.getPostId() != null && postMap.containsKey(employee.getPostId())) {
                respVO.setPostName(postMap.get(employee.getPostId()).getName());
            // å¡«å……岗位信息
            if (StrUtil.isNotBlank(employee.getPostIds())) {
                List<Long> employeePostIds = JSONUtil.toList(employee.getPostIds(), Long.class);
                respVO.setPostIds(employeePostIds);
                if (CollUtil.isNotEmpty(employeePostIds)) {
                    respVO.setPostNames(employeePostIds.stream()
                            .filter(postMap::containsKey)
                            .map(id -> postMap.get(id).getName())
                            .collect(Collectors.joining(",")));
                }
            }
            // è§£æž roleIds å¹¶å¡«å……角色名称
@@ -195,7 +204,7 @@
        // è¾“出示例数据,方便使用者参考
        List<HrmEmployeeImportExcelVO> list = Arrays.asList(
                HrmEmployeeImportExcelVO.builder().name("张三").gender(1).phone("13800138000")
                        .deptName("研发部").postName("部门经理").roleNames("普通管理员")
                        .deptName("研发部").postName("部门经理,项目经理").roleNames("普通管理员")
                        .hireDate(LocalDate.of(2020, 3, 1)).employeeStatus(1)
                        .idCard("320102199001150011").birthday(LocalDate.of(1990, 1, 15))
                        .nation("汉族").marriageStatus("已婚").age(36).education("本科").politicalStatus("中共党员")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
@@ -32,12 +32,12 @@
    @ExcelProperty("部门名称")
    private String deptName;
    @Schema(description = "岗位ID")
    private Long postId;
    @Schema(description = "岗位ID列表")
    private List<Long> postIds;
    @Schema(description = "岗位名称")
    @Schema(description = "岗位名称列表")
    @ExcelProperty("岗位名称")
    private String postName;
    private String postNames;
    @Schema(description = "员工姓名")
    @ExcelProperty("员工姓名")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
@@ -23,9 +23,9 @@
    @NotNull(message = "部门不能为空")
    private Long deptId;
    @Schema(description = "岗位ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "岗位不能为空")
    private Long postId;
    @Schema(description = "岗位ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
    @NotEmpty(message = "岗位不能为空")
    private List<Long> postIds;
    @Schema(description = "员工姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
    @NotEmpty(message = "员工姓名不能为空")
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
@@ -45,11 +45,11 @@
    private Long deptId;
    /**
     * å²—位ID
     * å²—位ID列表
     *
     * å…³è” PostDO#getId()
     * å…³è” PostDO#getId(),JSON æ ¼å¼å­˜å‚¨
     */
    private Long postId;
    private String postIds;
    /**
     * å‘˜å·¥ç¼–号
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
@@ -2,6 +2,8 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
@@ -255,9 +257,17 @@
        resp.setEmployeeName(employee.getName()); resp.setEmployeeNo(employee.getEmployeeNo());
        resp.setDeptId(employee.getDeptId());
        DeptRespDTO dept = employee.getDeptId() == null ? null : deptApi.getDept(employee.getDeptId());
        PostRespDTO post = employee.getPostId() == null ? null
                : postApi.getPostMap(Collections.singleton(employee.getPostId())).get(employee.getPostId());
        resp.setDeptName(dept == null ? null : dept.getName());
        resp.setPostName(post == null ? null : post.getName());
        // å²—位多选:解析 postIds JSON,拼接岗位名称
        if (StrUtil.isNotBlank(employee.getPostIds())) {
            List<Long> postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
            if (CollUtil.isNotEmpty(postIds)) {
                Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
                resp.setPostName(postIds.stream()
                        .filter(postMap::containsKey)
                        .map(id -> postMap.get(id).getName())
                        .collect(Collectors.joining(",")));
            }
        }
    }
}
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -33,6 +33,7 @@
import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -91,7 +92,7 @@
        // 1. æ ¡éªŒéƒ¨é—¨
        deptApi.validateDeptList(Collections.singleton(createReqVO.getDeptId()));
        // 2. æ ¡éªŒå²—位
        postApi.validPostList(Collections.singleton(createReqVO.getPostId()));
        postApi.validPostList(createReqVO.getPostIds());
        // 3. æ ¡éªŒæ‰‹æœºå·æ˜¯å¦å·²å­˜åœ¨
        if (employeeMapper.selectByPhone(createReqVO.getPhone()) != null) {
            throw exception(EMPLOYEE_PHONE_EXISTS);
@@ -101,7 +102,7 @@
        userCreateReqDTO.setUsername(createReqVO.getPhone());
        userCreateReqDTO.setNickname(createReqVO.getName());
        userCreateReqDTO.setDeptId(createReqVO.getDeptId());
        userCreateReqDTO.setPostIds(Collections.singleton(createReqVO.getPostId()));
        userCreateReqDTO.setPostIds(new LinkedHashSet<>(createReqVO.getPostIds()));
        userCreateReqDTO.setMobile(createReqVO.getPhone());
        userCreateReqDTO.setPassword(createReqVO.getPhone());
        userCreateReqDTO.setSex(createReqVO.getGender());
@@ -121,6 +122,10 @@
        // roleIds å­˜å‚¨ä¸º JSON
        if (CollUtil.isNotEmpty(createReqVO.getRoleIds())) {
            employee.setRoleIds(JSONUtil.toJsonStr(createReqVO.getRoleIds()));
        }
        // postIds å­˜å‚¨ä¸º JSON
        if (CollUtil.isNotEmpty(createReqVO.getPostIds())) {
            employee.setPostIds(JSONUtil.toJsonStr(createReqVO.getPostIds()));
        }
        employeeMapper.insert(employee);
        Long employeeId = employee.getId();
@@ -167,7 +172,7 @@
        // æ ¡éªŒéƒ¨é—¨
        deptApi.validateDeptList(Collections.singleton(updateReqVO.getDeptId()));
        // æ ¡éªŒå²—位
        postApi.validPostList(Collections.singleton(updateReqVO.getPostId()));
        postApi.validPostList(updateReqVO.getPostIds());
        // æ ¡éªŒæ‰‹æœºå·æ˜¯å¦å·²å­˜åœ¨ï¼ˆå¦‚果手机号有变更)
        if (!Objects.equals(employee.getPhone(), updateReqVO.getPhone())
                && employeeMapper.selectByPhone(updateReqVO.getPhone()) != null) {
@@ -179,7 +184,24 @@
        if (CollUtil.isNotEmpty(updateReqVO.getRoleIds())) {
            updateObj.setRoleIds(JSONUtil.toJsonStr(updateReqVO.getRoleIds()));
        }
        // postIds å­˜å‚¨ä¸º JSON
        if (CollUtil.isNotEmpty(updateReqVO.getPostIds())) {
            updateObj.setPostIds(JSONUtil.toJsonStr(updateReqVO.getPostIds()));
        }
        employeeMapper.updateById(updateObj);
        // åŒæ­¥æ›´æ–°å…³è”系统用户(岗位/部门/昵称/角色/邮箱/备注),保持与用户管理一致
        if (employee.getUserId() != null) {
            AdminUserUpdateReqDTO userUpdateReqDTO = new AdminUserUpdateReqDTO();
            userUpdateReqDTO.setId(employee.getUserId());
            userUpdateReqDTO.setNickname(updateReqVO.getName());
            userUpdateReqDTO.setDeptId(updateReqVO.getDeptId());
            userUpdateReqDTO.setPostIds(new LinkedHashSet<>(updateReqVO.getPostIds()));
            userUpdateReqDTO.setRoleIds(CollUtil.isEmpty(updateReqVO.getRoleIds()) ? null
                    : new LinkedHashSet<>(updateReqVO.getRoleIds()));
            userUpdateReqDTO.setEmail(updateReqVO.getEmail());
            userUpdateReqDTO.setRemark(updateReqVO.getRemark());
            adminUserApi.updateUser(userUpdateReqDTO);
        }
        Long employeeId = updateReqVO.getId();
        // åŒæ­¥æ•™è‚²ç»åކ
@@ -270,12 +292,16 @@
                respVO.setDeptName(dept.getName());
            }
        }
        // å¡«å……岗位名称
        if (employee.getPostId() != null) {
            Map<Long, PostRespDTO> postMap = postApi.getPostMap(Collections.singleton(employee.getPostId()));
            PostRespDTO post = postMap.get(employee.getPostId());
            if (post != null) {
                respVO.setPostName(post.getName());
        // å¡«å……岗位信息
        if (StrUtil.isNotBlank(employee.getPostIds())) {
            List<Long> postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
            respVO.setPostIds(postIds);
            if (CollUtil.isNotEmpty(postIds)) {
                Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
                respVO.setPostNames(postIds.stream()
                        .filter(postMap::containsKey)
                        .map(postId -> postMap.get(postId).getName())
                        .collect(Collectors.joining(",")));
            }
        }
        // å¡«å……角色名称
@@ -370,10 +396,10 @@
            try {
                // 3.1 åç§°è½¬ ID
                Long deptId = parseDeptId(importEmployee.getDeptName(), deptMap);
                Long postId = parsePostId(importEmployee.getPostName(), postMap);
                Set<Long> postIds = parsePostIds(importEmployee.getPostName(), postMap);
                Set<Long> roleIds = parseRoleIds(importEmployee.getRoleNames(), roleMap);
                // 3.2 æž„建保存请求
                HrmEmployeeSaveReqVO saveReqVO = buildImportSaveReqVO(importEmployee, deptId, postId, roleIds);
                HrmEmployeeSaveReqVO saveReqVO = buildImportSaveReqVO(importEmployee, deptId, postIds, roleIds);
                // 3.3 åˆ¤æ–­å­˜åœ¨æ€§ï¼šæŒ‰æ‰‹æœºå·
                HrmEmployeeDO existEmployee = employeeMapper.selectByPhone(importEmployee.getPhone());
                if (existEmployee == null) {
@@ -398,7 +424,7 @@
    }
    private HrmEmployeeSaveReqVO buildImportSaveReqVO(HrmEmployeeImportExcelVO importEmployee,
                                                      Long deptId, Long postId, Set<Long> roleIds) {
                                                      Long deptId, Set<Long> postIds, Set<Long> roleIds) {
        if (StrUtil.isBlank(importEmployee.getName())) {
            throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "员工姓名");
        }
@@ -413,7 +439,7 @@
        reqVO.setGender(importEmployee.getGender() != null ? importEmployee.getGender() : 1); // é»˜è®¤ç”·
        reqVO.setPhone(importEmployee.getPhone());
        reqVO.setDeptId(deptId);
        reqVO.setPostId(postId);
        reqVO.setPostIds(CollUtil.isEmpty(postIds) ? null : new ArrayList<>(postIds));
        reqVO.setRoleIds(CollUtil.isEmpty(roleIds) ? null : new ArrayList<>(roleIds));
        reqVO.setHireDate(importEmployee.getHireDate());
        reqVO.setEmployeeStatus(importEmployee.getEmployeeStatus() != null
@@ -441,15 +467,26 @@
        return dept.getId();
    }
    private Long parsePostId(String postName, Map<String, PostRespDTO> postMap) {
        if (StrUtil.isBlank(postName)) {
    private Set<Long> parsePostIds(String postNames, Map<String, PostRespDTO> postMap) {
        if (StrUtil.isBlank(postNames)) {
            throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "岗位名称");
        }
        PostRespDTO post = postMap.get(postName.trim());
        if (post == null) {
            throw exception(EMPLOYEE_POST_NAME_NOT_EXISTS, postName.trim());
        Set<Long> postIds = new LinkedHashSet<>();
        for (String postName : postNames.split("[,,]")) {
            String trimmed = postName.trim();
            if (StrUtil.isBlank(trimmed)) {
                continue;
        }
        return post.getId();
            PostRespDTO post = postMap.get(trimmed);
            if (post == null) {
                throw exception(EMPLOYEE_POST_NAME_NOT_EXISTS, trimmed);
            }
            postIds.add(post.getId());
        }
        if (CollUtil.isEmpty(postIds)) {
            throw exception(EMPLOYEE_IMPORT_FIELD_REQUIRED, "岗位名称");
        }
        return postIds;
    }
    private Set<Long> parseRoleIds(String roleNames, Map<String, RoleRespDTO> roleMap) {
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java
@@ -3,6 +3,7 @@
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO;
import java.util.Collection;
import java.util.Collections;
@@ -25,6 +26,13 @@
    Long createUser(AdminUserCreateReqDTO createReqDTO);
    /**
     * æ›´æ–°ç”¨æˆ·ï¼ˆä¾›å…¶ä»–模块同步更新系统用户部分信息,如岗位/部门/昵称等)
     *
     * @param updateReqDTO ç”¨æˆ·æ›´æ–°ä¿¡æ¯
     */
    void updateUser(AdminUserUpdateReqDTO updateReqDTO);
    /**
     * é€šè¿‡ç”¨æˆ· ID æŸ¥è¯¢ç”¨æˆ·
     *
     * @param id ç”¨æˆ·ID
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
@@ -53,6 +54,19 @@
    }
    @Override
    public void updateUser(AdminUserUpdateReqDTO updateReqDTO) {
        UserSaveReqVO updateReqVO = new UserSaveReqVO();
        updateReqVO.setId(updateReqDTO.getId());
        updateReqVO.setNickname(updateReqDTO.getNickname());
        updateReqVO.setDeptId(updateReqDTO.getDeptId());
        updateReqVO.setPostIds(updateReqDTO.getPostIds());
        updateReqVO.setRoleIds(updateReqDTO.getRoleIds());
        updateReqVO.setEmail(updateReqDTO.getEmail());
        updateReqVO.setRemark(updateReqDTO.getRemark());
        userService.updateUser(updateReqVO);
    }
    @Override
    @DataPermission(enable = false) // å¿½ç•¥æ•°æ®æƒé™ï¼Œé¿å…å› ä¸ºè¿‡æ»¤ï¼Œå¯¼è‡´æ— æ³•查询用户。类似:https://github.com/YunaiV/ruoyi-vue-pro/issues/1051
    public AdminUserRespDTO getUser(Long id) {
        AdminUserDO user = userService.getUser(id);
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserUpdateReqDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.system.api.user.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Set;
/**
 * Admin ç”¨æˆ·æ›´æ–° Request DTO
 *
 * ä¾›å…¶ä»–模块同步更新系统用户的部分信息(如 HRM å‘˜å·¥ç¼–辑时同步岗位/部门等)
 */
@Schema(description = "管理后台 - ç”¨æˆ·æ›´æ–° Request DTO")
@Data
public class AdminUserUpdateReqDTO {
    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "用户编号不能为空")
    private Long id;
    @Schema(description = "用户昵称", example = "张三")
    private String nickname;
    @Schema(description = "部门编号", example = "1")
    private Long deptId;
    @Schema(description = "岗位编号数组", example = "[1, 2]")
    private Set<Long> postIds;
    @Schema(description = "角色编号数组", example = "[1, 2]")
    private Set<Long> roleIds;
    @Schema(description = "用户邮箱", example = "zhangsan@example.com")
    private String email;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
}