From 4fc67846958fd482f0efda7b0047e5112e37cc48 Mon Sep 17 00:00:00 2001 From: keshav Date: Mon, 5 Oct 2026 12:39:14 +0530 Subject: [PATCH] feat: interest posting --- src/app/core/http/cache.interceptor.ts | 10 +- .../interest-posting-report.component.html | 140 ++++++- .../interest-posting-report.component.scss | 51 +++ .../interest-posting-report.component.ts | 210 +++++++++- .../interest-posting-report.service.ts | 47 ++- .../tds-interest-report.component.html | 387 +++++++++++------- .../tds-interest-report.component.ts | 98 ++++- src/app/tds-reports/tds-reports.compute.ts | 78 +++- src/app/tds-reports/tds-reports.shared.scss | 79 ++++ src/environments/.env.ts | 4 +- 10 files changed, 915 insertions(+), 189 deletions(-) diff --git a/src/app/core/http/cache.interceptor.ts b/src/app/core/http/cache.interceptor.ts index d6410f748..c58b0a4a0 100644 --- a/src/app/core/http/cache.interceptor.ts +++ b/src/app/core/http/cache.interceptor.ts @@ -36,7 +36,15 @@ export class CacheInterceptor implements HttpInterceptor { '/rbireserveconfig', '/tdsconfig', '/tds/deposit-interest', - '/savingsinterestpostingreports' + '/savingsinterestpostingreports', + // Cash in vaults and drawers changes with every transaction; a cached teller list or position + // kept showing a branch's old tellers and figures at day close. + '/tellers', + '/cashreports', + '/cashreconciliation', + '/cashsettlement', + '/cashtransfer', + '/branchday' ]; private isCacheable(url: string): boolean { diff --git a/src/app/interest-posting-report/interest-posting-report.component.html b/src/app/interest-posting-report/interest-posting-report.component.html index 6c4c92edb..b830e7b92 100644 --- a/src/app/interest-posting-report/interest-posting-report.component.html +++ b/src/app/interest-posting-report/interest-posting-report.component.html @@ -22,9 +22,134 @@ + + @if (isHeadOffice) { +
+ + +
+ @if (tab === 'others') { +
+ +
+ Each branch generates and approves its own reports. The head office can act here only for a branch that has + not opened the day, and only once every other open branch has closed. Anything it approves is marked (HO). +
+
+ } + } + + @if (showPreview) { +
+
+
+

Interest preview — earned up to {{ previewAsOf }}

+
+ What each account would be paid if interest were posted today. View only — nothing is saved, approved or + posted. +
+
+
+ Total so far + ₹{{ previewTotal | number: '1.2-2' }} + {{ previewRows.length }} account(s) +
+
+
+ + + +
+ @if (previewLoading) { +
Calculating interest up to today…
+ } @else { + + + + + + + + + + + + @for (g of previewGroups; track g.officeName + g.productName) { + + + + + + + + } @empty { + + + + } + +
BranchProductPosting dateAccountsInterest so far
{{ g.officeName }} + {{ g.productName }} + {{ g.postingDate }}{{ g.accounts }}₹{{ g.interest | number: '1.2-2' }}
No savings account has interest accrued in its current cycle.
+ @if (previewRows.length > 0) { + + + + + + + + + + + + + + + @for (r of previewRows; track r.savingsAccountId) { + + + + + + + + + + + } + + + + + + + +
BranchProductAccount noAccount holderBalanceRate %CycleInterest so far
{{ r.officeName }}{{ r.productName }}{{ r.accountNo }}{{ r.clientName }}₹{{ r.balance | number: '1.2-2' }}{{ r.rate | number: '1.2-2' }}{{ r.cycleStart }} → {{ r.postingDate }}₹{{ r.interest | number: '1.2-2' }}
Total + ₹{{ previewTotal | number: '1.2-2' }} +
+ } + } +
+ } + @if (errorMessage) {
@@ -52,9 +177,13 @@ @for (d of due; track d.productId + '-' + d.officeId) {
{{ d.productName }} · {{ d.officeName }} · {{ d.postingDate }} - + @if (d.canAct) { + + } @else if (d.blockedReason) { + {{ d.blockedReason }} + }
}
@@ -114,9 +243,12 @@ } @if (canApprove(r)) { } + @if (!r.canAct && r.blockedReason && (r.status === 'GENERATED' || r.status === 'APPROVED')) { + {{ r.blockedReason }} + } } @empty { diff --git a/src/app/interest-posting-report/interest-posting-report.component.scss b/src/app/interest-posting-report/interest-posting-report.component.scss index 6adbcaa77..a4d744927 100644 --- a/src/app/interest-posting-report/interest-posting-report.component.scss +++ b/src/app/interest-posting-report/interest-posting-report.component.scss @@ -274,3 +274,54 @@ font-weight: 600; color: var(--i-amber); } + +// Preview: refresh / export / close, above the totals. +.ipr-preview-actions { + display: flex; + gap: 6px; + justify-content: flex-end; + padding: 0 16px 10px; +} + +.ipr-tabs { + display: flex; + gap: 4px; + margin-bottom: 12px; + border-bottom: 1px solid var(--i-g200); +} + +.ipr-tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + margin-bottom: -1px; + font-size: 13px; + font-weight: 600; + color: var(--i-g500); + cursor: pointer; + background: transparent; + border: 1px solid transparent; + border-radius: 6px 6px 0 0; + + &.is-active { + color: var(--i-navy); + background: #fff; + border-color: var(--i-g200); + border-bottom-color: #fff; + } +} + +.ipr-alert--info { + background: var(--i-blue-l); + color: var(--i-blue); +} + +.ipr-blocked { + display: inline-block; + max-width: 320px; + font-size: 11px; + line-height: 1.35; + color: var(--i-g500); + white-space: normal; +} diff --git a/src/app/interest-posting-report/interest-posting-report.component.ts b/src/app/interest-posting-report/interest-posting-report.component.ts index d7a708060..c4fd3f1ad 100644 --- a/src/app/interest-posting-report/interest-posting-report.component.ts +++ b/src/app/interest-posting-report/interest-posting-report.component.ts @@ -11,11 +11,14 @@ import { FormsModule } from '@angular/forms'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { SettingsService } from 'app/settings/settings.service'; +import { exportWorkbook } from '../cashier/cashier-export'; import { InterestPostingDue, InterestPostingReport, InterestPostingReportDetail, - InterestPostingReportService + InterestPostingReportService, + InterestPreviewRow, + ReportScope } from './interest-posting-report.service'; /** @@ -51,15 +54,48 @@ export class InterestPostingReportComponent implements OnInit { /** Products with interest due and no report yet — the noon job ran before they had anything due, or failed. */ due: InterestPostingDue[] = []; + /** 'own' — the user's own branch; 'others' — every other branch, a head-office-only tab. */ + tab: ReportScope = 'own'; + isHeadOffice = false; + myBranch = ''; + ngOnInit(): void { this.postingDate = this.businessDateIso(); + this.reportService.context().subscribe({ + next: (ctx) => { + this.isHeadOffice = !!ctx?.headOffice; + this.myBranch = ctx?.officeName ?? ''; + }, + error: () => (this.isHeadOffice = false) + }); this.load(); } + switchTab(tab: ReportScope): void { + if (this.tab === tab) { + return; + } + this.tab = tab; + this.selected = null; + this.notice = ''; + this.load(); + } + + /** Acting for another branch is the head office's exception, so it is spelt out before it happens. */ + private confirmOnBehalf(officeName: string, action: string): boolean { + if (this.tab !== 'others') { + return true; + } + return confirm( + `${officeName} has not opened today. You are about to ${action} its interest posting report on its behalf, ` + + 'from the head office. Continue?' + ); + } + load(): void { this.loading = true; this.errorMessage = ''; - this.reportService.list(this.postingDate || undefined).subscribe({ + this.reportService.list(this.postingDate || undefined, undefined, undefined, this.tab).subscribe({ next: (reports) => { this.reports = reports ?? []; this.loading = false; @@ -75,13 +111,16 @@ export class InterestPostingReportComponent implements OnInit { this.errorMessage = this.messageOf(err, 'Could not load the interest posting reports.'); } }); - this.reportService.dueWithoutReport(this.postingDate || undefined).subscribe({ + this.reportService.dueWithoutReport(this.postingDate || undefined, this.tab).subscribe({ next: (due) => (this.due = due ?? []), error: () => (this.due = []) }); } generate(item: InterestPostingDue): void { + if (!this.confirmOnBehalf(item.officeName, 'generate')) { + return; + } this.busy = true; this.notice = ''; this.errorMessage = ''; @@ -111,7 +150,8 @@ export class InterestPostingReportComponent implements OnInit { if ( !confirm( `Regenerate the interest posting report for ${report.productName} — ${report.officeName}? It will need approving again.` - ) + ) || + !this.confirmOnBehalf(report.officeName, 'regenerate') ) { return; } @@ -138,7 +178,8 @@ export class InterestPostingReportComponent implements OnInit { !confirm( `Approve interest of ₹${report.totalInterest} across ${report.accountCount} account(s) for ${report.productName}? ` + 'It will be posted when the branch closes the day.' - ) + ) || + !this.confirmOnBehalf(report.officeName, 'approve') ) { return; } @@ -177,8 +218,165 @@ export class InterestPostingReportComponent implements OnInit { ); } + /** Pending reports that block this user's own day close; another branch's pending reports block only that branch. */ get pendingCount(): number { - return this.reports.filter((r) => r.status === 'GENERATED').length; + return this.tab === 'own' ? this.reports.filter((r) => r.status === 'GENERATED').length : 0; + } + + // ── Preview: what each account has earned so far, as if posted today. View only. ── + + showPreview = false; + previewLoading = false; + previewRows: InterestPreviewRow[] = []; + previewAsOf = ''; + + openPreview(): void { + this.showPreview = true; + this.previewLoading = true; + this.errorMessage = ''; + this.reportService.preview().subscribe({ + next: (rows) => { + this.previewRows = rows ?? []; + this.previewAsOf = this.previewRows[0]?.asOf ?? this.businessDateIso(); + this.previewLoading = false; + }, + error: (err) => { + this.previewLoading = false; + this.previewRows = []; + this.errorMessage = this.messageOf(err, 'Could not load the interest preview.'); + } + }); + } + + closePreview(): void { + this.showPreview = false; + } + + get previewTotal(): number { + return this.previewRows.reduce((sum, r) => sum + Number(r.interest || 0), 0); + } + + /** Totals by branch and product, for the summary above the account list and the export. */ + get previewGroups(): { + officeName: string; + productName: string; + postingDate: string; + accounts: number; + interest: number; + }[] { + const map = new Map< + string, + { officeName: string; productName: string; postingDate: string; accounts: number; interest: number } + >(); + this.previewRows.forEach((r) => { + const key = `${r.officeName}|${r.productName}`; + const g = map.get(key) ?? { + officeName: r.officeName, + productName: r.productName, + postingDate: r.postingDate, + accounts: 0, + interest: 0 + }; + g.accounts += 1; + g.interest += Number(r.interest || 0); + map.set(key, g); + }); + return [...map.values()]; + } + + exportPreview(): void { + const stamp = `Interest earned to ${this.previewAsOf} — preview, not posted`; + exportWorkbook( + [ + { + sheetName: 'By account', + title: 'Savings Interest Preview', + subtitle: stamp, + headers: [ + 'Branch', + 'Product', + 'Account No', + 'Customer', + 'Balance', + 'Rate %', + 'Cycle Start', + 'Posting Date', + 'Interest So Far' + ], + widths: [ + 18, + 22, + 20, + 26, + 16, + 8, + 12, + 12, + 16 + ], + rows: [ + ...this.previewRows.map((r) => [ + r.officeName, + r.productName, + r.accountNo, + r.clientName, + Number(r.balance || 0), + Number(r.rate || 0), + r.cycleStart, + r.postingDate, + Number(r.interest || 0) + ]), + [ + 'TOTAL', + '', + '', + '', + '', + '', + '', + '', + this.previewTotal + ] + ] + }, + { + sheetName: 'By branch & product', + title: 'Savings Interest Preview — Totals', + subtitle: stamp, + headers: [ + 'Branch', + 'Product', + 'Posting Date', + 'Accounts', + 'Interest So Far' + ], + widths: [ + 18, + 22, + 12, + 10, + 16 + ], + rows: [ + ...this.previewGroups.map((g) => [ + g.officeName, + g.productName, + g.postingDate, + g.accounts, + g.interest + ]), + [ + 'TOTAL', + '', + '', + this.previewRows.length, + this.previewTotal + ] + ] + } + ], + `savings-interest-preview-${this.previewAsOf}` + ); } private businessDateIso(): string { diff --git a/src/app/interest-posting-report/interest-posting-report.service.ts b/src/app/interest-posting-report/interest-posting-report.service.ts index 398dd2f8b..7d2466e49 100644 --- a/src/app/interest-posting-report/interest-posting-report.service.ts +++ b/src/app/interest-posting-report/interest-posting-report.service.ts @@ -12,6 +12,9 @@ import { Observable } from 'rxjs'; export type InterestPostingReportStatus = 'GENERATED' | 'APPROVED' | 'POSTED' | 'SUPERSEDED'; +/** Which branches a list covers: the user's own, or (head office only) every other branch. */ +export type ReportScope = 'own' | 'others'; + export interface InterestPostingReport { id: number; productId: number; @@ -32,6 +35,8 @@ export interface InterestPostingReport { failureReason: string | null; /** Whether the signed-in user may regenerate or approve it: their own branch, or the head office for a branch that did not open. */ canAct: boolean; + /** Why not, where canAct is false. */ + blockedReason: string | null; } export interface InterestPostingReportLine { @@ -60,6 +65,8 @@ export interface InterestPostingDue { officeId: number; officeName: string; postingDate: string; + canAct: boolean; + blockedReason: string | null; } export interface InterestPostingSummaryRow { @@ -78,6 +85,22 @@ export interface InterestPostingSummaryRow { postedAt: string | null; } +export interface InterestPreviewRow { + officeId: number; + officeName: string; + productId: number; + productName: string; + savingsAccountId: number; + accountNo: string; + clientName: string; + balance: number; + rate: number; + cycleStart: string; + postingDate: string; + asOf: string; + interest: number; +} + /** * The savings interest posting reports, one per branch and product: generated at noon on a posting * date, approved by the branch, posted when the branch closes the day. @@ -87,8 +110,15 @@ export class InterestPostingReportService { private http = inject(HttpClient); private readonly base = '/savingsinterestpostingreports'; - list(postingDate?: string, status?: string, officeId?: number): Observable { + /** scope 'own' is the user's own branch; 'others' (head office only) every other branch. */ + list( + postingDate?: string, + status?: string, + officeId?: number, + scope?: ReportScope + ): Observable { let params = new HttpParams(); + if (scope) params = params.set('scope', scope); if (postingDate) params = params.set('postingDate', postingDate); if (status) params = params.set('status', status); if (officeId) params = params.set('officeId', String(officeId)); @@ -96,12 +126,18 @@ export class InterestPostingReportService { } /** Branch and product pairs with interest due by the date and no report yet, that this user may generate. */ - dueWithoutReport(postingDate?: string): Observable { + dueWithoutReport(postingDate?: string, scope?: ReportScope): Observable { let params = new HttpParams(); + if (scope) params = params.set('scope', scope); if (postingDate) params = params.set('postingDate', postingDate); return this.http.get(`${this.base}/due-without-report`, { params }); } + /** The signed-in user's branch, and whether it is the head office. */ + context(): Observable<{ officeId: number; officeName: string; headOffice: boolean }> { + return this.http.get<{ officeId: number; officeName: string; headOffice: boolean }>(`${this.base}/context`); + } + detail(reportId: number): Observable { return this.http.get(`${this.base}/${reportId}`); } @@ -126,6 +162,13 @@ export class InterestPostingReportService { ); } + /** Interest each account has earned so far this cycle, as if posted today. View only. */ + preview(officeId?: number): Observable { + let params = new HttpParams(); + if (officeId) params = params.set('officeId', String(officeId)); + return this.http.get(`${this.base}/preview`, { params }); + } + /** The head office's branch-wise view over a date range. */ summary(fromDate?: string, toDate?: string, productId?: number): Observable { let params = new HttpParams(); diff --git a/src/app/tds-reports/tds-interest-report.component.html b/src/app/tds-reports/tds-interest-report.component.html index 9244384a1..f2e4c032e 100644 --- a/src/app/tds-reports/tds-interest-report.component.html +++ b/src/app/tds-reports/tds-interest-report.component.html @@ -13,15 +13,21 @@

