Promotions require an existing standard rate
Date: 07-08-2026 Author(s): Jayden Vicarey Source: Team consensus; implemented in PR #1584 (EM-3902)
Status
accepted
Context
A promotion is a temporary promotional rate on top of a product's standard discount or cashback rate. Face-value catalogues have no purchase-scheme model, so there is nothing to promote. Discount/cashback products can also lack a discount/cashback row (and thus no standard rate) if setup is incomplete.
Allowing promotion create in those cases produced hard failures (getMaxAllowedRate() on a null scheme model) or meaningless rate math.
Decision Drivers
- Promotions only make sense relative to a standard rate (
standard_rate < promotional_rate < 100) - Face-value and incomplete purchase-scheme setup are not promotable states
- Fail early with a clear validation/domain error, not a fatal on null
Considered Options
Allow promotion create without a standard rate
Infer or skip bounds; keep soft fallbacks (e.g. treat missing rate as 0).
Require a purchase-scheme model with a standard rate before promotion create
Reject face-value tenants and products without discount/cashback (and thus without a standard rate).
Decision Outcome
Chosen: Do not allow promotions without a standard rate.
Promotion create requires a discount or cashback purchase-scheme model that can supply the current standard rate and max allowed rate. Face-value products and products missing that model are rejected. PR #1584 enforces this on Hub promotion store and in Product::getMaxAllowedRate().
Consequences
Positive
- Invariant always has a real baseline rate to compare against
- Clear product rule for Hub authors and rate-guard callers
Negative
- Incomplete product setup must set a standard rate before any promotion can be scheduled
Neutral
- Rate-guard schedules (
RateScheduleData) assume a non-null current standard rate; eligibility stays at the HTTP/caller boundary (not insideRateConflictService) - Related: ADR 0003 — standard rates are permanent until overridden