# 员工柜员号优化设计文档

**文档版本**: v1.0
**创建日期**: 2026-02-05
**设计目标**: 统一标识符,移除tellerNo字段,将employeeId设置为柜员号

---

## 一、需求概述

### 1.1 需求背景

当前员工信息表中存在两个字段用于标识员工:

- `employee_id`: 数据库主键,自增ID
- `teller_no`: 柜员号,业务标识符

这种双标识符设计造成了字段冗余和业务混淆。

### 1.2 需求目标

- **移除 `teller_no` 字段**,简化数据结构
- **将 `employee_id` 改为手动输入的柜员号**(7位数字)
- **统一标识符**,避免业务混淆
- **保持数据完整性和业务连续性**

### 1.3 约束条件

- 系统处于开发阶段,无正式生产数据
- 柜员号必须为7位数字
- 柜员号必须唯一,不允许重复
- 柜员号为必填字段

---

## 二、数据库层设计

### 2.1 表结构修改

#### 删除字段

```sql
ALTER TABLE ccdi_employee DROP COLUMN teller_no;
```

#### 修改主键字段

```sql
-- 移除自增属性
ALTER TABLE ccdi_employee MODIFY employee_id BIGINT(20) NOT NULL;

-- 更新字段注释
ALTER TABLE ccdi_employee MODIFY COLUMN employee_id BIGINT(20) NOT NULL COMMENT '员工ID(柜员号,7位数字)';
```

#### 重建表方案(推荐,清空数据场景)

```sql
DROP TABLE IF EXISTS ccdi_employee;

CREATE TABLE ccdi_employee (
  employee_id BIGINT(20) NOT NULL COMMENT '员工ID(柜员号,7位数字)',
  name VARCHAR(100) NOT NULL COMMENT '姓名',
  dept_id BIGINT(20) DEFAULT NULL COMMENT '所属部门ID',
  id_card VARCHAR(18) NOT NULL COMMENT '身份证号',
  phone VARCHAR(11) DEFAULT NULL COMMENT '电话',
  hire_date DATE DEFAULT NULL COMMENT '入职时间',
  status CHAR(1) NOT NULL DEFAULT '0' COMMENT '状态(0在职 1离职)',
  create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
  create_time DATETIME DEFAULT NULL COMMENT '创建时间',
  update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
  update_time DATETIME DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (employee_id),
  KEY idx_dept_id (dept_id),
  KEY idx_status (status),
  UNIQUE KEY uk_id_card (id_card)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';
```

### 2.2 索引调整

- 移除: `UNIQUE KEY teller_no`
- 保留: `PRIMARY KEY (employee_id)` 天然保证唯一性

---

## 三、后端代码层设计

### 3.1 Entity 实体类 (CcdiEmployee.java)

**修改前**:

```java
@TableId(type = IdType.AUTO)
private Long employeeId;

private String tellerNo;
```

**修改后**:

```java
@TableId(type = IdType.INPUT)  // 改为手动输入
private Long employeeId;

// 删除 tellerNo 字段
```

### 3.2 DTO 类修改

#### CcdiEmployeeAddDTO.java

```java
/** 员工ID(柜员号) */
@NotNull(message = "柜员号不能为空")
@Min(value = 1000000L, message = "柜员号必须为7位数字")
@Max(value = 9999999L, message = "柜员号必须为7位数字")
private Long employeeId;

// 删除 tellerNo 字段
```

#### CcdiEmployeeEditDTO.java

```java
// employeeId 作为主键标识,通过路径参数传递,不在请求体中

// 删除 tellerNo 字段
```

#### CcdiEmployeeQueryDTO.java

```java
/** 柜员号(精确查询) */
@Min(value = 1000000L, message = "柜员号必须为7位数字")
@Max(value = 9999999L, message = "柜员号必须为7位数字")
private Long employeeId;

// 删除 tellerNo 字段
```

### 3.3 VO 类修改 (CcdiEmployeeVO.java)

```java
/** 员工ID(柜员号) */
private Long employeeId;

// 删除 tellerNo 字段
```

### 3.4 Service 层修改

#### 新增柜员号唯一性校验

```java
@Override
public void checkEmployeeIdUnique(Long employeeId) {
    CcdiEmployee existing = baseMapper.selectById(employeeId);
    if (existing != null) {
        throw new ServiceException("柜员号已存在,请使用其他柜员号");
    }
}
```

#### 新增员工方法调整

```java
@Override
public void addEmployee(CcdiEmployeeAddDTO dto) {
    // 1. 校验柜员号唯一性
    checkEmployeeIdUnique(dto.getEmployeeId());

    // 2. 校验身份证号唯一性
    checkIdCardUnique(dto.getIdCard());

    // 3. 转换并保存
    CcdiEmployee employee = BeanUtil.copyProperties(dto, CcdiEmployee.class);
    baseMapper.insert(employee);
}
```

