Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c18a6788c4 | |||
| 9de7a69756 | |||
| dccfe45019 | |||
| d12c340743 |
@@ -37,8 +37,8 @@ type (
|
||||
Id int64 `uri:"id" validate:"required,gt=0"`
|
||||
}
|
||||
GetPromoRuleListRequest {
|
||||
Page int64 `form:"page" validate:"required"`
|
||||
Size int64 `form:"size" validate:"required"`
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
Type string `form:"type" validate:"omitempty,oneof=new_user inactive_user campaign"`
|
||||
Enabled *bool `form:"enabled"`
|
||||
Search string `form:"search,omitempty"`
|
||||
@@ -49,12 +49,12 @@ type (
|
||||
}
|
||||
SetPromoPriceRequest {
|
||||
PromoRuleId int64 `json:"promo_rule_id" validate:"required,gt=0"`
|
||||
Items []PromoPriceItem `json:"items" validate:"required,dive"`
|
||||
Items []PromoPriceItem `json:"items" validate:"required,min=1,dive"`
|
||||
}
|
||||
GetPromoPriceListRequest {
|
||||
PromoRuleId int64 `form:"promo_rule_id" validate:"required,gt=0"`
|
||||
Page int64 `form:"page" validate:"required"`
|
||||
Size int64 `form:"size" validate:"required"`
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
}
|
||||
GetPromoPriceListResponse {
|
||||
Total int64 `json:"total"`
|
||||
@@ -64,8 +64,8 @@ type (
|
||||
Id int64 `uri:"id" validate:"required,gt=0"`
|
||||
}
|
||||
GetPromoUsageListRequest {
|
||||
Page int64 `form:"page" validate:"required"`
|
||||
Size int64 `form:"size" validate:"required"`
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
RuleId int64 `form:"rule_id,omitempty"`
|
||||
UserId int64 `form:"user_id,omitempty"`
|
||||
SubscribeId int64 `form:"subscribe_id,omitempty"`
|
||||
|
||||
+1
-1
@@ -229,6 +229,7 @@ type (
|
||||
Quantity int64 `json:"quantity"`
|
||||
Discount float64 `json:"discount"`
|
||||
MapApple string `json:"map_apple"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
}
|
||||
PromoPrice {
|
||||
Id int64 `json:"id"`
|
||||
@@ -285,7 +286,6 @@ type (
|
||||
UnitPrice int64 `json:"unit_price"`
|
||||
UnitTime string `json:"unit_time"`
|
||||
Discount []SubscribeDiscount `json:"discount"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
NodeCount int64 `json:"node_count"`
|
||||
Replacement int64 `json:"replacement"`
|
||||
Inventory int64 `json:"inventory"`
|
||||
|
||||
@@ -2,12 +2,14 @@ package promo
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DeletePriceLogic struct {
|
||||
@@ -27,6 +29,10 @@ func NewDeletePriceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delet
|
||||
func (l *DeletePriceLogic) DeletePrice(req *types.DeletePromoPriceRequest) error {
|
||||
price, err := l.svcCtx.PromoModel.FindPrice(l.ctx, req.Id)
|
||||
if err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
l.Errorw("[DeletePromoPrice] Price Not Found", logger.Field("id", req.Id))
|
||||
return errors.Wrapf(xerr.NewErrCodeMsg(404, "promo price not found"), "promo price not found: %d", req.Id)
|
||||
}
|
||||
l.Errorw("[DeletePromoPrice] Find Price Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find promo price error: %v", err.Error())
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package promo
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DeleteRuleLogic struct {
|
||||
@@ -25,6 +27,14 @@ func NewDeleteRuleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete
|
||||
}
|
||||
|
||||
func (l *DeleteRuleLogic) DeleteRule(req *types.DeletePromoRuleRequest) error {
|
||||
if _, err := l.svcCtx.PromoModel.FindRule(l.ctx, req.Id); err != nil {
|
||||
if stderrors.Is(err, gorm.ErrRecordNotFound) {
|
||||
l.Errorw("[DeletePromoRule] Rule Not Found", logger.Field("id", req.Id))
|
||||
return errors.Wrapf(xerr.NewErrCodeMsg(404, "promo rule not found"), "promo rule not found: %d", req.Id)
|
||||
}
|
||||
l.Errorw("[DeletePromoRule] Find Rule Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find promo rule error: %v", err.Error())
|
||||
}
|
||||
if err := l.svcCtx.PromoModel.DeleteRule(l.ctx, req.Id); err != nil {
|
||||
l.Errorw("[DeletePromoRule] Database Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete promo rule error: %v", err.Error())
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package promo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
promomodel "github.com/perfect-panel/server/internal/model/promo"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type fakePromoModel struct{}
|
||||
|
||||
func (fakePromoModel) QueryEligibleRules(context.Context, int64, int64) ([]*promomodel.RuleWithPrice, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) InsertUsage(context.Context, *promomodel.Usage, ...*gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) InsertRule(context.Context, *promomodel.Rule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) FindRule(context.Context, int64) (*promomodel.Rule, error) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
func (fakePromoModel) UpdateRule(context.Context, *promomodel.Rule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) DeleteRule(context.Context, int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) QueryRuleList(context.Context, int, int, string, *bool, string) (int64, []*promomodel.Rule, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) UpsertPrices(context.Context, int64, []*promomodel.SubscribePromo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) FindPrice(context.Context, int64) (*promomodel.SubscribePromo, error) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
func (fakePromoModel) DeletePrice(context.Context, int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) QueryPriceList(context.Context, int64, int, int) (int64, []*promomodel.SubscribePromo, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) QueryUsageList(context.Context, promomodel.UsageFilter) (int64, []*promomodel.Usage, error) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (fakePromoModel) Transaction(context.Context, func(*gorm.DB) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestDeleteRuleNotFoundReturns404(t *testing.T) {
|
||||
svcCtx := &svc.ServiceContext{PromoModel: fakePromoModel{}}
|
||||
err := NewDeleteRuleLogic(context.Background(), svcCtx).DeleteRule(&types.DeletePromoRuleRequest{Id: 1})
|
||||
assertCodeError(t, err, 404)
|
||||
}
|
||||
|
||||
func TestDeletePriceNotFoundReturns404(t *testing.T) {
|
||||
svcCtx := &svc.ServiceContext{PromoModel: fakePromoModel{}}
|
||||
err := NewDeletePriceLogic(context.Background(), svcCtx).DeletePrice(&types.DeletePromoPriceRequest{Id: 1})
|
||||
assertCodeError(t, err, 404)
|
||||
}
|
||||
|
||||
func assertCodeError(t *testing.T, err error, want uint32) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
codeErr, ok := pkgerrors.Cause(err).(*xerr.CodeError)
|
||||
if !ok {
|
||||
t.Fatalf("expected CodeError, got %T", pkgerrors.Cause(err))
|
||||
}
|
||||
if got := codeErr.GetErrCode(); got != want {
|
||||
t.Fatalf("unexpected error code: got %d want %d", got, want)
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"context"
|
||||
|
||||
promomodel "github.com/perfect-panel/server/internal/model/promo"
|
||||
subscribeModel "github.com/perfect-panel/server/internal/model/subscribe"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SetPriceLogic struct {
|
||||
@@ -31,16 +31,30 @@ func (l *SetPriceLogic) SetPrice(req *types.SetPromoPriceRequest) error {
|
||||
l.Errorw("[SetPromoPrice] Find Rule Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find promo rule error: %v", err.Error())
|
||||
}
|
||||
subscribeIds := make([]int64, 0, len(req.Items))
|
||||
seenSubscribeIds := make(map[int64]struct{}, len(req.Items))
|
||||
for _, item := range req.Items {
|
||||
if _, ok := seenSubscribeIds[item.SubscribeId]; ok {
|
||||
continue
|
||||
}
|
||||
seenSubscribeIds[item.SubscribeId] = struct{}{}
|
||||
subscribeIds = append(subscribeIds, item.SubscribeId)
|
||||
}
|
||||
var subscribes []*subscribeModel.Subscribe
|
||||
if err := l.svcCtx.DB.WithContext(l.ctx).Model(&subscribeModel.Subscribe{}).Where("id IN ?", subscribeIds).Find(&subscribes).Error; err != nil {
|
||||
l.Errorw("[SetPromoPrice] Find Subscribe Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error())
|
||||
}
|
||||
subscribeById := make(map[int64]*subscribeModel.Subscribe, len(subscribes))
|
||||
for _, sub := range subscribes {
|
||||
subscribeById[sub.Id] = sub
|
||||
}
|
||||
items := make([]*promomodel.SubscribePromo, 0, len(req.Items))
|
||||
cacheKeys := make([]string, 0, len(req.Items))
|
||||
for _, item := range req.Items {
|
||||
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, item.SubscribeId)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "subscribe plan not found")
|
||||
}
|
||||
l.Errorw("[SetPromoPrice] Find Subscribe Error", logger.Field("error", err.Error()))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribe error: %v", err.Error())
|
||||
sub, ok := subscribeById[item.SubscribeId]
|
||||
if !ok {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "subscribe plan not found")
|
||||
}
|
||||
originPrice := sub.UnitPrice * item.Quantity
|
||||
if item.PromoPrice >= originPrice {
|
||||
|
||||
@@ -40,8 +40,8 @@ func calculatePurchasePrice(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if promoResult != nil && promoResult.Eligible && promoResult.PromoPrice < unitPrice {
|
||||
result.PayableBase = promoResult.PromoPrice * quantity
|
||||
if promoResult != nil && promoResult.Eligible && promoResult.PromoPrice < originalPrice {
|
||||
result.PayableBase = promoResult.PromoPrice
|
||||
result.PromoRuleId = promoResult.RuleID
|
||||
result.PromoDiscount = originalPrice - result.PayableBase
|
||||
result.PromoPrice = promoResult.PromoPrice
|
||||
|
||||
@@ -66,7 +66,7 @@ func (m fakePromoModel) Transaction(context.Context, func(*gorm.DB) error) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCalculatePurchasePricePromoSkipsPercentDiscount(t *testing.T) {
|
||||
func TestCalculatePurchasePricePromoUsesQuantityTierTotalPrice(t *testing.T) {
|
||||
svcCtx := &svc.ServiceContext{
|
||||
DB: &gorm.DB{},
|
||||
PromoModel: fakePromoModel{rules: []*promo.RuleWithPrice{
|
||||
@@ -77,7 +77,7 @@ func TestCalculatePurchasePricePromoSkipsPercentDiscount(t *testing.T) {
|
||||
Type: promo.RuleTypeCampaign,
|
||||
Enabled: true,
|
||||
},
|
||||
PromoPrice: 600,
|
||||
PromoPrice: 279,
|
||||
},
|
||||
}},
|
||||
}
|
||||
@@ -87,9 +87,9 @@ func TestCalculatePurchasePricePromoSkipsPercentDiscount(t *testing.T) {
|
||||
svcCtx,
|
||||
1,
|
||||
2,
|
||||
1000,
|
||||
3,
|
||||
[]types.SubscribeDiscount{{Quantity: 3, Discount: 50}},
|
||||
100,
|
||||
7,
|
||||
[]types.SubscribeDiscount{{Quantity: 7, Discount: 50}},
|
||||
true,
|
||||
true,
|
||||
)
|
||||
@@ -97,11 +97,11 @@ func TestCalculatePurchasePricePromoSkipsPercentDiscount(t *testing.T) {
|
||||
t.Fatalf("calculatePurchasePrice returned error: %v", err)
|
||||
}
|
||||
|
||||
if result.OriginalPrice != 3000 {
|
||||
t.Fatalf("OriginalPrice = %d, want 3000", result.OriginalPrice)
|
||||
if result.OriginalPrice != 700 {
|
||||
t.Fatalf("OriginalPrice = %d, want 700", result.OriginalPrice)
|
||||
}
|
||||
if result.PayableBase != 1800 {
|
||||
t.Fatalf("PayableBase = %d, want 1800", result.PayableBase)
|
||||
if result.PayableBase != 279 {
|
||||
t.Fatalf("PayableBase = %d, want 279", result.PayableBase)
|
||||
}
|
||||
if result.DiscountAmount != 0 {
|
||||
t.Fatalf("DiscountAmount = %d, want 0", result.DiscountAmount)
|
||||
@@ -109,8 +109,11 @@ func TestCalculatePurchasePricePromoSkipsPercentDiscount(t *testing.T) {
|
||||
if result.PromoRuleId != 9 {
|
||||
t.Fatalf("PromoRuleId = %d, want 9", result.PromoRuleId)
|
||||
}
|
||||
if result.PromoDiscount != 1200 {
|
||||
t.Fatalf("PromoDiscount = %d, want 1200", result.PromoDiscount)
|
||||
if result.PromoDiscount != 421 {
|
||||
t.Fatalf("PromoDiscount = %d, want 421", result.PromoDiscount)
|
||||
}
|
||||
if result.PromoPrice != 279 {
|
||||
t.Fatalf("PromoPrice = %d, want 279", result.PromoPrice)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +128,7 @@ func TestCalculatePurchasePriceIgnoresInvalidPromoPrice(t *testing.T) {
|
||||
Type: promo.RuleTypeCampaign,
|
||||
Enabled: true,
|
||||
},
|
||||
PromoPrice: 1000,
|
||||
PromoPrice: 3000,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
|
||||
type subscribePromoCandidate struct {
|
||||
SubscribeId int64 `gorm:"column:subscribe_id"`
|
||||
Quantity int64 `gorm:"column:quantity"`
|
||||
RuleName string `gorm:"column:rule_name"`
|
||||
RuleType string `gorm:"column:rule_type"`
|
||||
PromoPrice int64 `gorm:"column:promo_price"`
|
||||
@@ -38,8 +39,8 @@ type promoRuleParams struct {
|
||||
InactiveMonths int `json:"inactive_months"`
|
||||
}
|
||||
|
||||
func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64) (map[int64]*types.SubscribePromo, error) {
|
||||
result := make(map[int64]*types.SubscribePromo)
|
||||
func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64) (map[int64]map[int64]*types.SubscribePromo, error) {
|
||||
result := make(map[int64]map[int64]*types.SubscribePromo)
|
||||
if len(subscribeIDs) == 0 || svcCtx == nil || svcCtx.DB == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -56,7 +57,7 @@ func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subs
|
||||
evaluator := promoEligibilityEvaluator{ctx: ctx, db: svcCtx.DB, userInfo: userInfo}
|
||||
now := time.Now()
|
||||
for _, candidate := range candidates {
|
||||
if _, exists := result[candidate.SubscribeId]; exists {
|
||||
if _, exists := result[candidate.SubscribeId][candidate.Quantity]; exists {
|
||||
continue
|
||||
}
|
||||
if !candidate.isActive(now) {
|
||||
@@ -69,7 +70,10 @@ func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subs
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
result[candidate.SubscribeId] = &types.SubscribePromo{
|
||||
if _, exists := result[candidate.SubscribeId]; !exists {
|
||||
result[candidate.SubscribeId] = make(map[int64]*types.SubscribePromo)
|
||||
}
|
||||
result[candidate.SubscribeId][candidate.Quantity] = &types.SubscribePromo{
|
||||
RuleName: candidate.RuleName,
|
||||
RuleType: candidate.RuleType,
|
||||
PromoPrice: candidate.PromoPrice,
|
||||
@@ -84,7 +88,7 @@ func querySubscribePromoCandidates(ctx context.Context, svcCtx *svc.ServiceConte
|
||||
var candidates []subscribePromoCandidate
|
||||
query := svcCtx.DB.WithContext(ctx).
|
||||
Table("subscribe_promo AS sp").
|
||||
Select("sp.subscribe_id, sp.promo_price, pr.name AS rule_name, pr.type AS rule_type, pr.params, pr.start_time, pr.end_time").
|
||||
Select("sp.subscribe_id, sp.quantity, sp.promo_price, pr.name AS rule_name, pr.type AS rule_type, pr.params, pr.start_time, pr.end_time").
|
||||
Joins("JOIN promo_rule AS pr ON pr.id = sp.promo_rule_id AND pr.deleted_at IS NULL").
|
||||
Where("sp.subscribe_id IN ? AND sp.promo_price > 0 AND pr.enabled = ?", subscribeIDs, true)
|
||||
if !loggedIn {
|
||||
@@ -92,6 +96,7 @@ func querySubscribePromoCandidates(ctx context.Context, svcCtx *svc.ServiceConte
|
||||
}
|
||||
err := query.
|
||||
Order("sp.subscribe_id ASC").
|
||||
Order("sp.quantity ASC").
|
||||
Order("pr.priority DESC").
|
||||
Order("pr.id ASC").
|
||||
Scan(&candidates).Error
|
||||
|
||||
@@ -56,11 +56,19 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
|
||||
var discount []types.SubscribeDiscount
|
||||
_ = json.Unmarshal([]byte(item.Discount), &discount)
|
||||
sub.Discount = discount
|
||||
list[i] = sub
|
||||
}
|
||||
list[i] = sub
|
||||
}
|
||||
|
||||
promos, err := loadSubscribePromoMap(l.ctx, l.svcCtx, subscribeIDs)
|
||||
if err != nil {
|
||||
l.Errorw("[QuerySubscribeListLogic] Query Promo Error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
for i := range list {
|
||||
applySubscribeDiscountPromos(&list[i], promos[list[i].Id])
|
||||
}
|
||||
|
||||
// 老版本客户端(无 X-App-Id)去掉每个套餐 discount 的最后一个
|
||||
hasAppId, _ := l.ctx.Value(constant.CtxKeyHasAppId).(bool)
|
||||
if !hasAppId {
|
||||
@@ -71,16 +79,13 @@ func (l *QuerySubscribeListLogic) QuerySubscribeList(req *types.QuerySubscribeLi
|
||||
}
|
||||
}
|
||||
|
||||
promos, err := loadSubscribePromoMap(l.ctx, l.svcCtx, subscribeIDs)
|
||||
if err != nil {
|
||||
l.Errorw("[QuerySubscribeListLogic] Query Promo Error", logger.Field("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
for i := range list {
|
||||
list[i].Promo = promos[list[i].Id]
|
||||
}
|
||||
|
||||
resp.List = list
|
||||
resp.Total = int64(len(list))
|
||||
return
|
||||
}
|
||||
|
||||
func applySubscribeDiscountPromos(subscribe *types.Subscribe, promoByQuantity map[int64]*types.SubscribePromo) {
|
||||
for i := range subscribe.Discount {
|
||||
subscribe.Discount[i].Promo = promoByQuantity[subscribe.Discount[i].Quantity]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,15 @@ func (m *defaultPromoModel) FindRule(ctx context.Context, id int64) (*Rule, erro
|
||||
}
|
||||
|
||||
func (m *defaultPromoModel) UpdateRule(ctx context.Context, data *Rule) error {
|
||||
return m.db.WithContext(ctx).Save(data).Error
|
||||
return m.db.WithContext(ctx).Model(&Rule{}).Where("id = ?", data.Id).Updates(map[string]interface{}{
|
||||
"name": data.Name,
|
||||
"type": data.Type,
|
||||
"params": data.Params,
|
||||
"priority": data.Priority,
|
||||
"enabled": data.Enabled,
|
||||
"start_time": data.StartTime,
|
||||
"end_time": data.EndTime,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (m *defaultPromoModel) DeleteRule(ctx context.Context, id int64) error {
|
||||
@@ -129,7 +137,10 @@ func (m *defaultPromoModel) UpsertPrices(ctx context.Context, ruleId int64, item
|
||||
return err
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return tx.Create(item).Error
|
||||
if err := tx.Create(item).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
existing.Quantity = item.Quantity
|
||||
existing.PromoPrice = item.PromoPrice
|
||||
|
||||
@@ -33,7 +33,7 @@ func (Rule) TableName() string {
|
||||
type SubscribePromo struct {
|
||||
Id int64 `gorm:"primaryKey"`
|
||||
SubscribeId int64 `gorm:"type:bigint unsigned;not null;comment:Subscribe ID"`
|
||||
Quantity int64 `gorm:"type:bigint;not null;default:1;comment:Quantity"`
|
||||
Quantity int64 `gorm:"type:int;not null;default:0;comment:购买数量"`
|
||||
PromoRuleId int64 `gorm:"type:bigint unsigned;not null;comment:Promo Rule ID"`
|
||||
PromoPrice int64 `gorm:"type:bigint;not null;default:0;comment:Promo Price"`
|
||||
CreatedAt time.Time `gorm:"<-:create;comment:Create Time"`
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func TestPromoPriceItemsMustNotBeEmpty(t *testing.T) {
|
||||
validate := validator.New()
|
||||
req := SetPromoPriceRequest{
|
||||
PromoRuleId: 1,
|
||||
Items: []PromoPriceItem{},
|
||||
}
|
||||
|
||||
if err := validate.Struct(req); err == nil {
|
||||
t.Fatal("expected empty promo price items to fail validation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromoListPageSizeLimit(t *testing.T) {
|
||||
validate := validator.New()
|
||||
tests := []struct {
|
||||
name string
|
||||
req any
|
||||
}{
|
||||
{
|
||||
name: "rule list",
|
||||
req: GetPromoRuleListRequest{
|
||||
Page: 1,
|
||||
Size: 201,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "price list",
|
||||
req: GetPromoPriceListRequest{
|
||||
PromoRuleId: 1,
|
||||
Page: 1,
|
||||
Size: 201,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "usage list",
|
||||
req: GetPromoUsageListRequest{
|
||||
Page: 1,
|
||||
Size: 201,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validate.Struct(tt.req); err == nil {
|
||||
t.Fatal("expected page size greater than 200 to fail validation")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -1147,8 +1147,8 @@ type GetCouponListResponse struct {
|
||||
|
||||
type GetPromoPriceListRequest struct {
|
||||
PromoRuleId int64 `form:"promo_rule_id" validate:"required,gt=0"`
|
||||
Page int64 `form:"page" validate:"required"`
|
||||
Size int64 `form:"size" validate:"required"`
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
}
|
||||
|
||||
type GetPromoPriceListResponse struct {
|
||||
@@ -1161,8 +1161,8 @@ type GetPromoRuleDetailRequest struct {
|
||||
}
|
||||
|
||||
type GetPromoRuleListRequest struct {
|
||||
Page int64 `form:"page" validate:"required"`
|
||||
Size int64 `form:"size" validate:"required"`
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
Type string `form:"type" validate:"omitempty,oneof=new_user inactive_user campaign"`
|
||||
Enabled *bool `form:"enabled"`
|
||||
Search string `form:"search,omitempty"`
|
||||
@@ -1174,8 +1174,8 @@ type GetPromoRuleListResponse struct {
|
||||
}
|
||||
|
||||
type GetPromoUsageListRequest struct {
|
||||
Page int64 `form:"page" validate:"required"`
|
||||
Size int64 `form:"size" validate:"required"`
|
||||
Page int64 `form:"page" validate:"required,gt=0"`
|
||||
Size int64 `form:"size" validate:"required,gt=0,lte=200"`
|
||||
RuleId int64 `form:"rule_id,omitempty"`
|
||||
UserId int64 `form:"user_id,omitempty"`
|
||||
SubscribeId int64 `form:"subscribe_id,omitempty"`
|
||||
@@ -2880,7 +2880,6 @@ type Subscribe struct {
|
||||
UnitPrice int64 `json:"unit_price"`
|
||||
UnitTime string `json:"unit_time"`
|
||||
Discount []SubscribeDiscount `json:"discount"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
NodeCount int64 `json:"node_count"`
|
||||
Replacement int64 `json:"replacement"`
|
||||
Inventory int64 `json:"inventory"`
|
||||
@@ -2941,10 +2940,11 @@ type SubscribeConfig struct {
|
||||
}
|
||||
|
||||
type SubscribeDiscount struct {
|
||||
Quantity int64 `json:"quantity"`
|
||||
Discount float64 `json:"discount"`
|
||||
NewUserOnly bool `json:"new_user_only"`
|
||||
MapApple string `json:"map_apple"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
Discount float64 `json:"discount"`
|
||||
NewUserOnly bool `json:"new_user_only"`
|
||||
MapApple string `json:"map_apple"`
|
||||
Promo *SubscribePromo `json:"promo"`
|
||||
}
|
||||
|
||||
type SubscribeGroup struct {
|
||||
@@ -3223,7 +3223,7 @@ type UpdateCouponRequest struct {
|
||||
|
||||
type SetPromoPriceRequest struct {
|
||||
PromoRuleId int64 `json:"promo_rule_id" validate:"required,gt=0"`
|
||||
Items []PromoPriceItem `json:"items" validate:"required,dive"`
|
||||
Items []PromoPriceItem `json:"items" validate:"required,min=1,dive"`
|
||||
}
|
||||
|
||||
type DeletePromoPriceRequest struct {
|
||||
|
||||
Reference in New Issue
Block a user