本次提交完成 add-wallet-transfer-tag-models 提案的实施和归档: ## 新增功能模块 - 钱包系统:用户/代理钱包管理,支持充值、扣款、退款、乐观锁防并发 - 换卡记录:物联卡更换历史追溯,包含套餐快照(JSONB) - 标签系统:设备/IoT卡/号卡的统一标签管理 - 运营商渠道:四大运营商(CMCC/CUCC/CTCC/CBN)的渠道管理 ## 数据库变更 - 新增 6 张表:tb_wallet, tb_wallet_transaction, tb_recharge_record, tb_card_replacement_record, tb_tag, tb_resource_tag - 修改 2 张表:tb_carrier(新增渠道字段), tb_order(新增混合支付字段) - 迁移版本:v6 → v7(执行时间 282.5ms) ## 代码变更 - 新增 8 个 Go 模型(符合统一规范:gorm.Model + BaseModel) - 新增 40+ 个常量定义(含完整中文注释) - 新增 7 个 Redis Key 生成函数 - 修复模型规范:移除重复字段,统一使用 gorm.Model 嵌入 ## 文档变更 - 新增 3 个业务文档:数据模型设计、字段说明、迁移验证报告 - 更新 AGENTS.md:新增 Model 模型规范和常量注释规范 - 新增 4 个 OpenSpec 规范:wallet, carrier, card-replacement, tag - 更新 1 个 OpenSpec 规范:iot-order(支持混合支付) ## 验证通过 - ✅ LSP 诊断:所有模型和常量文件无错误 - ✅ OpenSpec 验证:openspec validate --strict 通过 - ✅ 迁移执行:表结构创建成功,索引正确 - ✅ 提案归档:2026-01-13-add-wallet-transfer-tag-models 变更文件统计:29 个文件,新增 3682 行
7.5 KiB
7.5 KiB
card-replacement Specification
Purpose
TBD - created by archiving change add-wallet-transfer-tag-models. Update Purpose after archive.
Requirements
Requirement: 换卡记录实体定义
系统 SHALL 定义换卡记录(CardReplacementRecord)实体,记录老卡到新卡的完整转移过程,包括套餐权益、代理关系、所有者信息等。
核心概念:
- 换卡场景:老卡损坏、丢失或故障,需要更换新卡
- 权益转移:老卡的套餐(含剩余流量)、代理关系、所有者信息等全部转移到新卡
- 套餐继续生效:转移后套餐不作废,剩余流量继续可用
实体字段:
id:换卡记录 ID(主键,BIGINT)replacement_no:换卡单号(VARCHAR(50),唯一)old_card_id:老卡 ID(BIGINT,关联 tb_iot_card.id)old_iccid:老卡 ICCID(VARCHAR(50),冗余存储,防止老卡被删除后无法追踪)new_card_id:新卡 ID(BIGINT,关联 tb_iot_card.id)new_iccid:新卡 ICCID(VARCHAR(50),冗余存储)old_owner_type:老卡所有者类型(VARCHAR(20))old_owner_id:老卡所有者 ID(BIGINT)old_agent_id:老卡代理 ID(BIGINT,可空)new_owner_type:新卡所有者类型(VARCHAR(20))new_owner_id:新卡所有者 ID(BIGINT)new_agent_id:新卡代理 ID(BIGINT,可空)package_snapshot:套餐快照(JSONB,记录转移时的套餐详情)replacement_reason:换卡原因(VARCHAR(20),枚举值:"damaged"-损坏 | "lost"-丢失 | "malfunction"-故障 | "upgrade"-升级 | "other"-其他)remark:备注(TEXT)status:换卡状态(INT,1-待审批 2-已通过 3-已拒绝 4-已完成)approved_by:审批人 ID(BIGINT,可空)approved_at:审批时间(TIMESTAMP,可空)completed_at:完成时间(TIMESTAMP,可空)creator:创建人 ID(BIGINT)updater:更新人 ID(BIGINT)created_at:创建时间(TIMESTAMP,自动填充)updated_at:更新时间(TIMESTAMP,自动填充)deleted_at:删除时间(TIMESTAMP,可空,软删除)
套餐快照 JSON 格式示例:
{
"package_id": 3001,
"package_name": "月套餐 10GB",
"package_code": "PKG-M-001",
"data_limit_mb": 10240,
"data_usage_mb": 5120,
"real_data_usage_mb": 4000,
"virtual_data_usage_mb": 1120,
"data_remaining_mb": 5120,
"activated_at": "2026-01-01T00:00:00Z",
"expires_at": "2026-02-01T00:00:00Z",
"remaining_days": 15,
"order_id": 10001
}
Scenario: 创建换卡记录
- WHEN 用户(ID 为 2001)的老卡(ICCID 为 "8986001")损坏,需要换新卡(ICCID 为 "8986002")
- THEN 系统创建换卡记录,
old_card_id为老卡 ID,new_card_id为新卡 ID,replacement_reason为 "damaged",status为 1(待审批)
Scenario: 审批通过换卡
- WHEN 运营人员(ID 为 999)审批通过换卡记录(ID 为 5001)
- THEN 系统将换卡记录状态从 1(待审批)变更为 2(已通过),记录
approved_by为 999,approved_at为当前时间
Scenario: 完成换卡
- WHEN 换卡记录(ID 为 5001)状态为 2(已通过),系统执行换卡操作
- THEN 系统将:
- 记录老卡和新卡的快照信息(所有者、代理、套餐)
- 将老卡的套餐权益转移到新卡(套餐使用记录的
iot_card_id更新为新卡 ID) - 将新卡的
owner_type和owner_id更新为老卡的值 - 将新卡的代理关系更新为老卡的值(如有)
- 将换卡记录状态变更为 4(已完成),记录
completed_at为当前时间
Scenario: 拒绝换卡
- WHEN 运营人员(ID 为 999)拒绝换卡记录(ID 为 5001),原因为"新卡不符合要求"
- THEN 系统将换卡记录状态从 1(待审批)变更为 3(已拒绝),记录
approved_by为 999,approved_at为当前时间,remark为拒绝原因
Requirement: 套餐权益转移
系统 SHALL 在换卡完成后,将老卡的套餐权益(包括剩余流量、过期时间等)转移到新卡,套餐继续生效。
转移内容:
- 套餐使用记录(
tb_package_usage) - 剩余流量(
data_limit_mb - data_usage_mb) - 套餐过期时间(
expires_at) - 关联的订单信息
转移规则:
- 老卡的套餐使用记录的
iot_card_id更新为新卡 ID - 剩余流量完整保留
- 套餐过期时间不变
- 如果老卡有多个套餐(正式套餐 + 加油包),全部转移
Scenario: 套餐转移
- WHEN 老卡有月套餐(剩余 5120 MB 流量,还有 15 天过期)
- THEN 系统将套餐使用记录的
iot_card_id从老卡 ID 更新为新卡 ID,流量和过期时间保持不变
Scenario: 多套餐转移
- WHEN 老卡有正式套餐和 2 个加油包
- THEN 系统将所有套餐使用记录的
iot_card_id更新为新卡 ID,所有套餐继续生效
Requirement: 代理关系转移
系统 SHALL 在换卡完成后,将老卡的代理关系转移到新卡。
转移内容:
- 新卡的
owner_type更新为老卡的owner_type - 新卡的
owner_id更新为老卡的owner_id - 如果老卡通过代理销售,新卡继承相同的代理关系
Scenario: 代理关系转移
- WHEN 老卡的
owner_type为 "agent",owner_id为 123 - THEN 系统将新卡的
owner_type更新为 "agent",owner_id更新为 123
Requirement: 换卡记录查询
系统 SHALL 支持按老卡 ID、新卡 ID、用户 ID、换卡单号等条件查询换卡记录。
查询条件:
- 换卡单号(精确匹配)
- 老卡 ID(精确匹配)
- 新卡 ID(精确匹配)
- 老卡 ICCID(精确匹配或模糊匹配)
- 新卡 ICCID(精确匹配或模糊匹配)
- 换卡状态(单选或多选)
- 换卡原因(单选或多选)
- 创建时间范围
- 完成时间范围
分页:
- 默认每页 20 条,最大每页 100 条
- 返回总记录数和总页数
Scenario: 按老卡 ICCID 查询换卡记录
- WHEN 查询老卡 ICCID 为 "8986001" 的换卡记录
- THEN 系统返回所有
old_iccid为 "8986001" 的换卡记录列表
Scenario: 按状态查询换卡记录
- WHEN 查询状态为 1(待审批)的换卡记录
- THEN 系统返回所有
status为 1 的换卡记录列表,按创建时间倒序排列
Requirement: 换卡数据校验
系统 SHALL 对换卡数据进行校验,确保数据完整性和一致性。
校验规则:
old_card_id:必填,≥ 1,必须是有效的 IoT 卡 IDnew_card_id:必填,≥ 1,必须是有效的 IoT 卡 ID,不能与old_card_id相同old_iccid:必填,长度 19-20 字符new_iccid:必填,长度 19-20 字符,不能与old_iccid相同replacement_reason:必填,枚举值 "damaged" | "lost" | "malfunction" | "upgrade" | "other"status:必填,枚举值 1-4
Scenario: 换卡时老卡和新卡相同
- WHEN 创建换卡记录,
old_card_id和new_card_id都为 1001 - THEN 系统拒绝创建,返回错误信息"新卡不能与老卡相同"
Scenario: 换卡时新卡 ICCID 无效
- WHEN 创建换卡记录,
new_iccid长度为 15(小于 19) - THEN 系统拒绝创建,返回错误信息"ICCID 长度必须为 19-20 字符"
Scenario: 换卡时老卡不存在
- WHEN 创建换卡记录,
old_card_id为 99999(不存在的 IoT 卡) - THEN 系统拒绝创建,返回错误信息"老卡不存在"