Domain Settlement
Guard duplicated from ARCHITECTURE §18.4 — ARCHITECTURE wins on conflict.
Settlement Auto-Creation Guard
Section titled “Settlement Auto-Creation Guard”⚠️ SILENT DATA CORRUPTION: Wrong trigger set = phantom or missing settlements. Non-atomic impl = orphaned session status or orphaned settlement row on partial failure.
Trigger: ONLY these two terminal transitions
Section titled “Trigger: ONLY these two terminal transitions”lesson_session → COMPLETEDlesson_session → NO_SHOW_STUDENT
Do NOT auto-create settlements for any other terminal state (REJECTED, CANCELLED, RESCHEDULED, NO_SHOW_TUTOR).
Same-tx requirement
Section titled “Same-tx requirement”Both INSERTs happen in the same DB transaction as the session status update:
await db.transaction(async (tx) => { await tx.update(lesson_sessions).set({ status: 'COMPLETED' }).where(...); await tx.insert(student_settlements).values({ lesson_session_id: session.id, student_id: session.student_id, billing_period_id: /* OPEN billing_period covering session.scheduled_at */, gross_amount_idr: session.amount_student_final_idr, net_amount_idr: session.amount_student_final_idr, // gross = net at creation; adjustments come later }); await tx.insert(tutor_settlements).values({ lesson_session_id: session.id, tutor_id: session.tutor_id, billing_period_id: /* same OPEN period */, honor_amount_idr: session.amount_tutor_final_idr, net_amount_idr: session.amount_tutor_final_idr, // recomputed after any adjustments });});billing_period_id lookup
Section titled “billing_period_id lookup”Find the OPEN billing_period whose starts_on ≤ session.scheduled_at ≤ ends_on. If none found, surface an error — do not create settlement without a period.
Full spec: plans/ARCHITECTURE.md §18.4.