Skip to content

fix(loans): offer Pay EMI on the earliest unpaid installment, inside its pay window - #47

Open
parameshjava wants to merge 2 commits into
mainfrom
fix/pay-emi-earliest-only
Open

fix(loans): offer Pay EMI on the earliest unpaid installment, inside its pay window#47
parameshjava wants to merge 2 commits into
mainfrom
fix/pay-emi-earliest-only

Conversation

@parameshjava

@parameshjava parameshjava commented Aug 7, 2026

Copy link
Copy Markdown
Owner

No migration. Two rules for when Pay EMI appears on /admin/loans/[loan_number].

Rule 1 — earliest unpaid installment only

Two buttons showed on a loan whose schedule had just been moved back to its correct month. Both were correct under the old rule, but offering them together let an admin settle #2 while #1 was still outstanding, and payEmi has no ordering check of its own. A paid row stranded between unpaid ones is a state several things handle badly:

  • the schedule reads out of order
  • re-pricing has to skip around it (one of the confirmed findings on that path)
  • the date-shift repair refuses outright once anything is settled, so the loan can no longer be corrected

Rule 2 — inside the pay window

Payable used to mean "the 1st of the accrual month has passed", which opened the button as much as 40 days before the due date. Settling that early does the member no good: payEmi charges exactly what the installment says, so it moves cash without touching the interest.

₹20,000 spent as… Interest saved
EMIs paid early ₹0
prepayment, reduce EMI ₹740
prepayment, reduce tenure ₹1,265

(the ₹1,00,000 / 8% / 12-month case, two installments paid)

An installment is now collectable from 15 days before it falls due, or the 1st of the month it falls due in — whichever comes first. The 15-day window is the rule; the month clause keeps an installment dated late in a month collectable from the start of that month rather than only in its last fortnight.

Worked through, for a schedule due on the 10th:

Today Next unpaid Offered? Why
20 Jul 10 Aug no 21 days off
26 Jul 10 Aug yes window opens
7 Aug 10 Aug yes current month
20 Aug 10 Sep no 21 days off
26 Aug 10 Sep yes window opens

Arrears are unaffected. Anything past its due date is trivially inside the window, so an overdue installment stays collectable however old — the window bounds how far ahead an EMI can be settled, not how far behind. Catching up on two missed months still works: settle the earliest and the next one's button appears immediately.

Where the rule lives

Out of an inline filter in page.tsx and into payableInstallmentIds / payWindowOpensOn in emi-due.ts, beside the other EMI-due tallies — one definition of "payable", and testable.

Testing

emi-payable.test.ts — 14 cases: the window opening 15 days out and at the month start when that comes first, month and year boundary rolls, nothing offered beyond the window, offered from the opening day, only the earliest even when a later one is inside its own window, advancing once the earliest is paid, arrears collectable a year late, part-paid offered, paid/waived skipped when picking the earliest, nothing when all settled, picked by due date rather than row order, and an empty schedule.

npm test 352 passing · npm run lint clean (one pre-existing warning) · npm run build passes.

🤖 Generated with Claude Code

Two "Pay EMI" buttons appeared on a loan whose schedule had just been moved back
to its correct month. Both were correct under the old rule — an installment
became payable on the 1st of its accrual month, so on 7 August both #1 (accruing
in July) and #2 (accruing in August) qualified — but offering them together let
an admin settle #2 while #1 was still outstanding.

A paid row stranded between unpaid ones is a state several things handle badly:
the schedule reads out of order, re-pricing has to skip around it, and the
date-shift repair refuses outright once anything is settled. payEmi has no
ordering check of its own, so nothing prevented it.

The accrual-month rule is unchanged; it now applies to the earliest unpaid
installment alone. A member can still pay within the current cycle before the
10th, they just cannot skip one — and paying genuinely ahead of schedule is what
Prepay is for.

The rule moves out of the page and into `payableInstallmentIds` in emi-due.ts
alongside the other EMI-due tallies, so it is testable and there is one
definition of "payable" rather than an inline filter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
aits-fcf-tracker Ready Ready Preview Aug 7, 2026 2:24pm

Payable used to mean "the 1st of the accrual month has passed", which opened the
button up to 40 days before the due date. Settling an EMI that early does the
member no good at all — payEmi charges exactly what the installment says, so it
moves cash without touching the interest. Money offered that far ahead belongs
in a prepayment, which reduces the principal and does cut the interest.

An installment is now collectable from 15 days before it falls due, or the 1st
of the month it falls due in, whichever comes first. The 15-day window is the
rule; the month clause keeps an installment dated late in a month collectable
from the start of that month rather than only in its last fortnight.

Anything past its due date is trivially inside the window, so arrears stay
collectable however old — the window bounds how far AHEAD an EMI can be settled,
not how far behind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@parameshjava parameshjava changed the title fix(loans): offer Pay EMI on the earliest unpaid installment only fix(loans): offer Pay EMI on the earliest unpaid installment, inside its pay window Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant