feat(user): 支持后台调整等级并限制极品专区可见性
This commit is contained in:
@@ -221,10 +221,10 @@ func SnapshotForUser(tx *gorm.DB, userID uint64) (DiscountSnapshot, error) {
|
||||
snapshot.UserPoints = user.RenterGrowthPoints
|
||||
snapshot.ManualDepositFreeQuotaCent = manualQuotaCent
|
||||
snapshot.EffectiveDepositFreeQuotaCent = manualQuotaCent
|
||||
if !cfg.Enabled {
|
||||
if !cfg.Enabled && strings.TrimSpace(user.RenterGrowthLevelOverride) == "" {
|
||||
return snapshot, nil
|
||||
}
|
||||
level := LevelForPoints(cfg, user.RenterGrowthPoints)
|
||||
level := EffectiveLevelForUser(cfg, user.RenterGrowthPoints, user.RenterGrowthLevelOverride)
|
||||
return DiscountSnapshot{
|
||||
LevelCode: level.Code,
|
||||
LevelName: level.Name,
|
||||
@@ -307,7 +307,7 @@ func awardOrderSide(tx *gorm.DB, order *model.RentalOrder, cfg Config, role stri
|
||||
}
|
||||
beforePoints := user.RenterGrowthPoints
|
||||
beforeAvailable := user.PointsAvailable
|
||||
beforeLevel := LevelForPoints(cfg, beforePoints)
|
||||
beforeLevel := EffectiveLevelForUser(cfg, beforePoints, user.RenterGrowthLevelOverride)
|
||||
multiplierBps := beforeLevel.PointsMultiplierBps
|
||||
points := PointsForAmount(basisCent, cfg.PointsPerYuan, multiplierBps)
|
||||
if points <= 0 {
|
||||
@@ -319,7 +319,7 @@ func awardOrderSide(tx *gorm.DB, order *model.RentalOrder, cfg Config, role stri
|
||||
return nil
|
||||
}
|
||||
afterPoints := beforePoints + points
|
||||
afterLevel := LevelForPoints(cfg, afterPoints)
|
||||
afterLevel := LevelForUserAfterPoints(cfg, afterPoints, user.RenterGrowthLevelOverride)
|
||||
user.RenterGrowthPoints = afterPoints
|
||||
user.RenterGrowthLevel = afterLevel.Code
|
||||
user.PointsAvailable = beforeAvailable + points
|
||||
@@ -446,10 +446,10 @@ func revokeOrderSide(tx *gorm.DB, order *model.RentalOrder, cfg Config, role str
|
||||
beforeAvailable := maxInt64(user.PointsAvailable, 0)
|
||||
revokedPoints := minInt64(points, beforePoints)
|
||||
revokedAvailable := minInt64(points, beforeAvailable)
|
||||
beforeLevel := LevelForPoints(cfg, beforePoints)
|
||||
beforeLevel := EffectiveLevelForUser(cfg, beforePoints, user.RenterGrowthLevelOverride)
|
||||
afterPoints := beforePoints - revokedPoints
|
||||
afterLevel := LevelForPoints(cfg, afterPoints)
|
||||
if !cfg.Enabled {
|
||||
afterLevel := LevelForUserAfterPoints(cfg, afterPoints, user.RenterGrowthLevelOverride)
|
||||
if !cfg.Enabled && user.RenterGrowthLevelOverride == "" {
|
||||
beforeLevel = LevelRule{Code: DefaultLevelCode, Name: DefaultLevelName}
|
||||
afterLevel = beforeLevel
|
||||
}
|
||||
@@ -516,6 +516,43 @@ func LevelForPoints(cfg Config, points int64) LevelRule {
|
||||
return current
|
||||
}
|
||||
|
||||
func LevelForCode(cfg Config, code string) (LevelRule, bool) {
|
||||
cfg = normalizeConfig(cfg)
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return LevelRule{}, false
|
||||
}
|
||||
for _, level := range cfg.Levels {
|
||||
if level.Code == code {
|
||||
return level, true
|
||||
}
|
||||
}
|
||||
return LevelRule{}, false
|
||||
}
|
||||
|
||||
func EffectiveLevelForUser(cfg Config, points int64, override string) LevelRule {
|
||||
if level, ok := LevelForCode(cfg, override); ok {
|
||||
return level
|
||||
}
|
||||
return LevelForPoints(cfg, points)
|
||||
}
|
||||
|
||||
func LevelForUserAfterPoints(cfg Config, points int64, override string) LevelRule {
|
||||
if level, ok := LevelForCode(cfg, override); ok {
|
||||
return level
|
||||
}
|
||||
return LevelForPoints(cfg, points)
|
||||
}
|
||||
|
||||
func CanViewPremiumLevel(code string) bool {
|
||||
switch strings.TrimSpace(code) {
|
||||
case "platinum", "diamond", "peak":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func EffectiveDepositFreeQuotaCent(levelQuotaCent int64, manualQuotaCent int64) int64 {
|
||||
return addQuotaCent(levelQuotaCent, manualQuotaCent)
|
||||
}
|
||||
@@ -526,7 +563,14 @@ func SyncAllUserLevels(tx *gorm.DB, cfg Config) error {
|
||||
return nil
|
||||
}
|
||||
if !cfg.Enabled {
|
||||
return tx.Model(&model.User{}).Where("renter_growth_level <> ?", DefaultLevelCode).Update("renter_growth_level", DefaultLevelCode).Error
|
||||
if err := tx.Model(&model.User{}).
|
||||
Where("renter_growth_level_override = ? AND renter_growth_level <> ?", "", DefaultLevelCode).
|
||||
Update("renter_growth_level", DefaultLevelCode).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.User{}).
|
||||
Where("renter_growth_level_override <> ?", "").
|
||||
UpdateColumn("renter_growth_level", gorm.Expr("renter_growth_level_override")).Error
|
||||
}
|
||||
cfg = normalizeConfig(cfg)
|
||||
caseSQL := "CASE"
|
||||
@@ -537,7 +581,14 @@ func SyncAllUserLevels(tx *gorm.DB, cfg Config) error {
|
||||
}
|
||||
caseSQL += " ELSE ? END"
|
||||
args = append(args, DefaultLevelCode)
|
||||
return tx.Model(&model.User{}).Where("1 = 1").Update("renter_growth_level", gorm.Expr(caseSQL, args...)).Error
|
||||
if err := tx.Model(&model.User{}).
|
||||
Where("renter_growth_level_override = ?", "").
|
||||
Update("renter_growth_level", gorm.Expr(caseSQL, args...)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&model.User{}).
|
||||
Where("renter_growth_level_override <> ?", "").
|
||||
UpdateColumn("renter_growth_level", gorm.Expr("renter_growth_level_override")).Error
|
||||
}
|
||||
|
||||
func normalizeConfig(cfg Config) Config {
|
||||
|
||||
@@ -128,6 +128,48 @@ func TestLevelForPointsStartsFromNormal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectiveLevelForUserHonorsManualOverride(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
if got := EffectiveLevelForUser(cfg, 0, "diamond"); got.Code != "diamond" {
|
||||
t.Fatalf("手动等级 = %s, want diamond", got.Code)
|
||||
}
|
||||
if got := EffectiveLevelForUser(cfg, 5000, ""); got.Code != "peak" {
|
||||
t.Fatalf("自动等级 = %s, want peak", got.Code)
|
||||
}
|
||||
if !CanViewPremiumLevel("platinum") || !CanViewPremiumLevel("diamond") || !CanViewPremiumLevel("peak") {
|
||||
t.Fatal("铂金及以上等级应可查看极品专区")
|
||||
}
|
||||
if CanViewPremiumLevel(DefaultLevelCode) {
|
||||
t.Fatal("普通等级不应查看极品专区")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncAllUserLevelsPreservesManualOverride(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := database.AutoMigrateIfMissing(db, &model.User{}); err != nil {
|
||||
t.Fatalf("migrate failed: %v", err)
|
||||
}
|
||||
user := model.User{
|
||||
Phone: "13800009003",
|
||||
RenterGrowthPoints: 0,
|
||||
RenterGrowthLevel: "platinum",
|
||||
RenterGrowthLevelOverride: "platinum",
|
||||
}
|
||||
if err := db.Create(&user).Error; err != nil {
|
||||
t.Fatalf("create user failed: %v", err)
|
||||
}
|
||||
if err := SyncAllUserLevels(db, DefaultConfig()); err != nil {
|
||||
t.Fatalf("SyncAllUserLevels() error = %v", err)
|
||||
}
|
||||
var saved model.User
|
||||
if err := db.First(&saved, user.ID).Error; err != nil {
|
||||
t.Fatalf("find user failed: %v", err)
|
||||
}
|
||||
if saved.RenterGrowthLevel != "platinum" || saved.RenterGrowthLevelOverride != "platinum" {
|
||||
t.Fatalf("手动等级被同步覆盖:%+v", saved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDiscountCentCapsAtPlatformFee(t *testing.T) {
|
||||
if got := CalculateDiscountCent(10000, 60, 9900); got != 60 {
|
||||
t.Fatalf("discount = %d, want capped platform fee 60", got)
|
||||
|
||||
Reference in New Issue
Block a user