refactor: 一次性佣金配置从套餐级别提升到系列级别
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m29s

主要变更:
- 新增 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)
- 删除过时的单元测试(已被验收测试覆盖)
This commit is contained in:
2026-02-04 14:28:44 +08:00
parent fba8e9e76b
commit b18ecfeb55
106 changed files with 9899 additions and 6608 deletions

View File

@@ -1,6 +1,8 @@
package model
import (
"encoding/json"
"strconv"
"time"
"gorm.io/gorm"
@@ -11,26 +13,126 @@ import (
// 通过 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"`
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)
}

View File

@@ -2,18 +2,16 @@ package dto
// CreatePackageRequest 创建套餐请求
type CreatePackageRequest struct {
PackageCode string `json:"package_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"套餐编码"`
PackageName string `json:"package_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"套餐名称"`
SeriesID *uint `json:"series_id" validate:"omitempty" description:"套餐系列ID"`
PackageType string `json:"package_type" validate:"required,oneof=formal addon" required:"true" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths int `json:"duration_months" validate:"required,min=1,max=120" required:"true" minimum:"1" maximum:"120" description:"套餐时长(月数)"`
DataType *string `json:"data_type" validate:"omitempty,oneof=real virtual" description:"流量类型 (real:真流量, virtual:虚流量)"`
RealDataMB *int64 `json:"real_data_mb" validate:"omitempty,min=0" minimum:"0" description:"流量额度(MB)"`
VirtualDataMB *int64 `json:"virtual_data_mb" validate:"omitempty,min=0" minimum:"0" description:"虚流量额度(MB)"`
DataAmountMB *int64 `json:"data_amount_mb" validate:"omitempty,min=0" minimum:"0" description:"总流量额度(MB)"`
Price int64 `json:"price" validate:"required,min=0" required:"true" minimum:"0" description:"套餐价格(分)"`
SuggestedCostPrice *int64 `json:"suggested_cost_price" validate:"omitempty,min=0" minimum:"0" description:"建议成本价(分)"`
SuggestedRetailPrice *int64 `json:"suggested_retail_price" validate:"omitempty,min=0" minimum:"0" description:"建议售价(分)"`
PackageCode string `json:"package_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"套餐编码"`
PackageName string `json:"package_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"套餐名称"`
SeriesID *uint `json:"series_id" validate:"omitempty" description:"套餐系列ID"`
PackageType string `json:"package_type" validate:"required,oneof=formal addon" required:"true" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths int `json:"duration_months" validate:"required,min=1,max=120" required:"true" minimum:"1" maximum:"120" description:"套餐时长(月数)"`
RealDataMB *int64 `json:"real_data_mb" validate:"omitempty,min=0" minimum:"0" description:"真流量额度(MB)"`
VirtualDataMB *int64 `json:"virtual_data_mb" validate:"omitempty,min=0" minimum:"0" description:"流量额度(MB)"`
EnableVirtualData bool `json:"enable_virtual_data" description:"是否启用虚流量"`
SuggestedRetailPrice *int64 `json:"suggested_retail_price" validate:"omitempty,min=0" minimum:"0" description:"建议售价(分)"`
CostPrice int64 `json:"cost_price" validate:"required,min=0" required:"true" minimum:"0" description:"成本价(分)"`
}
// UpdatePackageRequest 更新套餐请求
@@ -22,13 +20,11 @@ type UpdatePackageRequest struct {
SeriesID *uint `json:"series_id" validate:"omitempty" description:"套餐系列ID"`
PackageType *string `json:"package_type" validate:"omitempty,oneof=formal addon" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths *int `json:"duration_months" validate:"omitempty,min=1,max=120" minimum:"1" maximum:"120" description:"套餐时长(月数)"`
DataType *string `json:"data_type" validate:"omitempty,oneof=real virtual" description:"流量类型 (real:真流量, virtual:虚流量)"`
RealDataMB *int64 `json:"real_data_mb" validate:"omitempty,min=0" minimum:"0" description:"真流量额度(MB)"`
VirtualDataMB *int64 `json:"virtual_data_mb" validate:"omitempty,min=0" minimum:"0" description:"虚流量额度(MB)"`
DataAmountMB *int64 `json:"data_amount_mb" validate:"omitempty,min=0" minimum:"0" description:"总流量额度(MB)"`
Price *int64 `json:"price" validate:"omitempty,min=0" minimum:"0" description:"套餐价格(分)"`
SuggestedCostPrice *int64 `json:"suggested_cost_price" validate:"omitempty,min=0" minimum:"0" description:"建议成本价(分)"`
EnableVirtualData *bool `json:"enable_virtual_data" description:"是否启用虚流量"`
SuggestedRetailPrice *int64 `json:"suggested_retail_price" validate:"omitempty,min=0" minimum:"0" description:"建议售价(分)"`
CostPrice *int64 `json:"cost_price" validate:"omitempty,min=0" minimum:"0" description:"成本价(分)"`
}
// PackageListRequest 套餐列表请求
@@ -61,28 +57,26 @@ type CommissionTierInfo struct {
// PackageResponse 套餐响应
type PackageResponse struct {
ID uint `json:"id" description:"套餐ID"`
PackageCode string `json:"package_code" description:"套餐编码"`
PackageName string `json:"package_name" description:"套餐名称"`
SeriesID *uint `json:"series_id" description:"套餐系列ID"`
SeriesName *string `json:"series_name" description:"套餐系列名称"`
PackageType string `json:"package_type" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths int `json:"duration_months" description:"套餐时长(月数)"`
DataType string `json:"data_type" description:"流量类型 (real:真流量, virtual:虚流量)"`
RealDataMB int64 `json:"real_data_mb" description:"流量额度(MB)"`
VirtualDataMB int64 `json:"virtual_data_mb" description:"虚流量额度(MB)"`
DataAmountMB int64 `json:"data_amount_mb" description:"总流量额度(MB)"`
Price int64 `json:"price" description:"套餐价格(分)"`
SuggestedCostPrice int64 `json:"suggested_cost_price" description:"建议成本价(分)"`
SuggestedRetailPrice int64 `json:"suggested_retail_price" description:"建议售价(分)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
ShelfStatus int `json:"shelf_status" description:"上架状态 (1:上架, 2:下架)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
CostPrice *int64 `json:"cost_price,omitempty" description:"成本价(分,仅代理用户可见)"`
ProfitMargin *int64 `json:"profit_margin,omitempty" description:"利润空间(分,仅代理用户可见)"`
CurrentCommissionRate string `json:"current_commission_rate,omitempty" description:"当前返佣比例(仅代理用户可见)"`
TierInfo *CommissionTierInfo `json:"tier_info,omitempty" description:"梯度返佣信息(仅代理用户可见)"`
ID uint `json:"id" description:"套餐ID"`
PackageCode string `json:"package_code" description:"套餐编码"`
PackageName string `json:"package_name" description:"套餐名称"`
SeriesID *uint `json:"series_id" description:"套餐系列ID"`
SeriesName *string `json:"series_name" description:"套餐系列名称"`
PackageType string `json:"package_type" description:"套餐类型 (formal:正式套餐, addon:附加套餐)"`
DurationMonths int `json:"duration_months" description:"套餐时长(月数)"`
RealDataMB int64 `json:"real_data_mb" description:"流量额度(MB)"`
VirtualDataMB int64 `json:"virtual_data_mb" description:"流量额度(MB)"`
EnableVirtualData bool `json:"enable_virtual_data" description:"是否启用虚流量"`
SuggestedRetailPrice int64 `json:"suggested_retail_price" description:"建议售价(分)"`
CostPrice int64 `json:"cost_price" description:"成本价(分)"`
OneTimeCommissionAmount *int64 `json:"one_time_commission_amount,omitempty" description:"一次性佣金金额(分,代理视角)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
ShelfStatus int `json:"shelf_status" description:"上架状态 (1:上架, 2:下架)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ProfitMargin *int64 `json:"profit_margin,omitempty" description:"利润空间(分,仅代理用户可见)"`
CurrentCommissionRate string `json:"current_commission_rate,omitempty" description:"当前返佣比例(仅代理用户可见)"`
TierInfo *CommissionTierInfo `json:"tier_info,omitempty" description:"梯度返佣信息(仅代理用户可见)"`
}
// UpdatePackageParams 更新套餐聚合参数

View File

@@ -1,24 +1,50 @@
package dto
// OneTimeCommissionTierDTO 一次性佣金梯度配置
type OneTimeCommissionTierDTO struct {
Dimension string `json:"dimension" validate:"required,oneof=sales_count sales_amount" required:"true" description:"统计维度 (sales_count:销量, sales_amount:销售额)"`
StatScope string `json:"stat_scope" validate:"omitempty,oneof=self self_and_sub" description:"统计范围 (self:仅自己, self_and_sub:自己+下级)"`
Threshold int64 `json:"threshold" validate:"required,min=0" required:"true" minimum:"0" description:"达标阈值"`
Amount int64 `json:"amount" validate:"required,min=0" required:"true" minimum:"0" description:"佣金金额(分)"`
}
// SeriesOneTimeCommissionConfigDTO 一次性佣金规则配置
type SeriesOneTimeCommissionConfigDTO struct {
Enable bool `json:"enable" description:"是否启用一次性佣金"`
TriggerType string `json:"trigger_type" validate:"omitempty,oneof=first_recharge accumulated_recharge" description:"触发类型 (first_recharge:首充, accumulated_recharge:累计充值)"`
Threshold int64 `json:"threshold" validate:"omitempty,min=0" minimum:"0" description:"触发阈值(分)"`
CommissionType string `json:"commission_type" validate:"omitempty,oneof=fixed tiered" description:"佣金类型 (fixed:固定, tiered:梯度)"`
CommissionAmount int64 `json:"commission_amount" validate:"omitempty,min=0" minimum:"0" description:"固定佣金金额commission_type=fixed时使用"`
Tiers []OneTimeCommissionTierDTO `json:"tiers" validate:"omitempty,dive" description:"梯度配置列表commission_type=tiered时使用"`
ValidityType string `json:"validity_type" validate:"omitempty,oneof=permanent fixed_date relative" description:"时效类型 (permanent:永久, fixed_date:固定日期, relative:相对时长)"`
ValidityValue string `json:"validity_value" validate:"omitempty" description:"时效值(日期或月数)"`
EnableForceRecharge bool `json:"enable_force_recharge" description:"是否启用强充"`
ForceCalcType string `json:"force_calc_type" validate:"omitempty,oneof=fixed dynamic" description:"强充计算类型 (fixed:固定, dynamic:动态)"`
ForceAmount int64 `json:"force_amount" validate:"omitempty,min=0" minimum:"0" description:"强充金额(分)"`
}
// CreatePackageSeriesRequest 创建套餐系列请求
type CreatePackageSeriesRequest struct {
SeriesCode string `json:"series_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"系列编码"`
SeriesName string `json:"series_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"系列名称"`
Description string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"`
SeriesCode string `json:"series_code" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"系列编码"`
SeriesName string `json:"series_name" validate:"required,min=1,max=255" required:"true" minLength:"1" maxLength:"255" description:"系列名称"`
Description string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"`
OneTimeCommissionConfig *SeriesOneTimeCommissionConfigDTO `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金规则配置"`
}
// UpdatePackageSeriesRequest 更新套餐系列请求
type UpdatePackageSeriesRequest struct {
SeriesName *string `json:"series_name" validate:"omitempty,min=1,max=255" minLength:"1" maxLength:"255" description:"系列名称"`
Description *string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"`
SeriesName *string `json:"series_name" validate:"omitempty,min=1,max=255" minLength:"1" maxLength:"255" description:"系列名称"`
Description *string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"描述"`
OneTimeCommissionConfig *SeriesOneTimeCommissionConfigDTO `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金规则配置"`
}
// PackageSeriesListRequest 套餐系列列表请求
type PackageSeriesListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
SeriesName *string `json:"series_name" query:"series_name" validate:"omitempty,max=255" maxLength:"255" description:"系列名称(模糊搜索)"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
SeriesName *string `json:"series_name" query:"series_name" validate:"omitempty,max=255" maxLength:"255" description:"系列名称(模糊搜索)"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
EnableOneTimeCommission *bool `json:"enable_one_time_commission" query:"enable_one_time_commission" description:"是否启用一次性佣金"`
}
// UpdatePackageSeriesStatusRequest 更新套餐系列状态请求
@@ -28,13 +54,15 @@ type UpdatePackageSeriesStatusRequest struct {
// PackageSeriesResponse 套餐系列响应
type PackageSeriesResponse struct {
ID uint `json:"id" description:"系列ID"`
SeriesCode string `json:"series_code" description:"系列编码"`
SeriesName string `json:"series_name" description:"系列名称"`
Description string `json:"description" description:"描述"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ID uint `json:"id" description:"系列ID"`
SeriesCode string `json:"series_code" description:"系列编码"`
SeriesName string `json:"series_name" description:"系列名称"`
Description string `json:"description" description:"描述"`
EnableOneTimeCommission bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *SeriesOneTimeCommissionConfigDTO `json:"one_time_commission_config,omitempty" description:"一次性佣金规则配置"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// UpdatePackageSeriesParams 更新套餐系列聚合参数

View File

@@ -1,24 +1,23 @@
package dto
// CreateShopPackageAllocationRequest 创建单套餐分配请求
type CreateShopPackageAllocationRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
PackageID uint `json:"package_id" validate:"required" required:"true" description:"套餐ID"`
CostPrice int64 `json:"cost_price" validate:"required,min=0" required:"true" minimum:"0" description:"覆盖的成本价(分)"`
CostPrice int64 `json:"cost_price" validate:"required,min=0" required:"true" minimum:"0" description:"该代理的成本价(分)"`
}
// UpdateShopPackageAllocationRequest 更新单套餐分配请求
type UpdateShopPackageAllocationRequest struct {
CostPrice *int64 `json:"cost_price" validate:"omitempty,min=0" minimum:"0" description:"覆盖的成本价(分)"`
CostPrice *int64 `json:"cost_price" validate:"omitempty,min=0" minimum:"0" description:"该代理的成本价(分)"`
}
// ShopPackageAllocationListRequest 单套餐分配列表请求
type ShopPackageAllocationListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ShopID *uint `json:"shop_id" query:"shop_id" validate:"omitempty" description:"被分配的店铺ID"`
PackageID *uint `json:"package_id" query:"package_id" validate:"omitempty" description:"套餐ID"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ShopID *uint `json:"shop_id" query:"shop_id" validate:"omitempty" description:"被分配的店铺ID"`
PackageID *uint `json:"package_id" query:"package_id" validate:"omitempty" description:"套餐ID"`
SeriesAllocationID *uint `json:"series_allocation_id" query:"series_allocation_id" validate:"omitempty" description:"系列分配ID"`
AllocatorShopID *uint `json:"allocator_shop_id" query:"allocator_shop_id" validate:"omitempty" description:"分配者店铺ID"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
}
// UpdateShopPackageAllocationStatusRequest 更新单套餐分配状态请求
@@ -26,23 +25,25 @@ type UpdateShopPackageAllocationStatusRequest struct {
Status int `json:"status" validate:"required,oneof=1 2" required:"true" description:"状态 (1:启用, 2:禁用)"`
}
// ShopPackageAllocationResponse 单套餐分配响应
type ShopPackageAllocationResponse struct {
ID uint `json:"id" description:"分配ID"`
ShopID uint `json:"shop_id" description:"被分配的店铺ID"`
ShopName string `json:"shop_name" description:"被分配的店铺名称"`
PackageID uint `json:"package_id" description:"套餐ID"`
PackageName string `json:"package_name" description:"套餐名称"`
PackageCode string `json:"package_code" description:"套餐编码"`
AllocationID uint `json:"allocation_id" description:"关联的系列分配ID"`
CostPrice int64 `json:"cost_price" description:"覆盖的成本价(分)"`
CalculatedCostPrice int64 `json:"calculated_cost_price" description:"原计算成本价(分),供参考"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ID uint `json:"id" description:"分配ID"`
ShopID uint `json:"shop_id" description:"被分配的店铺ID"`
ShopName string `json:"shop_name" description:"被分配的店铺名称"`
PackageID uint `json:"package_id" description:"套餐ID"`
PackageName string `json:"package_name" description:"套餐名称"`
PackageCode string `json:"package_code" description:"套餐编码"`
SeriesID uint `json:"series_id" description:"套餐系列ID"`
SeriesName string `json:"series_name" description:"套餐系列名称"`
SeriesAllocationID *uint `json:"series_allocation_id" description:"关联的系列分配ID"`
AllocatorShopID uint `json:"allocator_shop_id" description:"分配者店铺ID0表示平台分配"`
AllocatorShopName string `json:"allocator_shop_name" description:"分配者店铺名称"`
CostPrice int64 `json:"cost_price" description:"该代理的成本价(分)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// ShopPackageAllocationPageResult 套餐分配分页结果
// ShopPackageAllocationPageResult 套餐分配分页结果
type ShopPackageAllocationPageResult struct {
List []*ShopPackageAllocationResponse `json:"list" description:"分配列表"`
Total int64 `json:"total" description:"总数"`
@@ -51,13 +52,13 @@ type ShopPackageAllocationPageResult struct {
TotalPages int `json:"total_pages" description:"总页数"`
}
// UpdateShopPackageAllocationParams 更新套餐分配聚合参数
// UpdateShopPackageAllocationParams 更新套餐分配聚合参数
type UpdateShopPackageAllocationParams struct {
IDReq
UpdateShopPackageAllocationRequest
}
// UpdateShopPackageAllocationStatusParams 更新套餐分配状态聚合参数
// UpdateShopPackageAllocationStatusParams 更新套餐分配状态聚合参数
type UpdateShopPackageAllocationStatusParams struct {
IDReq
UpdateShopPackageAllocationStatusRequest

View File

@@ -8,15 +8,14 @@ type PriceAdjustment struct {
// BatchAllocatePackagesRequest 批量分配套餐请求
type BatchAllocatePackagesRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
PriceAdjustment *PriceAdjustment `json:"price_adjustment" validate:"omitempty" description:"可选加价配置"`
BaseCommission BaseCommissionConfig `json:"base_commission" validate:"required" required:"true" description:"基础返佣配置"`
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
PriceAdjustment *PriceAdjustment `json:"price_adjustment" validate:"omitempty" description:"可选加价配置"`
OneTimeCommissionAmount *int64 `json:"one_time_commission_amount" validate:"omitempty,min=0" minimum:"0" description:"该代理能拿到的一次性佣金(分)"`
}
// BatchAllocatePackagesResponse 批量分配套餐响应
type BatchAllocatePackagesResponse struct {
AllocationID uint `json:"allocation_id" description:"系列分配ID"`
TotalPackages int `json:"total_packages" description:"总套餐数"`
AllocatedCount int `json:"allocated_count" description:"成功分配数量"`
SkippedCount int `json:"skipped_count" description:"跳过数量(已存在)"`

View File

@@ -1,86 +1,58 @@
package dto
// BaseCommissionConfig 基础返佣配置
type BaseCommissionConfig struct {
Mode string `json:"mode" validate:"required,oneof=fixed percent" required:"true" description:"返佣模式 (fixed:固定金额, percent:百分比)"`
Value int64 `json:"value" validate:"required,min=0" required:"true" minimum:"0" description:"返佣值分或千分比如200=20%"`
}
// OneTimeCommissionConfig 一次性佣金配置
type OneTimeCommissionConfig struct {
Type string `json:"type" validate:"required,oneof=fixed tiered" required:"true" description:"一次性佣金类型 (fixed:固定, tiered:梯度)"`
Trigger string `json:"trigger" validate:"required,oneof=single_recharge accumulated_recharge" required:"true" description:"触发条件 (single_recharge:单次充值, accumulated_recharge:累计充值)"`
Threshold int64 `json:"threshold" validate:"required,min=1" required:"true" minimum:"1" description:"最低阈值(分)"`
Mode string `json:"mode" validate:"omitempty,oneof=fixed percent" description:"返佣模式 (fixed:固定金额, percent:百分比) - 固定类型时必填"`
Value int64 `json:"value" validate:"omitempty,min=1" minimum:"1" description:"佣金金额(分)或比例(千分比)- 固定类型时必填"`
Tiers []OneTimeCommissionTierEntry `json:"tiers" validate:"omitempty,dive" description:"梯度档位列表 - 梯度类型时必填"`
}
// OneTimeCommissionTierEntry 一次性佣金梯度档位条目
type OneTimeCommissionTierEntry struct {
TierType string `json:"tier_type" validate:"required,oneof=sales_count sales_amount" required:"true" description:"梯度类型 (sales_count:销量, sales_amount:销售额)"`
Threshold int64 `json:"threshold" validate:"required,min=1" required:"true" minimum:"1" description:"梯度阈值(销量或销售额分)"`
Mode string `json:"mode" validate:"required,oneof=fixed percent" required:"true" description:"返佣模式 (fixed:固定金额, percent:百分比)"`
Value int64 `json:"value" validate:"required,min=1" required:"true" minimum:"1" description:"返佣值(分或千分比)"`
}
// CreateShopSeriesAllocationRequest 创建套餐系列分配请求
type CreateShopSeriesAllocationRequest struct {
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
BaseCommission BaseCommissionConfig `json:"base_commission" validate:"required" required:"true" description:"基础返佣配置"`
EnableOneTimeCommission bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *OneTimeCommissionConfig `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金配置(启用一次性佣金时必填)"`
EnableForceRecharge *bool `json:"enable_force_recharge,omitempty" description:"是否启用强充(累计充值强充)"`
ForceRechargeAmount *int64 `json:"force_recharge_amount,omitempty" description:"强充金额(分,0表示使用阈值金额)"`
ForceRechargeTriggerType *int `json:"force_recharge_trigger_type,omitempty" description:"强充触发类型(1:单次充值, 2:累计充值)"`
ShopID uint `json:"shop_id" validate:"required" required:"true" description:"被分配的店铺ID"`
SeriesID uint `json:"series_id" validate:"required" required:"true" description:"套餐系列ID"`
OneTimeCommissionAmount int64 `json:"one_time_commission_amount" validate:"required,min=0" required:"true" minimum:"0" description:"该代理能拿的一次性佣金金额上限(分)"`
EnableOneTimeCommission *bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionTrigger string `json:"one_time_commission_trigger" validate:"omitempty,oneof=first_recharge accumulated_recharge" description:"一次性佣金触发类型 (first_recharge:首次充值, accumulated_recharge:累计充值)"`
OneTimeCommissionThreshold *int64 `json:"one_time_commission_threshold" validate:"omitempty,min=0" minimum:"0" description:"一次性佣金触发阈值(分)"`
EnableForceRecharge *bool `json:"enable_force_recharge" description:"是否启用强制充值"`
ForceRechargeAmount *int64 `json:"force_recharge_amount" validate:"omitempty,min=0" minimum:"0" description:"强制充值金额(分)"`
ForceRechargeTriggerType *int `json:"force_recharge_trigger_type" validate:"omitempty,oneof=1 2" description:"强充触发类型 (1:单次充值, 2:累计充值)"`
}
// UpdateShopSeriesAllocationRequest 更新套餐系列分配请求
type UpdateShopSeriesAllocationRequest struct {
BaseCommission *BaseCommissionConfig `json:"base_commission" validate:"omitempty" description:"基础返佣配置"`
EnableOneTimeCommission *bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *OneTimeCommissionConfig `json:"one_time_commission_config" validate:"omitempty" description:"一次性佣金配置"`
EnableForceRecharge *bool `json:"enable_force_recharge,omitempty" description:"是否启用强充(累计充值强充)"`
ForceRechargeAmount *int64 `json:"force_recharge_amount,omitempty" description:"强充金额(分,0表示使用阈值金额)"`
ForceRechargeTriggerType *int `json:"force_recharge_trigger_type,omitempty" description:"强充触发类型(1:单次充值, 2:累计充值)"`
OneTimeCommissionAmount *int64 `json:"one_time_commission_amount" validate:"omitempty,min=0" minimum:"0" description:"该代理能拿的一次性佣金金额上限(分)"`
EnableOneTimeCommission *bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionTrigger *string `json:"one_time_commission_trigger" validate:"omitempty,oneof=first_recharge accumulated_recharge" description:"一次性佣金触发类型"`
OneTimeCommissionThreshold *int64 `json:"one_time_commission_threshold" validate:"omitempty,min=0" minimum:"0" description:"一次性佣金触发阈值(分)"`
EnableForceRecharge *bool `json:"enable_force_recharge" description:"是否启用强制充值"`
ForceRechargeAmount *int64 `json:"force_recharge_amount" validate:"omitempty,min=0" minimum:"0" description:"强制充值金额(分)"`
ForceRechargeTriggerType *int `json:"force_recharge_trigger_type" validate:"omitempty,oneof=1 2" description:"强充触发类型 (1:单次充值, 2:累计充值)"`
Status *int `json:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
}
// ShopSeriesAllocationListRequest 套餐系列分配列表请求
type ShopSeriesAllocationListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ShopID *uint `json:"shop_id" query:"shop_id" validate:"omitempty" description:"被分配的店铺ID"`
SeriesID *uint `json:"series_id" query:"series_id" validate:"omitempty" description:"套餐系列ID"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ShopID *uint `json:"shop_id" query:"shop_id" validate:"omitempty" description:"被分配的店铺ID"`
SeriesID *uint `json:"series_id" query:"series_id" validate:"omitempty" description:"套餐系列ID"`
AllocatorShopID *uint `json:"allocator_shop_id" query:"allocator_shop_id" validate:"omitempty" description:"分配者店铺ID"`
Status *int `json:"status" query:"status" validate:"omitempty,oneof=1 2" description:"状态 (1:启用, 2:禁用)"`
}
// UpdateShopSeriesAllocationStatusRequest 更新套餐系列分配状态请求
type UpdateShopSeriesAllocationStatusRequest struct {
Status int `json:"status" validate:"required,oneof=1 2" required:"true" description:"状态 (1:启用, 2:禁用)"`
}
// ShopSeriesAllocationResponse 套餐系列分配响应
type ShopSeriesAllocationResponse struct {
ID uint `json:"id" description:"分配ID"`
ShopID uint `json:"shop_id" description:"被分配的店铺ID"`
ShopName string `json:"shop_name" description:"被分配的店铺名称"`
SeriesID uint `json:"series_id" description:"套餐系列ID"`
SeriesName string `json:"series_name" description:"套餐系列名称"`
AllocatorShopID uint `json:"allocator_shop_id" description:"分配者店铺ID"`
AllocatorShopName string `json:"allocator_shop_name" description:"分配者店铺名称"`
BaseCommission BaseCommissionConfig `json:"base_commission" description:"基础返佣配置"`
EnableOneTimeCommission bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionConfig *OneTimeCommissionConfig `json:"one_time_commission_config,omitempty" description:"一次性佣金配置"`
EnableForceRecharge bool `json:"enable_force_recharge" description:"是否启用强充"`
ForceRechargeAmount int64 `json:"force_recharge_amount" description:"强充金额(分)"`
ForceRechargeTriggerType int `json:"force_recharge_trigger_type" description:"强充触发类型(1:单次充值, 2:累计充值)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
ID uint `json:"id" description:"分配ID"`
ShopID uint `json:"shop_id" description:"被分配的店铺ID"`
ShopName string `json:"shop_name" description:"被分配的店铺名称"`
SeriesID uint `json:"series_id" description:"套餐系列ID"`
SeriesName string `json:"series_name" description:"套餐系列名称"`
SeriesCode string `json:"series_code" description:"套餐系列编码"`
AllocatorShopID uint `json:"allocator_shop_id" description:"分配者店铺ID0表示平台分配"`
AllocatorShopName string `json:"allocator_shop_name" description:"分配者店铺名称"`
OneTimeCommissionAmount int64 `json:"one_time_commission_amount" description:"该代理能拿的一次性佣金金额上限(分)"`
EnableOneTimeCommission bool `json:"enable_one_time_commission" description:"是否启用一次性佣金"`
OneTimeCommissionTrigger string `json:"one_time_commission_trigger" description:"一次性佣金触发类型"`
OneTimeCommissionThreshold int64 `json:"one_time_commission_threshold" description:"一次性佣金触发阈值(分)"`
EnableForceRecharge bool `json:"enable_force_recharge" description:"是否启用强制充值"`
ForceRechargeAmount int64 `json:"force_recharge_amount" description:"强制充值金额(分)"`
ForceRechargeTriggerType int `json:"force_recharge_trigger_type" description:"强充触发类型 (1:单次充值, 2:累计充值)"`
Status int `json:"status" description:"状态 (1:启用, 2:禁用)"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// ShopSeriesAllocationPageResult 套餐系列分配分页结果
type ShopSeriesAllocationPageResult struct {
List []*ShopSeriesAllocationResponse `json:"list" description:"分配列表"`
Total int64 `json:"total" description:"总数"`
@@ -89,14 +61,7 @@ type ShopSeriesAllocationPageResult struct {
TotalPages int `json:"total_pages" description:"总页数"`
}
// UpdateShopSeriesAllocationParams 更新套餐系列分配聚合参数
type UpdateShopSeriesAllocationParams struct {
IDReq
UpdateShopSeriesAllocationRequest
}
// UpdateShopSeriesAllocationStatusParams 更新套餐系列分配状态聚合参数
type UpdateShopSeriesAllocationStatusParams struct {
IDReq
UpdateShopSeriesAllocationStatusRequest
}

View File

@@ -1,6 +1,8 @@
package model
import (
"encoding/json"
"strconv"
"time"
"gorm.io/gorm"
@@ -11,35 +13,135 @@ import (
// 通过 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"`
CostPrice int64 `gorm:"column:cost_price;type:bigint;default:0;comment:成本价(分为单位)" json:"cost_price"`
DistributePrice int64 `gorm:"column:distribute_price;type:bigint;default:0;comment:分销价(分为单位)" json:"distribute_price"`
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"`
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"`
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"`
CostPrice int64 `gorm:"column:cost_price;type:bigint;default:0;comment:成本价(分为单位)" json:"cost_price"`
DistributePrice int64 `gorm:"column:distribute_price;type:bigint;default:0;comment:分销价(分为单位)" json:"distribute_price"`
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"`
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:"-"`
}
// 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)
}

View File

@@ -1,6 +1,7 @@
package model
import (
"encoding/json"
"time"
"gorm.io/gorm"
@@ -10,11 +11,13 @@ import (
// 套餐的分组,用于一次性分佣规则配置
type PackageSeries struct {
gorm.Model
BaseModel `gorm:"embedded"`
SeriesCode string `gorm:"column:series_code;type:varchar(100);uniqueIndex:idx_package_series_code,where:deleted_at IS NULL;not null;comment:系列编码" json:"series_code"`
SeriesName string `gorm:"column:series_name;type:varchar(255);not null;comment:系列名称" json:"series_name"`
Description string `gorm:"column:description;type:text;comment:描述" json:"description"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
BaseModel `gorm:"embedded"`
SeriesCode string `gorm:"column:series_code;type:varchar(100);uniqueIndex:idx_package_series_code,where:deleted_at IS NULL;not null;comment:系列编码" json:"series_code"`
SeriesName string `gorm:"column:series_name;type:varchar(255);not null;comment:系列名称" json:"series_name"`
Description string `gorm:"column:description;type:text;comment:描述" json:"description"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
OneTimeCommissionConfigJSON string `gorm:"column:one_time_commission_config;type:jsonb;default:'{}';comment:一次性佣金规则配置" json:"-"`
EnableOneTimeCommission bool `gorm:"column:enable_one_time_commission;default:false;comment:是否启用一次性佣金顶层字段支持SQL索引" json:"enable_one_time_commission"`
}
// TableName 指定表名
@@ -32,13 +35,11 @@ type Package struct {
SeriesID uint `gorm:"column:series_id;index;comment:套餐系列ID" json:"series_id"`
PackageType string `gorm:"column:package_type;type:varchar(50);not null;comment:套餐类型 formal-正式套餐 addon-附加套餐" json:"package_type"`
DurationMonths int `gorm:"column:duration_months;type:int;not null;comment:套餐时长(月数) 1-月套餐 12-年套餐" json:"duration_months"`
DataType string `gorm:"column:data_type;type:varchar(20);comment:流量类型 real-真流量 virtual-虚流量" json:"data_type"`
RealDataMB int64 `gorm:"column:real_data_mb;type:bigint;default:0;comment:真流量额度(MB)" json:"real_data_mb"`
VirtualDataMB int64 `gorm:"column:virtual_data_mb;type:bigint;default:0;comment:虚流量额度(MB,用于停机判断)" json:"virtual_data_mb"`
DataAmountMB int64 `gorm:"column:data_amount_mb;type:bigint;default:0;comment:总流量额度(MB)" json:"data_amount_mb"`
Price int64 `gorm:"column:price;type:bigint;not null;comment:套餐价格(分为单位)" json:"price"`
EnableVirtualData bool `gorm:"column:enable_virtual_data;type:boolean;default:false;not null;comment:是否启用虚流量" json:"enable_virtual_data"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
SuggestedCostPrice int64 `gorm:"column:suggested_cost_price;type:bigint;default:0;comment:建议成本价(分为单位)" json:"suggested_cost_price"`
CostPrice int64 `gorm:"column:cost_price;type:bigint;default:0;comment:成本价(分为单位)" json:"cost_price"`
SuggestedRetailPrice int64 `gorm:"column:suggested_retail_price;type:bigint;default:0;comment:建议售价(分为单位)" json:"suggested_retail_price"`
ShelfStatus int `gorm:"column:shelf_status;type:int;default:2;not null;comment:上架状态 1-上架 2-下架" json:"shelf_status"`
}
@@ -72,3 +73,68 @@ type PackageUsage struct {
func (PackageUsage) TableName() string {
return "tb_package_usage"
}
// OneTimeCommissionConfig 一次性佣金规则配置
type OneTimeCommissionConfig struct {
Enable bool `json:"enable"`
TriggerType string `json:"trigger_type"`
Threshold int64 `json:"threshold"`
CommissionType string `json:"commission_type"`
CommissionAmount int64 `json:"commission_amount"`
Tiers []OneTimeCommissionTier `json:"tiers,omitempty"`
ValidityType string `json:"validity_type"`
ValidityValue string `json:"validity_value"`
EnableForceRecharge bool `json:"enable_force_recharge"`
ForceCalcType string `json:"force_calc_type"`
ForceAmount int64 `json:"force_amount"`
}
// OneTimeCommissionTier 一次性佣金梯度配置
type OneTimeCommissionTier struct {
Dimension string `json:"dimension"`
StatScope string `json:"stat_scope"`
Threshold int64 `json:"threshold"`
Amount int64 `json:"amount"`
}
const (
OneTimeCommissionTriggerFirstRecharge = "first_recharge"
OneTimeCommissionTriggerAccumulatedRecharge = "accumulated_recharge"
OneTimeCommissionValidityPermanent = "permanent"
OneTimeCommissionValidityFixedDate = "fixed_date"
OneTimeCommissionValidityRelative = "relative"
OneTimeCommissionForceCalcFixed = "fixed"
OneTimeCommissionForceCalcDynamic = "dynamic"
OneTimeCommissionStatScopeSelf = "self"
OneTimeCommissionStatScopeSelfAndSub = "self_and_sub"
TierTypeSalesCount = "sales_count"
TierTypeSalesAmount = "sales_amount"
)
func (ps *PackageSeries) GetOneTimeCommissionConfig() (*OneTimeCommissionConfig, error) {
if ps.OneTimeCommissionConfigJSON == "" {
return nil, nil
}
var config OneTimeCommissionConfig
if err := json.Unmarshal([]byte(ps.OneTimeCommissionConfigJSON), &config); err != nil {
return nil, err
}
return &config, nil
}
func (ps *PackageSeries) SetOneTimeCommissionConfig(config *OneTimeCommissionConfig) error {
if config == nil {
ps.OneTimeCommissionConfigJSON = ""
return nil
}
data, err := json.Marshal(config)
if err != nil {
return err
}
ps.OneTimeCommissionConfigJSON = string(data)
return nil
}

View File

@@ -4,17 +4,15 @@ import (
"gorm.io/gorm"
)
// ShopPackageAllocation 店铺单套餐分配模型
// 用于对单个套餐设置覆盖成本价,优先级高于系列级别的加价计算
// 适用于特殊定价场景(如某个套餐给特定代理优惠价)
type ShopPackageAllocation struct {
gorm.Model
BaseModel `gorm:"embedded"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:被分配的店铺ID" json:"shop_id"`
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
AllocationID uint `gorm:"column:allocation_id;index;not null;comment:关联的系列分配ID" json:"allocation_id"`
CostPrice int64 `gorm:"column:cost_price;type:bigint;not null;comment:覆盖的成本价(分)" json:"cost_price"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
BaseModel `gorm:"embedded"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:被分配的店铺ID" json:"shop_id"`
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
AllocatorShopID uint `gorm:"column:allocator_shop_id;index;not null;default:0;comment:分配者店铺ID0表示平台分配" json:"allocator_shop_id"`
CostPrice int64 `gorm:"column:cost_price;type:bigint;not null;comment:该代理的成本价(分)" json:"cost_price"`
SeriesAllocationID *uint `gorm:"column:series_allocation_id;index;comment:关联的系列分配ID" json:"series_allocation_id"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
}
// TableName 指定表名

View File

@@ -4,59 +4,22 @@ import (
"gorm.io/gorm"
)
// ShopSeriesAllocation 店铺套餐系列分配模型
// 记录上级店铺为下级店铺分配的套餐系列,包含基础返佣配置和梯度返佣开关
// 分配者只能分配自己已被分配的套餐系列,且只能分配给直属下级
type ShopSeriesAllocation struct {
gorm.Model
BaseModel `gorm:"embedded"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:被分配的店铺ID" json:"shop_id"`
SeriesID uint `gorm:"column:series_id;index;not null;comment:套餐系列ID" json:"series_id"`
AllocatorShopID uint `gorm:"column:allocator_shop_id;index;not null;comment:分配者店铺ID(上级)" json:"allocator_shop_id"`
BaseCommissionMode string `gorm:"column:base_commission_mode;type:varchar(20);not null;default:percent;comment:基础返佣模式 fixed-固定金额 percent-百分比" json:"base_commission_mode"`
BaseCommissionValue int64 `gorm:"column:base_commission_value;type:bigint;not null;default:0;comment:基础返佣值(分或千分比,如200=20%)" json:"base_commission_value"`
// 一次性佣金配置
EnableOneTimeCommission bool `gorm:"column:enable_one_time_commission;type:boolean;not null;default:false;comment:是否启用一次性佣金" json:"enable_one_time_commission"`
OneTimeCommissionType string `gorm:"column:one_time_commission_type;type:varchar(20);comment:一次性佣金类型 fixed-固定 tiered-梯度" json:"one_time_commission_type"`
OneTimeCommissionTrigger string `gorm:"column:one_time_commission_trigger;type:varchar(30);comment:触发条件 single_recharge-单次充值 accumulated_recharge-累计充值" json:"one_time_commission_trigger"`
OneTimeCommissionThreshold int64 `gorm:"column:one_time_commission_threshold;type:bigint;default:0;comment:最低阈值(分)" json:"one_time_commission_threshold"`
OneTimeCommissionMode string `gorm:"column:one_time_commission_mode;type:varchar(20);comment:返佣模式 fixed-固定金额 percent-百分比" json:"one_time_commission_mode"`
OneTimeCommissionValue int64 `gorm:"column:one_time_commission_value;type:bigint;default:0;comment:佣金金额(分)或比例(千分比)" json:"one_time_commission_value"`
// 强充配置
EnableForceRecharge bool `gorm:"column:enable_force_recharge;type:boolean;default:false;comment:是否启用强充(累计充值时可选)" json:"enable_force_recharge"`
ForceRechargeAmount int64 `gorm:"column:force_recharge_amount;type:bigint;default:0;comment:强充金额(分,0表示使用阈值金额)" json:"force_recharge_amount"`
ForceRechargeTriggerType int `gorm:"column:force_recharge_trigger_type;type:int;default:2;comment:强充触发类型(1:单次充值, 2:累计充值)" json:"force_recharge_trigger_type"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
BaseModel `gorm:"embedded"`
ShopID uint `gorm:"column:shop_id;index;not null;comment:被分配的店铺ID" json:"shop_id"`
SeriesID uint `gorm:"column:series_id;index;not null;comment:套餐系列ID" json:"series_id"`
AllocatorShopID uint `gorm:"column:allocator_shop_id;index;not null;default:0;comment:分配者店铺ID0表示平台分配" json:"allocator_shop_id"`
OneTimeCommissionAmount int64 `gorm:"column:one_time_commission_amount;type:bigint;default:0;not null;comment:该代理能拿的一次性佣金金额上限(分)" json:"one_time_commission_amount"`
EnableOneTimeCommission bool `gorm:"column:enable_one_time_commission;default:false;not null;comment:是否启用一次性佣金" json:"enable_one_time_commission"`
OneTimeCommissionTrigger string `gorm:"column:one_time_commission_trigger;type:varchar(50);comment:一次性佣金触发类型" json:"one_time_commission_trigger"`
OneTimeCommissionThreshold int64 `gorm:"column:one_time_commission_threshold;type:bigint;default:0;not null;comment:一次性佣金触发阈值(分)" json:"one_time_commission_threshold"`
EnableForceRecharge bool `gorm:"column:enable_force_recharge;default:false;not null;comment:是否启用强制充值" json:"enable_force_recharge"`
ForceRechargeAmount int64 `gorm:"column:force_recharge_amount;type:bigint;default:0;not null;comment:强制充值金额(分)" json:"force_recharge_amount"`
ForceRechargeTriggerType int `gorm:"column:force_recharge_trigger_type;type:int;default:2;not null;comment:强充触发类型 1-单次充值 2-累计充值" json:"force_recharge_trigger_type"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-禁用" json:"status"`
}
// TableName 指定表名
func (ShopSeriesAllocation) TableName() string {
return "tb_shop_series_allocation"
}
// 返佣模式常量
const (
// CommissionModeFixed 固定金额返佣
CommissionModeFixed = "fixed"
// CommissionModePercent 百分比返佣(千分比)
CommissionModePercent = "percent"
)
// 一次性佣金类型常量
const (
// OneTimeCommissionTypeFixed 固定一次性佣金
OneTimeCommissionTypeFixed = "fixed"
// OneTimeCommissionTypeTiered 梯度一次性佣金
OneTimeCommissionTypeTiered = "tiered"
)
// 一次性佣金触发类型常量
const (
// OneTimeCommissionTriggerSingleRecharge 单次充值触发
OneTimeCommissionTriggerSingleRecharge = "single_recharge"
// OneTimeCommissionTriggerAccumulatedRecharge 累计充值触发
OneTimeCommissionTriggerAccumulatedRecharge = "accumulated_recharge"
)

View File

@@ -1,25 +0,0 @@
package model
import (
"time"
"gorm.io/gorm"
)
// ShopSeriesAllocationConfig 套餐系列分配配置版本模型
// 记录返佣配置的历史版本,订单创建时锁定配置版本
// 支持配置追溯和数据一致性保障
type ShopSeriesAllocationConfig struct {
gorm.Model
AllocationID uint `gorm:"column:allocation_id;index;not null;comment:关联的分配ID" json:"allocation_id"`
Version int `gorm:"column:version;type:int;not null;comment:配置版本号" json:"version"`
BaseCommissionMode string `gorm:"column:base_commission_mode;type:varchar(20);not null;comment:基础返佣模式(配置快照)" json:"base_commission_mode"`
BaseCommissionValue int64 `gorm:"column:base_commission_value;type:bigint;not null;comment:基础返佣值(配置快照)" json:"base_commission_value"`
EffectiveFrom time.Time `gorm:"column:effective_from;type:timestamptz;not null;comment:生效开始时间" json:"effective_from"`
EffectiveTo *time.Time `gorm:"column:effective_to;type:timestamptz;comment:生效结束时间(NULL表示当前生效)" json:"effective_to"`
}
// TableName 指定表名
func (ShopSeriesAllocationConfig) TableName() string {
return "tb_shop_series_allocation_config"
}

View File

@@ -1,36 +0,0 @@
package model
import (
"gorm.io/gorm"
)
// ShopSeriesOneTimeCommissionTier 一次性佣金梯度配置模型
// 记录基于销售业绩的一次性佣金梯度档位
// 当系列分配的累计销量或销售额达到不同阈值时,返不同的一次性佣金金额
type ShopSeriesOneTimeCommissionTier struct {
gorm.Model
BaseModel `gorm:"embedded"`
AllocationID uint `gorm:"column:allocation_id;index;not null;comment:系列分配ID" json:"allocation_id"`
TierType string `gorm:"column:tier_type;type:varchar(20);not null;comment:梯度类型 sales_count-销量 sales_amount-销售额" json:"tier_type"`
ThresholdValue int64 `gorm:"column:threshold_value;type:bigint;not null;comment:梯度阈值(销量或销售额分)" json:"threshold_value"`
CommissionMode string `gorm:"column:commission_mode;type:varchar(20);not null;default:fixed;comment:返佣模式 fixed-固定金额 percent-百分比" json:"commission_mode"`
CommissionValue int64 `gorm:"column:commission_value;type:bigint;not null;comment:返佣值(分或千分比)" json:"commission_value"`
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 1-启用 2-停用" json:"status"`
}
// TableName 指定表名
func (ShopSeriesOneTimeCommissionTier) TableName() string {
return "tb_shop_series_one_time_commission_tier"
}
// 梯度类型常量
const (
// TierTypeSalesCount 销量梯度
TierTypeSalesCount = "sales_count"
// TierTypeSalesAmount 销售额梯度
TierTypeSalesAmount = "sales_amount"
)
// 返佣模式常量在 shop_series_allocation.go 中定义
// CommissionModeFixed = "fixed"
// CommissionModePercent = "percent"