feat: 实现物联网卡独立管理和批量导入功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m42s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m42s
新增物联网卡独立管理模块,支持单卡查询、批量导入和状态管理。主要变更包括: 功能特性: - 新增物联网卡 CRUD 接口(查询、分页列表、删除) - 支持 CSV/Excel 批量导入物联网卡 - 实现异步导入任务处理和进度跟踪 - 新增 ICCID 号码格式校验器(支持 Luhn 算法) - 新增 CSV 文件解析工具(支持编码检测和错误处理) 数据库变更: - 移除 iot_card 和 device 表的 owner_id/owner_type 字段 - 新增 iot_card_import_task 导入任务表 - 为导入任务添加运营商类型字段 测试覆盖: - 新增 IoT 卡 Store 层单元测试 - 新增 IoT 卡导入任务单元测试 - 新增 IoT 卡集成测试(包含导入流程测试) - 新增 CSV 工具和 ICCID 校验器测试 文档更新: - 更新 OpenAPI 文档(新增 7 个 IoT 卡接口) - 归档 OpenSpec 变更提案 - 更新 API 文档规范和生成器指南 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-01-23
|
||||
@@ -0,0 +1,56 @@
|
||||
# Change: IoT 卡单卡管理与所有权模型重构
|
||||
|
||||
## Why
|
||||
|
||||
当前 IoT 卡和设备模型中使用 `owner_type` + `owner_id` 表示所有权,与数据权限使用的 `shop_id` 字段存在冗余,且语义不清晰:
|
||||
- 代理分销给下级时,所有权实际是转移到下级店铺(shop_id 变化)
|
||||
- 企业用户没有"所有权",是通过授权表(EnterpriseCardAuthorization)管理
|
||||
- 个人客户完全没有所有权概念,是基于 ICCID/设备号操作
|
||||
|
||||
同时,业务需要"单卡管理"功能:查看和导入未绑定设备的 IoT 卡,支持大批量 CSV 导入(几万条)并跟踪导入任务状态。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 模型重构(**BREAKING**)
|
||||
- **移除 IotCard 的 owner_type/owner_id 字段**:改用 shop_id 表示所有权(NULL=平台所有,有值=店铺所有)
|
||||
- **移除 Device 的 owner_type/owner_id 字段**:同上
|
||||
- 保留 AssetAllocationRecord 和 CardReplacementRecord 中的 Owner 字段(历史记录追溯用)
|
||||
|
||||
### 新增功能
|
||||
- **单卡列表 API**:查询未绑定设备的 IoT 卡,支持多维度筛选
|
||||
- **批量导入 ICCID API**:支持 CSV 文件上传,异步处理,支持几万条数据
|
||||
- **导入任务记录表**:跟踪导入进度、成功/跳过/失败统计及详情
|
||||
|
||||
### ICCID 校验规则调整
|
||||
- 电信:严格 19 位
|
||||
- 联通/移动/广电:严格 20 位
|
||||
- 支持字母数字混合(移动 ICCID 有字母)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `iot-card-import-task`: IoT 卡导入任务管理,包含导入任务模型、进度跟踪、结果详情记录
|
||||
|
||||
### Modified Capabilities
|
||||
- `iot-card`: 移除 owner_type/owner_id 字段,改用 shop_id;新增单卡列表查询;调整 ICCID 校验规则
|
||||
- `iot-device`: 移除 owner_type/owner_id 字段,改用 shop_id
|
||||
|
||||
## Impact
|
||||
|
||||
### 数据库变更
|
||||
- `tb_iot_card` 表:删除 owner_type、owner_id 列
|
||||
- `tb_device` 表:删除 owner_type、owner_id 列
|
||||
- 新增 `tb_iot_card_import_task` 表
|
||||
|
||||
### 代码影响
|
||||
- `internal/model/iot_card.go`:移除 OwnerType、OwnerID 字段
|
||||
- `internal/model/device.go`:移除 OwnerType、OwnerID 字段
|
||||
- `internal/model/iot_card_import_task.go`:新增
|
||||
- `openspec/specs/iot-card/spec.md`:修改所有权相关描述
|
||||
- `openspec/specs/iot-device/spec.md`:修改所有权相关描述
|
||||
|
||||
### API 影响
|
||||
- 新增:`GET /api/admin/iot-cards/standalone` - 单卡列表
|
||||
- 新增:`POST /api/admin/iot-cards/import` - 发起导入任务
|
||||
- 新增:`GET /api/admin/iot-cards/import-tasks` - 导入任务列表
|
||||
- 新增:`GET /api/admin/iot-cards/import-tasks/:id` - 导入任务详情
|
||||
@@ -0,0 +1,174 @@
|
||||
# IoT Card Import Task - Delta Spec
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 导入任务实体定义
|
||||
|
||||
系统 SHALL 定义 IoT 卡导入任务(IotCardImportTask)实体,用于跟踪 IoT 卡批量导入的进度和结果。
|
||||
|
||||
**实体字段**:
|
||||
|
||||
**任务信息**:
|
||||
- `id`: 任务 ID(主键,BIGINT)
|
||||
- `task_no`: 任务编号(VARCHAR(50),唯一,格式: IMP-YYYYMMDD-XXXXXX)
|
||||
- `status`: 任务状态(INT,1-待处理 2-处理中 3-已完成 4-失败)
|
||||
|
||||
**导入参数**:
|
||||
- `carrier_id`: 运营商 ID(BIGINT,必填)
|
||||
- `batch_no`: 批次号(VARCHAR(100),可选)
|
||||
- `file_name`: 原始文件名(VARCHAR(255),可选)
|
||||
|
||||
**进度统计**:
|
||||
- `total_count`: 总数(INT,CSV 文件总行数)
|
||||
- `success_count`: 成功数(INT,成功导入的 ICCID 数量)
|
||||
- `skip_count`: 跳过数(INT,因重复等原因跳过的数量)
|
||||
- `fail_count`: 失败数(INT,因格式错误等原因失败的数量)
|
||||
|
||||
**结果详情**:
|
||||
- `skipped_items`: 跳过记录详情(JSONB,结构: [{line, iccid, reason}])
|
||||
- `failed_items`: 失败记录详情(JSONB,结构: [{line, iccid, reason}])
|
||||
|
||||
**时间和错误**:
|
||||
- `started_at`: 开始处理时间(TIMESTAMP,可空)
|
||||
- `completed_at`: 完成时间(TIMESTAMP,可空)
|
||||
- `error_message`: 任务级错误信息(TEXT,可空,如文件解析失败等)
|
||||
|
||||
**系统字段**:
|
||||
- `shop_id`: 店铺 ID(BIGINT,可空,记录发起导入的店铺)
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
- `creator`: 创建人 ID(BIGINT)
|
||||
- `updater`: 更新人 ID(BIGINT)
|
||||
|
||||
#### Scenario: 创建导入任务
|
||||
|
||||
- **WHEN** 管理员上传 CSV 文件发起导入
|
||||
- **THEN** 系统创建导入任务记录,`status` 为 1(待处理),`total_count` 为 CSV 行数,返回任务 ID
|
||||
|
||||
#### Scenario: 导入任务开始处理
|
||||
|
||||
- **WHEN** Worker 开始处理导入任务
|
||||
- **THEN** 系统将任务 `status` 从 1(待处理) 变更为 2(处理中),`started_at` 记录当前时间
|
||||
|
||||
#### Scenario: 导入任务完成
|
||||
|
||||
- **WHEN** Worker 完成导入任务处理
|
||||
- **THEN** 系统将任务 `status` 变更为 3(已完成),`completed_at` 记录当前时间,更新 `success_count`、`skip_count`、`fail_count`
|
||||
|
||||
#### Scenario: 导入任务失败
|
||||
|
||||
- **WHEN** Worker 处理导入任务时发生严重错误(如文件损坏)
|
||||
- **THEN** 系统将任务 `status` 变更为 4(失败),`error_message` 记录错误信息
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务状态流转
|
||||
|
||||
系统 SHALL 管理导入任务的状态流转,确保状态变更符合业务规则。
|
||||
|
||||
**状态定义**:
|
||||
- **1-待处理**: 任务已创建,等待 Worker 处理
|
||||
- **2-处理中**: Worker 正在处理导入
|
||||
- **3-已完成**: 导入处理完成(可能有部分失败)
|
||||
- **4-失败**: 任务级别错误,导入中断
|
||||
|
||||
**状态流转规则**:
|
||||
- 待处理(1) → 处理中(2): Worker 开始处理
|
||||
- 处理中(2) → 已完成(3): 处理完成
|
||||
- 处理中(2) → 失败(4): 发生严重错误
|
||||
- 待处理(1) → 失败(4): 文件验证失败等
|
||||
|
||||
#### Scenario: 正常状态流转
|
||||
|
||||
- **WHEN** 导入任务经历完整生命周期
|
||||
- **THEN** 状态依次变更: 待处理(1) → 处理中(2) → 已完成(3)
|
||||
|
||||
#### Scenario: 异常状态流转
|
||||
|
||||
- **WHEN** 导入任务处理过程中发生严重错误
|
||||
- **THEN** 状态变更: 待处理(1) → 处理中(2) → 失败(4)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务列表查询
|
||||
|
||||
系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。
|
||||
|
||||
**查询条件**:
|
||||
- 任务状态(status): 可选,1-待处理 2-处理中 3-已完成 4-失败
|
||||
- 运营商 ID(carrier_id): 可选
|
||||
- 批次号(batch_no): 可选,模糊匹配
|
||||
- 创建时间范围: 可选
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 默认按创建时间倒序排列
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺发起的导入任务
|
||||
|
||||
#### Scenario: 查询所有导入任务
|
||||
|
||||
- **WHEN** 管理员查询导入任务列表
|
||||
- **THEN** 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间
|
||||
|
||||
#### Scenario: 按状态筛选导入任务
|
||||
|
||||
- **WHEN** 管理员查询状态为 2(处理中) 的导入任务
|
||||
- **THEN** 系统返回所有正在处理的导入任务列表
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务详情查询
|
||||
|
||||
系统 SHALL 支持查询单个导入任务的详细信息,包括跳过/失败记录详情。
|
||||
|
||||
**详情信息**:
|
||||
- 任务基本信息: 任务编号、状态、运营商、批次号、文件名
|
||||
- 进度统计: 总数、成功数、跳过数、失败数
|
||||
- 时间信息: 创建时间、开始时间、完成时间
|
||||
- 跳过记录详情: 行号、ICCID、原因
|
||||
- 失败记录详情: 行号、ICCID、原因
|
||||
- 错误信息: 任务级错误(如有)
|
||||
|
||||
#### Scenario: 查询导入任务详情
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的详情
|
||||
- **THEN** 系统返回任务完整信息,包括跳过和失败记录的详细列表
|
||||
|
||||
#### Scenario: 查询导入任务的跳过记录
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的跳过记录
|
||||
- **THEN** 系统返回跳过记录列表,每条包含: 行号(line)、ICCID、原因(如"ICCID 已存在")
|
||||
|
||||
#### Scenario: 查询导入任务的失败记录
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的失败记录
|
||||
- **THEN** 系统返回失败记录列表,每条包含: 行号(line)、ICCID、原因(如"电信 ICCID 必须为 19 位")
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务数据校验
|
||||
|
||||
系统 SHALL 对导入任务数据进行校验,确保数据完整性和一致性。
|
||||
|
||||
**校验规则**:
|
||||
- 任务编号(task_no): 必填,系统自动生成,格式 IMP-YYYYMMDD-XXXXXX,唯一
|
||||
- 任务状态(status): 必填,枚举值 1(待处理) | 2(处理中) | 3(已完成) | 4(失败)
|
||||
- 运营商 ID(carrier_id): 必填,必须是有效的运营商 ID
|
||||
- 总数(total_count): 必填,≥ 0
|
||||
- 成功数(success_count): 必填,≥ 0,≤ total_count
|
||||
- 跳过数(skip_count): 必填,≥ 0,≤ total_count
|
||||
- 失败数(fail_count): 必填,≥ 0,≤ total_count
|
||||
- 数量一致性: success_count + skip_count + fail_count ≤ total_count
|
||||
|
||||
#### Scenario: 创建任务时运营商 ID 无效
|
||||
|
||||
- **WHEN** 创建导入任务时 carrier_id 不存在
|
||||
- **THEN** 系统拒绝创建,返回错误信息"运营商 ID 无效"
|
||||
|
||||
#### Scenario: 更新任务时数量不一致
|
||||
|
||||
- **WHEN** 更新导入任务时 success_count + skip_count + fail_count > total_count
|
||||
- **THEN** 系统拒绝更新,返回错误信息"统计数量不一致"
|
||||
@@ -0,0 +1,246 @@
|
||||
# IoT Card Management - Delta Spec
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: IoT 卡实体定义
|
||||
|
||||
系统 SHALL 定义 IoT 卡(IotCard)实体,包含 IoT 卡(物联网卡/流量卡/SIM卡)的商品属性、状态属性、店铺归属信息和 Gateway 集成字段。
|
||||
|
||||
**核心概念**: IoT 卡 = 物联网卡 = SIM 卡 = 网卡 = 流量卡(同一个东西,不同叫法)。系统使用 ICCID 作为 IoT 卡的唯一标识。
|
||||
|
||||
**卡业务类型**:
|
||||
- **普通卡(normal)**: 需要实名认证才能激活使用,遵循运营商实名制要求
|
||||
- **行业卡(industry)**: 不需要实名认证,可以直接激活使用,适用于企业/行业客户批量采购场景
|
||||
|
||||
**实体字段**:
|
||||
|
||||
**商品属性**:
|
||||
- `id`: IoT 卡 ID(主键,BIGINT)
|
||||
- `iccid`: ICCID(VARCHAR(20),唯一,IoT卡的唯一标识,电信19位/联通移动广电20位,支持字母数字混合)
|
||||
- `card_type`: 卡类型(VARCHAR(50),如 "4G"、"5G"、"NB-IoT")
|
||||
- `card_category`: 卡业务类型(VARCHAR(20),枚举值:"normal"-普通卡 | "industry"-行业卡,默认 "normal")
|
||||
- `carrier_id`: 运营商 ID(BIGINT,关联 carriers 表,如中国移动、中国联通、中国电信、中国广电)
|
||||
- `imsi`: IMSI(VARCHAR(50),可选,国际移动用户识别码)
|
||||
- `msisdn`: 手机号码(VARCHAR(20),可选,即卡接入号)
|
||||
- `batch_no`: 批次号(VARCHAR(100),用于批量导入追溯)
|
||||
- `supplier`: 供应商名称(VARCHAR(255),可选)
|
||||
- `cost_price`: 成本价(BIGINT,分为单位,平台进货价)
|
||||
- `distribute_price`: 分销价(BIGINT,分为单位,分销给代理的价格)
|
||||
|
||||
**店铺归属和状态**:
|
||||
- `shop_id`: 店铺 ID(BIGINT,可空,NULL 表示平台所有,有值表示店铺所有)
|
||||
- `status`: IoT 卡状态(INT,1-在库 2-已分销 3-已激活 4-已停用)
|
||||
- `activated_at`: 激活时间(TIMESTAMP,可空)
|
||||
|
||||
**Gateway 集成字段**(从 Gateway 项目同步):
|
||||
- `activation_status`: 激活状态(INT,0-未激活 1-已激活)
|
||||
- `real_name_status`: 实名状态(INT,0-未实名 1-已实名)
|
||||
- `network_status`: 网络状态(INT,0-停机 1-开机)
|
||||
- `data_usage_mb`: 累计流量使用(BIGINT,MB 为单位,默认 0)
|
||||
- `last_sync_time`: 最后一次与 Gateway 同步时间(TIMESTAMP,可空)
|
||||
|
||||
**轮询控制字段**:
|
||||
- `enable_polling`: 是否参与轮询(BOOLEAN,默认 true,用于控制是否对该卡进行定时轮询)
|
||||
- `last_data_check_at`: 最后一次卡流量检查时间(TIMESTAMP,可空)
|
||||
- `last_real_name_check_at`: 最后一次实名检查时间(TIMESTAMP,可空)
|
||||
|
||||
**系统字段**:
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
- `creator`: 创建人 ID(BIGINT)
|
||||
- `updater`: 更新人 ID(BIGINT)
|
||||
|
||||
#### Scenario: 创建平台库存 IoT 卡
|
||||
|
||||
- **WHEN** 平台批量导入 IoT 卡数据,ICCID 为 "89860123456789012345"
|
||||
- **THEN** 系统创建 IoT 卡记录,`shop_id` 为 NULL(平台所有),状态为 1(在库),`activation_status` 为 0(未激活)
|
||||
|
||||
#### Scenario: 平台分销 IoT 卡给代理店铺
|
||||
|
||||
- **WHEN** 平台将在库 IoT 卡分销给代理店铺(店铺 ID 为 10),设置分销价为 5000 分
|
||||
- **THEN** 系统将 IoT 卡状态从 1(在库) 变更为 2(已分销),`shop_id` 设置为 10,`distribute_price` 设置为 5000
|
||||
|
||||
#### Scenario: 代理店铺分销 IoT 卡给下级店铺
|
||||
|
||||
- **WHEN** 代理店铺(ID 为 10)将已分销 IoT 卡分销给下级店铺(ID 为 20)
|
||||
- **THEN** 系统将 IoT 卡的 `shop_id` 从 10 变更为 20,状态保持 2(已分销)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: IoT 卡平台自营和代理分销
|
||||
|
||||
系统 SHALL 支持 IoT 卡的平台自营销售和代理分销两种模式,通过 `shop_id` 区分所有者。
|
||||
|
||||
**平台自营**:
|
||||
- `shop_id` 为 NULL
|
||||
- 平台直接销售给终端用户
|
||||
- 销售价格由平台自主定价
|
||||
|
||||
**代理分销**:
|
||||
- `shop_id` 为店铺 ID
|
||||
- 代理商可以销售给终端用户或下级代理
|
||||
- 分销价格由上级设置(`distribute_price`),代理商可在分销价基础上加价
|
||||
|
||||
**企业授权**:
|
||||
- 企业用户没有 IoT 卡所有权
|
||||
- 通过 `EnterpriseCardAuthorization` 表管理企业对 IoT 卡的访问权限
|
||||
- 授权由店铺发起,企业只能操作被授权的卡
|
||||
|
||||
#### Scenario: 查询平台自营 IoT 卡库存
|
||||
|
||||
- **WHEN** 查询平台自营 IoT 卡库存
|
||||
- **THEN** 系统返回 `shop_id` 为 NULL 且 `status` 为 1(在库) 的 IoT 卡列表
|
||||
|
||||
#### Scenario: 查询代理店铺 IoT 卡库存
|
||||
|
||||
- **WHEN** 代理店铺(ID 为 10)查询自己的 IoT 卡库存
|
||||
- **THEN** 系统返回 `shop_id` 为 10 且 `status` 为 2(已分销) 的 IoT 卡列表
|
||||
|
||||
---
|
||||
|
||||
### Requirement: IoT 卡数据校验
|
||||
|
||||
系统 SHALL 对 IoT 卡数据进行校验,确保数据完整性和一致性。
|
||||
|
||||
**校验规则**:
|
||||
- ICCID(iccid):必填,字母数字混合,长度根据运营商:电信19位,联通/移动/广电20位,唯一
|
||||
- 卡类型(card_type):必填,长度 1-50 字符
|
||||
- 卡业务类型(card_category):必填,枚举值 "normal"(普通卡) | "industry"(行业卡),默认 "normal"
|
||||
- 运营商 ID(carrier_id):必填,≥ 1,必须是有效的运营商 ID
|
||||
- 成本价(cost_price):必填,≥ 0,单位为分
|
||||
- 分销价(distribute_price):可选,≥ 0,单位为分,≥ 成本价
|
||||
- 店铺 ID(shop_id):可选,NULL 表示平台所有,有值必须是有效的店铺 ID
|
||||
- 激活状态(activation_status):必填,枚举值 0(未激活) | 1(已激活)
|
||||
- 实名状态(real_name_status):必填,枚举值 0(未实名) | 1(已实名),当 card_category 为 "industry"(行业卡)时可以保持 0
|
||||
- 网络状态(network_status):必填,枚举值 0(停机) | 1(开机)
|
||||
- 轮询开关(enable_polling):必填,布尔值 true | false
|
||||
|
||||
#### Scenario: 创建电信 IoT 卡时 ICCID 长度校验
|
||||
|
||||
- **WHEN** 创建电信 IoT 卡(carrier_id 对应电信),ICCID 长度为 20
|
||||
- **THEN** 系统拒绝创建,返回错误信息"电信 ICCID 必须为 19 位"
|
||||
|
||||
#### Scenario: 创建移动 IoT 卡时 ICCID 长度校验
|
||||
|
||||
- **WHEN** 创建移动 IoT 卡(carrier_id 对应移动),ICCID 长度为 19
|
||||
- **THEN** 系统拒绝创建,返回错误信息"该运营商 ICCID 必须为 20 位"
|
||||
|
||||
#### Scenario: 创建 IoT 卡时 ICCID 包含字母
|
||||
|
||||
- **WHEN** 创建移动 IoT 卡,ICCID 为 "8986001234567890123A"(包含字母 A)
|
||||
- **THEN** 系统允许创建,因为移动 ICCID 支持字母
|
||||
|
||||
#### Scenario: 创建 IoT 卡时 ICCID 重复
|
||||
|
||||
- **WHEN** 平台创建 IoT 卡,ICCID 为已存在的 "89860123456789012345"
|
||||
- **THEN** 系统拒绝创建,返回错误信息"ICCID 已存在"
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 单卡列表查询
|
||||
|
||||
系统 SHALL 提供单卡列表查询功能,用于管理未绑定设备的 IoT 卡资产。
|
||||
|
||||
**单卡定义**: 单卡是指未绑定到任何设备的 IoT 卡,即在 `device_sim_bindings` 表中不存在 `bind_status = 1`(已绑定) 的记录。
|
||||
|
||||
**查询条件**:
|
||||
- 套餐 ID(package_id): 可选,筛选已购买指定套餐的卡
|
||||
- 是否分销(is_distributed): 可选,true-已分销 false-未分销
|
||||
- 卡号状态(status): 可选,1-在库 2-已分销 3-已激活 4-已停用
|
||||
- 运营商(carrier_id): 可选,运营商 ID
|
||||
- 分销商 ID(shop_id): 可选,店铺 ID
|
||||
- 网卡号段(iccid_range): 可选,格式 "起始ICCID-结束ICCID"
|
||||
- ICCID: 可选,模糊匹配
|
||||
- 卡接入号(msisdn): 可选,模糊匹配
|
||||
- 是否换卡(is_replaced): 可选,true-有换卡记录 false-无换卡记录
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 返回总记录数和总页数
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺的卡
|
||||
|
||||
#### Scenario: 查询未绑定设备的单卡列表
|
||||
|
||||
- **WHEN** 管理员查询单卡列表
|
||||
- **THEN** 系统返回所有未绑定设备的 IoT 卡(在 device_sim_bindings 中无 bind_status=1 记录的卡)
|
||||
|
||||
#### Scenario: 按运营商筛选单卡
|
||||
|
||||
- **WHEN** 管理员查询运营商 ID 为 1(电信)的单卡
|
||||
- **THEN** 系统返回 carrier_id = 1 且未绑定设备的 IoT 卡列表
|
||||
|
||||
#### Scenario: 按网卡号段筛选单卡
|
||||
|
||||
- **WHEN** 管理员查询 ICCID 号段为 "8986001000000000000-8986001999999999999" 的单卡
|
||||
- **THEN** 系统返回 ICCID 在该号段范围内且未绑定设备的 IoT 卡列表
|
||||
|
||||
#### Scenario: 按是否换卡筛选单卡
|
||||
|
||||
- **WHEN** 管理员查询有换卡记录的单卡(is_replaced=true)
|
||||
- **THEN** 系统返回在 card_replacement_records 表中有记录的 IoT 卡列表
|
||||
|
||||
---
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: IoT 卡批量导入
|
||||
|
||||
系统 SHALL 支持通过 CSV 文件批量导入 IoT 卡 ICCID,支持大批量数据(几万条),异步处理并跟踪导入进度。
|
||||
|
||||
**导入方式**:
|
||||
- 上传 CSV 文件,每行一个 ICCID
|
||||
- 在界面选择运营商、批次号等公共参数
|
||||
- 不支持一次导入多种运营商的卡
|
||||
|
||||
**导入参数**:
|
||||
- CSV 文件(必填): 仅包含 ICCID 列
|
||||
- 运营商 ID(必填): 在界面选择
|
||||
- 批次号(可选): 在界面填写
|
||||
|
||||
**校验规则**:
|
||||
- ICCID 格式校验: 字母数字混合,长度根据运营商(电信19位,其他20位)
|
||||
- ICCID 唯一性校验: 重复 ICCID 跳过,不中断导入
|
||||
|
||||
**处理规则**:
|
||||
- 异步处理: 创建导入任务后立即返回任务 ID
|
||||
- 分批处理: 每批 1000 条
|
||||
- 重复处理: 跳过已存在的 ICCID,记录跳过原因
|
||||
- 格式错误: 记录失败原因,继续处理其他行
|
||||
|
||||
**导入结果**:
|
||||
- 总数(total_count)
|
||||
- 成功数(success_count)
|
||||
- 跳过数(skip_count): 因重复等原因跳过
|
||||
- 失败数(fail_count): 因格式错误等原因失败
|
||||
- 跳过详情: 包含行号、ICCID、原因
|
||||
- 失败详情: 包含行号、ICCID、原因
|
||||
|
||||
#### Scenario: 发起 IoT 卡批量导入
|
||||
|
||||
- **WHEN** 管理员上传包含 10000 个 ICCID 的 CSV 文件,选择运营商为电信,批次号为 "BATCH-2025-001"
|
||||
- **THEN** 系统创建导入任务,返回任务 ID,后台异步处理导入
|
||||
|
||||
#### Scenario: 导入时跳过重复 ICCID
|
||||
|
||||
- **WHEN** CSV 文件中的 ICCID "8986001234567890123" 已存在于系统中
|
||||
- **THEN** 系统跳过该 ICCID,记录跳过原因为"ICCID 已存在",继续处理其他 ICCID
|
||||
|
||||
#### Scenario: 导入时记录格式错误
|
||||
|
||||
- **WHEN** CSV 文件第 100 行的 ICCID "12345" 长度不符合电信卡要求(19位)
|
||||
- **THEN** 系统记录失败,原因为"电信 ICCID 必须为 19 位",行号为 100,继续处理其他 ICCID
|
||||
|
||||
#### Scenario: 查询导入任务进度
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的进度
|
||||
- **THEN** 系统返回任务状态、总数、成功数、跳过数、失败数、开始时间、完成时间
|
||||
|
||||
#### Scenario: 查询导入任务失败详情
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的失败详情
|
||||
- **THEN** 系统返回失败记录列表,每条包含行号、ICCID、失败原因
|
||||
@@ -0,0 +1,172 @@
|
||||
# IoT Device Management - Delta Spec
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 设备实体定义
|
||||
|
||||
系统 SHALL 定义设备(Device)实体,用于管理用户的物联网设备(如 GPS 追踪器、智能传感器等),支持设备与 IoT 卡的绑定关系、设备批量分配和设备操作。
|
||||
|
||||
**核心概念**: 设备不在卡管系统中销售,主要用于:
|
||||
1. 用户设备管理(用户添加自己的设备,绑定 IoT 卡)
|
||||
2. 方便运营人员管理投诉和代理要求(通过设备维度批量查看绑定的所有 IoT 卡)
|
||||
3. 设备操作(重启、修改账号密码、重置等)
|
||||
4. 设备批量分配(运营人员在别的系统报单后发货,把设备和绑定的 IoT 卡一起分配给代理)
|
||||
|
||||
**实体字段**:
|
||||
|
||||
**基本属性**:
|
||||
- `id`: 设备 ID(主键,BIGINT)
|
||||
- `device_no`: 设备编号(唯一,VARCHAR(100))
|
||||
- `device_name`: 设备名称(VARCHAR(255))
|
||||
- `device_model`: 设备型号(VARCHAR(100))
|
||||
- `device_type`: 设备类型(VARCHAR(50),如 "GPS Tracker"、"Camera"、"Sensor")
|
||||
- `max_sim_slots`: 最大 IoT 卡插槽数量(INT,1-4,默认 4)
|
||||
- `manufacturer`: 设备制造商(VARCHAR(255),可选)
|
||||
- `batch_no`: 批次号(VARCHAR(100),用于批量导入追溯)
|
||||
|
||||
**店铺归属和状态**:
|
||||
- `shop_id`: 店铺 ID(BIGINT,可空,NULL 表示平台库存,有值表示店铺所有)
|
||||
- `status`: 设备状态(INT,1-在库 2-已分销 3-已激活 4-已停用)
|
||||
- `activated_at`: 激活时间(TIMESTAMP,可空)
|
||||
|
||||
**设备操作配置**(预留字段,用于后续设备操作功能):
|
||||
- `device_username`: 设备登录账号(VARCHAR(100),可选)
|
||||
- `device_password_encrypted`: 设备登录密码(加密存储,VARCHAR(255),可选)
|
||||
- `device_api_endpoint`: 设备 API 接口地址(VARCHAR(500),可选)
|
||||
|
||||
**系统字段**:
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
- `creator`: 创建人 ID(BIGINT)
|
||||
- `updater`: 更新人 ID(BIGINT)
|
||||
|
||||
#### Scenario: 用户添加设备
|
||||
|
||||
- **WHEN** 用户添加自己的设备(设备编号为 "GPS-001",设备名称为 "物流车辆追踪器")
|
||||
- **THEN** 系统创建设备记录,根据用户归属设置 `shop_id`,状态为 1(在库)
|
||||
|
||||
#### Scenario: 平台导入设备到库存
|
||||
|
||||
- **WHEN** 平台批量导入设备数据(准备发货给代理)
|
||||
- **THEN** 系统创建设备记录,`shop_id` 为 NULL(平台库存),状态为 1(在库)
|
||||
|
||||
#### Scenario: 运营人员批量分配设备给代理店铺
|
||||
|
||||
- **WHEN** 运营人员将平台库存设备(ID 为 1001)分配给代理店铺(ID 为 10)
|
||||
- **THEN** 系统将设备的 `shop_id` 设置为 10,同时自动将该设备绑定的所有 IoT 卡的 `shop_id` 也设置为 10
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 设备批量分配
|
||||
|
||||
系统 SHALL 支持运营人员批量分配设备给代理店铺,设备分配时自动分配该设备绑定的所有 IoT 卡。
|
||||
|
||||
**分配规则**:
|
||||
- 只能分配 `shop_id` 为 NULL 的设备(平台库存)
|
||||
- 分配时,设备的 `shop_id` 设置为目标店铺 ID
|
||||
- 分配时,设备绑定的所有 IoT 卡的 `shop_id` 也设置为目标店铺 ID
|
||||
- 分配操作记录到操作日志
|
||||
|
||||
#### Scenario: 运营人员批量分配设备
|
||||
|
||||
- **WHEN** 运营人员将 10 台设备(平台库存)分配给代理店铺(ID 为 10)
|
||||
- **THEN** 系统将这 10 台设备的 `shop_id` 设置为 10,同时将这些设备绑定的所有 IoT 卡的 `shop_id` 也设置为 10
|
||||
|
||||
#### Scenario: 分配已分配的设备
|
||||
|
||||
- **WHEN** 运营人员尝试分配 `shop_id` 不为 NULL 的设备
|
||||
- **THEN** 系统拒绝分配,返回错误信息"该设备已分配给店铺,不能重复分配"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 设备与 IoT 卡绑定关系
|
||||
|
||||
系统 SHALL 管理设备与 IoT 卡的绑定关系,一个设备可以绑定 1-4 张 IoT 卡。
|
||||
|
||||
**绑定规则**:
|
||||
- 一个设备最多绑定 4 张 IoT 卡(由 `max_sim_slots` 字段控制)
|
||||
- 一个 IoT 卡同一时间只能绑定一个设备
|
||||
- 绑定时记录插槽位置(slot_position: 1, 2, 3, 4)
|
||||
- 绑定时记录绑定时间和绑定状态(1-已绑定 2-已解绑)
|
||||
- 绑定/解绑操作不改变 IoT 卡的 shop_id(所有权由分销操作管理,而非绑定操作)
|
||||
|
||||
**中间表 device_sim_bindings**:
|
||||
- `id`: 绑定记录 ID(主键,BIGINT)
|
||||
- `device_id`: 设备 ID(BIGINT)
|
||||
- `iot_card_id`: IoT 卡 ID(BIGINT)
|
||||
- `slot_position`: 插槽位置(INT,1-4)
|
||||
- `bind_status`: 绑定状态(INT,1-已绑定 2-已解绑)
|
||||
- `bind_time`: 绑定时间(TIMESTAMP)
|
||||
- `unbind_time`: 解绑时间(TIMESTAMP,可空)
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
|
||||
#### Scenario: 绑定 IoT 卡到设备
|
||||
|
||||
- **WHEN** 用户将 IoT 卡(ID 为 101)绑定到设备(ID 为 1001)的插槽 1
|
||||
- **THEN** 系统创建绑定记录,`device_id` 为 1001,`iot_card_id` 为 101,`slot_position` 为 1,`bind_status` 为 1(已绑定),`bind_time` 为当前时间
|
||||
|
||||
#### Scenario: 解绑 IoT 卡
|
||||
|
||||
- **WHEN** 用户解绑设备的 IoT 卡(绑定记录 ID 为 10)
|
||||
- **THEN** 系统将绑定记录的 `bind_status` 从 1(已绑定) 变更为 2(已解绑),`unbind_time` 记录解绑时间,IoT 卡的 `shop_id` 保持不变
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 设备查询和筛选
|
||||
|
||||
系统 SHALL 支持多维度查询和筛选设备,包括状态、店铺归属、批次号、设备类型等。
|
||||
|
||||
**查询条件**:
|
||||
- 设备编号(精确匹配或模糊匹配)
|
||||
- 设备名称(模糊匹配)
|
||||
- 设备状态(单选或多选)
|
||||
- 店铺 ID(shop_id): 可选,NULL 表示平台库存
|
||||
- 批次号(精确匹配)
|
||||
- 设备类型(单选或多选)
|
||||
- 设备制造商(模糊匹配)
|
||||
- 激活时间范围(开始时间 - 结束时间)
|
||||
- 创建时间范围(开始时间 - 结束时间)
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 返回总记录数和总页数
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺的设备
|
||||
|
||||
#### Scenario: 查询平台库存设备
|
||||
|
||||
- **WHEN** 运营人员查询平台库存设备
|
||||
- **THEN** 系统返回 `shop_id` 为 NULL 的设备列表
|
||||
|
||||
#### Scenario: 代理查询自己店铺的设备
|
||||
|
||||
- **WHEN** 代理店铺(ID 为 10)查询自己的设备
|
||||
- **THEN** 系统返回 `shop_id` 为 10(及其下级店铺)的设备列表
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 设备数据校验
|
||||
|
||||
系统 SHALL 对设备数据进行校验,确保数据完整性和一致性。
|
||||
|
||||
**校验规则**:
|
||||
- 设备编号(device_no):必填,长度 1-100 字符,唯一
|
||||
- 设备名称(device_name):可选,长度 1-255 字符
|
||||
- 设备型号(device_model):可选,长度 1-100 字符
|
||||
- 设备类型(device_type):可选,长度 1-50 字符
|
||||
- 最大插槽数(max_sim_slots):必填,1-4 之间的整数
|
||||
- 店铺 ID(shop_id):可选,NULL 表示平台库存,有值必须是有效的店铺 ID
|
||||
- 设备状态(status):必填,枚举值 1(在库) | 2(已分销) | 3(已激活) | 4(已停用)
|
||||
|
||||
#### Scenario: 创建设备时插槽数超出范围
|
||||
|
||||
- **WHEN** 用户创建设备,最大插槽数为 5
|
||||
- **THEN** 系统拒绝创建,返回错误信息"最大插槽数必须在 1-4 之间"
|
||||
|
||||
#### Scenario: 创建设备时设备编号重复
|
||||
|
||||
- **WHEN** 用户创建设备,设备编号为已存在的 "DEV-001"
|
||||
- **THEN** 系统拒绝创建,返回错误信息"设备编号已存在"
|
||||
@@ -0,0 +1,75 @@
|
||||
# Tasks: IoT 卡单卡管理与所有权模型重构
|
||||
|
||||
## 1. 模型重构(清理 Owner 字段)
|
||||
|
||||
- [x] 1.1 修改 IotCard 模型:移除 OwnerType、OwnerID 字段
|
||||
- [x] 1.2 修改 Device 模型:移除 OwnerType、OwnerID 字段
|
||||
- [x] 1.3 创建数据库迁移:删除 tb_iot_card 的 owner_type、owner_id 列
|
||||
- [x] 1.4 创建数据库迁移:删除 tb_device 的 owner_type、owner_id 列
|
||||
- [x] 1.5 更新相关 DTO:移除 OwnerType、OwnerID 相关字段
|
||||
- [x] 1.6 更新相关 Service/Store:移除 Owner 相关逻辑,改用 ShopID
|
||||
|
||||
## 2. 导入任务模型
|
||||
|
||||
- [x] 2.1 创建 IotCardImportTask 模型
|
||||
- [x] 2.2 创建数据库迁移:tb_iot_card_import_task 表
|
||||
- [x] 2.3 创建 IotCardImportTaskStore
|
||||
- [x] 2.4 创建导入任务相关 DTO
|
||||
|
||||
## 3. ICCID 校验逻辑
|
||||
|
||||
- [x] 3.1 在 pkg/validator 中添加 ICCID 校验函数
|
||||
- [x] 3.2 实现根据运营商校验 ICCID 长度(电信19位,其他20位)
|
||||
- [x] 3.3 支持字母数字混合校验(移动有字母)
|
||||
- [x] 3.4 更新现有导入逻辑使用新校验函数
|
||||
|
||||
## 4. 单卡列表 API
|
||||
|
||||
- [x] 4.1 创建单卡列表查询 DTO(请求/响应)
|
||||
- [x] 4.2 实现 IotCardStore.ListStandalone 方法(未绑定设备的卡)
|
||||
- [x] 4.3 实现 IotCardService.ListStandalone 方法
|
||||
- [x] 4.4 实现 IotCardHandler.ListStandalone 方法
|
||||
- [x] 4.5 注册路由 GET /api/admin/iot-cards/standalone
|
||||
|
||||
## 5. 批量导入 API
|
||||
|
||||
- [x] 5.1 创建导入请求 DTO(含 CSV 文件上传)
|
||||
- [x] 5.2 实现 CSV 解析逻辑
|
||||
- [x] 5.3 实现 IotCardImportService.CreateImportTask 方法
|
||||
- [x] 5.4 实现 IotCardImportHandler.Import 方法
|
||||
- [x] 5.5 注册路由 POST /api/admin/iot-cards/import
|
||||
|
||||
## 6. 异步导入 Worker
|
||||
|
||||
- [x] 6.1 创建 IotCardImportTask Asynq 任务类型
|
||||
- [x] 6.2 实现 IotCardImportHandler(Worker 处理器)
|
||||
- [x] 6.3 实现分批处理逻辑(1000条/批)
|
||||
- [x] 6.4 实现 ICCID 去重检查
|
||||
- [x] 6.5 实现进度更新和结果记录
|
||||
|
||||
## 7. 导入任务查询 API
|
||||
|
||||
- [x] 7.1 创建导入任务列表查询 DTO
|
||||
- [x] 7.2 创建导入任务详情 DTO
|
||||
- [x] 7.3 实现 IotCardImportTaskService.List 方法
|
||||
- [x] 7.4 实现 IotCardImportTaskService.GetByID 方法
|
||||
- [x] 7.5 实现 IotCardImportTaskHandler.List 方法
|
||||
- [x] 7.6 实现 IotCardImportTaskHandler.GetByID 方法
|
||||
- [x] 7.7 注册路由 GET /api/admin/iot-cards/import-tasks
|
||||
- [x] 7.8 注册路由 GET /api/admin/iot-cards/import-tasks/:id
|
||||
|
||||
## 8. 测试
|
||||
|
||||
- [x] 8.1 IotCardStore.ListStandalone 单元测试
|
||||
- [x] 8.2 ICCID 校验函数单元测试
|
||||
- [x] 8.3 CSV 解析逻辑单元测试
|
||||
- [x] 8.4 导入 Worker 单元测试
|
||||
- [x] 8.5 单卡列表 API 集成测试
|
||||
- [x] 8.6 批量导入 API 集成测试
|
||||
|
||||
## 9. 文档和规范更新
|
||||
|
||||
- [x] 9.1 更新 iot-card/spec.md(同步 delta 变更)
|
||||
- [x] 9.2 更新 iot-device/spec.md(同步 delta 变更)
|
||||
- [x] 9.3 创建 iot-card-import-task/spec.md
|
||||
- [x] 9.4 更新 API 文档(通过 openspec archive 自动完成)
|
||||
176
openspec/specs/iot-card-import-task/spec.md
Normal file
176
openspec/specs/iot-card-import-task/spec.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# iot-card-import-task Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change iot-card-standalone-management. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: 导入任务实体定义
|
||||
|
||||
系统 SHALL 定义 IoT 卡导入任务(IotCardImportTask)实体,用于跟踪 IoT 卡批量导入的进度和结果。
|
||||
|
||||
**实体字段**:
|
||||
|
||||
**任务信息**:
|
||||
- `id`: 任务 ID(主键,BIGINT)
|
||||
- `task_no`: 任务编号(VARCHAR(50),唯一,格式: IMP-YYYYMMDD-XXXXXX)
|
||||
- `status`: 任务状态(INT,1-待处理 2-处理中 3-已完成 4-失败)
|
||||
|
||||
**导入参数**:
|
||||
- `carrier_id`: 运营商 ID(BIGINT,必填)
|
||||
- `batch_no`: 批次号(VARCHAR(100),可选)
|
||||
- `file_name`: 原始文件名(VARCHAR(255),可选)
|
||||
|
||||
**进度统计**:
|
||||
- `total_count`: 总数(INT,CSV 文件总行数)
|
||||
- `success_count`: 成功数(INT,成功导入的 ICCID 数量)
|
||||
- `skip_count`: 跳过数(INT,因重复等原因跳过的数量)
|
||||
- `fail_count`: 失败数(INT,因格式错误等原因失败的数量)
|
||||
|
||||
**结果详情**:
|
||||
- `skipped_items`: 跳过记录详情(JSONB,结构: [{line, iccid, reason}])
|
||||
- `failed_items`: 失败记录详情(JSONB,结构: [{line, iccid, reason}])
|
||||
|
||||
**时间和错误**:
|
||||
- `started_at`: 开始处理时间(TIMESTAMP,可空)
|
||||
- `completed_at`: 完成时间(TIMESTAMP,可空)
|
||||
- `error_message`: 任务级错误信息(TEXT,可空,如文件解析失败等)
|
||||
|
||||
**系统字段**:
|
||||
- `shop_id`: 店铺 ID(BIGINT,可空,记录发起导入的店铺)
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
- `creator`: 创建人 ID(BIGINT)
|
||||
- `updater`: 更新人 ID(BIGINT)
|
||||
|
||||
#### Scenario: 创建导入任务
|
||||
|
||||
- **WHEN** 管理员上传 CSV 文件发起导入
|
||||
- **THEN** 系统创建导入任务记录,`status` 为 1(待处理),`total_count` 为 CSV 行数,返回任务 ID
|
||||
|
||||
#### Scenario: 导入任务开始处理
|
||||
|
||||
- **WHEN** Worker 开始处理导入任务
|
||||
- **THEN** 系统将任务 `status` 从 1(待处理) 变更为 2(处理中),`started_at` 记录当前时间
|
||||
|
||||
#### Scenario: 导入任务完成
|
||||
|
||||
- **WHEN** Worker 完成导入任务处理
|
||||
- **THEN** 系统将任务 `status` 变更为 3(已完成),`completed_at` 记录当前时间,更新 `success_count`、`skip_count`、`fail_count`
|
||||
|
||||
#### Scenario: 导入任务失败
|
||||
|
||||
- **WHEN** Worker 处理导入任务时发生严重错误(如文件损坏)
|
||||
- **THEN** 系统将任务 `status` 变更为 4(失败),`error_message` 记录错误信息
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务状态流转
|
||||
|
||||
系统 SHALL 管理导入任务的状态流转,确保状态变更符合业务规则。
|
||||
|
||||
**状态定义**:
|
||||
- **1-待处理**: 任务已创建,等待 Worker 处理
|
||||
- **2-处理中**: Worker 正在处理导入
|
||||
- **3-已完成**: 导入处理完成(可能有部分失败)
|
||||
- **4-失败**: 任务级别错误,导入中断
|
||||
|
||||
**状态流转规则**:
|
||||
- 待处理(1) → 处理中(2): Worker 开始处理
|
||||
- 处理中(2) → 已完成(3): 处理完成
|
||||
- 处理中(2) → 失败(4): 发生严重错误
|
||||
- 待处理(1) → 失败(4): 文件验证失败等
|
||||
|
||||
#### Scenario: 正常状态流转
|
||||
|
||||
- **WHEN** 导入任务经历完整生命周期
|
||||
- **THEN** 状态依次变更: 待处理(1) → 处理中(2) → 已完成(3)
|
||||
|
||||
#### Scenario: 异常状态流转
|
||||
|
||||
- **WHEN** 导入任务处理过程中发生严重错误
|
||||
- **THEN** 状态变更: 待处理(1) → 处理中(2) → 失败(4)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务列表查询
|
||||
|
||||
系统 SHALL 支持查询导入任务列表,用于管理和监控导入任务。
|
||||
|
||||
**查询条件**:
|
||||
- 任务状态(status): 可选,1-待处理 2-处理中 3-已完成 4-失败
|
||||
- 运营商 ID(carrier_id): 可选
|
||||
- 批次号(batch_no): 可选,模糊匹配
|
||||
- 创建时间范围: 可选
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 默认按创建时间倒序排列
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺发起的导入任务
|
||||
|
||||
#### Scenario: 查询所有导入任务
|
||||
|
||||
- **WHEN** 管理员查询导入任务列表
|
||||
- **THEN** 系统返回导入任务列表,包含任务编号、状态、运营商、总数、成功数、跳过数、失败数、创建时间
|
||||
|
||||
#### Scenario: 按状态筛选导入任务
|
||||
|
||||
- **WHEN** 管理员查询状态为 2(处理中) 的导入任务
|
||||
- **THEN** 系统返回所有正在处理的导入任务列表
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务详情查询
|
||||
|
||||
系统 SHALL 支持查询单个导入任务的详细信息,包括跳过/失败记录详情。
|
||||
|
||||
**详情信息**:
|
||||
- 任务基本信息: 任务编号、状态、运营商、批次号、文件名
|
||||
- 进度统计: 总数、成功数、跳过数、失败数
|
||||
- 时间信息: 创建时间、开始时间、完成时间
|
||||
- 跳过记录详情: 行号、ICCID、原因
|
||||
- 失败记录详情: 行号、ICCID、原因
|
||||
- 错误信息: 任务级错误(如有)
|
||||
|
||||
#### Scenario: 查询导入任务详情
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的详情
|
||||
- **THEN** 系统返回任务完整信息,包括跳过和失败记录的详细列表
|
||||
|
||||
#### Scenario: 查询导入任务的跳过记录
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的跳过记录
|
||||
- **THEN** 系统返回跳过记录列表,每条包含: 行号(line)、ICCID、原因(如"ICCID 已存在")
|
||||
|
||||
#### Scenario: 查询导入任务的失败记录
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的失败记录
|
||||
- **THEN** 系统返回失败记录列表,每条包含: 行号(line)、ICCID、原因(如"电信 ICCID 必须为 19 位")
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 导入任务数据校验
|
||||
|
||||
系统 SHALL 对导入任务数据进行校验,确保数据完整性和一致性。
|
||||
|
||||
**校验规则**:
|
||||
- 任务编号(task_no): 必填,系统自动生成,格式 IMP-YYYYMMDD-XXXXXX,唯一
|
||||
- 任务状态(status): 必填,枚举值 1(待处理) | 2(处理中) | 3(已完成) | 4(失败)
|
||||
- 运营商 ID(carrier_id): 必填,必须是有效的运营商 ID
|
||||
- 总数(total_count): 必填,≥ 0
|
||||
- 成功数(success_count): 必填,≥ 0,≤ total_count
|
||||
- 跳过数(skip_count): 必填,≥ 0,≤ total_count
|
||||
- 失败数(fail_count): 必填,≥ 0,≤ total_count
|
||||
- 数量一致性: success_count + skip_count + fail_count ≤ total_count
|
||||
|
||||
#### Scenario: 创建任务时运营商 ID 无效
|
||||
|
||||
- **WHEN** 创建导入任务时 carrier_id 不存在
|
||||
- **THEN** 系统拒绝创建,返回错误信息"运营商 ID 无效"
|
||||
|
||||
#### Scenario: 更新任务时数量不一致
|
||||
|
||||
- **WHEN** 更新导入任务时 success_count + skip_count + fail_count > total_count
|
||||
- **THEN** 系统拒绝更新,返回错误信息"统计数量不一致"
|
||||
|
||||
@@ -10,9 +10,7 @@ This capability supports:
|
||||
- Integration with Gateway project for real-time status synchronization
|
||||
- Batch import and multi-dimensional querying
|
||||
- Support for normal cards (require real-name verification) and industry cards (no real-name required)
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: IoT 卡实体定义
|
||||
|
||||
系统 SHALL 定义 IoT 卡(IotCard)实体,包含 IoT 卡(物联网卡/流量卡/SIM卡)的商品属性、状态属性、所有权信息和 Gateway 集成字段。
|
||||
@@ -161,35 +159,60 @@ This capability supports:
|
||||
|
||||
### Requirement: IoT 卡批量导入
|
||||
|
||||
系统 SHALL 支持批量导入 IoT 卡数据,用于初始化库存或补充库存。
|
||||
系统 SHALL 支持通过 CSV 文件批量导入 IoT 卡 ICCID,支持大批量数据(几万条),异步处理并跟踪导入进度。
|
||||
|
||||
**导入字段**:
|
||||
- ICCID(必填)
|
||||
- 卡类型(必填,如 "4G"、"5G"、"NB-IoT")
|
||||
- 卡业务类型(可选,枚举值 "normal" | "industry",默认 "normal")
|
||||
- 运营商 ID(必填,从 carriers 表中选择)
|
||||
- IMSI(可选)
|
||||
- 手机号码(可选)
|
||||
- 供应商(可选)
|
||||
- 成本价(必填)
|
||||
- 批次号(必填)
|
||||
**导入方式**:
|
||||
- 上传 CSV 文件,每行一个 ICCID
|
||||
- 在界面选择运营商、批次号等公共参数
|
||||
- 不支持一次导入多种运营商的卡
|
||||
|
||||
**导入规则**:
|
||||
- ICCID 必须唯一,重复 ICCID 将被拒绝
|
||||
- 导入的 IoT 卡默认状态为 1(在库),所有者为平台(`owner_type` 为 "platform",`owner_id` 为 0)
|
||||
- 导入成功后记录操作日志
|
||||
**导入参数**:
|
||||
- CSV 文件(必填): 仅包含 ICCID 列
|
||||
- 运营商 ID(必填): 在界面选择
|
||||
- 批次号(可选): 在界面填写
|
||||
|
||||
#### Scenario: 批量导入 IoT 卡成功
|
||||
**校验规则**:
|
||||
- ICCID 格式校验: 字母数字混合,长度根据运营商(电信19位,其他20位)
|
||||
- ICCID 唯一性校验: 重复 ICCID 跳过,不中断导入
|
||||
|
||||
- **WHEN** 平台上传包含 100 条 IoT 卡数据的 CSV 文件
|
||||
- **THEN** 系统创建 100 条 IoT 卡记录,状态为 1(在库),所有者为平台,返回导入成功消息
|
||||
**处理规则**:
|
||||
- 异步处理: 创建导入任务后立即返回任务 ID
|
||||
- 分批处理: 每批 1000 条
|
||||
- 重复处理: 跳过已存在的 ICCID,记录跳过原因
|
||||
- 格式错误: 记录失败原因,继续处理其他行
|
||||
|
||||
#### Scenario: 批量导入包含重复 ICCID
|
||||
**导入结果**:
|
||||
- 总数(total_count)
|
||||
- 成功数(success_count)
|
||||
- 跳过数(skip_count): 因重复等原因跳过
|
||||
- 失败数(fail_count): 因格式错误等原因失败
|
||||
- 跳过详情: 包含行号、ICCID、原因
|
||||
- 失败详情: 包含行号、ICCID、原因
|
||||
|
||||
- **WHEN** 平台上传的 CSV 文件中包含已存在的 ICCID
|
||||
- **THEN** 系统拒绝重复 ICCID 的 IoT 卡,返回错误信息并列出重复 ICCID,其他有效 IoT 卡正常导入
|
||||
#### Scenario: 发起 IoT 卡批量导入
|
||||
|
||||
---
|
||||
- **WHEN** 管理员上传包含 10000 个 ICCID 的 CSV 文件,选择运营商为电信,批次号为 "BATCH-2025-001"
|
||||
- **THEN** 系统创建导入任务,返回任务 ID,后台异步处理导入
|
||||
|
||||
#### Scenario: 导入时跳过重复 ICCID
|
||||
|
||||
- **WHEN** CSV 文件中的 ICCID "8986001234567890123" 已存在于系统中
|
||||
- **THEN** 系统跳过该 ICCID,记录跳过原因为"ICCID 已存在",继续处理其他 ICCID
|
||||
|
||||
#### Scenario: 导入时记录格式错误
|
||||
|
||||
- **WHEN** CSV 文件第 100 行的 ICCID "12345" 长度不符合电信卡要求(19位)
|
||||
- **THEN** 系统记录失败,原因为"电信 ICCID 必须为 19 位",行号为 100,继续处理其他 ICCID
|
||||
|
||||
#### Scenario: 查询导入任务进度
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的进度
|
||||
- **THEN** 系统返回任务状态、总数、成功数、跳过数、失败数、开始时间、完成时间
|
||||
|
||||
#### Scenario: 查询导入任务失败详情
|
||||
|
||||
- **WHEN** 管理员查询导入任务(ID 为 1)的失败详情
|
||||
- **THEN** 系统返回失败记录列表,每条包含行号、ICCID、失败原因
|
||||
|
||||
### Requirement: IoT 卡查询和筛选
|
||||
|
||||
@@ -302,3 +325,51 @@ This capability supports:
|
||||
|
||||
- **WHEN** 平台创建 IoT 卡,成本价为 50.00,分销价为 40.00
|
||||
- **THEN** 系统拒绝创建,返回错误信息"分销价不能低于成本价"
|
||||
|
||||
### Requirement: 单卡列表查询
|
||||
|
||||
系统 SHALL 提供单卡列表查询功能,用于管理未绑定设备的 IoT 卡资产。
|
||||
|
||||
**单卡定义**: 单卡是指未绑定到任何设备的 IoT 卡,即在 `device_sim_bindings` 表中不存在 `bind_status = 1`(已绑定) 的记录。
|
||||
|
||||
**查询条件**:
|
||||
- 套餐 ID(package_id): 可选,筛选已购买指定套餐的卡
|
||||
- 是否分销(is_distributed): 可选,true-已分销 false-未分销
|
||||
- 卡号状态(status): 可选,1-在库 2-已分销 3-已激活 4-已停用
|
||||
- 运营商(carrier_id): 可选,运营商 ID
|
||||
- 分销商 ID(shop_id): 可选,店铺 ID
|
||||
- 网卡号段(iccid_range): 可选,格式 "起始ICCID-结束ICCID"
|
||||
- ICCID: 可选,模糊匹配
|
||||
- 卡接入号(msisdn): 可选,模糊匹配
|
||||
- 是否换卡(is_replaced): 可选,true-有换卡记录 false-无换卡记录
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 返回总记录数和总页数
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺的卡
|
||||
|
||||
#### Scenario: 查询未绑定设备的单卡列表
|
||||
|
||||
- **WHEN** 管理员查询单卡列表
|
||||
- **THEN** 系统返回所有未绑定设备的 IoT 卡(在 device_sim_bindings 中无 bind_status=1 记录的卡)
|
||||
|
||||
#### Scenario: 按运营商筛选单卡
|
||||
|
||||
- **WHEN** 管理员查询运营商 ID 为 1(电信)的单卡
|
||||
- **THEN** 系统返回 carrier_id = 1 且未绑定设备的 IoT 卡列表
|
||||
|
||||
#### Scenario: 按网卡号段筛选单卡
|
||||
|
||||
- **WHEN** 管理员查询 ICCID 号段为 "8986001000000000000-8986001999999999999" 的单卡
|
||||
- **THEN** 系统返回 ICCID 在该号段范围内且未绑定设备的 IoT 卡列表
|
||||
|
||||
#### Scenario: 按是否换卡筛选单卡
|
||||
|
||||
- **WHEN** 管理员查询有换卡记录的单卡(is_replaced=true)
|
||||
- **THEN** 系统返回在 card_replacement_records 表中有记录的 IoT 卡列表
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -10,9 +10,7 @@ This capability supports:
|
||||
- Device-level package purchases with shared data pool
|
||||
- Batch device allocation to agents
|
||||
- Remote device operations (reboot, password change, reset)
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 设备实体定义
|
||||
|
||||
系统 SHALL 定义设备(Device)实体,用于管理用户的物联网设备(如 GPS 追踪器、智能传感器等),支持设备与 IoT 卡的绑定关系、设备批量分配和设备操作。
|
||||
@@ -27,7 +25,7 @@ This capability supports:
|
||||
|
||||
**基本属性**:
|
||||
- `id`: 设备 ID(主键,BIGINT)
|
||||
- `device_no`: 设备编号(唯一,VARCHAR(50))
|
||||
- `device_no`: 设备编号(唯一,VARCHAR(100))
|
||||
- `device_name`: 设备名称(VARCHAR(255))
|
||||
- `device_model`: 设备型号(VARCHAR(100))
|
||||
- `device_type`: 设备类型(VARCHAR(50),如 "GPS Tracker"、"Camera"、"Sensor")
|
||||
@@ -35,35 +33,36 @@ This capability supports:
|
||||
- `manufacturer`: 设备制造商(VARCHAR(255),可选)
|
||||
- `batch_no`: 批次号(VARCHAR(100),用于批量导入追溯)
|
||||
|
||||
**所有权和状态**:
|
||||
- `owner_type`: 所有者类型(VARCHAR(20),"platform"-平台库存(等待分配) | "agent"-代理商 | "user"-用户)
|
||||
- `owner_id`: 所有者 ID(BIGINT,platform 时为 0,agent/user 时为对应的 ID)
|
||||
- `status`: 设备状态(INT,1-未激活 2-已激活 3-已停用)
|
||||
**店铺归属和状态**:
|
||||
- `shop_id`: 店铺 ID(BIGINT,可空,NULL 表示平台库存,有值表示店铺所有)
|
||||
- `status`: 设备状态(INT,1-在库 2-已分销 3-已激活 4-已停用)
|
||||
- `activated_at`: 激活时间(TIMESTAMP,可空)
|
||||
|
||||
**设备操作配置**(预留字段,用于后续设备操作功能):
|
||||
- `device_username`: 设备登录账号(VARCHAR(100),可选)
|
||||
- `device_password_encrypted`: 设备登录密码(加密存储,TEXT,可选)
|
||||
- `device_password_encrypted`: 设备登录密码(加密存储,VARCHAR(255),可选)
|
||||
- `device_api_endpoint`: 设备 API 接口地址(VARCHAR(500),可选)
|
||||
|
||||
**系统字段**:
|
||||
- `created_at`: 创建时间(TIMESTAMP,自动填充)
|
||||
- `updated_at`: 更新时间(TIMESTAMP,自动填充)
|
||||
- `creator`: 创建人 ID(BIGINT)
|
||||
- `updater`: 更新人 ID(BIGINT)
|
||||
|
||||
#### Scenario: 用户添加设备
|
||||
|
||||
- **WHEN** 用户添加自己的设备(设备编号为 "GPS-001",设备名称为 "物流车辆追踪器")
|
||||
- **THEN** 系统创建设备记录,`owner_type` 为 "user",`owner_id` 为用户 ID,状态为 1(未激活)
|
||||
- **THEN** 系统创建设备记录,根据用户归属设置 `shop_id`,状态为 1(在库)
|
||||
|
||||
#### Scenario: 平台导入设备到库存
|
||||
|
||||
- **WHEN** 平台批量导入设备数据(准备发货给代理)
|
||||
- **THEN** 系统创建设备记录,`owner_type` 为 "platform",`owner_id` 为 0,状态为 1(未激活)
|
||||
- **THEN** 系统创建设备记录,`shop_id` 为 NULL(平台库存),状态为 1(在库)
|
||||
|
||||
#### Scenario: 运营人员批量分配设备给代理
|
||||
#### Scenario: 运营人员批量分配设备给代理店铺
|
||||
|
||||
- **WHEN** 运营人员将平台库存设备(ID 为 1001)分配给代理商(用户 ID 为 123)
|
||||
- **THEN** 系统将设备的 `owner_type` 变更为 "agent",`owner_id` 设置为 123,同时自动分配该设备绑定的所有 IoT 卡给代理
|
||||
- **WHEN** 运营人员将平台库存设备(ID 为 1001)分配给代理店铺(ID 为 10)
|
||||
- **THEN** 系统将设备的 `shop_id` 设置为 10,同时自动将该设备绑定的所有 IoT 卡的 `shop_id` 也设置为 10
|
||||
|
||||
---
|
||||
|
||||
@@ -102,7 +101,7 @@ This capability supports:
|
||||
- 一个 IoT 卡同一时间只能绑定一个设备
|
||||
- 绑定时记录插槽位置(slot_position: 1, 2, 3, 4)
|
||||
- 绑定时记录绑定时间和绑定状态(1-已绑定 2-已解绑)
|
||||
- 设备绑定 IoT 卡后,IoT 卡的 `owner_type` 变更为 "device",`owner_id` 变更为设备 ID
|
||||
- 绑定/解绑操作不改变 IoT 卡的 shop_id(所有权由分销操作管理,而非绑定操作)
|
||||
|
||||
**中间表 device_sim_bindings**:
|
||||
- `id`: 绑定记录 ID(主键,BIGINT)
|
||||
@@ -118,27 +117,12 @@ This capability supports:
|
||||
#### Scenario: 绑定 IoT 卡到设备
|
||||
|
||||
- **WHEN** 用户将 IoT 卡(ID 为 101)绑定到设备(ID 为 1001)的插槽 1
|
||||
- **THEN** 系统创建绑定记录,`device_id` 为 1001,`iot_card_id` 为 101,`slot_position` 为 1,`bind_status` 为 1(已绑定),`bind_time` 为当前时间,IoT 卡的 `owner_type` 变更为 "device",`owner_id` 变更为 1001
|
||||
|
||||
#### Scenario: 绑定超过最大插槽数量
|
||||
|
||||
- **WHEN** 用户尝试将第 5 张 IoT 卡绑定到最大插槽数为 4 的设备
|
||||
- **THEN** 系统拒绝绑定,返回错误信息"设备插槽已满,最多支持 4 张 IoT 卡"
|
||||
|
||||
#### Scenario: 绑定已被占用的 IoT 卡
|
||||
|
||||
- **WHEN** 用户尝试绑定已被其他设备绑定的 IoT 卡
|
||||
- **THEN** 系统拒绝绑定,返回错误信息"该 IoT 卡已被其他设备绑定"
|
||||
- **THEN** 系统创建绑定记录,`device_id` 为 1001,`iot_card_id` 为 101,`slot_position` 为 1,`bind_status` 为 1(已绑定),`bind_time` 为当前时间
|
||||
|
||||
#### Scenario: 解绑 IoT 卡
|
||||
|
||||
- **WHEN** 用户解绑设备的 IoT 卡(绑定记录 ID 为 10)
|
||||
- **THEN** 系统将绑定记录的 `bind_status` 从 1(已绑定) 变更为 2(已解绑),`unbind_time` 记录解绑时间,IoT 卡的 `owner_type` 和 `owner_id` 重置
|
||||
|
||||
#### Scenario: 查询设备当前绑定的 IoT 卡
|
||||
|
||||
- **WHEN** 用户查询设备(ID 为 1001)当前绑定的 IoT 卡
|
||||
- **THEN** 系统返回 `device_id` 为 1001 且 `bind_status` 为 1(已绑定) 的所有绑定记录,包含 IoT 卡信息(ICCID、运营商、激活状态等)和插槽位置
|
||||
- **THEN** 系统将绑定记录的 `bind_status` 从 1(已绑定) 变更为 2(已解绑),`unbind_time` 记录解绑时间,IoT 卡的 `shop_id` 保持不变
|
||||
|
||||
---
|
||||
|
||||
@@ -179,23 +163,23 @@ This capability supports:
|
||||
|
||||
### Requirement: 设备批量分配
|
||||
|
||||
系统 SHALL 支持运营人员批量分配设备给代理,设备分配时自动分配该设备绑定的所有 IoT 卡。
|
||||
系统 SHALL 支持运营人员批量分配设备给代理店铺,设备分配时自动分配该设备绑定的所有 IoT 卡。
|
||||
|
||||
**分配规则**:
|
||||
- 只能分配 `owner_type` 为 "platform" 的设备(平台库存)
|
||||
- 分配时,设备的 `owner_type` 变更为 "agent",`owner_id` 设置为代理用户 ID
|
||||
- 分配时,设备绑定的所有 IoT 卡的 `owner_type` 也变更为 "agent",`owner_id` 设置为代理用户 ID
|
||||
- 只能分配 `shop_id` 为 NULL 的设备(平台库存)
|
||||
- 分配时,设备的 `shop_id` 设置为目标店铺 ID
|
||||
- 分配时,设备绑定的所有 IoT 卡的 `shop_id` 也设置为目标店铺 ID
|
||||
- 分配操作记录到操作日志
|
||||
|
||||
#### Scenario: 运营人员批量分配设备
|
||||
|
||||
- **WHEN** 运营人员将 10 台设备(平台库存)分配给代理商(用户 ID 为 123)
|
||||
- **THEN** 系统将这 10 台设备的 `owner_type` 变更为 "agent",`owner_id` 设置为 123,同时将这些设备绑定的所有 IoT 卡也分配给代理 123
|
||||
- **WHEN** 运营人员将 10 台设备(平台库存)分配给代理店铺(ID 为 10)
|
||||
- **THEN** 系统将这 10 台设备的 `shop_id` 设置为 10,同时将这些设备绑定的所有 IoT 卡的 `shop_id` 也设置为 10
|
||||
|
||||
#### Scenario: 分配已分配的设备
|
||||
|
||||
- **WHEN** 运营人员尝试分配 `owner_type` 为 "agent" 的设备
|
||||
- **THEN** 系统拒绝分配,返回错误信息"该设备已分配给代理,不能重复分配"
|
||||
- **WHEN** 运营人员尝试分配 `shop_id` 不为 NULL 的设备
|
||||
- **THEN** 系统拒绝分配,返回错误信息"该设备已分配给店铺,不能重复分配"
|
||||
|
||||
---
|
||||
|
||||
@@ -259,14 +243,13 @@ This capability supports:
|
||||
|
||||
### Requirement: 设备查询和筛选
|
||||
|
||||
系统 SHALL 支持多维度查询和筛选设备,包括状态、所有者、批次号、设备类型等。
|
||||
系统 SHALL 支持多维度查询和筛选设备,包括状态、店铺归属、批次号、设备类型等。
|
||||
|
||||
**查询条件**:
|
||||
- 设备编号(精确匹配或模糊匹配)
|
||||
- 设备名称(模糊匹配)
|
||||
- 设备状态(单选或多选)
|
||||
- 所有者类型(platform | agent | user)
|
||||
- 所有者 ID(仅当所有者类型为 agent/user 时有效)
|
||||
- 店铺 ID(shop_id): 可选,NULL 表示平台库存
|
||||
- 批次号(精确匹配)
|
||||
- 设备类型(单选或多选)
|
||||
- 设备制造商(模糊匹配)
|
||||
@@ -277,25 +260,19 @@ This capability supports:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 返回总记录数和总页数
|
||||
|
||||
**数据权限**:
|
||||
- 基于 shop_id 自动应用数据权限过滤
|
||||
- 代理只能看到自己店铺及下级店铺的设备
|
||||
|
||||
#### Scenario: 查询平台库存设备
|
||||
|
||||
- **WHEN** 运营人员查询平台库存设备
|
||||
- **THEN** 系统返回 `owner_type` 为 "platform" 的设备列表
|
||||
- **THEN** 系统返回 `shop_id` 为 NULL 的设备列表
|
||||
|
||||
#### Scenario: 代理查询自己的设备
|
||||
#### Scenario: 代理查询自己店铺的设备
|
||||
|
||||
- **WHEN** 代理商(用户 ID 为 123)查询自己的设备
|
||||
- **THEN** 系统返回 `owner_type` 为 "agent" 且 `owner_id` 为 123 的设备列表
|
||||
|
||||
#### Scenario: 用户查询自己的设备
|
||||
|
||||
- **WHEN** 用户(用户 ID 为 2001)查询自己的设备
|
||||
- **THEN** 系统返回 `owner_type` 为 "user" 且 `owner_id` 为 2001 的设备列表,包含设备绑定的所有 IoT 卡信息
|
||||
|
||||
#### Scenario: 运营人员通过设备查看绑定的所有 IoT 卡
|
||||
|
||||
- **WHEN** 运营人员需要处理投诉,查询设备(ID 为 1001)绑定的所有 IoT 卡
|
||||
- **THEN** 系统返回设备信息和绑定的所有 IoT 卡详细信息(ICCID、运营商、激活状态、流量使用等),方便统一查看和管理
|
||||
- **WHEN** 代理店铺(ID 为 10)查询自己的设备
|
||||
- **THEN** 系统返回 `shop_id` 为 10(及其下级店铺)的设备列表
|
||||
|
||||
---
|
||||
|
||||
@@ -304,14 +281,13 @@ This capability supports:
|
||||
系统 SHALL 对设备数据进行校验,确保数据完整性和一致性。
|
||||
|
||||
**校验规则**:
|
||||
- 设备编号(device_no):必填,长度 1-50 字符,唯一
|
||||
- 设备名称(device_name):必填,长度 1-255 字符
|
||||
- 设备型号(device_model):必填,长度 1-100 字符
|
||||
- 设备类型(device_type):必填,长度 1-50 字符
|
||||
- 设备编号(device_no):必填,长度 1-100 字符,唯一
|
||||
- 设备名称(device_name):可选,长度 1-255 字符
|
||||
- 设备型号(device_model):可选,长度 1-100 字符
|
||||
- 设备类型(device_type):可选,长度 1-50 字符
|
||||
- 最大插槽数(max_sim_slots):必填,1-4 之间的整数
|
||||
- 所有者类型(owner_type):必填,枚举值 "platform" | "agent" | "user"
|
||||
- 所有者 ID(owner_id):必填,≥ 0,当 owner_type 为 "platform" 时必须为 0
|
||||
- 设备状态(status):必填,枚举值 1(未激活) | 2(已激活) | 3(已停用)
|
||||
- 店铺 ID(shop_id):可选,NULL 表示平台库存,有值必须是有效的店铺 ID
|
||||
- 设备状态(status):必填,枚举值 1(在库) | 2(已分销) | 3(已激活) | 4(已停用)
|
||||
|
||||
#### Scenario: 创建设备时插槽数超出范围
|
||||
|
||||
@@ -322,3 +298,4 @@ This capability supports:
|
||||
|
||||
- **WHEN** 用户创建设备,设备编号为已存在的 "DEV-001"
|
||||
- **THEN** 系统拒绝创建,返回错误信息"设备编号已存在"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user