98 lines
4.1 KiB
Go
98 lines
4.1 KiB
Go
package main
|
||
|
||
import (
|
||
messages "git.k6n.net/mats/go-cart-actor/proto/cart"
|
||
"git.k6n.net/mats/platform/catalog"
|
||
)
|
||
|
||
// taxClassToBp maps the canonical TaxClass string identifiers published on
|
||
// catalog.Projection to basis-points-of-a-percent (rate × 100) — the single
|
||
// platform-wide rate scale used by AddItem.Tax (int32 basis points), the
|
||
// mutation registry, and the tax.Provider Compute formula.
|
||
//
|
||
// Values reflect the standard tariff names used by the Nordic configuration.
|
||
// Per-country / per-tenant numeric resolution (e.g. Finland 25.5%, Ireland
|
||
// 13.5%) is delegated to platform/tax and is a follow-up: this static map
|
||
// gets the common case right (2500 / 1200 / 600 / 0) and lets the cache path
|
||
// ship while the platform/tax lookup is wired into the cart pool server.
|
||
var taxClassToBp = map[string]int{
|
||
"standard": 2500,
|
||
"reduced": 1200, // common reduced rate (e.g. SE/NO food)
|
||
"lowered": 600, // super-reduced
|
||
"zero": 0, // zero-rated (export, healthcare, ...)
|
||
"exempt": 0, // similarly untaxed, separate nominal class
|
||
}
|
||
|
||
// resolveTaxBp returns the basis-point rate for a TaxClass string, falling
|
||
// back to platform "standard" (25%) when the class is unrecognised. The
|
||
// default matches the grain's existing behaviour (AddItem falls back to 2500
|
||
// when m.Tax == 0 — see pkg/cart/mutation_add_item.go).
|
||
func resolveTaxBp(class string) int {
|
||
if bp, ok := taxClassToBp[class]; ok {
|
||
return bp
|
||
}
|
||
return 2500
|
||
}
|
||
|
||
// ApplyProjectionOverlay overlays the cache's authoritative catalog facts onto
|
||
// an AddItem message that was just produced by a PRODUCT_BASE_URL HTTP fetch.
|
||
//
|
||
// Authoritative-from-cache fields (these WIN over the HTTP-fetched value when
|
||
// both are present, because the bus is the live stream):
|
||
//
|
||
// Price (bus has post-class-overrides; product service has static)
|
||
// Tax (TaxClass → bp via resolveTaxBp; only when TaxClass is set —
|
||
// an empty TaxClass means the producer didn't classify,
|
||
// so we leave the HTTP-fetched rate intact rather than
|
||
// quietly swapping in a default 2500 and miscategorising)
|
||
// Title (canonical, post-trim)
|
||
// Image (canonical)
|
||
// InventoryTracked (only flips to true; false is the zero value in proto
|
||
// and may be unset on the wire, so we don't second-guess)
|
||
// DropShip (only flips to true; same reasoning)
|
||
//
|
||
// The HTTP-fetched values remain authoritative for fields the cache does not
|
||
// carry: SellerId / SellerName (marketplace split), Stock (per docs/inventory-
|
||
// shape.md stock is queried synchronously from inventory, not cached),
|
||
// OrgPrice / Discount (not yet on the projection schema), the dynamic
|
||
// ExtraJson product data (configurator options — width/height used by
|
||
// accessory child-pricing), and the catalog uint32 ItemId (the product
|
||
// service's integer id which messages.AddItem.ItemId dedupes on — Projection
|
||
// only carries the opaque string ID, so a cache-only build path is blocked
|
||
// and the HTTP fetch is preserved).
|
||
//
|
||
// Tombstoned SKUs (catalog.projection_published with deleted=true) → the
|
||
// caller checks idx.IsDeleted(sku) before this helper and short-circuits.
|
||
func ApplyProjectionOverlay(msg *messages.AddItem, p catalog.Projection) {
|
||
if msg == nil {
|
||
return
|
||
}
|
||
// Price: positive bus values override HTTP. A zero PriceIncVat is treated
|
||
// as "no value" rather than "free".
|
||
if p.PriceIncVat.Int64() > 0 {
|
||
msg.Price = p.PriceIncVat.Int64()
|
||
}
|
||
// Tax: only override when the bus has classified the SKU. An empty TaxClass
|
||
// means the producer had nothing to say about VAT; the HTTP rate is more
|
||
// trustworthy in that case than a guessed 2500.
|
||
if p.TaxClass != "" {
|
||
msg.Tax = int32(resolveTaxBp(p.TaxClass))
|
||
}
|
||
// Display fields: only override if non-empty so the HTTP-fanned value
|
||
// remains in place when the cache hasn't populated them yet.
|
||
if p.Title != "" {
|
||
msg.Name = p.Title
|
||
}
|
||
if p.Image != "" {
|
||
msg.Image = p.Image
|
||
}
|
||
// Flags: only flip to true (positive boolean signals). A false is the
|
||
// proto-zero value and not worth overriding.
|
||
if p.InventoryTracked {
|
||
msg.InventoryTracked = true
|
||
}
|
||
if p.DropShip {
|
||
msg.DropShip = true
|
||
}
|
||
}
|