Files
platform/inventory/inventory.go
T
2026-06-28 17:52:49 +02:00

67 lines
2.8 KiB
Go

// Package inventory is the platform's shared inventory seam: the cross-module
// vocabulary for stock *levels*. It deliberately carries no quantities — exact
// stock is queried synchronously from the inventory service at decision points
// (PDP, add-to-cart, checkout reserve), and only coarse level crossings travel
// over the bus. Putting exact quantity on the bus spams it and ships a number
// that is stale the moment it is published; a level crossing is the only
// inventory signal other modules receive. See docs/inventory-shape.md.
//
// Like platform/catalog, this package holds only the wire value types plus the
// single classification function, so the producer and every consumer agree on
// the cutoffs. It imports only the stdlib — never an app module, never a
// transport library.
package inventory
import "encoding/json"
// Level is the coarse, bus-safe stock state. Consumers map a Level to a display
// hint / availability flag; they never see (or need) the exact quantity.
type Level string
const (
LevelNone Level = "none" // nothing available to sell
LevelLow Level = "low" // at or below the low watermark, but still > 0
LevelHigh Level = "high" // comfortably above the low watermark
)
// LevelFor classifies an exact on-hand quantity into a Level given the low
// watermark. It is the single source of the cutoffs: the producer computes the
// emitted level with it, and any consumer that needs to reclassify a quantity
// (e.g. after a synchronous exact query) uses the same function so the two can
// never disagree. qty <= 0 → none; 0 < qty <= low → low; qty > low → high.
func LevelFor(qty, lowWatermark int64) Level {
switch {
case qty <= 0:
return LevelNone
case qty <= lowWatermark:
return LevelLow
default:
return LevelHigh
}
}
// InStock reports whether the level means the SKU can be sold (any level other
// than none) — a convenience for consumers that only care about the boolean.
func (l Level) InStock() bool { return l != LevelNone }
// LevelChanged is the payload of an event.InventoryLevelChanged envelope: the
// SKU + location whose level crossed a threshold, and the new level — never a
// quantity. The envelope's Subject duplicates SKU; Meta["country"] carries the
// tenant/country for consumer routing (matching the catalog wire convention).
type LevelChanged struct {
SKU string `json:"sku"`
Location string `json:"location"`
Level Level `json:"level"`
}
// Encode serialises the payload for event.Event.Payload. Kept here so producer
// and consumers share the exact wire shape; any future renaming lands here.
func (c LevelChanged) Encode() ([]byte, error) { return json.Marshal(c) }
// Decode parses an InventoryLevelChanged payload — the consumer side of Encode.
func Decode(raw []byte) (LevelChanged, error) {
var c LevelChanged
err := json.Unmarshal(raw, &c)
return c, err
}