Files
junhong_cmp_fiber/internal/model/card_replacement.go
huang 6e2dc325d7 新增钱包、换卡、标签系统的数据模型和规范
本次提交完成 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 行
2026-01-13 15:47:32 +08:00

72 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import (
"database/sql/driver"
"encoding/json"
"time"
"gorm.io/gorm"
)
// PackageSnapshot 套餐快照,记录换卡时的套餐信息
type PackageSnapshot struct {
PackageID uint `json:"package_id"` // 套餐ID
PackageName string `json:"package_name"` // 套餐名称
PackageType string `json:"package_type"` // 套餐类型
DataQuota int64 `json:"data_quota"` // 流量额度KB
DataUsed int64 `json:"data_used"` // 已使用流量KB
ValidFrom time.Time `json:"valid_from"` // 生效时间
ValidTo time.Time `json:"valid_to"` // 失效时间
Price int64 `json:"price"` // 套餐价格(分)
RemainingDays int `json:"remaining_days"` // 剩余天数
TransferReason string `json:"transfer_reason,omitempty"` // 转移原因
}
// Value 实现 driver.Valuer 接口
func (p PackageSnapshot) Value() (driver.Value, error) {
return json.Marshal(p)
}
// Scan 实现 sql.Scanner 接口
func (p *PackageSnapshot) Scan(value interface{}) error {
if value == nil {
return nil
}
bytes, ok := value.([]byte)
if !ok {
return nil
}
return json.Unmarshal(bytes, p)
}
// CardReplacementRecord 换卡记录模型
// 记录物联卡更换历史,包含套餐快照便于追溯
// 支持损坏、丢失、故障等多种换卡原因,需要审批流程
type CardReplacementRecord struct {
gorm.Model
BaseModel `gorm:"embedded"`
ReplacementNo string `gorm:"column:replacement_no;type:varchar(50);not null;uniqueIndex:idx_card_replacement_no,where:deleted_at IS NULL;comment:换卡单号" json:"replacement_no"`
OldCardID uint `gorm:"column:old_card_id;not null;index:idx_card_replacement_old_card;comment:老卡ID" json:"old_card_id"`
OldIccid string `gorm:"column:old_iccid;type:varchar(50);not null;comment:老卡ICCID" json:"old_iccid"`
NewCardID uint `gorm:"column:new_card_id;not null;index:idx_card_replacement_new_card;comment:新卡ID" json:"new_card_id"`
NewIccid string `gorm:"column:new_iccid;type:varchar(50);not null;comment:新卡ICCID" json:"new_iccid"`
OldOwnerType string `gorm:"column:old_owner_type;type:varchar(20);not null;index:idx_card_replacement_old_owner,priority:1;comment:老卡所有者类型" json:"old_owner_type"`
OldOwnerID uint `gorm:"column:old_owner_id;not null;index:idx_card_replacement_old_owner,priority:2;comment:老卡所有者ID" json:"old_owner_id"`
OldAgentID *uint `gorm:"column:old_agent_id;comment:老卡代理ID" json:"old_agent_id,omitempty"`
NewOwnerType string `gorm:"column:new_owner_type;type:varchar(20);not null;index:idx_card_replacement_new_owner,priority:1;comment:新卡所有者类型" json:"new_owner_type"`
NewOwnerID uint `gorm:"column:new_owner_id;not null;index:idx_card_replacement_new_owner,priority:2;comment:新卡所有者ID" json:"new_owner_id"`
NewAgentID *uint `gorm:"column:new_agent_id;comment:新卡代理ID" json:"new_agent_id,omitempty"`
PackageSnapshot *PackageSnapshot `gorm:"column:package_snapshot;type:jsonb;comment:套餐快照" json:"package_snapshot,omitempty"`
ReplacementReason string `gorm:"column:replacement_reason;type:varchar(20);not null;comment:换卡原因 damaged-损坏 lost-丢失 malfunction-故障 upgrade-升级 other-其他" json:"replacement_reason"`
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
Status int `gorm:"column:status;type:int;not null;default:1;index:idx_card_replacement_status;comment:换卡状态 1-待审批 2-已通过 3-已拒绝 4-已完成" json:"status"`
ApprovedBy *uint `gorm:"column:approved_by;comment:审批人ID" json:"approved_by,omitempty"`
ApprovedAt *time.Time `gorm:"column:approved_at;comment:审批时间" json:"approved_at,omitempty"`
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
}
// TableName 指定表名
func (CardReplacementRecord) TableName() string {
return "tb_card_replacement_record"
}