- 添加 IoT 核心业务表:运营商、IoT 卡、设备、号卡、套餐、订单等 - 添加分佣系统表:分佣规则、分佣记录、运营商结算等 - 添加轮询和流量管理表:轮询配置、流量使用记录等 - 添加财务和系统管理表:佣金提现、换卡申请等 - 实现完整的 GORM 模型和常量定义 - 添加数据库迁移脚本和详细文档 - 集成 OpenSpec 工作流工具(opsx 命令和 skills) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
11 KiB
11 KiB
IoT Package Management
Purpose
Manage IoT packages (data plans) for IoT cards and devices, including package definitions, real/virtual data coexistence, single-card packages, device-level packages with shared data pools, and agent package allocation.
This capability supports:
- Package entity definition with real and virtual data types
- Formal packages and addon packages (data top-ups)
- Single-card package purchases
- Device-level package purchases with shared data pool across all bound cards
- Agent package allocation with retail pricing
- Commission calculation (counted once for device-level packages regardless of card count)
Requirements
ADDED Requirements
Requirement: 套餐实体定义
系统 SHALL 定义套餐(Package)实体,包含套餐的基本属性、定价、流量配置。
核心概念: 套餐只适用于 IoT 卡(ICCID),用户可以为单张 IoT 卡购买套餐,也可以为设备购买套餐(套餐分配到设备绑定的所有 IoT 卡,流量设备级共享)。
实体字段:
id: 套餐 ID(主键,BIGINT)package_code: 套餐编码(VARCHAR(50),唯一)package_name: 套餐名称(VARCHAR(255))series_id: 套餐系列 ID(BIGINT,关联 package_series 表,用于组织套餐分组和配置一次性分佣)package_type: 套餐类型(VARCHAR(20),"formal"-正式套餐 | "addon"-加油包)duration_months: 套餐时长(INT,月数,1-月套餐 12-年套餐,加油包为 0)real_data_mb: 真流量额度(BIGINT,MB 为单位,可选)virtual_data_mb: 虚流量额度(BIGINT,MB 为单位,用于停机判断,可选)data_amount_mb: 总流量额度(BIGINT,MB 为单位,real_data_mb + virtual_data_mb)price: 套餐价格(DECIMAL(10,2),元)status: 套餐状态(INT,1-上架 2-下架)created_at: 创建时间(TIMESTAMP,自动填充)updated_at: 更新时间(TIMESTAMP,自动填充)
套餐类型说明:
- 正式套餐(formal): 每张 IoT 卡只能有一个有效的正式套餐,购买新的正式套餐会替换旧的
- 加油包(addon): 每张 IoT 卡可以购买多个加油包,与正式套餐共存
Scenario: 创建月套餐
- WHEN 平台创建月套餐,套餐编码为 "PKG-M-001",套餐名称为 "月套餐 10GB",套餐系列 ID 为 1,类型为正式套餐,时长为 1 个月,真流量为 10240 MB,虚流量为 0,价格为 30.00 元
- THEN 系统创建套餐记录,
package_code为 "PKG-M-001",series_id为 1,package_type为 "formal",duration_months为 1,real_data_mb为 10240,virtual_data_mb为 0,data_amount_mb为 10240,price为 30.00
Scenario: 创建年套餐
- WHEN 平台创建年套餐,套餐编码为 "PKG-Y-001",套餐名称为 "年套餐 120GB",套餐系列 ID 为 1,类型为正式套餐,时长为 12 个月,真流量为 122880 MB,虚流量为 0,价格为 300.00 元
- THEN 系统创建套餐记录,
package_code为 "PKG-Y-001",series_id为 1,package_type为 "formal",duration_months为 12,real_data_mb为 122880,virtual_data_mb为 0,data_amount_mb为 122880,price为 300.00
Scenario: 创建流量加油包
- WHEN 平台创建加油包,套餐编码为 "PKG-ADD-001",套餐名称为 "流量包 5GB",套餐系列 ID 为 2,类型为加油包,时长为 0,真流量为 5120 MB,虚流量为 0,价格为 10.00 元
- THEN 系统创建套餐记录,
package_code为 "PKG-ADD-001",series_id为 2,package_type为 "addon",duration_months为 0,real_data_mb为 5120,virtual_data_mb为 0,data_amount_mb为 5120,price为 10.00
Requirement: 套餐流量类型和真虚流量共存
系统 SHALL 支持真流量和虚流量两种流量类型,两者可以共存于同一套餐中。
流量类型定义:
- 真流量(real_data_mb): 实际可用的流量,可在运营商网络中使用
- 虚流量(virtual_data_mb): 虚拟流量,用于停机判断(虚流量用完后停机,即使真流量还有剩余)
- 总流量(data_amount_mb): 真流量 + 虚流量的总和
重要规则:
- 真流量和虚流量可以同时存在于一个套餐中
- 停机判断基于虚流量(虚流量用完后停机)
- 套餐可以只有真流量、只有虚流量、或两者都有
Scenario: 创建真虚流量共存的套餐
- WHEN 平台创建套餐,真流量为 8000 MB,虚流量为 2000 MB
- THEN 系统创建套餐记录,
real_data_mb为 8000,virtual_data_mb为 2000,data_amount_mb为 10000
Scenario: 创建纯真流量套餐
- WHEN 平台创建套餐,真流量为 10240 MB,虚流量为 0
- THEN 系统创建套餐记录,
real_data_mb为 10240,virtual_data_mb为 0,data_amount_mb为 10240
Scenario: 创建纯虚流量套餐
- WHEN 平台创建套餐,真流量为 0,虚流量为 10240 MB
- THEN 系统创建套餐记录,
real_data_mb为 0,virtual_data_mb为 10240,data_amount_mb为 10240
Scenario: 虚流量用完停机
- WHEN 套餐的虚流量为 2000 MB,用户已使用 2000 MB 虚流量,但真流量还剩余 5000 MB
- THEN 系统判断虚流量已用完,触发停机操作,即使真流量还有剩余
Requirement: 单卡套餐购买
系统 SHALL 支持用户为单张 IoT 卡购买套餐。
购买规则:
- 每张 IoT 卡只能有一个有效的正式套餐
- 购买新的正式套餐会替换旧的正式套餐
- 可以同时购买多个加油包
- 套餐购买后创建套餐订单记录
Scenario: 为 IoT 卡购买正式套餐
- WHEN 用户为 IoT 卡(ICCID 为 "8986...")购买月套餐(套餐 ID 为 1001),价格为 30.00 元
- THEN 系统创建套餐订单,
order_type为 1(套餐订单),iot_card_id为 IoT 卡 ID,package_id为 1001,amount为 30.00
Scenario: 为 IoT 卡购买加油包
- WHEN 用户为 IoT 卡购买流量加油包(套餐 ID 为 2001),价格为 10.00 元
- THEN 系统创建套餐订单,IoT 卡的正式套餐保持不变,加油包作为额外套餐生效
Scenario: 购买新正式套餐替换旧套餐
- WHEN 用户为 IoT 卡购买新的月套餐,该 IoT 卡已有月套餐
- THEN 系统创建新订单,旧的正式套餐失效,新套餐生效
Requirement: 设备级套餐购买和流量共享
系统 SHALL 支持用户为设备购买套餐,套餐分配到设备绑定的所有 IoT 卡,流量设备级共享。
设备套餐业务规则:
- 用户为设备购买套餐时,套餐会分配到设备绑定的所有 IoT 卡(1-4 张)
- 套餐的流量是设备级别共享的(例如 3000G/月共享,不管用哪张卡)
- 分佣只计算一次(不按卡数倍增)
- 订单表通过
device_id字段关联设备,通过device_sim_bindings表查找绑定的所有 IoT 卡 - 设备购买的套餐不受单卡套餐限制(设备套餐和单卡套餐独立管理)
流量共享机制:
- 设备绑定的所有 IoT 卡共享套餐流量池
- 任意一张 IoT 卡使用流量都会从共享池扣除
- 流量池耗尽后,所有绑定的 IoT 卡都无法使用
订单记录:
- 订单表
device_id字段记录设备 ID(设备级套餐订单) - 订单表
iot_card_id字段为 NULL(不关联具体 IoT 卡) - 通过
device_sim_bindings表查询设备绑定的所有 IoT 卡
Scenario: 为设备购买套餐
- WHEN 用户为设备(ID 为 1001,绑定 3 张 IoT 卡)购买年套餐,价格为 399.00 元,流量为 3000G/月
- THEN 系统创建套餐订单,
order_type为 1(套餐订单),device_id为 1001,iot_card_id为 NULL,amount为 399.00,套餐分配到 3 张绑定的 IoT 卡
Scenario: 设备流量共享
- WHEN 设备(绑定 3 张 IoT 卡)购买套餐 3000G/月,其中一张 IoT 卡使用 1000G 流量
- THEN 流量池剩余 2000G,其他两张 IoT 卡可以使用剩余的 2000G
Scenario: 设备套餐分佣只计算一次
- WHEN 设备(绑定 3 张 IoT 卡)购买套餐,长期佣金为 100.00 元
- THEN 系统创建一条分佣记录,金额为 100.00 元(不是 3 × 100.00 元)
Requirement: 套餐分配给代理
系统 SHALL 支持将套餐分配给代理商,代理可以在平台设置的成本价基础上加价销售。
分配规则:
- 平台为套餐设置成本价(分配给代理的价格)
- 代理可以在成本价基础上加价,但不能超过成本价的 2 倍
- 分配记录存储在
agent_package_allocations表
agent_package_allocations 表:
id: 分配记录 ID(主键,BIGINT)agent_id: 代理用户 ID(BIGINT)package_id: 套餐 ID(BIGINT)cost_price: 成本价(DECIMAL(10,2),平台给代理的价格)retail_price: 零售价(DECIMAL(10,2),代理设置的终端销售价格)status: 分配状态(INT,1-有效 2-无效)created_at: 创建时间(TIMESTAMP,自动填充)updated_at: 更新时间(TIMESTAMP,自动填充)
Scenario: 平台分配套餐给代理
- WHEN 平台将套餐(ID 为 1001)分配给代理(用户 ID 为 123),成本价为 25.00 元
- THEN 系统创建分配记录,
agent_id为 123,package_id为 1001,cost_price为 25.00,状态为 1(有效)
Scenario: 代理设置零售价
- WHEN 代理(用户 ID 为 123)为套餐(ID 为 1001)设置零售价为 30.00 元
- THEN 系统更新分配记录,
retail_price为 30.00
Scenario: 代理零售价超过 2 倍成本价
- WHEN 代理设置零售价为 60.00 元,成本价为 25.00 元(2 倍为 50.00 元)
- THEN 系统拒绝设置,返回错误信息"零售价不能超过成本价的 2 倍"
Requirement: 套餐数据校验
系统 SHALL 对套餐数据进行校验,确保数据完整性和一致性。
校验规则:
- 套餐编码(package_code):必填,长度 1-50 字符,唯一
- 套餐名称(package_name):必填,长度 1-255 字符
- 套餐系列 ID(series_id):必填,≥ 1,必须是有效的套餐系列 ID
- 套餐类型(package_type):必填,枚举值 "formal" | "addon"
- 套餐时长(duration_months):必填,≥ 0(正式套餐 ≥ 1,加油包为 0)
- 真流量额度(real_data_mb):可选,≥ 0
- 虚流量额度(virtual_data_mb):可选,≥ 0
- 总流量额度(data_amount_mb):必填,≥ 0,必须等于 real_data_mb + virtual_data_mb
- 套餐价格(price):必填,≥ 0,最多 2 位小数
- 状态(status):必填,枚举值 1(上架) | 2(下架)
Scenario: 创建套餐时价格为负数
- WHEN 平台创建套餐,价格为 -10.00
- THEN 系统拒绝创建,返回错误信息"套餐价格必须 ≥ 0"
Scenario: 创建套餐时套餐编码重复
- WHEN 平台创建套餐,套餐编码为已存在的 "PKG-M-001"
- THEN 系统拒绝创建,返回错误信息"套餐编码已存在"
Scenario: 创建正式套餐时时长为 0
- WHEN 平台创建正式套餐,套餐类型为 "formal",时长为 0
- THEN 系统拒绝创建,返回错误信息"正式套餐时长必须 ≥ 1"