修复(#86): 排除永久订阅回归促销误判
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/perfect-panel/server/pkg/xerr"
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PromoResult struct {
|
type PromoResult struct {
|
||||||
@@ -148,6 +149,12 @@ func evaluateInactiveUserPromo(
|
|||||||
err := db.WithContext(ctx).
|
err := db.WithContext(ctx).
|
||||||
Model(&user.Subscribe{}).
|
Model(&user.Subscribe{}).
|
||||||
Where("user_id = ?", userID).
|
Where("user_id = ?", userID).
|
||||||
|
Order(clause.OrderBy{
|
||||||
|
Expression: clause.Expr{
|
||||||
|
SQL: "CASE WHEN expire_time = ? THEN 0 ELSE 1 END",
|
||||||
|
Vars: []interface{}{permanentSubscribeExpireTime()},
|
||||||
|
},
|
||||||
|
}).
|
||||||
Order("expire_time DESC").
|
Order("expire_time DESC").
|
||||||
Limit(1).
|
Limit(1).
|
||||||
Take(&lastSub).Error
|
Take(&lastSub).Error
|
||||||
@@ -158,8 +165,19 @@ func evaluateInactiveUserPromo(
|
|||||||
return false, time.Time{}, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query promo inactive user subscription failed")
|
return false, time.Time{}, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query promo inactive user subscription failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
threshold := now.AddDate(0, -params.InactiveMonths, 0)
|
return isInactivePromoEligible(lastSub.ExpireTime, now, params.InactiveMonths), ruleExpiresAt, nil
|
||||||
return lastSub.ExpireTime.Before(threshold) || lastSub.ExpireTime.Equal(threshold), ruleExpiresAt, nil
|
}
|
||||||
|
|
||||||
|
func isInactivePromoEligible(expireTime time.Time, now time.Time, inactiveMonths int) bool {
|
||||||
|
if expireTime.Equal(permanentSubscribeExpireTime()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
threshold := now.AddDate(0, -inactiveMonths, 0)
|
||||||
|
return expireTime.Before(threshold) || expireTime.Equal(threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
func permanentSubscribeExpireTime() time.Time {
|
||||||
|
return time.UnixMilli(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func promoRuleExpiresAt(rule *promo.RuleWithPrice) time.Time {
|
func promoRuleExpiresAt(rule *promo.RuleWithPrice) time.Time {
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsInactivePromoEligible(t *testing.T) {
|
||||||
|
now := time.Date(2026, time.May, 27, 12, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expireAt time.Time
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "expired before inactive threshold is eligible",
|
||||||
|
expireAt: now.AddDate(0, -4, 0),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expired exactly at inactive threshold is eligible",
|
||||||
|
expireAt: now.AddDate(0, -3, 0),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "recently expired subscription is not eligible",
|
||||||
|
expireAt: now.AddDate(0, -2, 0),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "active future subscription is not eligible",
|
||||||
|
expireAt: now.Add(time.Hour),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "permanent subscription marker is not eligible",
|
||||||
|
expireAt: time.UnixMilli(0),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := isInactivePromoEligible(tt.expireAt, now, 3)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("isInactivePromoEligible() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user