feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-14 18:27:28 +08:00
parent b5147d1acb
commit b9c3875c08
77 changed files with 5832 additions and 2393 deletions

View File

@@ -192,93 +192,83 @@ TBD - created by archiving change iot-card-standalone-management. Update Purpose
### Requirement: Excel 文件格式规范
系统 SHALL 要求 Excel 文件必须包含 ICCID 和 MSISDN 两列。
系统 SHALL 要求 Excel 文件必须包含 ICCID 和 MSISDN 两列,并支持可选的 `virtual_no`
**文件格式要求**:
- **文件格式**: 仅支持 `.xlsx` (Excel 2007+)
- **Sheet**: 读取第一个sheet,或优先读取名为"导入数据"的sheet
- **表头行**: 第1行(可选,但建议包含)
- **表头识别关键字**:
- ICCID列: iccid/ICCID/卡号/号码
- MSISDN列: msisdn/MSISDN/接入号/手机号/电话/号码
- **列数要求**: 至少2列(ICCID和MSISDN)
- **列格式**: 应设置为文本格式(避免长数字被转为科学记数法)
- **Sheet**: 读取第一个sheet或优先读取名为"导入数据"的sheet
- **表头行**: 第1行可选但建议包含
- **表头识别关键字**:
- ICCID 列: iccid/ICCID/卡号/号码
- MSISDN 列: msisdn/MSISDN/接入号/手机号/电话/号码
- virtual_no 列(新增,可选): virtual_no/VirtualNo/虚拟号/设备号
- **列数要求**: 至少 2 列ICCID 和 MSISDNvirtual_no 为可选第三列
- **列格式**: 应设置为文本格式(避免长数字被转为科学记数法)
**解析规则**:
- 自动检测表头(第1行包含识别关键字则跳过)
- 自动检测表头第1行包含识别关键字则跳过
- 自动去除单元格首尾空格
- 跳过空行
- ICCID 为空的行记录为失败
- MSISDN 为空的行记录为失败
- virtual_no 为空的行:跳过该列(不填入,保留原值)
**示例Excel内容**:
**virtual_no 导入规则(只补空白)**:
- 该行 virtual_no 不为空 + 数据库当前值为 NULL填入新值
- 该行 virtual_no 不为空 + 数据库当前值已有值:跳过(不覆盖)
- **批次级唯一性检查**:在执行导入前,先检查整批中所有非空 virtual_no 是否与数据库现存值重复;有任意冲突则**整批失败**,响应中返回冲突的 virtual_no 及行号列表
**示例 Excel 内容**:
```
| ICCID | MSISDN |
|----------------------|-------------|
| 89860012345678901234 | 13800000001 |
| 89860012345678901235 | 13800000002 |
| ICCID | MSISDN | 虚拟号 |
|----------------------|-------------|-----------|
| 89860012345678901234 | 13800000001 | CARD-001 |
| 89860012345678901235 | 13800000002 | |
```
#### Scenario: 解析标准双列 Excel 文件
#### Scenario: 解析标准双列 Excel 文件(无 virtual_no 列)
- **GIVEN** Excel 文件内容为:
```
| ICCID | MSISDN |
| 89860012345678901234 | 13800000001 |
| 89860012345678901235 | 13800000002 |
```
- **WHEN** 系统解析该 Excel 文件
- **THEN** 解析结果包含 2 条有效记录,每条包含 ICCID 和 MSISDN
- **WHEN** Excel 文件只含 ICCID 和 MSISDN 两列,无虚拟号列
- **THEN** 解析结果包含 2 条有效记录virtual_no 字段为空,不影响导入逻辑
#### Scenario: 解析含 virtual_no 列的三列 Excel
- **WHEN** Excel 文件含 ICCID、MSISDN、虚拟号三列某行 virtual_no = "CARD-001",对应卡当前 virtual_no 为 NULL
- **THEN** 解析后该卡的 virtual_no 填入 "CARD-001"
#### Scenario: virtual_no 已有值时不覆盖
- **WHEN** Excel 中某行 virtual_no = "CARD-NEW",但该卡数据库中已有 virtual_no = "CARD-OLD"
- **THEN** 该卡的 virtual_no 保持 "CARD-OLD" 不变,该行跳过(不报错,不计入失败)
#### Scenario: 批次中有 virtual_no 与现存数据重复
- **WHEN** Excel 中某行 virtual_no = "CARD-001",但数据库中另一张卡已有 virtual_no = "CARD-001"
- **THEN** 系统拒绝整批导入,响应返回冲突的 virtual_no 值和行号,提示"虚拟号重复,整批导入已终止"
#### Scenario: 支持中文表头
- **GIVEN** Excel 文件内容为:
```
| 卡号 | 接入号 |
| 89860012345678901234 | 13800000001 |
```
- **GIVEN** Excel 文件表头为 `卡号 | 接入号 | 虚拟号`
- **WHEN** 系统解析该 Excel 文件
- **THEN** 系统正确识别列,解析结果包含 1 条有效记录
- **THEN** 系统正确识别三列,按规则处理 virtual_no
#### Scenario: 拒绝非Excel格式文件
- **GIVEN** 上传文件扩展名为 .csv
- **WHEN** 系统尝试解析该文件
- **THEN** 系统返回错误 "不支持的文件格式 .csv,请上传Excel文件(.xlsx)"
#### Scenario: Excel文件无工作表
- **GIVEN** Excel 文件不包含任何工作表
- **WHEN** 系统尝试解析该 Excel 文件
- **THEN** 系统返回错误 "Excel文件无工作表"
- **THEN** 系统返回错误"不支持的文件格式 .csv请上传Excel文件(.xlsx)"
#### Scenario: MSISDN 为空的行记录失败
- **GIVEN** Excel 文件内容为:
```
| ICCID | MSISDN |
| 89860012345678901234 | 13800000001 |
| 89860012345678901235 | |
```
- **GIVEN** Excel 文件第二行 MSISDN 为空
- **WHEN** 系统解析该 Excel 文件
- **THEN** 第一条记录解析成功,第二条记录标记为失败,原因为 "MSISDN 不能为空"
#### Scenario: ICCID 为空的行记录失败
- **GIVEN** Excel 文件内容为:
```
| ICCID | MSISDN |
| 89860012345678901234 | 13800000001 |
| | 13800000002 |
```
- **WHEN** 系统解析该 Excel 文件
- **THEN** 第一条记录解析成功,第二条记录标记为失败,原因为 "ICCID 不能为空"
- **THEN** 第一条记录解析成功第二条记录标记为失败原因为"MSISDN 不能为空"
#### Scenario: 长数字无损解析
- **GIVEN** Excel 文件中ICCID列设置为文本格式,包含20位数字 "89860012345678901234"
- **GIVEN** Excel 文件中 ICCID 列设置为文本格式包含 20 位数字 "89860012345678901234"
- **WHEN** 系统解析该 Excel 文件
- **THEN** ICCID 完整保留为 "89860012345678901234",无精度损失,无科学记数法
- **THEN** ICCID 完整保留为 "89860012345678901234"无精度损失无科学记数法
---