feat: 实现单卡资产分配与回收功能
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
- 新增单卡分配/回收 API(支持 ICCID 列表、号段范围、筛选条件三种选卡方式) - 新增资产分配记录查询 API(支持多条件筛选和分页) - 新增 AssetAllocationRecord 模型、Store、Service、Handler 完整实现 - 扩展 IotCardStore 新增批量更新、号段查询、筛选查询等方法 - 修复 GORM Callback 处理 slice 类型(BatchCreate)的问题 - 新增完整的单元测试和集成测试 - 同步 OpenSpec 规范并归档 change
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Change: 单卡资产分配功能
|
||||
|
||||
## Why
|
||||
|
||||
平台和代理商需要将单卡(未绑定设备的 IoT 卡)分销给下级代理,实现资产的层级流转。当前系统只有企业卡授权功能(授权企业可见特定卡),缺少代理商之间的卡所有权转移功能。
|
||||
|
||||
业务场景:
|
||||
- 平台批量导入卡后,分销给一级代理
|
||||
- 一级代理继续分销给二级代理
|
||||
- 上级可以回收已分销的卡
|
||||
- 查看卡的分配/回收历史记录
|
||||
|
||||
## What Changes
|
||||
|
||||
### 新增功能
|
||||
|
||||
**单卡分配 API**
|
||||
- `POST /api/admin/iot-cards/standalone/allocate` - 批量分配单卡给直属下级店铺
|
||||
- `POST /api/admin/iot-cards/standalone/recall` - 批量回收已分配的单卡
|
||||
|
||||
**分配记录查询 API**
|
||||
- `GET /api/admin/asset-allocation-records` - 分配记录列表(支持分页和筛选)
|
||||
- `GET /api/admin/asset-allocation-records/:id` - 分配记录详情
|
||||
|
||||
**分配方式支持**
|
||||
- ICCID 列表选择
|
||||
- ICCID 号段范围(起始~结束)
|
||||
- 筛选条件批量(运营商、批次号等)
|
||||
|
||||
### 业务规则
|
||||
|
||||
**分配规则**
|
||||
- 只能分配给直属下级店铺,不可跨级
|
||||
- 平台只能分配在库(status=1)的卡
|
||||
- 代理可以分配已分销(status=2)的卡(继续往下分销)
|
||||
- 分配后状态变更:在库(1)→已分销(2),已分销(2)保持不变
|
||||
- 分配后 shop_id 变更为目标店铺 ID
|
||||
|
||||
**回收规则**
|
||||
- 只有上级可以回收,代理不能主动退回
|
||||
- 平台回收:shop_id 变为 NULL
|
||||
- 店铺回收:shop_id 变为执行回收的店铺 ID
|
||||
- 只能回收直属下级的卡,不可跨级回收
|
||||
|
||||
**可见性**
|
||||
- 分配后上级仍能看到和管理(通过数据权限机制实现)
|
||||
|
||||
**注意**
|
||||
- 本次只做单卡分配,设备卡分配暂不实现
|
||||
- 分配不涉及费用,纯资产所有权转移
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `asset-allocation-record`: 资产分配记录管理,包含分配记录的查询功能
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `iot-card`: 新增单卡分配和回收功能
|
||||
|
||||
## Impact
|
||||
|
||||
### API 影响
|
||||
- 新增:`POST /api/admin/iot-cards/standalone/allocate`
|
||||
- 新增:`POST /api/admin/iot-cards/standalone/recall`
|
||||
- 新增:`GET /api/admin/asset-allocation-records`
|
||||
- 新增:`GET /api/admin/asset-allocation-records/:id`
|
||||
|
||||
### 代码影响
|
||||
- `internal/handler/admin/iot_card.go`:新增 AllocateCards、RecallCards 方法
|
||||
- `internal/handler/admin/asset_allocation_record.go`:新增(分配记录 Handler)
|
||||
- `internal/service/iot_card/service.go`:新增分配、回收业务逻辑
|
||||
- `internal/service/asset_allocation_record/service.go`:新增(分配记录 Service)
|
||||
- `internal/store/postgres/iot_card_store.go`:新增批量更新 shop_id 方法
|
||||
- `internal/store/postgres/asset_allocation_record_store.go`:新增(分配记录 Store)
|
||||
- `internal/model/dto/iot_card_dto.go`:新增分配、回收相关 DTO
|
||||
- `internal/model/dto/asset_allocation_record_dto.go`:新增(分配记录 DTO)
|
||||
- `internal/routes/asset_allocation_record.go`:新增(分配记录路由)
|
||||
|
||||
### 数据库影响
|
||||
- 无表结构变更(使用现有 `tb_iot_card` 和 `tb_asset_allocation_record` 表)
|
||||
@@ -0,0 +1,101 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 资产分配记录查询
|
||||
|
||||
系统 SHALL 提供资产分配记录的查询功能,支持查看卡和设备在平台与代理商之间的流转历史。
|
||||
|
||||
**记录类型**:
|
||||
- `allocate`: 分配记录(上级分配给下级)
|
||||
- `recall`: 回收记录(上级从下级回收)
|
||||
|
||||
**资产类型**:
|
||||
- `iot_card`: 物联网卡(单卡)
|
||||
- `device`: 设备(未来扩展)
|
||||
|
||||
**查询条件**:
|
||||
- `allocation_type`(可选): 分配类型,枚举值 "allocate" | "recall"
|
||||
- `asset_type`(可选): 资产类型,枚举值 "iot_card" | "device"
|
||||
- `asset_identifier`(可选): 资产标识符(ICCID 或设备号),模糊匹配
|
||||
- `allocation_no`(可选): 分配单号,精确匹配
|
||||
- `from_shop_id`(可选): 来源店铺 ID
|
||||
- `to_shop_id`(可选): 目标店铺 ID
|
||||
- `operator_id`(可选): 操作人 ID
|
||||
- `created_at_start`(可选): 创建时间起始
|
||||
- `created_at_end`(可选): 创建时间结束
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 返回总记录数和总页数
|
||||
|
||||
**数据权限**:
|
||||
- 平台用户可查看所有记录
|
||||
- 代理用户只能查看与自己店铺相关的记录(作为来源或目标)
|
||||
|
||||
**API 端点**: `GET /api/admin/asset-allocation-records`
|
||||
|
||||
**响应字段**:
|
||||
- `id`: 记录 ID
|
||||
- `allocation_no`: 分配单号
|
||||
- `allocation_type`: 分配类型
|
||||
- `allocation_type_name`: 分配类型名称(分配/回收)
|
||||
- `asset_type`: 资产类型
|
||||
- `asset_type_name`: 资产类型名称(物联网卡/设备)
|
||||
- `asset_id`: 资产 ID
|
||||
- `asset_identifier`: 资产标识符
|
||||
- `from_owner_type`: 来源所有者类型
|
||||
- `from_owner_id`: 来源所有者 ID
|
||||
- `from_owner_name`: 来源所有者名称
|
||||
- `to_owner_type`: 目标所有者类型
|
||||
- `to_owner_id`: 目标所有者 ID
|
||||
- `to_owner_name`: 目标所有者名称
|
||||
- `operator_id`: 操作人 ID
|
||||
- `operator_name`: 操作人名称
|
||||
- `remark`: 备注
|
||||
- `created_at`: 创建时间
|
||||
|
||||
#### Scenario: 查询所有分配记录
|
||||
|
||||
- **WHEN** 平台管理员查询分配记录列表,不带任何筛选条件
|
||||
- **THEN** 系统返回所有分配和回收记录,按创建时间倒序排列
|
||||
|
||||
#### Scenario: 按资产类型筛选记录
|
||||
|
||||
- **WHEN** 管理员查询资产类型为 "iot_card" 的记录
|
||||
- **THEN** 系统只返回物联网卡的分配/回收记录,不包含设备记录
|
||||
|
||||
#### Scenario: 按分配类型筛选记录
|
||||
|
||||
- **WHEN** 管理员查询分配类型为 "allocate" 的记录
|
||||
- **THEN** 系统只返回分配记录,不包含回收记录
|
||||
|
||||
#### Scenario: 按 ICCID 模糊查询
|
||||
|
||||
- **WHEN** 管理员输入 asset_identifier = "8986001"
|
||||
- **THEN** 系统返回 ICCID 包含 "8986001" 的所有分配记录
|
||||
|
||||
#### Scenario: 代理查询自己相关的记录
|
||||
|
||||
- **WHEN** 代理用户(店铺 ID=10)查询分配记录
|
||||
- **THEN** 系统只返回 from_owner_id=10 或 to_owner_id=10 的记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 资产分配记录详情
|
||||
|
||||
系统 SHALL 提供资产分配记录详情查询功能。
|
||||
|
||||
**API 端点**: `GET /api/admin/asset-allocation-records/:id`
|
||||
|
||||
**响应**:
|
||||
- 包含记录的所有字段
|
||||
- 关联卡 ID 列表(如果是设备分配,包含设备下的所有卡 ID)
|
||||
|
||||
#### Scenario: 查询分配记录详情
|
||||
|
||||
- **WHEN** 管理员查询分配记录详情(ID=1)
|
||||
- **THEN** 系统返回该记录的完整信息,包括来源/目标所有者名称、操作人名称等
|
||||
|
||||
#### Scenario: 查询不存在的记录
|
||||
|
||||
- **WHEN** 管理员查询不存在的分配记录(ID=999)
|
||||
- **THEN** 系统返回 404 错误,提示"分配记录不存在"
|
||||
@@ -0,0 +1,129 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 单卡分配功能
|
||||
|
||||
系统 SHALL 支持将单卡(未绑定设备的 IoT 卡)分配给直属下级店铺,实现资产所有权的层级流转。
|
||||
|
||||
**分配规则**:
|
||||
- 只能分配给直属下级店铺,不可跨级分配
|
||||
- 平台(shop_id=NULL)只能分配状态为在库(status=1)的卡
|
||||
- 代理店铺可以分配状态为已分销(status=2)的卡(继续往下分销)
|
||||
- 分配后状态变更:在库(1)→已分销(2),已分销(2)保持不变
|
||||
- 分配后 shop_id 变更为目标店铺 ID
|
||||
- 分配不涉及费用,纯资产所有权转移
|
||||
- 分配后上级仍能看到和管理(通过数据权限机制)
|
||||
|
||||
**选卡方式**(三选一):
|
||||
- ICCID 列表:指定具体的 ICCID 列表
|
||||
- 号段范围:指定起始 ICCID 和结束 ICCID
|
||||
- 筛选条件:按运营商、批次号、状态等条件批量选择
|
||||
|
||||
**API 端点**: `POST /api/admin/iot-cards/standalone/allocate`
|
||||
|
||||
**请求参数**:
|
||||
- `to_shop_id`(必填): 目标店铺 ID
|
||||
- `selection_type`(必填): 选卡方式,枚举值 "list" | "range" | "filter"
|
||||
- `iccids`(selection_type=list 时必填): ICCID 列表
|
||||
- `iccid_start`(selection_type=range 时必填): 起始 ICCID
|
||||
- `iccid_end`(selection_type=range 时必填): 结束 ICCID
|
||||
- `carrier_id`(selection_type=filter 时可选): 运营商 ID
|
||||
- `batch_no`(selection_type=filter 时可选): 批次号
|
||||
- `status`(selection_type=filter 时可选): 卡状态
|
||||
- `remark`(可选): 备注
|
||||
|
||||
**响应**:
|
||||
- `total_count`: 待分配总数
|
||||
- `success_count`: 成功数
|
||||
- `fail_count`: 失败数
|
||||
- `failed_items`: 失败项列表(包含 ICCID 和失败原因)
|
||||
|
||||
#### Scenario: 平台通过 ICCID 列表分配单卡给一级代理
|
||||
|
||||
- **WHEN** 平台管理员选择 3 张在库单卡(ICCID 列表),分配给一级代理店铺(ID=10)
|
||||
- **THEN** 系统将这 3 张卡的 shop_id 更新为 10,status 从 1 变为 2,创建分配记录,返回成功数 3
|
||||
|
||||
#### Scenario: 平台通过号段范围批量分配单卡
|
||||
|
||||
- **WHEN** 平台管理员指定 ICCID 范围 "8986001000000000000" 至 "8986001000000000099",分配给一级代理店铺(ID=10)
|
||||
- **THEN** 系统查询该范围内的所有在库单卡,批量更新 shop_id 和 status,创建分配记录
|
||||
|
||||
#### Scenario: 代理通过筛选条件分配单卡给下级
|
||||
|
||||
- **WHEN** 一级代理(店铺 ID=10)按条件筛选(运营商=电信,批次号=BATCH-001)自己的已分销卡,分配给二级代理店铺(ID=20)
|
||||
- **THEN** 系统查询符合条件的卡,校验店铺 20 是店铺 10 的直属下级,批量更新 shop_id 为 20,status 保持 2
|
||||
|
||||
#### Scenario: 拒绝跨级分配
|
||||
|
||||
- **WHEN** 平台尝试将卡直接分配给二级代理店铺(非直属下级)
|
||||
- **THEN** 系统拒绝分配,返回错误"只能分配给直属下级店铺"
|
||||
|
||||
#### Scenario: 拒绝平台分配已分销的卡
|
||||
|
||||
- **WHEN** 平台尝试分配状态为已分销(status=2)的卡
|
||||
- **THEN** 系统拒绝分配,返回错误"在库状态的卡才能分配,请先回收"
|
||||
|
||||
#### Scenario: 拒绝分配已绑定设备的卡
|
||||
|
||||
- **WHEN** 用户尝试分配已绑定设备的卡(在 device_sim_bindings 中 bind_status=1)
|
||||
- **THEN** 系统拒绝分配,返回错误"已绑定设备的卡不能单独分配"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 单卡回收功能
|
||||
|
||||
系统 SHALL 支持上级回收已分配给直属下级的单卡,将卡的所有权收回。
|
||||
|
||||
**回收规则**:
|
||||
- 只有上级可以回收,代理不能主动退回给上级
|
||||
- 只能回收直属下级的卡,不可跨级回收
|
||||
- 平台回收:shop_id 变为 NULL,status 变为 1(在库)
|
||||
- 店铺回收:shop_id 变为执行回收的店铺 ID,status 保持 2(已分销)
|
||||
- 只能回收单卡(未绑定设备的卡)
|
||||
|
||||
**选卡方式**(与分配相同,三选一):
|
||||
- ICCID 列表
|
||||
- 号段范围
|
||||
- 筛选条件
|
||||
|
||||
**API 端点**: `POST /api/admin/iot-cards/standalone/recall`
|
||||
|
||||
**请求参数**:
|
||||
- `from_shop_id`(必填): 来源店铺 ID(被回收方)
|
||||
- `selection_type`(必填): 选卡方式,枚举值 "list" | "range" | "filter"
|
||||
- `iccids`(selection_type=list 时必填): ICCID 列表
|
||||
- `iccid_start`(selection_type=range 时必填): 起始 ICCID
|
||||
- `iccid_end`(selection_type=range 时必填): 结束 ICCID
|
||||
- `carrier_id`(selection_type=filter 时可选): 运营商 ID
|
||||
- `batch_no`(selection_type=filter 时可选): 批次号
|
||||
- `remark`(可选): 备注
|
||||
|
||||
**响应**:
|
||||
- `total_count`: 待回收总数
|
||||
- `success_count`: 成功数
|
||||
- `fail_count`: 失败数
|
||||
- `failed_items`: 失败项列表
|
||||
|
||||
#### Scenario: 平台回收一级代理的单卡
|
||||
|
||||
- **WHEN** 平台管理员选择一级代理店铺(ID=10)的 5 张单卡进行回收
|
||||
- **THEN** 系统将这 5 张卡的 shop_id 更新为 NULL,status 从 2 变为 1,创建回收记录
|
||||
|
||||
#### Scenario: 一级代理回收二级代理的单卡
|
||||
|
||||
- **WHEN** 一级代理(店铺 ID=10)选择二级代理店铺(ID=20)的 3 张单卡进行回收
|
||||
- **THEN** 系统将这 3 张卡的 shop_id 更新为 10,status 保持 2,创建回收记录
|
||||
|
||||
#### Scenario: 拒绝回收非直属下级的卡
|
||||
|
||||
- **WHEN** 一级代理(店铺 ID=10)尝试回收非直属下级店铺(ID=30,归属于店铺 ID=20)的卡
|
||||
- **THEN** 系统拒绝回收,返回错误"只能回收直属下级店铺的卡"
|
||||
|
||||
#### Scenario: 拒绝代理主动退回
|
||||
|
||||
- **WHEN** 二级代理(店铺 ID=20)尝试将卡退回给上级店铺(ID=10)
|
||||
- **THEN** 系统拒绝操作,返回错误"不能主动退回卡给上级,请联系上级进行回收"
|
||||
|
||||
#### Scenario: 拒绝回收已绑定设备的卡
|
||||
|
||||
- **WHEN** 用户尝试回收已绑定设备的卡
|
||||
- **THEN** 系统拒绝回收,返回错误"已绑定设备的卡不能单独回收"
|
||||
@@ -0,0 +1,76 @@
|
||||
# Tasks: 单卡资产分配功能
|
||||
|
||||
## 1. DTO 定义
|
||||
|
||||
- [x] 1.1 创建 AllocateStandaloneCardsRequest DTO(支持三种选卡方式)
|
||||
- [x] 1.2 创建 AllocateStandaloneCardsResponse DTO(返回成功/失败统计)
|
||||
- [x] 1.3 创建 RecallStandaloneCardsRequest DTO
|
||||
- [x] 1.4 创建 RecallStandaloneCardsResponse DTO
|
||||
- [x] 1.5 创建 ListAssetAllocationRecordRequest DTO
|
||||
- [x] 1.6 创建 AssetAllocationRecordResponse DTO
|
||||
- [x] 1.7 创建 ListAssetAllocationRecordResponse DTO
|
||||
|
||||
## 2. Store 层
|
||||
|
||||
- [x] 2.1 创建 AssetAllocationRecordStore
|
||||
- [x] 2.1.1 实现 Create 方法
|
||||
- [x] 2.1.2 实现 BatchCreate 方法
|
||||
- [x] 2.1.3 实现 GetByID 方法
|
||||
- [x] 2.1.4 实现 List 方法(支持筛选和分页)
|
||||
- [x] 2.2 实现 IotCardStore.BatchUpdateShopIDAndStatus 方法
|
||||
- [x] 2.3 实现 IotCardStore.GetStandaloneByICCIDRange 方法(号段查询)
|
||||
- [x] 2.4 实现 IotCardStore.GetStandaloneByFilters 方法(筛选条件查询,排除已绑定设备的卡)
|
||||
- [x] 2.5 实现 IotCardStore.GetByICCIDs 方法
|
||||
- [x] 2.6 实现 IotCardStore.GetBoundCardIDs 方法
|
||||
|
||||
## 3. Service 层
|
||||
|
||||
- [x] 3.1 创建 AssetAllocationRecordService
|
||||
- [x] 3.1.1 实现 List 方法
|
||||
- [x] 3.1.2 实现 GetByID 方法
|
||||
- [x] 3.2 实现 IotCardService.AllocateCards 方法
|
||||
- [x] 3.2.1 校验目标店铺是当前用户的直属下级
|
||||
- [x] 3.2.2 根据选卡方式获取待分配的卡列表
|
||||
- [x] 3.2.3 校验卡是单卡(未绑定设备)
|
||||
- [x] 3.2.4 校验卡状态和所有权
|
||||
- [x] 3.2.5 批量更新 shop_id 和 status
|
||||
- [x] 3.2.6 创建分配记录
|
||||
- [x] 3.3 实现 IotCardService.RecallCards 方法
|
||||
- [x] 3.3.1 校验来源店铺是当前用户的直属下级
|
||||
- [x] 3.3.2 根据选卡方式获取待回收的卡列表
|
||||
- [x] 3.3.3 校验卡是单卡(未绑定设备)
|
||||
- [x] 3.3.4 批量更新 shop_id(平台→NULL,店铺→回收方ID)和 status
|
||||
- [x] 3.3.5 创建回收记录
|
||||
|
||||
## 4. Handler 层
|
||||
|
||||
- [x] 4.1 创建 AssetAllocationRecordHandler
|
||||
- [x] 4.1.1 实现 List 方法
|
||||
- [x] 4.1.2 实现 GetByID 方法
|
||||
- [x] 4.2 实现 IotCardHandler.AllocateCards 方法
|
||||
- [x] 4.3 实现 IotCardHandler.RecallCards 方法
|
||||
|
||||
## 5. 路由注册
|
||||
|
||||
- [x] 5.1 注册 POST /api/admin/iot-cards/standalone/allocate
|
||||
- [x] 5.2 注册 POST /api/admin/iot-cards/standalone/recall
|
||||
- [x] 5.3 注册 GET /api/admin/asset-allocation-records
|
||||
- [x] 5.4 注册 GET /api/admin/asset-allocation-records/:id
|
||||
- [x] 5.5 更新 Bootstrap(handlers.go、services.go、stores.go、types.go)
|
||||
- [x] 5.6 更新文档生成器(cmd/api/docs.go 和 cmd/gendocs/main.go)
|
||||
|
||||
## 6. 测试
|
||||
|
||||
- [x] 6.1 AssetAllocationRecordStore 单元测试
|
||||
- [x] 6.2 IotCardStore.BatchUpdateShopIDAndStatus 单元测试
|
||||
- [x] 6.3 IotCardStore.GetStandaloneByICCIDRange 单元测试
|
||||
- [x] 6.4 IotCardStore.GetStandaloneByFilters 单元测试
|
||||
- [x] 6.5 IotCardStore.GetByICCIDs 单元测试
|
||||
- [x] 6.6 IotCardStore.GetBoundCardIDs 单元测试
|
||||
- [x] 6.7 分配 API 集成测试(TestStandaloneCardAllocation_AllocateByList)
|
||||
- [x] 6.8 回收 API 集成测试(TestStandaloneCardAllocation_Recall)
|
||||
- [x] 6.9 分配记录查询 API 集成测试(TestAssetAllocationRecord_List)
|
||||
|
||||
## 7. 文档更新
|
||||
|
||||
- [x] 7.1 同步 delta spec 到主规范
|
||||
107
openspec/specs/asset-allocation-record/spec.md
Normal file
107
openspec/specs/asset-allocation-record/spec.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Asset Allocation Record
|
||||
|
||||
## Purpose
|
||||
|
||||
管理资产(IoT 卡、设备)在平台与代理商之间的流转记录,支持分配和回收操作的完整追溯。
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 资产分配记录查询
|
||||
|
||||
系统 SHALL 提供资产分配记录的查询功能,支持查看卡和设备在平台与代理商之间的流转历史。
|
||||
|
||||
**记录类型**:
|
||||
- `allocate`: 分配记录(上级分配给下级)
|
||||
- `recall`: 回收记录(上级从下级回收)
|
||||
|
||||
**资产类型**:
|
||||
- `iot_card`: 物联网卡(单卡)
|
||||
- `device`: 设备(未来扩展)
|
||||
|
||||
**查询条件**:
|
||||
- `allocation_type`(可选): 分配类型,枚举值 "allocate" | "recall"
|
||||
- `asset_type`(可选): 资产类型,枚举值 "iot_card" | "device"
|
||||
- `asset_identifier`(可选): 资产标识符(ICCID 或设备号),模糊匹配
|
||||
- `allocation_no`(可选): 分配单号,精确匹配
|
||||
- `from_shop_id`(可选): 来源店铺 ID
|
||||
- `to_shop_id`(可选): 目标店铺 ID
|
||||
- `operator_id`(可选): 操作人 ID
|
||||
- `created_at_start`(可选): 创建时间起始
|
||||
- `created_at_end`(可选): 创建时间结束
|
||||
|
||||
**分页**:
|
||||
- 默认每页 20 条,最大每页 100 条
|
||||
- 返回总记录数和总页数
|
||||
|
||||
**数据权限**:
|
||||
- 平台用户可查看所有记录
|
||||
- 代理用户只能查看与自己店铺相关的记录(作为来源或目标)
|
||||
|
||||
**API 端点**: `GET /api/admin/asset-allocation-records`
|
||||
|
||||
**响应字段**:
|
||||
- `id`: 记录 ID
|
||||
- `allocation_no`: 分配单号
|
||||
- `allocation_type`: 分配类型
|
||||
- `allocation_type_name`: 分配类型名称(分配/回收)
|
||||
- `asset_type`: 资产类型
|
||||
- `asset_type_name`: 资产类型名称(物联网卡/设备)
|
||||
- `asset_id`: 资产 ID
|
||||
- `asset_identifier`: 资产标识符
|
||||
- `from_owner_type`: 来源所有者类型
|
||||
- `from_owner_id`: 来源所有者 ID
|
||||
- `from_owner_name`: 来源所有者名称
|
||||
- `to_owner_type`: 目标所有者类型
|
||||
- `to_owner_id`: 目标所有者 ID
|
||||
- `to_owner_name`: 目标所有者名称
|
||||
- `operator_id`: 操作人 ID
|
||||
- `operator_name`: 操作人名称
|
||||
- `remark`: 备注
|
||||
- `created_at`: 创建时间
|
||||
|
||||
#### Scenario: 查询所有分配记录
|
||||
|
||||
- **WHEN** 平台管理员查询分配记录列表,不带任何筛选条件
|
||||
- **THEN** 系统返回所有分配和回收记录,按创建时间倒序排列
|
||||
|
||||
#### Scenario: 按资产类型筛选记录
|
||||
|
||||
- **WHEN** 管理员查询资产类型为 "iot_card" 的记录
|
||||
- **THEN** 系统只返回物联网卡的分配/回收记录,不包含设备记录
|
||||
|
||||
#### Scenario: 按分配类型筛选记录
|
||||
|
||||
- **WHEN** 管理员查询分配类型为 "allocate" 的记录
|
||||
- **THEN** 系统只返回分配记录,不包含回收记录
|
||||
|
||||
#### Scenario: 按 ICCID 模糊查询
|
||||
|
||||
- **WHEN** 管理员输入 asset_identifier = "8986001"
|
||||
- **THEN** 系统返回 ICCID 包含 "8986001" 的所有分配记录
|
||||
|
||||
#### Scenario: 代理查询自己相关的记录
|
||||
|
||||
- **WHEN** 代理用户(店铺 ID=10)查询分配记录
|
||||
- **THEN** 系统只返回 from_owner_id=10 或 to_owner_id=10 的记录
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 资产分配记录详情
|
||||
|
||||
系统 SHALL 提供资产分配记录详情查询功能。
|
||||
|
||||
**API 端点**: `GET /api/admin/asset-allocation-records/:id`
|
||||
|
||||
**响应**:
|
||||
- 包含记录的所有字段
|
||||
- 关联卡 ID 列表(如果是设备分配,包含设备下的所有卡 ID)
|
||||
|
||||
#### Scenario: 查询分配记录详情
|
||||
|
||||
- **WHEN** 管理员查询分配记录详情(ID=1)
|
||||
- **THEN** 系统返回该记录的完整信息,包括来源/目标所有者名称、操作人名称等
|
||||
|
||||
#### Scenario: 查询不存在的记录
|
||||
|
||||
- **WHEN** 管理员查询不存在的分配记录(ID=999)
|
||||
- **THEN** 系统返回 404 错误,提示"分配记录不存在"
|
||||
@@ -373,3 +373,133 @@ This capability supports:
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 单卡分配功能
|
||||
|
||||
系统 SHALL 支持将单卡(未绑定设备的 IoT 卡)分配给直属下级店铺,实现资产所有权的层级流转。
|
||||
|
||||
**分配规则**:
|
||||
- 只能分配给直属下级店铺,不可跨级分配
|
||||
- 平台(shop_id=NULL)只能分配状态为在库(status=1)的卡
|
||||
- 代理店铺可以分配状态为已分销(status=2)的卡(继续往下分销)
|
||||
- 分配后状态变更:在库(1)→已分销(2),已分销(2)保持不变
|
||||
- 分配后 shop_id 变更为目标店铺 ID
|
||||
- 分配不涉及费用,纯资产所有权转移
|
||||
- 分配后上级仍能看到和管理(通过数据权限机制)
|
||||
|
||||
**选卡方式**(三选一):
|
||||
- ICCID 列表:指定具体的 ICCID 列表
|
||||
- 号段范围:指定起始 ICCID 和结束 ICCID
|
||||
- 筛选条件:按运营商、批次号、状态等条件批量选择
|
||||
|
||||
**API 端点**: `POST /api/admin/iot-cards/standalone/allocate`
|
||||
|
||||
**请求参数**:
|
||||
- `to_shop_id`(必填): 目标店铺 ID
|
||||
- `selection_type`(必填): 选卡方式,枚举值 "list" | "range" | "filter"
|
||||
- `iccids`(selection_type=list 时必填): ICCID 列表
|
||||
- `iccid_start`(selection_type=range 时必填): 起始 ICCID
|
||||
- `iccid_end`(selection_type=range 时必填): 结束 ICCID
|
||||
- `carrier_id`(selection_type=filter 时可选): 运营商 ID
|
||||
- `batch_no`(selection_type=filter 时可选): 批次号
|
||||
- `status`(selection_type=filter 时可选): 卡状态
|
||||
- `remark`(可选): 备注
|
||||
|
||||
**响应**:
|
||||
- `total_count`: 待分配总数
|
||||
- `success_count`: 成功数
|
||||
- `fail_count`: 失败数
|
||||
- `failed_items`: 失败项列表(包含 ICCID 和失败原因)
|
||||
|
||||
#### Scenario: 平台通过 ICCID 列表分配单卡给一级代理
|
||||
|
||||
- **WHEN** 平台管理员选择 3 张在库单卡(ICCID 列表),分配给一级代理店铺(ID=10)
|
||||
- **THEN** 系统将这 3 张卡的 shop_id 更新为 10,status 从 1 变为 2,创建分配记录,返回成功数 3
|
||||
|
||||
#### Scenario: 平台通过号段范围批量分配单卡
|
||||
|
||||
- **WHEN** 平台管理员指定 ICCID 范围 "8986001000000000000" 至 "8986001000000000099",分配给一级代理店铺(ID=10)
|
||||
- **THEN** 系统查询该范围内的所有在库单卡,批量更新 shop_id 和 status,创建分配记录
|
||||
|
||||
#### Scenario: 代理通过筛选条件分配单卡给下级
|
||||
|
||||
- **WHEN** 一级代理(店铺 ID=10)按条件筛选(运营商=电信,批次号=BATCH-001)自己的已分销卡,分配给二级代理店铺(ID=20)
|
||||
- **THEN** 系统查询符合条件的卡,校验店铺 20 是店铺 10 的直属下级,批量更新 shop_id 为 20,status 保持 2
|
||||
|
||||
#### Scenario: 拒绝跨级分配
|
||||
|
||||
- **WHEN** 平台尝试将卡直接分配给二级代理店铺(非直属下级)
|
||||
- **THEN** 系统拒绝分配,返回错误"只能分配给直属下级店铺"
|
||||
|
||||
#### Scenario: 拒绝平台分配已分销的卡
|
||||
|
||||
- **WHEN** 平台尝试分配状态为已分销(status=2)的卡
|
||||
- **THEN** 系统拒绝分配,返回错误"在库状态的卡才能分配,请先回收"
|
||||
|
||||
#### Scenario: 拒绝分配已绑定设备的卡
|
||||
|
||||
- **WHEN** 用户尝试分配已绑定设备的卡(在 device_sim_bindings 中 bind_status=1)
|
||||
- **THEN** 系统拒绝分配,返回错误"已绑定设备的卡不能单独分配"
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 单卡回收功能
|
||||
|
||||
系统 SHALL 支持上级回收已分配给直属下级的单卡,将卡的所有权收回。
|
||||
|
||||
**回收规则**:
|
||||
- 只有上级可以回收,代理不能主动退回给上级
|
||||
- 只能回收直属下级的卡,不可跨级回收
|
||||
- 平台回收:shop_id 变为 NULL,status 变为 1(在库)
|
||||
- 店铺回收:shop_id 变为执行回收的店铺 ID,status 保持 2(已分销)
|
||||
- 只能回收单卡(未绑定设备的卡)
|
||||
|
||||
**选卡方式**(与分配相同,三选一):
|
||||
- ICCID 列表
|
||||
- 号段范围
|
||||
- 筛选条件
|
||||
|
||||
**API 端点**: `POST /api/admin/iot-cards/standalone/recall`
|
||||
|
||||
**请求参数**:
|
||||
- `from_shop_id`(必填): 来源店铺 ID(被回收方)
|
||||
- `selection_type`(必填): 选卡方式,枚举值 "list" | "range" | "filter"
|
||||
- `iccids`(selection_type=list 时必填): ICCID 列表
|
||||
- `iccid_start`(selection_type=range 时必填): 起始 ICCID
|
||||
- `iccid_end`(selection_type=range 时必填): 结束 ICCID
|
||||
- `carrier_id`(selection_type=filter 时可选): 运营商 ID
|
||||
- `batch_no`(selection_type=filter 时可选): 批次号
|
||||
- `remark`(可选): 备注
|
||||
|
||||
**响应**:
|
||||
- `total_count`: 待回收总数
|
||||
- `success_count`: 成功数
|
||||
- `fail_count`: 失败数
|
||||
- `failed_items`: 失败项列表
|
||||
|
||||
#### Scenario: 平台回收一级代理的单卡
|
||||
|
||||
- **WHEN** 平台管理员选择一级代理店铺(ID=10)的 5 张单卡进行回收
|
||||
- **THEN** 系统将这 5 张卡的 shop_id 更新为 NULL,status 从 2 变为 1,创建回收记录
|
||||
|
||||
#### Scenario: 一级代理回收二级代理的单卡
|
||||
|
||||
- **WHEN** 一级代理(店铺 ID=10)选择二级代理店铺(ID=20)的 3 张单卡进行回收
|
||||
- **THEN** 系统将这 3 张卡的 shop_id 更新为 10,status 保持 2,创建回收记录
|
||||
|
||||
#### Scenario: 拒绝回收非直属下级的卡
|
||||
|
||||
- **WHEN** 一级代理(店铺 ID=10)尝试回收非直属下级店铺(ID=30,归属于店铺 ID=20)的卡
|
||||
- **THEN** 系统拒绝回收,返回错误"只能回收直属下级店铺的卡"
|
||||
|
||||
#### Scenario: 拒绝代理主动退回
|
||||
|
||||
- **WHEN** 二级代理(店铺 ID=20)尝试将卡退回给上级店铺(ID=10)
|
||||
- **THEN** 系统拒绝操作,返回错误"不能主动退回卡给上级,请联系上级进行回收"
|
||||
|
||||
#### Scenario: 拒绝回收已绑定设备的卡
|
||||
|
||||
- **WHEN** 用户尝试回收已绑定设备的卡
|
||||
- **THEN** 系统拒绝回收,返回错误"已绑定设备的卡不能单独回收"
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user