### 3.5 Mapper XML 修改

#### ResultMap 调整

```xml
<resultMap type="com.ruoyi.ccdi.domain.vo.CcdiEmployeeVO" id="CcdiEmployeeVOResult">
    <id property="employeeId" column="employee_id"/>
    <result property="name" column="name"/>
    <!-- 删除 tellerNo 映射 -->
    <result property="deptId" column="dept_id"/>
    <result property="deptName" column="dept_name"/>
    <result property="idCard" column="id_card"/>
    <result property="phone" column="phone"/>
    <result property="hireDate" column="hire_date"/>
    <result property="status" column="status"/>
    <result property="createTime" column="create_time"/>
</resultMap>
```

#### 查询 SQL 调整

```xml
<select id="selectEmployeePageWithDept" resultMap="CcdiEmployeeVOResult">
    SELECT
        e.employee_id, e.name, e.dept_id, e.id_card, e.phone,
        e.hire_date, e.status, e.create_time,
        d.dept_name
    FROM ccdi_employee e
    LEFT JOIN sys_dept d ON e.dept_id = d.dept_id
    <where>
        <if test="query.name != null and query.name != ''">
            AND e.name LIKE CONCAT('%', #{query.name}, '%')
        </if>
        <if test="query.employeeId != null">
            AND e.employee_id = #{query.employeeId}
        </if>
        <!-- 删除 teller_no 查询条件 -->
        <if test="query.deptId != null">
            AND e.dept_id = #{query.deptId}
        </if>
        <if test="query.idCard != null and query.idCard != ''">
            AND e.id_card LIKE CONCAT('%', #{query.idCard}, '%')
        </if>
        <if test="query.status != null and query.status != ''">
            AND e.status = #{query.status}
        </if>
    </where>
    ORDER BY e.create_time DESC
</select>
```

### 3.6 Controller 层修改

#### 接口参数调整

- **POST /ccdi/employee**: 新增接口,接收 `employeeId` 作为必填字段
- **PUT /ccdi/employee/{employeeId}**: 编辑接口,`employeeId` 作为路径参数不可修改
- **GET /ccdi/employee/list**: 列表查询,移除 `tellerNo` 查询参数,保留 `employeeId` 精确查询

#### Swagger 注释更新

```java
@Operation(summary = "新增员工信息", description = "employeeId为柜员号,7位数字")
```

---

## 四、前端代码层设计

### 4.1 查询表单调整

```vue
<!-- 删除原来的 tellerNo 查询条件 -->

<!-- 新增:员工ID(柜员号)查询 -->
<el-form-item label="柜员号" prop="employeeId">
  <el-input
    v-model="queryParams.employeeId"
    placeholder="请输入7位柜员号"
    clearable
    maxlength="7"
    oninput="value=value.replace(/[^\d]/g,'')"
    style="width: 240px"
    @keyup.enter.native="handleQuery"
  />
</el-form-item>
```

### 4.2 表格列调整

```vue
<!-- 删除 -->
<!-- <el-table-column label="柜员号" prop="tellerNo" /> -->

<!-- 新增 -->
<el-table-column label="柜员号" align="center" prop="employeeId" :show-overflow-tooltip="true"/>
```

### 4.3 新增/编辑对话框调整

```vue
<!-- 新增模式:可输入 -->
<el-form-item label="柜员号" prop="employeeId" v-if="!isEdit">
  <el-input
    v-model="form.employeeId"
    placeholder="请输入7位柜员号"
    clearable
    maxlength="7"
    oninput="value=value.replace(/[^\d]/g,'')"
    style="width: 240px"
  />
</el-form-item>

<!-- 编辑模式:只读 -->
<el-form-item label="柜员号" prop="employeeId" v-if="isEdit">
  <el-input v-model="form.employeeId" disabled style="width: 240px"/>
</el-form-item>
```

### 4.4 JavaScript 数据结构

```javascript
data() {
  return {
    queryParams: {
      name: null,
      employeeId: null,  // 替代 tellerNo
      deptId: null,
      idCard: null,
      status: null
    },
    form: {
      employeeId: null,  // 替代 tellerNo
      name: null,
      deptId: null,
      // ...
    }
  }
}
```

### 4.5 表单校验规则

```javascript
rules: {
  employeeId: [
    { required: true, message: "柜员号不能为空", trigger: "blur" },
    { pattern: /^\d{7}$/, message: "柜员号必须为7位数字", trigger: "blur" }
  ],
  // 其他规则...
}
```

---

## 五、测试方案

### 5.1 新增员工测试

