Files
huang 194078674a
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 4m45s
feat: 实现单卡资产分配与回收功能
- 新增单卡分配/回收 API(支持 ICCID 列表、号段范围、筛选条件三种选卡方式)
- 新增资产分配记录查询 API(支持多条件筛选和分页)
- 新增 AssetAllocationRecord 模型、Store、Service、Handler 完整实现
- 扩展 IotCardStore 新增批量更新、号段查询、筛选查询等方法
- 修复 GORM Callback 处理 slice 类型(BatchCreate)的问题
- 新增完整的单元测试和集成测试
- 同步 OpenSpec 规范并归档 change
2026-01-24 15:46:15 +08:00

5.6 KiB
Raw Blame History

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"
  • iccidsselection_type=list 时必填): ICCID 列表
  • iccid_startselection_type=range 时必填): 起始 ICCID
  • iccid_endselection_type=range 时必填): 结束 ICCID
  • carrier_idselection_type=filter 时可选): 运营商 ID
  • batch_noselection_type=filter 时可选): 批次号
  • statusselection_type=filter 时可选): 卡状态
  • remark(可选): 备注

响应:

  • total_count: 待分配总数
  • success_count: 成功数
  • fail_count: 失败数
  • failed_items: 失败项列表(包含 ICCID 和失败原因)

Scenario: 平台通过 ICCID 列表分配单卡给一级代理

  • WHEN 平台管理员选择 3 张在库单卡ICCID 列表分配给一级代理店铺ID=10
  • THEN 系统将这 3 张卡的 shop_id 更新为 10status 从 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 为 20status 保持 2

Scenario: 拒绝跨级分配

  • WHEN 平台尝试将卡直接分配给二级代理店铺(非直属下级)
  • THEN 系统拒绝分配,返回错误"只能分配给直属下级店铺"

Scenario: 拒绝平台分配已分销的卡

  • WHEN 平台尝试分配状态为已分销(status=2)的卡
  • THEN 系统拒绝分配,返回错误"在库状态的卡才能分配,请先回收"

Scenario: 拒绝分配已绑定设备的卡

  • WHEN 用户尝试分配已绑定设备的卡(在 device_sim_bindings 中 bind_status=1
  • THEN 系统拒绝分配,返回错误"已绑定设备的卡不能单独分配"

Requirement: 单卡回收功能

系统 SHALL 支持上级回收已分配给直属下级的单卡,将卡的所有权收回。

回收规则:

  • 只有上级可以回收,代理不能主动退回给上级
  • 只能回收直属下级的卡,不可跨级回收
  • 平台回收shop_id 变为 NULLstatus 变为 1在库
  • 店铺回收shop_id 变为执行回收的店铺 IDstatus 保持 2已分销
  • 只能回收单卡(未绑定设备的卡)

选卡方式(与分配相同,三选一):

  • ICCID 列表
  • 号段范围
  • 筛选条件

API 端点: POST /api/admin/iot-cards/standalone/recall

请求参数:

  • from_shop_id(必填): 来源店铺 ID被回收方
  • selection_type(必填): 选卡方式,枚举值 "list" | "range" | "filter"
  • iccidsselection_type=list 时必填): ICCID 列表
  • iccid_startselection_type=range 时必填): 起始 ICCID
  • iccid_endselection_type=range 时必填): 结束 ICCID
  • carrier_idselection_type=filter 时可选): 运营商 ID
  • batch_noselection_type=filter 时可选): 批次号
  • remark(可选): 备注

响应:

  • total_count: 待回收总数
  • success_count: 成功数
  • fail_count: 失败数
  • failed_items: 失败项列表

Scenario: 平台回收一级代理的单卡

  • WHEN 平台管理员选择一级代理店铺ID=10的 5 张单卡进行回收
  • THEN 系统将这 5 张卡的 shop_id 更新为 NULLstatus 从 2 变为 1创建回收记录

Scenario: 一级代理回收二级代理的单卡

  • WHEN 一级代理(店铺 ID=10选择二级代理店铺ID=20的 3 张单卡进行回收
  • THEN 系统将这 3 张卡的 shop_id 更新为 10status 保持 2创建回收记录

Scenario: 拒绝回收非直属下级的卡

  • WHEN 一级代理(店铺 ID=10尝试回收非直属下级店铺ID=30归属于店铺 ID=20的卡
  • THEN 系统拒绝回收,返回错误"只能回收直属下级店铺的卡"

Scenario: 拒绝代理主动退回

  • WHEN 二级代理(店铺 ID=20尝试将卡退回给上级店铺ID=10
  • THEN 系统拒绝操作,返回错误"不能主动退回卡给上级,请联系上级进行回收"

Scenario: 拒绝回收已绑定设备的卡

  • WHEN 用户尝试回收已绑定设备的卡
  • THEN 系统拒绝回收,返回错误"已绑定设备的卡不能单独回收"