Deposit Interest & TDS

- Quarter-wise and yearly interest on time deposits, with tax deductible under {{ statute.section }} · - {{ scopeLabel }} + Interest each customer earned this year, and whether TDS has to be deducted on it · + Showing: {{ scopeLabel }}
- +
+ + +
@if (loadError) { @@ -31,57 +37,87 @@ } + + @if (showHelp) { +
+
How to read this page
+
    +
  1. + Every customer with an account is listed. Each account is either + Tracked — its interest counts toward the TDS limit — or + Not tracked. Open a row () to see which + account is which, and why. +
  2. +
  3. + Only fixed and recurring deposits are tracked. Savings interest is shown for information but + never attracts TDS. +
  4. + @if (rules) { +
  5. + A customer's tracked interest for the whole year, across every branch, is checked against the limit: + ₹{{ rules.general | number: '1.0-0' }}, or ₹{{ rules.senior | number: '1.0-0' }} for a senior citizen (60+). +
  6. +
  7. + Once over the limit, TDS is {{ rules.rate | number: '1.0-2' }}% of the + whole year's interest — {{ rules.rateNoPan | number: '1.0-2' }}% if no PAN is on + record. +
  8. + } +
  9. A customer who has submitted Form 15G/15H is not deducted, even over the limit.
  10. +
  11. + The TDS figures here are worked out by this report from the interest actually posted. They + are not debited from the customer's account automatically. +
  12. +
+
+ } +
{{ summary.deductees | number }}
-
Deductees
+
Customers · {{ summary.trackedCustomers | number }} with an FD/RD being tracked
₹{{ summary.applicableInterest | number: '1.0-0' }}
-
Interest on FD / RD
+
FD / RD interest — counts toward the limit
₹{{ summary.exemptInterest | number: '1.0-0' }}
-
Savings interest — outside the charge
-
-
-
₹{{ summary.totalTds | number: '1.0-0' }}
-
TDS payable
+
Savings interest — shown only, no TDS
{{ summary.crossed | number }}
-
Crossed the threshold
+
Customers over the limit
- -
-
₹{{ summary.tdsSaved | number: '1.0-0' }}
-
Saved by {{ summary.declarations | number }} declarations
+
+
₹{{ summary.totalTds | number: '1.0-0' }}
+
TDS to be deducted
- @if (summary.noPan > 0) { -
-
{{ summary.noPan | number }}
-
No PAN — deducted at 20%
-
- }
- -
-
Quarter by quarter
-
- @for (q of summary.byQuarter; track q.quarter) { -
-
{{ quarterLabel[q.quarter] }}
-
₹{{ q.interest | number: '1.0-0' }}
-
TDS ₹{{ q.tds | number: '1.0-0' }}
-
+ + @if (summary.nearThreshold || summary.declarations || summary.noPan) { +
+ @if (summary.nearThreshold) { + + } + @if (summary.declarations) { + + {{ summary.declarations | number }} with 15G/15H — ₹{{ summary.tdsSaved | number: '1.0-0' }} not deducted + + } + @if (summary.noPan) { + + {{ summary.noPan | number }} FD/RD customers without PAN — higher rate applies + }
-
+ }
@@ -99,8 +135,8 @@ Branch @@ -117,32 +153,32 @@ @if (hasFilters) { @@ -150,7 +186,7 @@ }
- +
@@ -176,87 +212,76 @@ - + - - - - - - - - + + + + - @for (row of pagedRows; track trackRow($index, row)) { - + - - + - @for (q of quarters; track q) { - - } - @@ -264,89 +289,138 @@ @if (isExpanded(row)) { - } } @if (visibleRows.length === 0) { - } @@ -359,8 +433,7 @@
Showing {{ rangeStart | number }}–{{ rangeEnd | number }} of - {{ visibleRows.length | number }} - {{ selectedTab === 'paying' ? 'deductees paying TDS' : 'deductees not paying TDS' }} + {{ visibleRows.length | number }} customers
Customer · PANCustomer BranchForm · BandQ1Q2Q3Q4Year totalThresholdCrossedAccountsFD / RD interest this yearLimitStatus TDSSaved
{{ row.deductee.name }}
- {{ row.deductee.customerId }} · + ID {{ row.deductee.customerId }} · @if (row.panOnRecord) { - {{ row.deductee.pan }} + PAN {{ row.deductee.pan }} } @else { no PAN } + @if (row.band === 'SENIOR') { + Senior 60+ + }
{{ branchName(row.branchCode) }} - - {{ row.form === '121' ? '121' : row.form }} - -
{{ row.band === 'SENIOR' ? 'Senior' : 'General' }}
- @if (row.hasDeclaration) { -
{{ row.declarationUin }}
+
{{ row.branchCode || '—' }} + @if (row.trackedAccounts) { + {{ row.trackedAccounts }} tracked + } + @if (untrackedOf(row).length) { + {{ untrackedOf(row).length }} not tracked } - @if (row.quarters[q]) { - ₹{{ row.quarters[q] | number: '1.0-0' }} - } @else { - — - } - ₹{{ row.tdsApplicableInterest | number: '1.0-0' }} @if (row.exemptInterest) { -
+₹{{ row.exemptInterest | number: '1.0-0' }} SB (exempt)
+
+ ₹{{ row.exemptInterest | number: '1.0-0' }} savings (no TDS)
}
₹{{ row.threshold | number: '1.0-0' }} - @if (row.crossedInQuarter) { - Q{{ row.crossedInQuarter }} - } @else if (row.nearThreshold) { - ₹{{ row.headroom | number: '1.0-0' }} left - } @else { - — - } + + {{ statusOf(row).label }} + @if (row.schedule.totalTds) { ₹{{ row.schedule.totalTds | number: '1.0-0' }}
@ {{ row.rateApplied * 100 | number: '1.0-0' }}%
- } @else { - — - } -
- @if (row.tdsSaved) { - ₹{{ row.tdsSaved | number: '1.0-0' }} + } @else if (row.tdsSaved) { + ₹{{ row.tdsSaved | number: '1.0-0' }} saved } @else { — } -
- @for (note of row.schedule.notes; track note) { -
- -
{{ note }}
-
- } - @if (row.jointHeldInterest) { -
- -
- ₹{{ row.jointHeldInterest | number: '1.0-0' }} of interest sits on deposits where this customer - is a joint holder but not the first-named one. Tax there is deducted against the first-named - holder, so it is excluded from the figures above. -
-
- } - +
+ +
Accounts of {{ row.deductee.name }}
- - - - - - + + + + + + - @for (line of row.schedule.lines; track line.quarter) { - + @for (a of row.accounts; track a.accountNo) { + + + - - - - + + + - }
QuarterInterestCumulativeLiability to dateTDS this quarterReturn dueAccount no.TypeTracked for TDS?Interest this yearPostingsLast posted
{{ a.accountNo }}{{ accountTypeLabel[a.type] }} - {{ line.label }} - @if (line.thresholdCrossedHere) { - crossed here - } - @if (line.declarationActive) { - Form 121 active - } - ₹{{ line.interest | number: '1.0-0' }}₹{{ line.cumInterest | number: '1.0-0' }}₹{{ line.cumLiability | number: '1.0-0' }} - @if (line.tds !== 0) { - ₹{{ line.tds | number: '1.0-0' }} + @if (a.tracked) { + Yes } @else { - — + No } + {{ a.reason }} + ₹{{ a.interest | number: '1.2-2' }}{{ a.postings }} + {{ a.lastPostedOn ? (a.lastPostedOn | date: 'd MMM y') : 'No interest posted yet' }} {{ line.dueDate }}
-
- Settlement: {{ row.mode === 'QUARTERLY' ? 'Quarterly' : 'Year end' }} (set by - the deposit product) - @if (row.declarationAcceptedOn) { - Declaration accepted: {{ row.declarationAcceptedOn | date: 'd MMM y' }} + + @if (row.trackedAccounts) { +
How the TDS is worked out, quarter by quarter
+ @for (note of row.schedule.notes; track note) { +
+ +
{{ note }}
+
+ } + @if (row.schedule.lines.length) { + + + + + + + + + + + + @for (line of row.schedule.lines; track line.quarter) { + + + + + + + + } + +
QuarterInterest in quarterTotal so farTDS this quarterReturn due
+ {{ line.label }} + @if (line.thresholdCrossedHere) { + limit crossed here + } + @if (line.declarationActive) { + 15G/15H active + } + ₹{{ line.interest | number: '1.0-0' }} + ₹{{ line.cumInterest | number: '1.0-0' }} + of ₹{{ row.threshold | number: '1.0-0' }} + + @if (line.tds !== 0) { + ₹{{ line.tds | number: '1.0-0' }} + } @else { + — + } + {{ line.dueDate }}
+ } @else { +
No interest has been posted on the tracked deposits this year yet.
} @if (row.paidWithoutDeduction) { - - Paid without deduction: - ₹{{ row.paidWithoutDeduction | number: '1.0-0' }} — reportable in - {{ statute.statement }} - +
+ + Paid without deduction because of 15G/15H: + ₹{{ row.paidWithoutDeduction | number: '1.0-0' }} — still reported in + {{ statute.statement }} + +
} -
+ } @else { +
+ +
+ This customer has no fixed or recurring deposit, so nothing here counts toward TDS. Savings + interest is shown for information only. +
+
+ } + + @if (row.jointHeldInterest) { +
+ +
+ ₹{{ row.jointHeldInterest | number: '1.0-0' }} of interest is on joint deposits where this + customer is not the first-named holder, so it is counted under that holder instead. +
+
+ }
- No customers in this section match the filters. + + + @if (loading) { + Loading… + } @else if (selectedTab === 'paying') { + No customer needs TDS deducted for these filters. Check the "No TDS" tab. + } @else { + No customers match these filters. + }