Files
chengqian 108078614d fix: 金额 float64 → decimal 全链路迁移,消除浮点累积漂移
将全库资金金额从 float64 迁移到 shopspring/decimal,落定边界架构:
入口 NewFromFloat → 内部多步运算全程 decimal → 出口 InexactFloat64;
DB NUMERIC(20,10) 走 driver.Valuer 精确直传,Redis 钱包用整数 micro-USD,
CNY→USD 换汇仅在 payment.FulfillOrder recharge 分支。

- money.go 重写为 decimal 原生(运算/收敛/比较/边界/micro 工具)
- billing/tenant/admin/payment/open/task 各层金额运算 decimal 化
- entity/do 资金列重生成为 decimal.Decimal(可空列 *decimal.Decimal)
- 000013 迁移:非金额 NUMERIC 列 → double precision(5 表 13 列)
- Res 出口 .InexactFloat64() 收敛,JSONB 快照缝隙显式转 float64
- CLAUDE.md 新增 decimal 强约束;补充迁移基线/完成总结文档
2026-07-23 22:08:54 +08:00

127 lines
3.8 KiB
Go

package admin
import (
"context"
"github.com/qianfree/team-api/api/admin/v1"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/billing"
"github.com/qianfree/team-api/internal/logic/common"
"github.com/qianfree/team-api/internal/model/do"
"github.com/qianfree/team-api/internal/model/entity"
)
func (s *sAdmin) ListTenantLevelConfigs(ctx context.Context, _ *v1.TenantLevelConfigListReq) (*v1.TenantLevelConfigListRes, error) {
var configs []*entity.TntTenantLevelConfigs
err := dao.TntTenantLevelConfigs.Ctx(ctx).
OrderAsc("level").
Scan(&configs)
if err != nil {
return nil, err
}
list := make([]*v1.TenantLevelConfigItem, 0, len(configs))
for _, c := range configs {
list = append(list, &v1.TenantLevelConfigItem{
Id: c.Id,
Level: c.Level,
Name: c.Name,
CumulativeRechargeThreshold: billing.InexactFloat64(c.CumulativeRechargeThreshold),
MaxMembers: c.MaxMembers,
MaxConcurrency: c.MaxConcurrency,
PriceMultiplier: billing.InexactFloat64(c.PriceMultiplier),
SortOrder: c.SortOrder,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
})
}
return &v1.TenantLevelConfigListRes{List: list}, nil
}
func (s *sAdmin) CreateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigCreateReq) (*v1.TenantLevelConfigCreateRes, error) {
// 检查 level 是否已存在
count, _ := dao.TntTenantLevelConfigs.Ctx(ctx).Where("level", req.Level).Count()
if count > 0 {
return nil, common.NewBadRequestError("等级号已存在")
}
result, err := dao.TntTenantLevelConfigs.Ctx(ctx).Insert(do.TntTenantLevelConfigs{
Level: req.Level,
Name: req.Name,
CumulativeRechargeThreshold: req.CumulativeRechargeThreshold,
MaxMembers: req.MaxMembers,
MaxConcurrency: req.MaxConcurrency,
PriceMultiplier: req.PriceMultiplier,
SortOrder: req.SortOrder,
})
if err != nil {
return nil, err
}
id, _ := result.LastInsertId()
return &v1.TenantLevelConfigCreateRes{ID: id}, nil
}
func (s *sAdmin) UpdateTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigUpdateReq) (*v1.TenantLevelConfigUpdateRes, error) {
data := do.TntTenantLevelConfigs{}
hasUpdate := false
if req.Name != nil {
data.Name = *req.Name
hasUpdate = true
}
if req.CumulativeRechargeThreshold != nil {
data.CumulativeRechargeThreshold = *req.CumulativeRechargeThreshold
hasUpdate = true
}
if req.MaxMembers != nil {
data.MaxMembers = *req.MaxMembers
hasUpdate = true
}
if req.MaxConcurrency != nil {
data.MaxConcurrency = *req.MaxConcurrency
hasUpdate = true
}
if req.PriceMultiplier != nil {
data.PriceMultiplier = *req.PriceMultiplier
hasUpdate = true
}
if req.SortOrder != nil {
data.SortOrder = *req.SortOrder
hasUpdate = true
}
if !hasUpdate {
return &v1.TenantLevelConfigUpdateRes{}, nil
}
_, err := dao.TntTenantLevelConfigs.Ctx(ctx).
Where("id", req.Id).
Data(data).
Update()
if err != nil {
return nil, err
}
return &v1.TenantLevelConfigUpdateRes{}, nil
}
func (s *sAdmin) DeleteTenantLevelConfig(ctx context.Context, req *v1.TenantLevelConfigDeleteReq) (*v1.TenantLevelConfigDeleteRes, error) {
var config *entity.TntTenantLevelConfigs
err := dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Scan(&config)
if err != nil {
return nil, err
}
if config == nil {
return &v1.TenantLevelConfigDeleteRes{}, nil
}
if config.Level == 1 {
return nil, common.NewBadRequestError("不允许删除默认等级 LV1")
}
_, err = dao.TntTenantLevelConfigs.Ctx(ctx).Where("id", req.Id).Delete()
if err != nil {
return nil, err
}
return &v1.TenantLevelConfigDeleteRes{}, nil
}