Skip to content

2026-07-17 — XP-140 apply-price-change loop → set-based UPDATE

  • packages/service/src/pricing/apply-price-change.ts — the D3 APPEND branch of applyPerSettlementAdjustment looped one UPDATE per settlement row per side (2N write round-trips inside the tx). Replaced both per-row loops (student + tutor) with one set-based UPDATE … WHERE lesson_session_id IN (sesiIds) per side, arithmetic done in-SQL: adjustment_amount_idr = adjustment_amount_idr + delta, net recomputed per domain-billing-period.md. APPEND path is now 1 guard SELECT + 2 UPDATEs regardless of N.
  • New assert in apply-price-change-batch.integration.test.ts on the isolated SD_6 axis: 2 delivered sesi → 2 student + 2 tutor settlements → one batch bumps all four cells; every row’s adjustment + net checked exactly.
  • delta is constant across affected rows and nothing (lock/audit) interleaves per-row — audit fires once in the caller — so the loop was pure round-trip waste, safe to collapse. Row set provably identical to the loop’s per-row eq(id, s.id) within the same tx-scoped SELECT.
  • LOCKED-immutability guard unchanged (still throws → whole-tx rollback); the guard SELECT was narrowed to only lesson_session_id + billing_period_status since the DB now does the arithmetic.
  • Postgres evaluates every SET RHS against the OLD row, so referencing adjustment_amount_idr inside the net expression yields the pre-increment value: net = gross + (old_adj + delta) = gross + new_adj. An inline comment documents this so a future reader won’t “fix” it into a self-referential read. Proven by the new test’s non-degenerate tutor cell (transport 5k, pph 1k → net 59_000), not just by reasoning.

None. No canonical doc (plans/ARCHITECTURE.md, plans/scope/*.md, .claude/rules/*.md) changed.