# Pricing

Phase 9 owns currencies, effective price lists and entries, relational packages,
ordered adjustment rules, and deterministic calculation. Catalog records remain
owned by Catalog; Pricing stores supported subject type/ID references and checks
their eligibility at every activation and calculation boundary.

## Invariants

- Currency codes are uppercase ISO 4217-style three-letter identifiers. A
  currency with an active price list cannot be disabled.
- Price lists, entries, packages, and rules use `draft -> active -> archived ->
  draft` lifecycle transitions and optimistic `lock_version` checks.
- Effective intervals are half-open: `effective_from` is inclusive and
  `effective_until` is exclusive. A null boundary is open-ended.
- Active price lists for the same currency and context cannot overlap. Active
  entries for the same list and subject cannot overlap.
- Price-list and entry writers lock their stable parent row before overlap
  checks. Transaction retries handle database deadlocks without weakening the
  invariant.
- Active price entries require published services/templates or active packages.
  Active packages require at least one published catalog item. Duplicate package
  subjects are rejected before the composite primary key is reached.
- Only draft entries and rules may be deleted. Archived lists are read-only.
- Money uses database `DECIMAL(19,4)` and fixed-scale integer arithmetic in PHP.
  Binary floating-point is never used by the engine.

## Calculation contract

`PricingInput` is versioned and contains the selected price-list public ID,
currency, context, UTC calculation instant, and typed subject selections. The
resolver requires an active list and effective entry for every resolved line.
An unpriced package expands into its relational service/template items; a direct
package entry takes precedence when one exists.

The pure `PricingEngine` receives a resolved immutable snapshot. It calculates
fixed or per-unit lines, then applies rules by ascending priority and stable ID.
Supported conditions are `always`, `minimum_subtotal`, and `subject_present`.
Supported effects are discount, surcharge, and tax, using fixed or percentage
values. Discounts are capped so pre-tax totals cannot become negative. The
result includes source entry/rule IDs, lines, subtotal, adjustments, tax, total,
currency, engine version, and a bounded explanation.

Phase 12 quotations must persist this output as an immutable snapshot. Repricing
may create a new draft result but must never rewrite a submitted quotation.

## Authorization and audit

- `pricing.view` grants the pricing console and calculation preview.
- `pricing.manage` grants currency, list, entry, package, and rule mutations.
  Operators who manage pricing should normally receive both permissions.
- Mutations emit redacted pricing create/update/activate/archive/delete audit
  actions. Audits contain stable IDs and lifecycle metadata, not an unbounded
  calculation input.

## Operations

- Configure at least one enabled currency before creating a list.
- Build active entries and rules on a draft list, then activate the list after
  reviewing its interval and dependencies.
- Use the admin preview at a boundary instant when changing effective prices.
  Adjacent intervals may meet at the same timestamp without overlapping.
- Archive dependent entries before archiving a package. Catalog subjects that
  become unpublished fail closed during calculation and must be reconciled by a
  pricing operator.
- Treat engine-version or arithmetic-policy changes as reviewed releases. Keep
  old engine versions interpretable for future quotation snapshots.

MySQL 8 remains the release-certification database for gap-lock and decimal
behavior. SQLite coverage verifies application rules locally but is not a
substitute for the defined MySQL CI job.
