package model import ( "encoding/json" "strconv" "time" "gorm.io/gorm" ) // IotCard IoT 卡模型 // 物联网卡/流量卡的统一管理实体 // 通过 shop_id 区分所有权:NULL=平台所有,有值=店铺所有 type IotCard struct { gorm.Model BaseModel `gorm:"embedded"` ICCID string `gorm:"column:iccid;type:varchar(20);uniqueIndex:idx_iot_card_iccid,where:deleted_at IS NULL;not null;comment:ICCID(唯一标识,电信19位/其他20位)" json:"iccid"` CardCategory string `gorm:"column:card_category;type:varchar(20);default:'normal';not null;comment:卡业务类型 normal-普通卡 industry-行业卡" json:"card_category"` CarrierID uint `gorm:"column:carrier_id;index;not null;comment:运营商ID" json:"carrier_id"` CarrierType string `gorm:"column:carrier_type;type:varchar(20);comment:运营商类型(CMCC/CUCC/CTCC/CBN),导入时快照" json:"carrier_type"` CarrierName string `gorm:"column:carrier_name;type:varchar(100);comment:运营商名称,导入时快照" json:"carrier_name"` IMSI string `gorm:"column:imsi;type:varchar(50);comment:IMSI" json:"imsi"` MSISDN string `gorm:"column:msisdn;type:varchar(20);comment:MSISDN(手机号码)" json:"msisdn"` BatchNo string `gorm:"column:batch_no;type:varchar(100);comment:批次号" json:"batch_no"` Supplier string `gorm:"column:supplier;type:varchar(255);comment:供应商" json:"supplier"` Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-在库 2-已分销 3-已激活 4-已停用" json:"status"` ShopID *uint `gorm:"column:shop_id;index;comment:店铺ID(NULL=平台所有,有值=店铺所有)" json:"shop_id,omitempty"` ActivatedAt *time.Time `gorm:"column:activated_at;comment:激活时间" json:"activated_at"` ActivationStatus int `gorm:"column:activation_status;type:int;default:0;not null;comment:激活状态 0-未激活 1-已激活" json:"activation_status"` RealNameStatus int `gorm:"column:real_name_status;type:int;default:0;not null;comment:实名状态 0-未实名 1-已实名(行业卡可以保持0)" json:"real_name_status"` NetworkStatus int `gorm:"column:network_status;type:int;default:0;not null;comment:网络状态 0-停机 1-开机" json:"network_status"` DataUsageMB int64 `gorm:"column:data_usage_mb;type:bigint;default:0;comment:累计流量使用(MB)" json:"data_usage_mb"` CurrentMonthUsageMB float64 `gorm:"column:current_month_usage_mb;type:decimal(10,2);default:0;comment:本月已用流量(MB) - Gateway返回的自然月流量总量" json:"current_month_usage_mb"` CurrentMonthStartDate *time.Time `gorm:"column:current_month_start_date;type:date;comment:本月开始日期 - 用于检测跨月流量重置" json:"current_month_start_date"` LastMonthTotalMB float64 `gorm:"column:last_month_total_mb;type:decimal(10,2);default:0;comment:上月结束时的总流量(MB) - 用于跨月流量计算" json:"last_month_total_mb"` EnablePolling bool `gorm:"column:enable_polling;type:boolean;default:true;comment:是否参与轮询 true-参与 false-不参与" json:"enable_polling"` LastDataCheckAt *time.Time `gorm:"column:last_data_check_at;comment:最后一次流量检查时间" json:"last_data_check_at"` LastRealNameCheckAt *time.Time `gorm:"column:last_real_name_check_at;comment:最后一次实名检查时间" json:"last_real_name_check_at"` LastSyncTime *time.Time `gorm:"column:last_sync_time;comment:最后一次与Gateway同步时间" json:"last_sync_time"` SeriesID *uint `gorm:"column:series_id;index;comment:套餐系列ID(关联PackageSeries)" json:"series_id,omitempty"` FirstCommissionPaid bool `gorm:"column:first_commission_paid;type:boolean;default:false;comment:一次性佣金是否已发放(废弃,使用按系列追踪)" json:"first_commission_paid"` AccumulatedRecharge int64 `gorm:"column:accumulated_recharge;type:bigint;default:0;comment:累计充值金额(分,废弃,使用按系列追踪)" json:"accumulated_recharge"` AccumulatedRechargeBySeriesJSON string `gorm:"column:accumulated_recharge_by_series;type:jsonb;default:'{}';comment:按套餐系列追踪的累计充值金额" json:"-"` FirstRechargeTriggeredBySeriesJSON string `gorm:"column:first_recharge_triggered_by_series;type:jsonb;default:'{}';comment:按套餐系列追踪的首充触发状态" json:"-"` // 任务 24.1: 停复机相关字段 FirstRealnameAt *time.Time `gorm:"column:first_realname_at;comment:首次实名时间(用于触发首次实名激活)" json:"first_realname_at,omitempty"` StoppedAt *time.Time `gorm:"column:stopped_at;comment:停机时间" json:"stopped_at,omitempty"` ResumedAt *time.Time `gorm:"column:resumed_at;comment:最近复机时间" json:"resumed_at,omitempty"` StopReason string `gorm:"column:stop_reason;type:varchar(50);comment:停机原因(traffic_exhausted=流量耗尽,manual=手动停机,arrears=欠费)" json:"stop_reason,omitempty"` AssetStatus int `gorm:"column:asset_status;type:int;not null;default:1;comment:业务状态 1-在库 2-已销售 3-已换货 4-已停用" json:"asset_status"` Generation int `gorm:"column:generation;type:int;not null;default:1;comment:资产世代编号" json:"generation"` IsStandalone bool `gorm:"column:is_standalone;type:boolean;default:true;not null;comment:是否为独立卡(未绑定设备) 由触发器自动维护" json:"is_standalone"` VirtualNo string `gorm:"column:virtual_no;type:varchar(50);uniqueIndex:idx_iot_card_virtual_no,where:deleted_at IS NULL AND virtual_no IS NOT NULL AND virtual_no <> '';comment:虚拟号(可空,全局唯一)" json:"virtual_no,omitempty"` } // TableName 指定表名 func (IotCard) TableName() string { return "tb_iot_card" } func (c *IotCard) GetAccumulatedRechargeBySeriesMap() (map[uint]int64, error) { result := make(map[uint]int64) if c.AccumulatedRechargeBySeriesJSON == "" || c.AccumulatedRechargeBySeriesJSON == "{}" { return result, nil } var raw map[string]int64 if err := json.Unmarshal([]byte(c.AccumulatedRechargeBySeriesJSON), &raw); err != nil { return nil, err } for k, v := range raw { id, err := strconv.ParseUint(k, 10, 64) if err != nil { continue } result[uint(id)] = v } return result, nil } func (c *IotCard) SetAccumulatedRechargeBySeriesMap(m map[uint]int64) error { raw := make(map[string]int64) for k, v := range m { raw[strconv.FormatUint(uint64(k), 10)] = v } data, err := json.Marshal(raw) if err != nil { return err } c.AccumulatedRechargeBySeriesJSON = string(data) return nil } func (c *IotCard) GetAccumulatedRechargeBySeries(seriesID uint) int64 { m, err := c.GetAccumulatedRechargeBySeriesMap() if err != nil { return 0 } return m[seriesID] } func (c *IotCard) AddAccumulatedRechargeBySeries(seriesID uint, amount int64) error { m, err := c.GetAccumulatedRechargeBySeriesMap() if err != nil { m = make(map[uint]int64) } m[seriesID] += amount return c.SetAccumulatedRechargeBySeriesMap(m) } func (c *IotCard) GetFirstRechargeTriggeredBySeriesMap() (map[uint]bool, error) { result := make(map[uint]bool) if c.FirstRechargeTriggeredBySeriesJSON == "" || c.FirstRechargeTriggeredBySeriesJSON == "{}" { return result, nil } var raw map[string]bool if err := json.Unmarshal([]byte(c.FirstRechargeTriggeredBySeriesJSON), &raw); err != nil { return nil, err } for k, v := range raw { id, err := strconv.ParseUint(k, 10, 64) if err != nil { continue } result[uint(id)] = v } return result, nil } func (c *IotCard) SetFirstRechargeTriggeredBySeriesMap(m map[uint]bool) error { raw := make(map[string]bool) for k, v := range m { raw[strconv.FormatUint(uint64(k), 10)] = v } data, err := json.Marshal(raw) if err != nil { return err } c.FirstRechargeTriggeredBySeriesJSON = string(data) return nil } func (c *IotCard) IsFirstRechargeTriggeredBySeries(seriesID uint) bool { m, err := c.GetFirstRechargeTriggeredBySeriesMap() if err != nil { return false } return m[seriesID] } func (c *IotCard) SetFirstRechargeTriggeredBySeries(seriesID uint, triggered bool) error { m, err := c.GetFirstRechargeTriggeredBySeriesMap() if err != nil { m = make(map[uint]bool) } m[seriesID] = triggered return c.SetFirstRechargeTriggeredBySeriesMap(m) }