link user
This commit is contained in:
+38
-17
@@ -31,7 +31,7 @@ type Effect interface {
|
||||
// Apply mutates the cart for a qualifying action and returns the discount it
|
||||
// took (nil for non-monetary effects such as free shipping) plus whether
|
||||
// anything took effect worth recording.
|
||||
Apply(g *cart.CartGrain, rule PromotionRule, a Action) (discount *cart.Price, applied bool)
|
||||
Apply(g *cart.CartGrain, rule PromotionRule, a Action) (discount *cart.Price, itemIds []uint32, applied bool)
|
||||
// Progress reports how far the cart is from (further) unlocking this action,
|
||||
// as an open key/value payload. qualified says whether the owning rule
|
||||
// currently applies, letting the effect distinguish "remaining to unlock"
|
||||
@@ -77,8 +77,9 @@ func (s *PromotionService) ApplyResults(g *cart.CartGrain, results []EvaluationR
|
||||
}
|
||||
recorded := false
|
||||
if res.Applicable {
|
||||
if discount, applied := eff.Apply(g, res.Rule, a); applied {
|
||||
if discount, itemIds, applied := eff.Apply(g, res.Rule, a); applied {
|
||||
entry.Discount = discount
|
||||
entry.ItemIds = itemIds
|
||||
recorded = true
|
||||
}
|
||||
}
|
||||
@@ -111,9 +112,9 @@ type percentageDiscountEffect struct{}
|
||||
|
||||
func (percentageDiscountEffect) Type() ActionType { return ActionPercentageDiscount }
|
||||
|
||||
func (percentageDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action) (*cart.Price, bool) {
|
||||
func (percentageDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action) (*cart.Price, []uint32, bool) {
|
||||
d := applyPercentageDiscount(g, percentFromValue(a.Value))
|
||||
return d, d != nil
|
||||
return d, nil, d != nil
|
||||
}
|
||||
|
||||
func (percentageDiscountEffect) Progress(s *PromotionService, rule PromotionRule, a Action, ctx *PromotionEvalContext, qualified bool) (map[string]any, bool) {
|
||||
@@ -129,8 +130,8 @@ type freeShippingEffect struct{}
|
||||
|
||||
func (freeShippingEffect) Type() ActionType { return ActionFreeShipping }
|
||||
|
||||
func (freeShippingEffect) Apply(_ *cart.CartGrain, _ PromotionRule, _ Action) (*cart.Price, bool) {
|
||||
return nil, true
|
||||
func (freeShippingEffect) Apply(_ *cart.CartGrain, _ PromotionRule, _ Action) (*cart.Price, []uint32, bool) {
|
||||
return nil, nil, true
|
||||
}
|
||||
|
||||
func (freeShippingEffect) Progress(s *PromotionService, rule PromotionRule, a Action, ctx *PromotionEvalContext, qualified bool) (map[string]any, bool) {
|
||||
@@ -148,9 +149,9 @@ type tieredDiscountEffect struct{}
|
||||
|
||||
func (tieredDiscountEffect) Type() ActionType { return ActionTieredDiscount }
|
||||
|
||||
func (tieredDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action) (*cart.Price, bool) {
|
||||
func (tieredDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action) (*cart.Price, []uint32, bool) {
|
||||
d := applyTieredDiscount(g, a)
|
||||
return d, d != nil
|
||||
return d, nil, d != nil
|
||||
}
|
||||
|
||||
func (tieredDiscountEffect) Progress(s *PromotionService, rule PromotionRule, a Action, ctx *PromotionEvalContext, qualified bool) (map[string]any, bool) {
|
||||
@@ -387,7 +388,7 @@ type unitItem struct {
|
||||
price money.Cents
|
||||
}
|
||||
|
||||
func (buyXGetYEffect) Apply(g *cart.CartGrain, rule PromotionRule, a Action) (*cart.Price, bool) {
|
||||
func (buyXGetYEffect) Apply(g *cart.CartGrain, rule PromotionRule, a Action) (*cart.Price, []uint32, bool) {
|
||||
config := a.Config
|
||||
buy := int(firstNum(config, "buy"))
|
||||
if buy <= 0 {
|
||||
@@ -416,7 +417,7 @@ func (buyXGetYEffect) Apply(g *cart.CartGrain, rule PromotionRule, a Action) (*c
|
||||
n := len(units)
|
||||
numFree := (n / (buy + get)) * get
|
||||
if numFree <= 0 {
|
||||
return nil, false
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// Sort units by price ascending so we discount the cheapest ones
|
||||
@@ -431,13 +432,15 @@ func (buyXGetYEffect) Apply(g *cart.CartGrain, rule PromotionRule, a Action) (*c
|
||||
})
|
||||
|
||||
totalDiscount := cart.NewPrice()
|
||||
affectedMap := make(map[uint32]bool)
|
||||
for i := 0; i < numFree; i++ {
|
||||
unitDiscount := scalePrice(&units[i].item.Price, discount)
|
||||
totalDiscount.Add(*unitDiscount)
|
||||
affectedMap[units[i].item.Id] = true
|
||||
}
|
||||
|
||||
if totalDiscount.IncVat <= 0 {
|
||||
return nil, false
|
||||
return nil, nil, false
|
||||
}
|
||||
// Never discount below zero.
|
||||
if totalDiscount.IncVat > g.TotalPrice.IncVat {
|
||||
@@ -445,7 +448,14 @@ func (buyXGetYEffect) Apply(g *cart.CartGrain, rule PromotionRule, a Action) (*c
|
||||
}
|
||||
g.TotalDiscount.Add(*totalDiscount)
|
||||
g.TotalPrice.Subtract(*totalDiscount)
|
||||
return totalDiscount, true
|
||||
|
||||
var itemIds []uint32
|
||||
for id := range affectedMap {
|
||||
itemIds = append(itemIds, id)
|
||||
}
|
||||
slices.Sort(itemIds)
|
||||
|
||||
return totalDiscount, itemIds, true
|
||||
}
|
||||
|
||||
func (buyXGetYEffect) Progress(s *PromotionService, rule PromotionRule, a Action, ctx *PromotionEvalContext, qualified bool) (map[string]any, bool) {
|
||||
@@ -460,9 +470,9 @@ type bundleDiscountEffect struct{}
|
||||
|
||||
func (bundleDiscountEffect) Type() ActionType { return ActionBundleDiscount }
|
||||
|
||||
func (bundleDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action) (*cart.Price, bool) {
|
||||
func (bundleDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action) (*cart.Price, []uint32, bool) {
|
||||
if a.BundleConfig == nil {
|
||||
return nil, false
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// 1. Represent all cart items as individual units
|
||||
@@ -545,10 +555,11 @@ func (bundleDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action)
|
||||
}
|
||||
|
||||
if len(formedBundles) == 0 {
|
||||
return nil, false
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
totalDiscount := cart.NewPrice()
|
||||
affectedMap := make(map[uint32]bool)
|
||||
for _, bundle := range formedBundles {
|
||||
// Calculate the original bundle price
|
||||
bundlePrice := cart.NewPrice()
|
||||
@@ -579,11 +590,14 @@ func (bundleDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action)
|
||||
|
||||
if bundleDiscount != nil && bundleDiscount.IncVat > 0 {
|
||||
totalDiscount.Add(*bundleDiscount)
|
||||
for _, item := range bundle {
|
||||
affectedMap[item.Id] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if totalDiscount.IncVat <= 0 {
|
||||
return nil, false
|
||||
return nil, nil, false
|
||||
}
|
||||
// Never discount below zero.
|
||||
if totalDiscount.IncVat > g.TotalPrice.IncVat {
|
||||
@@ -591,7 +605,14 @@ func (bundleDiscountEffect) Apply(g *cart.CartGrain, _ PromotionRule, a Action)
|
||||
}
|
||||
g.TotalDiscount.Add(*totalDiscount)
|
||||
g.TotalPrice.Subtract(*totalDiscount)
|
||||
return totalDiscount, true
|
||||
|
||||
var itemIds []uint32
|
||||
for id := range affectedMap {
|
||||
itemIds = append(itemIds, id)
|
||||
}
|
||||
slices.Sort(itemIds)
|
||||
|
||||
return totalDiscount, itemIds, true
|
||||
}
|
||||
|
||||
func (bundleDiscountEffect) Progress(s *PromotionService, rule PromotionRule, a Action, ctx *PromotionEvalContext, qualified bool) (map[string]any, bool) {
|
||||
|
||||
@@ -184,6 +184,13 @@ func TestBuyXGetYEffect(t *testing.T) {
|
||||
if got := g.TotalPrice.IncVat; got != 2000 {
|
||||
t.Errorf("total price = %d, want 2000", got)
|
||||
}
|
||||
if len(g.AppliedPromotions) != 1 {
|
||||
t.Fatalf("expected 1 applied promotion, got %d", len(g.AppliedPromotions))
|
||||
}
|
||||
itemIds := g.AppliedPromotions[0].ItemIds
|
||||
if len(itemIds) != 1 || itemIds[0] != 1 {
|
||||
t.Errorf("expected affected itemIds [1], got %v", itemIds)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleDiscountEffect(t *testing.T) {
|
||||
@@ -247,4 +254,11 @@ func TestBundleDiscountEffect(t *testing.T) {
|
||||
if got := g.TotalPrice.IncVat; got != 4000 {
|
||||
t.Errorf("total price = %d, want 4000", got)
|
||||
}
|
||||
if len(g.AppliedPromotions) != 1 {
|
||||
t.Fatalf("expected 1 applied promotion, got %d", len(g.AppliedPromotions))
|
||||
}
|
||||
itemIds := g.AppliedPromotions[0].ItemIds
|
||||
if len(itemIds) != 2 || itemIds[0] != 1 || itemIds[1] != 2 {
|
||||
t.Errorf("expected affected itemIds [1, 2], got %v", itemIds)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user