/** * 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 { Component, OnInit, inject } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import * as ExcelJS from 'exceljs'; import { BranchScopeService } from '../core/branch-scope.service'; import { GL_BRANCHES, glBranchName } from '../gl-reports/gl-reports-data'; import { FY_TAX_CONFIG, Quarter, QUARTER_LABEL } from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute'; import { TDS_FYS } from './tds-reports-data'; import { NEAR_THRESHOLD_BAND, TdsInterestRow, TdsInterestSummary, TdsReportFilter, tdsInterestRows, tdsInterestSummary } from './tds-reports.compute'; @Component({ selector: 'mifosx-tds-interest-report', templateUrl: './tds-interest-report.component.html', styleUrls: ['./tds-reports.shared.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, FaIconComponent, FormsModule ] }) export class TdsInterestReportComponent implements OnInit { private scope = inject(BranchScopeService); readonly branches = GL_BRANCHES.filter((b) => b.code !== 'HO'); readonly financialYears = TDS_FYS; readonly quarterLabel = QUARTER_LABEL; readonly nearBand = NEAR_THRESHOLD_BAND; readonly quarters: Quarter[] = [ 1, 2, 3, 4 ]; filter: TdsReportFilter = { fy: TDS_FYS[0], branchCode: '', quarter: 0, band: '', search: '', crossedOnly: false, nearThresholdOnly: false }; /** Which customer's quarter-wise detail is open. */ expandedId: string | null = null; exporting = false; ngOnInit(): void { // A branch login sees only its own book and cannot widen the scope. const own = this.scope.ownBranchCode; if (own) { this.filter.branchCode = own; } } /** * Provision and return references for the selected year. The charge moved from * section 194A to section 393(1) on 01 April 2026 and the quarterly return * became Form 140, so the labels are read from the config rather than written * into the template. */ get statute(): { section: string; statement: string; frequency: string } { const cfg = FY_TAX_CONFIG[this.filter.fy]; return { section: cfg?.interestTdsSection ?? '—', statement: cfg?.tdsStatementForm ?? '—', frequency: cfg?.declarationStatementFrequency === 'MONTHLY' ? 'monthly' : 'quarterly' }; } get canSelectBranch(): boolean { return this.scope.canSelectBranch; } get scopeLabel(): string { return this.filter.branchCode ? glBranchName(this.filter.branchCode) : 'All branches'; } get rows(): TdsInterestRow[] { return tdsInterestRows(this.filter); } get summary(): TdsInterestSummary { return tdsInterestSummary(this.filter); } get hasFilters(): boolean { return !!( this.filter.search || this.filter.quarter || this.filter.band || this.filter.crossedOnly || this.filter.nearThresholdOnly ); } clearFilters(): void { this.filter = { ...this.filter, quarter: 0, band: '', search: '', crossedOnly: false, nearThresholdOnly: false }; } /** Jump straight to the customers worth chasing a declaration from. */ showNearThreshold(): void { this.filter = { ...this.filter, crossedOnly: false, nearThresholdOnly: true }; } toggleExpanded(row: TdsInterestRow): void { this.expandedId = this.expandedId === row.deductee.customerId ? null : row.deductee.customerId; } isExpanded(row: TdsInterestRow): boolean { return this.expandedId === row.deductee.customerId; } branchName(code: string): string { return glBranchName(code); } trackRow(_: number, row: TdsInterestRow): string { return row.deductee.customerId; } async exportWorkbook(): Promise { this.exporting = true; try { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet(`TDS Interest FY ${this.filter.fy}`); ws.addRow([`Deposit Interest & TDS — FY ${this.filter.fy}`]); ws.addRow([`Scope: ${this.scopeLabel}`]); ws.addRow([]); ws.addRow([ 'Customer', 'CIF', 'PAN', 'Branch', 'Form', 'Band', 'Age', 'Q1', 'Q2', 'Q3', 'Q4', 'Interest (FD/RD)', 'Savings interest (exempt)', 'Threshold', 'Crossed in', 'Rate %', 'TDS payable', 'Declaration', 'UIN', 'TDS saved', 'Paid without deduction' ]); for (const r of this.rows) { ws.addRow([ r.deductee.name, r.deductee.customerId, r.deductee.pan || 'NOT ON RECORD', this.branchName(r.branchCode), r.form === '121' ? 'Form 121' : `Form ${r.form}`, r.band === 'SENIOR' ? 'Senior citizen' : 'General', r.age, r.quarters[1], r.quarters[2], r.quarters[3], r.quarters[4], r.tdsApplicableInterest, r.exemptInterest, r.threshold, r.crossedInQuarter ? `Q${r.crossedInQuarter}` : '—', r.rateApplied * 100, r.schedule.totalTds, r.hasDeclaration ? 'Yes' : 'No', r.declarationUin ?? '', r.tdsSaved, r.paidWithoutDeduction ]); } const s = this.summary; ws.addRow([]); ws.addRow([ 'TOTAL', `${s.deductees} deductees`, '', '', '', '', '', s.byQuarter[0].interest, s.byQuarter[1].interest, s.byQuarter[2].interest, s.byQuarter[3].interest, s.applicableInterest, s.exemptInterest, '', '', '', s.totalTds, `${s.declarations} filed`, '', s.tdsSaved, s.paidWithoutDeduction ]); ws.getRow(1).font = { bold: true, size: 13 }; ws.getRow(4).font = { bold: true }; ws.columns.forEach((c) => (c.width = 18)); const buffer = await wb.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `TDS-Interest-FY${this.filter.fy}.xlsx`; a.click(); URL.revokeObjectURL(url); } finally { this.exporting = false; } } }