parent item mutations
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user