feat: updates for cashier, tds reports, rbi reserve and dashboard

This commit is contained in:
2026-10-07 10:13:28 +05:30
parent 5ab4e9eaf0
commit 6226682b36
50 changed files with 3765 additions and 1636 deletions
+27 -167
View File
@@ -7,22 +7,16 @@
*/
/**
* Deductees and their deposit interest, for the two TDS reports.
* Deductees, their deposit interest and the challans, for the two TDS reports.
*
* The bank's deduction obligation runs on time deposits, so the seed
* gives each customer a mix of fixed and recurring deposits — and some savings
* accounts, which are outside the section and must stay out of the aggregate.
*
* Everything is generated from a fixed seed so the figures do not move between
* loads, and the challan schedule is deliberately imperfect: one quarter is
* deposited late so the default-interest column has something to show.
* Everything here is what the platform returned. The challans used to be generated from the
* computed deductions - challan numbers, BSR codes and deposit dates made up, one quarter
* deliberately late - so the remittance report reconciled against figures nobody had entered.
*/
import { GL_BRANCHES } from '../gl-reports/gl-reports-data';
import {
configuredFys,
Declaration,
Form15Kind,
InterestCredit,
TdsAccountInput
} from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute';
@@ -32,8 +26,14 @@ export function tdsFys(): string[] {
return configuredFys();
}
/** Business date the book is drawn to — Q1 of 2026-27 is closed, Q2 is running. */
export const TDS_AS_AT = '2026-08-01';
/** A deposit as the platform reports it: the interest credited and the TDS actually withheld. */
export interface TdsReportAccount extends TdsAccountInput {
/** TDS the platform actually deducted from this account in the year. */
deductions: InterestCredit[];
/** The account's own holder, when the tax is deducted against somebody else (minor, joint). */
holderClientId?: number;
holderName?: string;
}
export interface TdsDeductee {
clientId: number;
@@ -44,115 +44,35 @@ export interface TdsDeductee {
branchCode: string;
/** Declarations on record, keyed by financial year. */
declarations: Record<string, Declaration>;
accounts: TdsAccountInput[];
accounts: TdsReportAccount[];
}
/** One month's remittance of deducted tax to the Government. */
/** One challan Head Office entered: tax remitted to the Government. */
export interface TdsChallan {
id: number;
fy: string;
/** Quarter the deduction belongs to. */
quarter: 1 | 2 | 3 | 4;
/** Month the tax was deducted in, e.g. '2026-06'. */
month: string;
amount: number;
/** Statutory date by which it had to be deposited. */
dueDate: string;
/** When it actually reached the Government — null while still pending. */
depositedOn: string | null;
challanNo: string | null;
bsrCode: string | null;
challanNo: string;
bsrCode: string;
depositedOn: string;
paidFrom: string;
remarks: string | null;
reversed: boolean;
}
/** Deterministic linear congruential generator — same figures on every load. */
function lcg(seed: number): () => number {
let s = seed >>> 0;
return () => {
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
return s / 4294967296;
};
}
function pick<T>(rand: () => number, list: T[]): T {
return list[Math.floor(rand() * list.length)];
}
const FIRST_NAMES = [
'Ramesh',
'Sunita',
'Vitthal',
'Kamala',
'Ganpat',
'Shobha',
'Dnyaneshwar',
'Mangala',
'Bhaskar',
'Anita',
'Tukaram',
'Sarika',
'Nivrutti',
'Rekha',
'Popat',
'Chhaya',
'Ashok',
'Vaishali',
'Sitaram',
'Jayshree',
'Madhukar',
'Pushpa',
'Eknath',
'Sulochana',
'Hanumant',
'Kalpana'
];
const SURNAMES = [
'Jadhav',
'Pawar',
'Shinde',
'Gaikwad',
'More',
'Kadam',
'Bhosale',
'Patil',
'Sawant',
'Chavan',
'Salunkhe',
'Mane',
'Deshmukh',
'Thorat',
'Nikam',
'Wagh'
];
/**
* Quarter-end dates for a financial year. Interest is credited on these, which
* is what makes the deduction fall due.
*/
function quarterEnds(fy: string): string[] {
const y = Number(fy.slice(0, 4));
return [
`${y}-06-30`,
`${y}-09-30`,
`${y}-12-31`,
`${y + 1}-03-31`
];
}
/** A well-formed individual PAN — fourth character 'P'. */
function makePan(rand: () => number, surname: string): string {
const letter = () => String.fromCharCode(65 + Math.floor(rand() * 26));
const digits = `${1000 + Math.floor(rand() * 9000)}`;
return `${letter()}${letter()}${letter()}P${surname[0].toUpperCase()}${digits}${letter()}`;
/** Tax the platform withheld in one month. */
export interface TdsDeductedMonth {
month: string;
amount: number;
postings: number;
}
/**
* The deductees the reports run on, loaded from the platform.
*
* This used to be sixty-four customers generated from a fixed seed — names, PANs, deposits and all.
* The computation over them is the real section 194A calculation, so the figures looked like a
* filing and were not one: not a rupee of it came from the bank's own books.
*
* It is set once by whoever loads the screen and read by the pure computation below, which is why
* It is set once by whoever loads the screen and read by the pure computation, which is why
* this is a module-level holder rather than a service the compute would have to inject.
*/
let liveDeductees: TdsDeductee[] = [];
@@ -188,63 +108,3 @@ export function depositDueDate(month: string): string {
const nextM = m === 12 ? 1 : m + 1;
return `${nextY}-${`${nextM}`.padStart(2, '0')}-07`;
}
let challanCache: Record<string, TdsChallan[]> | null = null;
/**
* The remittance schedule, derived from what the engine says was deducted.
*
* Deductions land at each quarter end, so there is one challan per quarter. The
* Q2 challan of the prior year is deliberately deposited a month and a bit late
* so the late-deposit interest column is exercised, and the current year's latest
* quarter is left pending, which is the ordinary state of a live book.
*/
export function tdsChallans(deductedByQuarter: Record<string, Record<number, number>>): Record<string, TdsChallan[]> {
if (challanCache) {
return challanCache;
}
const out: Record<string, TdsChallan[]> = {};
for (const fy of Object.keys(deductedByQuarter)) {
const y = Number(fy.slice(0, 4));
const months: Record<number, string> = {
1: `${y}-06`,
2: `${y}-09`,
3: `${y}-12`,
4: `${y + 1}-03`
};
const rows: TdsChallan[] = [];
for (const q of [
1,
2,
3,
4
] as (1 | 2 | 3 | 4)[]) {
const amount = Math.round(deductedByQuarter[fy]?.[q] ?? 0);
if (amount <= 0) {
continue;
}
const month = months[q];
const dueDate = depositDueDate(month);
// Anything falling after the business date has not been deposited yet.
const pending = dueDate > TDS_AS_AT;
const late = fy === '2025-26' && q === 2;
rows.push({
fy,
quarter: q,
month,
amount,
dueDate,
depositedOn: pending ? null : late ? `${y}-11-14` : dueDate,
challanNo: pending ? null : `${y}${q}${`${amount % 10000}`.padStart(4, '0')}`,
bsrCode: pending ? null : '0510308'
});
}
out[fy] = rows;
}
challanCache = out;
return out;
}