Files
2026-09-17 12:16:20 +08:00

178 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## ADDED Requirements
### Requirement: Phone-Asset Association List Page
The admin frontend SHALL provide a phone-asset association list page accessible only to super admin and platform accounts, listing phoneasset relationships created via H5 SMS verification.
#### Scenario: Query association list
- **GIVEN** 用户打开「手机号资产关联」列表页
- **WHEN** 用户提交分页、资产标识、手机号、状态或创建时间范围筛选条件
- **THEN** 系统 MUST call `GET /api/admin/phone-asset-associations`
- **AND** 系统 MUST support query parameters `page`, `page_size`, `asset_identifier`, `phone`, `status`, `created_at_start`, and `created_at_end`
- **AND** 系统 MUST parse `data.page`, `data.size`, `data.total`, and `data.items` from the response
- **AND** 资产标识 MUST 支持 ICCID、虚拟号、IMEI、SN 或接入号精确匹配;手机号 MUST 使用完整值精确匹配
#### Scenario: Render association table columns
- **GIVEN** 关联列表接口返回 `items`
- **WHEN** 表格渲染每一行关联
- **THEN** 系统 MUST 展示资产类型/ID/标识(`asset_type``asset_id``asset_identifier`)、完整手机号(`phone`)、建立时间(`established_at`)、建立来源(`source`,中文映射固定为 H5 短信验证)与状态
- **AND** 系统 MUST 用 `status_name` 展示状态中文名称
- **AND** 对已失效关系(`status=0`MUST 展示失效时间、失效方式(`invalidation_method_name`)与失效原因(`invalidation_reason`,可为空)
#### Scenario: Vehicle the 403 semantics
- **GIVEN** 非超管/平台账号访问关联列表或任何解绑接口
- **WHEN** 后端返回 403
- **THEN** 前端 MUST 直接展示后端文案「无权限操作该资源或资源不存在」
- **AND** 前端 MUST NOT 区分越权、资产不存在与已无有效关系(后端明确不形成可枚举差异)
### Requirement: Single Association Unbind
The admin frontend SHALL support unbinding a single phoneasset association with a mandatory reason and confirmation.
#### Scenario: Unbind single association with reason and confirmation
- **GIVEN** 用户在关联列表选择一条有效关联执行解绑
- **WHEN** 用户填写解除原因1500 字符)并勾选二次确认后提交
- **THEN** 系统 MUST call `DELETE /api/admin/phone-asset-associations/{id}` with the association id in the path
- **AND** 请求 MUST 携带 `reason``confirmed=true`,二者缺一即不发请求(前端表单校验兜底,后端兜底拒绝)
- **AND** 系统 MUST 展示响应中的解除结果,包括失效时间与本次解除的有效关系数(`unbound_count`
#### Scenario: Refuse without reason or confirmation
- **GIVEN** 用户未填写原因或未勾选二次确认
- **WHEN** 用户点击确认解绑
- **THEN** 前端 MUST 阻止提交并提示原因必填/需二次确认
- **AND** 没有任何关系被解除的承诺 MUST 与后端说明一致,前端不调用接口
### Requirement: Batch Asset Unbind
The admin frontend SHALL support unbinding all current valid associations of multiple assets in one request with per-item results.
#### Scenario: Batch unbind selected assets
- **GIVEN** 用户勾选多个资产(列表或他处选择的资产集合)执行批量解绑
- **WHEN** 用户填写解除原因并二次确认后提交
- **THEN** 系统 MUST call `POST /api/admin/phone-asset-associations/batch-unbind`
- **AND** 请求体 MUST 包含 `assets`(每项 `asset_type`+`asset_id`)、`confirmed=true``reason`
- **AND** 请求前前端 MUST 按 `(asset_type, asset_id)` 去重并限制数量(后端上限 200前端同步校验
- **AND** 系统 MUST 解析 `success_count``fail_count` 与逐项结果 `items`(每项含 `asset_id``asset_type``success`、失败 `reason``unbound_count`
- **AND** 部分成功 MUST 不回滚成功项,前端 MUST 按逐项结果展示成功/失败明细
#### Scenario: Show failure copy for failed items
- **GIVEN** 批量解绑存在失败项(越权、资产不存在或已无有效关系)
- **WHEN** 逐项结果返回失败
- **THEN** 前端 MUST 直接展示后端返回的失败文案
- **AND** 失败项 MUST 可被定位到对应资产标识与类型
### Requirement: CSV Unbind Import Task
The admin frontend SHALL support creating, listing, and viewing phone-asset unbind import tasks driven by an uploaded CSV.
#### Scenario: Provide CSV template download
- **GIVEN** 用户进入「CSV 解绑导入」入口
- **WHEN** 用户需要模板
- **THEN** 系统 MUST 提供可下载的 CSV 模板,表头固定为 `资产标识,备注`,备注列为可选
- **AND** 模板 MUST 以 UTF-8 编码(可带 BOM
#### Scenario: Upload CSV and create unbind import task
- **GIVEN** 用户选择 CSV 文件UTF-8 可带 BOM非 UTF-8 按 GBK 解码;无行数上限)
- **WHEN** 用户填写任务级解绑原因并二次确认后创建任务
- **THEN** 系统 MUST 先调用 `POST /api/admin/storage/upload-url``purpose=phone_unbind_import`,仅接受 `.csv`
- **AND** 系统 MUST 使用预签名 URL 完成 PUT 直传,取回 `file_key`
- **AND** 系统 MUST call `POST /api/admin/phone-asset-associations/unbind-imports`,请求体包含 `file_key`、任务级 `reason``confirmed=true`
- **AND** 系统 MUST 展示创建返回的任务编号、状态1 待处理 / 2 处理中 / 3 已完成 / 4 失败)与中文状态名
#### Scenario: List unbind import tasks
- **GIVEN** 用户打开「解绑导入任务」列表
- **WHEN** 用户按分页/状态等条件查询
- **THEN** 系统 MUST call `GET /api/admin/phone-asset-associations/unbind-imports`
- **AND** 列表 MUST 展示任务编号、文件名、状态、成功/失败行数、总数、创建人、创建/开始/完成时间与任务级解绑原因
#### Scenario: View unbind import task detail with row results
- **GIVEN** 用户在任务列表点击某个任务
- **WHEN** 任务详情加载
- **THEN** 系统 MUST call `GET /api/admin/phone-asset-associations/unbind-imports/{id}`
- **AND** 详情 MUST 展示任务级信息(含 `unbind_reason``error_message` 与任务统计)
- **AND** 逐行结果 MUST 展示行号(`line`,自数据首行起计)、`asset_identifier` 原文、定位到的资产类型/ID未定位时为空/0、行状态3 成功 / 4 失败)与失败原因
- **AND** 成功行 MUST 展示解绑当时完整关联手机号快照(`associated_phones`)与该行解除的有效关系数(`unbound_count`
- **AND** 任务级失败时 `items` 为空数组,前端 MUST 展示任务的 `error_message`
### Requirement: Associated Phones Display on Existing Lists
The admin frontend SHALL display the current associated phone numbers of an asset on existing device, standalone IoT card, and asset resolve surfaces.
#### Scenario: Show associated phones on device list
- **GIVEN** `GET /api/admin/devices` 响应包含 `associated_phones`
- **WHEN** 设备列表渲染
- **THEN** 系统 MUST 展示「关联手机号」列,值为完整手机号数组(无关联显示为空/「-」)
#### Scenario: Show associated phones on standalone IoT card list
- **GIVEN** `GET /api/admin/iot-cards/standalone` 响应包含 `associated_phones`
- **WHEN** 单卡列表渲染
- **THEN** 系统 MUST 展示「关联手机号」列,多号以「、」分隔
#### Scenario: Show associated phones on asset resolve detail
- **GIVEN** `GET /api/admin/assets/resolve/{identifier}` 响应包含 `associated_phones`
- **WHEN** 资产详情渲染
- **THEN** 系统 MUST 展示「关联手机号」字段,无关联显示为空/「-」
### Requirement: Storage and Export Contract Extension
The admin frontend SHALL extend the upload-purpose enum and align with the export column change for card and device export files.
#### Scenario: Upload URL for phone unbind imports
- **GIVEN** 需要上传解绑导入 CSV
- **WHEN** 调用 `POST /api/admin/storage/upload-url`
- **THEN** 请求 MUST 使用 `purpose=phone_unbind_import`
- **AND** `FilePurpose` 类型 MUST 增加 `phone_unbind_import`
- **AND** 上传目录前缀为 `phone-unbind-imports/YYYY/MM/DD/uuid.csv`,前端不感知前缀,仅约束扩展名为 `.csv`
#### Scenario: Export files gain associated phone column
- **GIVEN** 卡导出/设备导出任务完成并生成文件
- **WHEN** 用户下载导出文件
- **THEN** 文件表头尾部 MUST 包含「关联手机号」列(多号以「、」连接)
- **AND** 前端导出任务列表/详情页 MUST NOT 为此改动调用任何新接口
- **AND** 历史任务重导出仍按旧表头输出,前端不需要兼容性处理
### Requirement: Role-Based Entry Visibility and No Creation Entry
The admin frontend SHALL gate all new phone-asset association pages and actions to super admin/platform accounts, and MUST NOT offer any create or supplement entry.
#### Scenario: Hide entries for non-platform roles
- **GIVEN** 当前用户为代理/企业/个人账号
- **WHEN** 渲染菜单与页面按钮
- **THEN** 导航菜单、解绑按钮与 CSV 导入入口 MUST 不渲染
- **AND** 若有越权直达路由,后端 403 文案 MUST 原样展示
#### Scenario: No create/supplement entry
- **GIVEN** 用户打开「手机号资产关联」相关页面
- **WHEN** 页面可用操作被枚举
- **THEN** 页面 MUST NOT 提供创建或补录关联的入口
- **AND** 关联只能由 H5 短信验证产生,前端默认不展示任何新增表单
### Requirement: Unify Backend-Returned Error Copy
The admin frontend SHALL render backend-provided fixed error copy verbatim without composing its own messages.
#### Scenario: Display fixed failure copy
- **GIVEN** 解绑相关接口返回失败
- **WHEN** 失败文案为约定固定文案之一
- **THEN** 前端 MUST 直接在弹窗/错误提示中展示「无权限操作该资源或资源不存在」
- **AND** 对后端可能返回的「该手机号最多关联10项有效资产」「新手机号已存在与待迁移资产相同的有效关联换绑已回滚」MUST 仅作透传展示,不在前端拼接或改写