Domain Pricing Snapshot
Guard duplicated from ARCHITECTURE §14.7 + §15 — ARCHITECTURE wins on conflict.
Pricing Snapshot Guard
Section titled “Pricing Snapshot Guard”WARNING — SILENT DATA CORRUPTION: Reading live pricing at billing time silently corrupts the economic record the moment anyone edits a pricing row.
Model: Base + Override (ADR-001)
Section titled “Model: Base + Override (ADR-001)”Pricing is resolved via a 4-branch fallback (resolveStudentPrice / resolveTutorPrice):
- Applicability gate — if subject not sold at this tingkat → null (not sold)
- Override row exists (
student_price_overrides/tutor_price_overrides) → use overrideamount_idr - Base row exists (
student_base_prices/tutor_base_prices) → use baseamount_idr - Else → null (unpriced)
The snapshot captures the output of resolvePrice — the resolved amount plus a source+ref pair for traceability. There are no student_pricing_matrix / tutor_pricing_matrix tables (old flat model dropped per ADR-001).
At REQUESTED → APPROVED transition (mandatory)
Section titled “At REQUESTED → APPROVED transition (mandatory)”Capture all of these on lesson_sessions in the same tx as the status update:
| Field | Source |
|---|---|
amount_student_idr | Snapshot — output of resolveStudentPrice() at approval time |
student_price_source | 'OVERRIDE' or 'BASE' — which branch resolved |
student_price_ref_id | FK → student_price_overrides.id OR student_base_prices.id (traceability) |
amount_tutor_idr | Snapshot — output of resolveTutorPrice() at approval time |
tutor_price_source | 'OVERRIDE' or 'BASE' |
tutor_price_ref_id | FK → tutor_price_overrides.id OR tutor_base_prices.id |
mode_surcharge_idr | Optional per-session override (nullable escape hatch) |
At billing time (terminal states)
Section titled “At billing time (terminal states)”Read lesson_sessions.amount_student_final_idr / amount_tutor_final_idr. NEVER re-read from student_base_prices, tutor_base_prices, student_price_overrides, or tutor_price_overrides — those may have changed.
Pricing lookup (reference — resolvePrice inputs)
Section titled “Pricing lookup (reference — resolvePrice inputs)”- Student:
(academic_year_id, subject_id, segmentasi_id, tingkat_id)→ resolved amount - Tutor:
(academic_year_id, subject_id, segmentasi_id, tingkat_id, golongan_id)→ resolved amount subject_level_idis NOT a pricing axis (dropped per ADR-001)- Mid-year pricing row edits are allowed — snapshot immunity means existing sessions are unaffected
Implementation location
Section titled “Implementation location”resolveStudentPrice / resolveTutorPrice live in:
packages/service/src/pricing/resolve-price.ts
Unit tests (all 4 branches): packages/service/src/pricing/__tests__/resolve-price.test.ts
Full spec: plans/ARCHITECTURE.md §14.4 (resolvePrice) and §14.7 (snapshot fields) and §15 (sesi state machine + billing implications).