Design Rationale — Pricing
Non-obvious design choices for the pricing module — the WHY behind each decision.
Pricing (locked 2026-06-09)
Carved out of inventory into its own schema
Decision: Pricing rules live in pricing schema, not inventory. Inventory holds the base price on item_variant; all rule-based pricing is pricing.
Why: Pricing rules have distinct consumers (POS checkout, order quoting, promotions engine) and distinct lifecycle (rules change frequently; item records change rarely). Bundling into inventory schema would bloat a core schema with a high-churn concern.
Rejected: Price columns only on item_variant — can't express customer-specific prices, quantity breaks, scheduled promotions, or account-level pricing without a rule table.
One price_rule table absorbs all pricing kinds
Decision: price_rule handles level-based, customer-specific, quantity-break, and date-scheduled pricing via scope_type + price_type + min_qty + valid_from/valid_until — not a separate table per pricing kind.
Why: Discriminator-over-sprawl (see Cross-Cutting Patterns) — all pricing kinds are "a rule that sets a price under certain conditions." A single table with well-chosen columns handles all variants cleanly.
Rejected: Separate sale_price_rule, contract_price_rule, tier_price_rule tables — proliferates tables, duplicates resolution logic, and creates ambiguity when multiple rule types apply to the same item.
Precedence resolution is PricingService logic, not a DB constraint
Decision: Which rule wins when multiple rules apply (customer-specific > group > default, etc.) is PricingService policy, not a database constraint.
Why: Precedence is a business rule that may evolve (e.g. "group beats customer-specific for loyalty members"). Encoding precedence in schema constraints would freeze it.
Guard: Do not add DB constraints trying to enforce pricing precedence. The resolution algorithm belongs in PricingService.resolvePrice(). Schema changes are expensive; rule changes should be code changes.