| 测试场景      | 输入数据          | 预期结果           |
|-----------|---------------|----------------|
| 正常场景      | 柜员号: 1000000  | 新增成功           |
| 格式错误-少于7位 | 柜员号: 123456   | 提示"柜员号必须为7位数字" |
| 格式错误-多于7位 | 柜员号: 12345678 | 提示"柜员号必须为7位数字" |
| 格式错误-非数字  | 柜员号: 123456a  | 提示"柜员号必须为7位数字" |
| 唯一性冲突     | 重复的柜员号        | 提示"柜员号已存在"     |
| 必填校验      | 柜员号为空         | 提示"柜员号不能为空"    |

### 5.2 编辑员工测试

| 测试场景 | 操作            | 预期结果       |
|------|---------------|------------|
| 正常编辑 | 修改其他字段,柜员号不可变 | 编辑成功,柜员号不变 |
| 只读验证 | 尝试修改柜员号       | 柜员号输入框禁用   |

### 5.3 查询测试

| 测试场景 | 输入      | 预期结果              |
|------|---------|-------------------|
| 精确查询 | 输入7位柜员号 | 返回匹配的员工记录         |
| 列表显示 | 查看列表    | 显示employeeId作为柜员号 |

---

## 六、文档更新清单

### 6.1 API 文档更新

- **文件路径**: `doc/api/员工信息管理API文档.md`
- **更新内容**:
    1. 新增接口:移除 `tellerNo`,新增 `employeeId` 参数说明
    2. 编辑接口:更新路径参数为 `employeeId`
    3. 查询接口:移除 `tellerNo` 查询参数,新增 `employeeId`
    4. 返回数据:移除 `tellerNo` 字段
    5. 字段说明表:更新 `employeeId` 为"员工ID(柜员号,7位数字)"

### 6.2 测试脚本

- **文件路径**: `doc/test/2026-02-05-employee-modify-test.sh`
- **测试账号**: username: admin, password: admin123
- **测试接口**: `/login/test` 获取 token

### 6.3 数据库脚本

- **文件路径**: `sql/modify_employee_id_to_teller_no.sql`
- **执行顺序**:
    1. 删除 `teller_no` 字段
    2. 修改 `employee_id` 为非自增
    3. 更新字段注释

---

## 七、实施步骤

### 7.1 数据库修改

1. 备份现有数据库(如有数据)
2. 执行 SQL 脚本修改表结构
3. 验证表结构修改成功

### 7.2 后端代码修改

1. 修改 Entity 实体类
2. 修改 DTO 类(Add/Edit/Query)
3. 修改 VO 类
4. 修改 Service 层,添加唯一性校验
5. 修改 Mapper XML
6. 修改 Controller 层
7. 编译后端项目,确保无错误

### 7.3 前端代码修改

1. 修改查询表单
2. 修改表格列
3. 修改新增/编辑对话框
4. 修改 JavaScript 数据结构和方法
5. 添加表单校验规则
6. 编译前端项目,确保无错误

### 7.4 测试验证

1. 执行测试脚本
2. 验证新增功能
3. 验证编辑功能
4. 验证查询功能
5. 验证唯一性校验
6. 验证格式校验
7. 生成测试报告

### 7.5 文档更新

1. 更新 API 文档
2. 更新测试报告
3. 提交代码到版本控制

---

## 八、风险评估与应对

### 8.1 风险点

1. **数据迁移风险**: 如果有正式数据,需要迁移方案
    - **应对**: 当前为开发阶段,无正式数据,直接修改

2. **接口兼容性**: 前端调用可能受影响
    - **应对**: 同步修改前端代码和接口调用

3. **业务逻辑依赖**: 其他模块可能引用 `tellerNo`
    - **应对**: 全局搜索 `tellerNo` 引用,同步修改

### 8.2 回滚方案

如果修改后出现问题,可以:

1. 恢复数据库表结构(添加回 `teller_no` 字段)
2. 恢复代码到修改前的版本
3. 恢复前端代码到修改前的版本

---

## 九、验收标准

### 9.1 功能验收

- ✅ 数据库 `teller_no` 字段已删除
- ✅ `employee_id` 改为非自增,手动输入
- ✅ 后端代码所有 `tellerNo` 引用已移除
- ✅ 前端页面显示 `employeeId` 作为柜员号
- ✅ 新增员工时必须输入7位数字柜员号
- ✅ 柜员号唯一性校验生效
- ✅ 柜员号格式校验生效
- ✅ 编辑时柜员号不可修改

### 9.2 性能验收

- ✅ 接口响应时间无明显变化
- ✅ 数据库查询效率正常

### 9.3 文档验收

- ✅ API 文档已更新
- ✅ 测试脚本已生成
- ✅ 测试报告已生成

---

**文档结束**
