/** * Copyright since 2025 Mifos Initiative * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { isTdsApplicable, tdsDeducteeOf } from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute'; import { TDS_AS_AT, TDS_FYS, depositDueDate, tdsDeductees } from './tds-reports-data'; import { NEAR_THRESHOLD_BAND, TdsReportFilter, deductedByQuarter, tds26qSummary, tdsInterestRows, tdsInterestRowsForFy, tdsInterestSummary, tdsRemittanceRows } from './tds-reports.compute'; /** The complete year in the seed — the one a return would be filed for. */ const CLOSED_FY = '2025-26'; function filter(overrides: Partial = {}): TdsReportFilter { return { fy: CLOSED_FY, branchCode: '', quarter: 0, band: '', search: '', crossedOnly: false, nearThresholdOnly: false, ...overrides }; } describe('the seed has every case the reports need to show', () => { it('produces deductees on both sides of the threshold', () => { const rows = tdsInterestRowsForFy(CLOSED_FY); expect(rows.length).toBeGreaterThan(20); expect(rows.some((r) => r.crossed)).toBe(true); expect(rows.some((r) => !r.crossed)).toBe(true); expect(rows.some((r) => r.nearThreshold)).toBe(true); expect(rows.some((r) => r.band === 'GENERAL')).toBe(true); expect(rows.some((r) => r.band === 'SENIOR')).toBe(true); expect(rows.some((r) => !r.panOnRecord)).toBe(true); expect(rows.some((r) => r.hasDeclaration)).toBe(true); }); it('gives senior citizens the higher threshold', () => { const rows = tdsInterestRowsForFy(CLOSED_FY); for (const row of rows) { expect(row.threshold).toBe(row.band === 'SENIOR' ? 100_000 : 50_000); } }); it('deducts at the higher rate wherever PAN is missing', () => { for (const row of tdsInterestRowsForFy(CLOSED_FY)) { expect(row.rateApplied).toBe(row.panOnRecord ? 0.1 : 0.2); } }); }); describe('the reports follow the Act that governed each year', () => { it('shows 15G / 15H for FY 2025-26 and Form 121 for FY 2026-27', () => { const old = tdsInterestRowsForFy('2025-26'); expect(old.some((r) => r.form === '15G')).toBe(true); expect(old.some((r) => r.form === '15H')).toBe(true); expect(old.some((r) => r.form === '121')).toBe(false); // The two forms merged on 01 April 2026. const current = tdsInterestRowsForFy('2026-27'); expect(current.every((r) => r.form === '121')).toBe(true); }); it('keeps the age-based threshold band in both years', () => { for (const fy of TDS_FYS) { const rows = tdsInterestRowsForFy(fy); expect(rows.some((r) => r.band === 'SENIOR')).toBe(true); expect(rows.some((r) => r.band === 'GENERAL')).toBe(true); for (const row of rows) { expect(row.threshold).toBe(row.band === 'SENIOR' ? 100_000 : 50_000); } } }); }); describe('savings interest stays out of the 194A aggregate', () => { it('reports it separately and never inside the applicable interest', () => { const rows = tdsInterestRowsForFy(CLOSED_FY); const withSavings = rows.filter((r) => r.exemptInterest > 0); expect(withSavings.length).toBeGreaterThan(0); for (const row of withSavings) { // The applicable figure must equal only the time-deposit interest where // this customer is the deductee. const expected = row.deductee.accounts .filter((a) => isTdsApplicable(a) && tdsDeducteeOf(a) === row.deductee.clientId) .flatMap((a) => a.credits) .filter((c) => c.date >= '2025-04-01' && c.date <= '2026-03-31') .reduce((s, c) => s + c.amount, 0); expect(row.tdsApplicableInterest).toBe(expected); } }); it('leaves the savings totals out of the summary base', () => { const s = tdsInterestSummary(filter()); expect(s.exemptInterest).toBeGreaterThan(0); expect(s.applicableInterest).toBe(tdsInterestRows(filter()).reduce((acc, r) => acc + r.tdsApplicableInterest, 0)); }); }); describe('joint deposits are attributed to the first-named holder only', () => { it('excludes second-holder interest from the aggregate and shows it apart', () => { const rows = tdsInterestRowsForFy(CLOSED_FY); const secondHolders = rows.filter((r) => r.jointHeldInterest > 0); expect(secondHolders.length).toBeGreaterThan(0); for (const row of secondHolders) { const jointAccounts = row.deductee.accounts.filter( (a) => isTdsApplicable(a) && tdsDeducteeOf(a) !== row.deductee.clientId ); expect(jointAccounts.length).toBeGreaterThan(0); // None of that interest may appear in the figure the threshold is tested // against, or one deposit would be taxed against two people. const jointTotal = jointAccounts .flatMap((a) => a.credits) .filter((c) => c.date >= '2025-04-01' && c.date <= '2026-03-31') .reduce((s, c) => s + c.amount, 0); expect(row.jointHeldInterest).toBe(jointTotal); expect(row.tdsApplicableInterest).toBeLessThan(row.tdsApplicableInterest + jointTotal); } }); }); describe('quarter figures add up to the year', () => { it('per row', () => { for (const row of tdsInterestRowsForFy(CLOSED_FY)) { const quarterInterest = row.quarters[1] + row.quarters[2] + row.quarters[3] + row.quarters[4]; expect(quarterInterest).toBe(row.tdsApplicableInterest); expect(row.schedule.lines.reduce((s, l) => s + l.tds, 0)).toBe(row.schedule.totalTds); } }); it('and across the summary', () => { const s = tdsInterestSummary(filter()); expect(s.byQuarter.reduce((acc, q) => acc + q.interest, 0)).toBe(s.applicableInterest); expect(s.byQuarter.reduce((acc, q) => acc + q.tds, 0)).toBe(s.totalTds); }); }); describe('filters', () => { it("narrows to a branch without changing anyone's figures", () => { const all = tdsInterestRows(filter()); const branch = all[0].branchCode; const scoped = tdsInterestRows(filter({ branchCode: branch })); expect(scoped.length).toBeGreaterThan(0); expect(scoped.every((r) => r.branchCode === branch)).toBe(true); // A branch view must not re-aggregate — the threshold is computed bank-wide. const sample = scoped[0]; const sameRow = all.find((r) => r.deductee.customerId === sample.deductee.customerId); expect(sample.tdsApplicableInterest).toBe(sameRow?.tdsApplicableInterest); }); it('finds the customers worth chasing a declaration from', () => { const near = tdsInterestRows(filter({ nearThresholdOnly: true })); expect(near.length).toBeGreaterThan(0); for (const row of near) { expect(row.crossed).toBe(false); expect(row.headroom).toBeGreaterThan(0); expect(row.headroom).toBeLessThanOrEqual(NEAR_THRESHOLD_BAND); } }); it('narrows to customers who crossed', () => { const crossed = tdsInterestRows(filter({ crossedOnly: true })); expect(crossed.length).toBeGreaterThan(0); expect(crossed.every((r) => r.crossed && r.tdsApplicableInterest > r.threshold)).toBe(true); }); it('searches on name, CIF and PAN', () => { const target = tdsInterestRowsForFy(CLOSED_FY).find((r) => r.panOnRecord)!; expect(tdsInterestRows(filter({ search: target.deductee.customerId })).length).toBe(1); expect(tdsInterestRows(filter({ search: target.deductee.pan })).length).toBeGreaterThanOrEqual(1); }); }); describe('declarations', () => { it('save tax and record the interest paid gross', () => { const declared = tdsInterestRowsForFy(CLOSED_FY).filter((r) => r.hasDeclaration); expect(declared.length).toBeGreaterThan(0); expect(declared.some((r) => r.tdsSaved > 0)).toBe(true); // Interest paid without deduction is a mandatory 26Q disclosure, so it must // be carried even though no tax came off it. expect(declared.some((r) => r.paidWithoutDeduction > 0)).toBe(true); for (const row of declared) { expect(row.declarationUin).toBeTruthy(); } }); it('leaves tax deducted before a mid-year declaration in place', () => { // Some declarations in the seed were accepted in October. Tax already taken // in Q1/Q2 cannot be reversed, so those rows still carry a deduction. const midYear = tdsInterestRowsForFy(CLOSED_FY).filter( (r) => r.hasDeclaration && r.declarationAcceptedOn && r.declarationAcceptedOn.slice(5, 7) === '10' ); expect(midYear.length).toBeGreaterThan(0); expect(midYear.some((r) => r.schedule.totalTds > 0)).toBe(true); }); }); describe('remittance schedule', () => { it('deposits by the seventh of the following month, and 30 April for March', () => { expect(depositDueDate('2025-06')).toBe('2025-07-07'); expect(depositDueDate('2025-12')).toBe('2026-01-07'); expect(depositDueDate('2026-03')).toBe('2026-04-30'); }); it('remits exactly what the engine says was deducted', () => { const deducted = deductedByQuarter(CLOSED_FY); const rows = tdsRemittanceRows(CLOSED_FY, TDS_AS_AT); for (const row of rows) { expect(row.amount).toBe(Math.round(deducted[row.quarter])); } }); it('charges section 201(1A) interest on a late deposit and nothing on a timely one', () => { const rows = tdsRemittanceRows(CLOSED_FY, TDS_AS_AT); const late = rows.filter((r) => r.late); expect(late.length).toBeGreaterThan(0); for (const row of late) { expect(row.delayMonths).toBeGreaterThan(0); expect(row.defaultInterest).toBe(Math.round(row.amount * 0.015 * row.delayMonths)); } for (const row of rows.filter((r) => !r.late && !r.overdue)) { expect(row.defaultInterest).toBe(0); } }); }); describe('the 26Q reconciliation control', () => { it.each(TDS_FYS)('balances for FY %s', (fy) => { const s = tds26qSummary(fy, TDS_AS_AT); // Deducted must equal deposited plus what is still within its due date. // Anything else means tax has gone missing between the ledger and the // Government, and the return must not be filed until it is found. expect(s.difference).toBe(0); expect(s.totalDeducted).toBe(s.totalDeposited + s.pendingDeposit); }); it('splits the tax between the standard and the no-PAN rate without loss', () => { const s = tds26qSummary(CLOSED_FY, TDS_AS_AT); expect(s.tdsAtStandardRate + s.tdsAtHigherRate).toBe(s.totalDeducted); expect(s.tdsAtHigherRate).toBeGreaterThan(0); }); it('splits interest into the deducted and the paid-gross halves', () => { const s = tds26qSummary(CLOSED_FY, TDS_AS_AT); expect(s.interestWithDeduction + s.interestWithoutDeduction).toBe(s.totalInterestPaid); // The gross-paid half is the Form 121 disclosure and must not be zero here, // or the report would never exercise that line. expect(s.interestWithoutDeduction).toBeGreaterThan(0); }); it('agrees with the interest report on the same year', () => { const summary = tdsInterestSummary(filter()); expect(tds26qSummary(CLOSED_FY, TDS_AS_AT).totalDeducted).toBe(summary.totalTds); }); }); describe('the part-run current year', () => { it('has postings only up to the business date', () => { for (const deductee of tdsDeductees()) { for (const account of deductee.accounts) { for (const credit of account.credits) { expect(credit.date <= TDS_AS_AT).toBe(true); } } } }); it('shows the closed quarter as settled and future obligations as not yet due', () => { const rows = tdsRemittanceRows('2026-27', TDS_AS_AT); // Q1 (Apr–Jun) has closed, so its tax was deducted and is due by 07 Jul. expect(rows.some((r) => r.quarter === 1)).toBe(true); // Q3 cannot appear: no interest has posted there and nothing settles there. expect(rows.some((r) => r.quarter === 3)).toBe(false); // Q4 *can* appear even in a part-run year — deposits on annual interest // posting settle once, at year end — but that is a future obligation, so it // must read as pending and must never be flagged overdue. for (const row of rows.filter((r) => r.quarter === 4)) { expect(row.depositedOn).toBeNull(); expect(row.overdue).toBe(false); expect(row.defaultInterest).toBe(0); } }); });