cart and checkout
Build and Publish / BuildAndDeployAmd64 (push) Failing after 4s
Build and Publish / BuildAndDeployArm64 (push) Failing after 6s

This commit is contained in:
2026-06-27 19:49:00 +02:00
parent 492f54ff45
commit 528c59bfd3
67 changed files with 3618 additions and 1031 deletions
+34 -2
View File
@@ -42,6 +42,7 @@ type PromotionEvalContext struct {
CustomerLifetimeValue float64
OrderCount int
Now time.Time
CouponCodes []string
}
// ContextOption allows customization of fields when building a PromotionEvalContext.
@@ -67,6 +68,11 @@ func WithNow(t time.Time) ContextOption {
return func(c *PromotionEvalContext) { c.Now = t }
}
// WithCouponCodes sets active coupon codes.
func WithCouponCodes(codes []string) ContextOption {
return func(c *PromotionEvalContext) { c.CouponCodes = codes }
}
// NewContextFromCart builds a PromotionEvalContext from a CartGrain and optional metadata.
func NewContextFromCart(g *cart.CartGrain, opts ...ContextOption) *PromotionEvalContext {
ctx := &PromotionEvalContext{
@@ -74,6 +80,7 @@ func NewContextFromCart(g *cart.CartGrain, opts ...ContextOption) *PromotionEval
CartTotalIncVat: 0,
TotalItemQuantity: 0,
Now: time.Now(),
CouponCodes: make([]string, 0),
}
if g.TotalPrice != nil {
ctx.CartTotalIncVat = g.TotalPrice.IncVat
@@ -91,6 +98,9 @@ func NewContextFromCart(g *cart.CartGrain, opts ...ContextOption) *PromotionEval
})
ctx.TotalItemQuantity += uint32(it.Quantity)
}
for _, v := range g.Vouchers {
ctx.CouponCodes = append(ctx.CouponCodes, v.Code)
}
for _, o := range opts {
o(ctx)
}
@@ -106,18 +116,29 @@ type PromotionService struct {
// even one that has already applied (e.g. a tiered discount at 5% pointing at
// the next 9% tier). Register a new Effect to add a new action type.
effects map[ActionType]Effect
// DefaultTaxProvider provides the VAT rate used when constructing synthetic
// carts for stateless evaluation and preview. When nil, falls back to 25 %.
DefaultTaxProvider cart.TaxProvider
}
// NewPromotionService constructs a PromotionService with an optional tracer and
// the built-in effect handlers.
func NewPromotionService(t Tracer) *PromotionService {
// NewPromotionService constructs a PromotionService with an optional tracer.
// When tp is non-nil, it is used as DefaultTaxProvider for synthetic cart
// construction (stateless evaluation, preview); otherwise the service falls
// back to 25 % VAT.
func NewPromotionService(t Tracer, tp ...cart.TaxProvider) *PromotionService {
if t == nil {
t = NoopTracer{}
}
return &PromotionService{
s := &PromotionService{
tracer: t,
effects: defaultEffects(),
}
if len(tp) > 0 && tp[0] != nil {
s.DefaultTaxProvider = tp[0]
}
return s
}
// RegisterEffect adds (or overrides) the handler for an action type, so new
@@ -360,11 +381,22 @@ func evaluateBaseCondition(b BaseCondition, ctx *PromotionEvalContext) bool {
return evalDayOfWeek(ctx.Now, b)
case CondTimeOfDay:
return evalTimeOfDay(ctx.Now, b)
case CondCouponCode:
return evalAnyCouponMatch(ctx.CouponCodes, b)
default:
return false
}
}
func evalAnyCouponMatch(codes []string, b BaseCondition) bool {
for _, code := range codes {
if evalStringCompare(code, b) {
return true
}
}
return false
}
func evalAnyItemMatch(pred func(PromotionItem) bool, b BaseCondition, ctx *PromotionEvalContext) bool {
if slices.ContainsFunc(ctx.Items, pred) {
return true