Files
junhong_cmp_fiber/internal/model/device.go
huang b18ecfeb55
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s
refactor: 一次性佣金配置从套餐级别提升到系列级别
主要变更:
- 新增 tb_shop_series_allocation 表,存储系列级别的一次性佣金配置
- ShopPackageAllocation 移除 one_time_commission_amount 字段
- PackageSeries 新增 enable_one_time_commission 字段控制是否启用一次性佣金
- 新增 /api/admin/shop-series-allocations CRUD 接口
- 佣金计算逻辑改为从 ShopSeriesAllocation 获取一次性佣金金额
- 删除废弃的 ShopSeriesOneTimeCommissionTier 模型
- OpenAPI Tag '系列分配' 和 '单套餐分配' 合并为 '套餐分配'

迁移脚本:
- 000042: 重构佣金套餐模型
- 000043: 简化佣金分配
- 000044: 一次性佣金分配重构
- 000045: PackageSeries 添加 enable_one_time_commission 字段

测试:
- 新增验收测试 (shop_series_allocation, commission_calculation)
- 新增流程测试 (one_time_commission_chain)
- 删除过时的单元测试(已被验收测试覆盖)
2026-02-04 14:28:44 +08:00

139 lines
5.7 KiB
Go
Raw 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 (
"encoding/json"
"strconv"
"time"
"gorm.io/gorm"
)
// Device 设备模型
// 物联网设备(如 GPS 追踪器、智能传感器)
// 通过 shop_id 区分所有权NULL=平台库存,有值=店铺所有
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"`
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)
}