package model import ( "encoding/json" "strconv" "time" "gorm.io/gorm" ) // Device 设备模型 // 物联网设备(如 GPS 追踪器、智能传感器) // 通过 shop_id 区分所有权:NULL=平台库存,有值=店铺所有 // 标识符说明:device_no 为虚拟号/别名,imei/sn 为设备真实标识 type Device struct { gorm.Model BaseModel `gorm:"embedded"` DeviceNo string `gorm:"column:device_no;type:varchar(100);uniqueIndex:idx_device_no,where:deleted_at IS NULL;not null;comment:设备虚拟号/别名(用户友好的短标识)" json:"device_no"` IMEI string `gorm:"column:imei;type:varchar(20);comment:设备IMEI(有蜂窝网络的设备标识,用于Gateway API调用)" json:"imei"` SN string `gorm:"column:sn;type:varchar(100);comment:设备序列号(厂商唯一标识,预留字段)" json:"sn"` DeviceName string `gorm:"column:device_name;type:varchar(255);comment:设备名称" json:"device_name"` DeviceModel string `gorm:"column:device_model;type:varchar(100);comment:设备型号" json:"device_model"` DeviceType string `gorm:"column:device_type;type:varchar(50);comment:设备类型" json:"device_type"` MaxSimSlots int `gorm:"column:max_sim_slots;type:int;default:4;comment:最大插槽数量(默认4)" json:"max_sim_slots"` Manufacturer string `gorm:"column:manufacturer;type:varchar(255);comment:制造商" json:"manufacturer"` BatchNo string `gorm:"column:batch_no;type:varchar(100);comment:批次号" json:"batch_no"` ShopID *uint `gorm:"column:shop_id;index;comment:店铺ID(NULL=平台库存,有值=店铺所有)" json:"shop_id,omitempty"` Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-在库 2-已分销 3-已激活 4-已停用" json:"status"` ActivatedAt *time.Time `gorm:"column:activated_at;comment:激活时间" json:"activated_at"` DeviceUsername string `gorm:"column:device_username;type:varchar(100);comment:设备登录用户名" json:"device_username"` DevicePasswordEncrypted string `gorm:"column:device_password_encrypted;type:varchar(255);comment:设备登录密码(加密)" json:"device_password_encrypted"` DeviceAPIEndpoint string `gorm:"column:device_api_endpoint;type:varchar(500);comment:设备API端点" json:"device_api_endpoint"` 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:"-"` } // TableName 指定表名 func (Device) TableName() string { return "tb_device" } func (d *Device) GetAccumulatedRechargeBySeriesMap() (map[uint]int64, error) { result := make(map[uint]int64) if d.AccumulatedRechargeBySeriesJSON == "" || d.AccumulatedRechargeBySeriesJSON == "{}" { return result, nil } var raw map[string]int64 if err := json.Unmarshal([]byte(d.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 (d *Device) 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 } d.AccumulatedRechargeBySeriesJSON = string(data) return nil } func (d *Device) GetAccumulatedRechargeBySeries(seriesID uint) int64 { m, err := d.GetAccumulatedRechargeBySeriesMap() if err != nil { return 0 } return m[seriesID] } func (d *Device) AddAccumulatedRechargeBySeries(seriesID uint, amount int64) error { m, err := d.GetAccumulatedRechargeBySeriesMap() if err != nil { m = make(map[uint]int64) } m[seriesID] += amount return d.SetAccumulatedRechargeBySeriesMap(m) } func (d *Device) GetFirstRechargeTriggeredBySeriesMap() (map[uint]bool, error) { result := make(map[uint]bool) if d.FirstRechargeTriggeredBySeriesJSON == "" || d.FirstRechargeTriggeredBySeriesJSON == "{}" { return result, nil } var raw map[string]bool if err := json.Unmarshal([]byte(d.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 (d *Device) 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 } d.FirstRechargeTriggeredBySeriesJSON = string(data) return nil } func (d *Device) IsFirstRechargeTriggeredBySeries(seriesID uint) bool { m, err := d.GetFirstRechargeTriggeredBySeriesMap() if err != nil { return false } return m[seriesID] } func (d *Device) SetFirstRechargeTriggeredBySeries(seriesID uint, triggered bool) error { m, err := d.GetFirstRechargeTriggeredBySeriesMap() if err != nil { m = make(map[uint]bool) } m[seriesID] = triggered return d.SetFirstRechargeTriggeredBySeriesMap(m) }