96 lines
4.1 KiB
Go
96 lines
4.1 KiB
Go
package cart
|
|
|
|
// EvaluatedItem is one line in the per-line promotion breakdown exposed
|
|
// alongside a cart. It mirrors the line that was evaluated (sku,
|
|
// quantity, unit list price, optional OrgPrice for the strikethrough) and
|
|
// adds the per-line discount the engine applied (orgPrice-based +
|
|
// promotion-based, additive) plus the resulting effective per-unit and
|
|
// per-line totals.
|
|
//
|
|
// Lives in pkg/cart rather than pkg/promotions so CartGrain can carry the
|
|
// breakdown as a derived field (populated by the canonical promotion
|
|
// pipeline that already manages TotalDiscount/AppliedPromotions), and so
|
|
// every consumer of the grain — UCP cart response, legacy /cart HTTP
|
|
// handler, cart-mcp, AMQP mutation feed — naturally sees the same shape
|
|
// without ad-hoc wrappers at each call site. JSON tags are the canonical
|
|
// EvaluatedItem shape that /promotions/evaluate-with-cart has returned
|
|
// since the breakdown feature was introduced.
|
|
//
|
|
// Distributing the total cart discount down to the line level lets the
|
|
// storefront render "Item X: 100 kr → 80 kr" without re-doing the math
|
|
// on the client and lets verifiers confirm which promotion hit which
|
|
// line (per-line DiscountIncVat is the engine's authoritative answer).
|
|
type EvaluatedItem struct {
|
|
Sku string `json:"sku"`
|
|
Name string `json:"name,omitempty"`
|
|
Quantity uint16 `json:"qty"`
|
|
PriceIncVat int64 `json:"priceIncVat"` // per-unit list price
|
|
OrgPriceIncVat int64 `json:"orgPriceIncVat,omitempty"` // per-unit pre-discount list price (for strikethrough)
|
|
DiscountIncVat int64 `json:"discountIncVat"` // per-line TOTAL discount (orgPrice + promotion), incVat in öre
|
|
EffectivePriceIncVat int64 `json:"effectivePriceIncVat"` // per-unit, after discount, incVat in öre (rounded)
|
|
EffectiveTotalIncVat int64 `json:"effectiveTotalIncVat"` // per-line, after discount, incVat in öre (exact)
|
|
}
|
|
|
|
// MapEvaluatedItems walks the grain's items and produces the per-line
|
|
// EvaluatedItem list. The grain's item.Discount carries the TOTAL per-line
|
|
// discount (orgPrice + promotion — additive, set by UpdateTotals and the
|
|
// effects' per-line distribution). The effective totals are derived as
|
|
// (price * qty - discount), clamped to 0 (a promotion that over-discounted
|
|
// a line shows as free, not negative). The effective per-unit price is the
|
|
// per-line total divided by quantity, rounded to the nearest öre so the
|
|
// per-unit display matches the per-line total when multiplied back.
|
|
//
|
|
// Returns nil for a nil grain (callers that always pass a non-nil grain
|
|
// — the typical case — get a non-nil empty slice for an empty cart, which
|
|
// is the desired behavior for JSON omitempty at the consumer).
|
|
//
|
|
// Exported so the canonical promotion pipeline (pkg/promotions.EvaluateAndApply)
|
|
// and the manual /promotions/evaluate-with-cart preview handler can produce
|
|
// the same per-line shape.
|
|
func MapEvaluatedItems(g *CartGrain) []EvaluatedItem {
|
|
if g == nil {
|
|
return nil
|
|
}
|
|
out := make([]EvaluatedItem, 0, len(g.Items))
|
|
for _, it := range g.Items {
|
|
if it == nil {
|
|
continue
|
|
}
|
|
var name string
|
|
if it.Meta != nil {
|
|
name = it.Meta.Name
|
|
}
|
|
var orgPrice int64
|
|
if it.OrgPrice != nil {
|
|
orgPrice = it.OrgPrice.IncVat.Int64()
|
|
}
|
|
var discount int64
|
|
if it.Discount != nil {
|
|
discount = it.Discount.IncVat.Int64()
|
|
}
|
|
priceRow := it.Price.IncVat.Int64() * int64(it.Quantity)
|
|
effTotal := priceRow - discount
|
|
if effTotal < 0 {
|
|
effTotal = 0
|
|
}
|
|
var effUnit int64
|
|
if it.Quantity > 0 {
|
|
// Nearest-öre rounding so per-unit * qty matches the
|
|
// per-line total (within 1 öre either way). Half-up:
|
|
// (effTotal + qty/2) / qty.
|
|
effUnit = (effTotal + int64(it.Quantity)/2) / int64(it.Quantity)
|
|
}
|
|
out = append(out, EvaluatedItem{
|
|
Sku: it.Sku,
|
|
Name: name,
|
|
Quantity: it.Quantity,
|
|
PriceIncVat: it.Price.IncVat.Int64(),
|
|
OrgPriceIncVat: orgPrice,
|
|
DiscountIncVat: discount,
|
|
EffectivePriceIncVat: effUnit,
|
|
EffectiveTotalIncVat: effTotal,
|
|
})
|
|
}
|
|
return out
|
|
}
|