feat(loans): move a mis-anchored schedule to the month it should start in - #46
Merged
Merged
Conversation
…t in Detection landed last commit but left the admin with nowhere to go: the dialog said the schedule starts a month late and then explained that re-pricing would not fix it. This adds the fix. It moves dates and nothing else. Every unpaid installment shifts by the same whole number of months, so installment numbers, principal, interest and EMI are all untouched and the cadence is preserved exactly — the reported loan's twelve installments move from 10 Sep 2026 through 10 Aug 2027 back to 10 Aug 2026 through 10 Jul 2027. Regenerating via fn_generate_emi_schedule would have rebuilt every amount too, which is far more than a wrong anchor calls for. It is refused once any installment carries money: a settled installment's due date is what it was settled against, and moving the rows around it would interleave them with history. planScheduleShift checks it, and fn_shift_emi_schedule (migration 054) counts settled rows itself and raises before touching anything, so no caller can bypass it. Like 053 the write is all-or-nothing — a shortfall against the expected row count aborts rather than leaving half the schedule on the old cadence. The confirmation names both ends of the move and, when the shift would pull installments onto or before today, says how many become immediately due and may attract a late fee. That is the one genuinely lossy consequence, so it is stated rather than discovered afterwards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the anchor detection already on
main. That commit could tell you the schedule started a month late; this one lets you fix it. Includes migration054_shift_emi_schedule.sql.The case
A converted loan whose installments start 10 Sep 2026 when the cutover floor —
greatest(start_date, emi_cutover_date)— puts #1 on 10 Aug 2026. Recompute EMI correctly reported "no changes": re-pricing only scales interest and never moves a due date. So the dialog diagnosed the problem and then offered nothing.What the fix does
Moves dates and nothing else. Every unpaid installment shifts by the same whole number of months, so installment numbers, principal, interest and EMI are all untouched and the cadence is preserved exactly:
Regenerating through
fn_generate_emi_schedule(what the existing Recalculate button does) would have rebuilt every amount as well, which is far more than a wrong anchor calls for — and it refuses once anything is paid.What it refuses
Any installment carrying money. A settled installment's due date is the date it was settled against, and moving the rows around it would interleave them with history.
planScheduleShiftchecks it, andfn_shift_emi_schedulecounts settled rows itself and raises before touching anything, so no caller can bypass it — including a direct RPC call.Like migration 053, the write is all-or-nothing: a shortfall against the expected row count raises and rolls back, rather than leaving half the schedule on the old cadence.
The one lossy consequence
Shifting earlier can pull installments onto or before today. The confirmation says how many, in as many words:
Stated up front rather than discovered afterwards. In the reported loan it's zero — the whole schedule is still ahead.
UI
The amber warning in the Recompute EMI dialog gains the action, and the card's button becomes "Fix schedule dates" when drift is detected, so a drifted loan leads with the thing that's actually wrong rather than the re-pricing that isn't.
Testing
emi-schedule-shift.test.ts— 11 cases: the whole schedule moves by the drift with gaps preserved and every date still a 10th, installment numbers stay attached to their own rows, forward shift for a back-dated schedule, year-boundary roll, the becoming-due count, refusal on a full payment and on a part payment, waived rows moving with the rest, no-drift, and fingerprint sensitivity.npm test338 passing ·npm run lintclean (one pre-existing warning) ·npm run buildpasses.Deploy note
Three migrations are now outstanding against
main:052,053and this054. If053was applied before its signature changed, drop the old overload:🤖 Generated with Claude Code