/** * 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, 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 { FY_TAX_CONFIG, QUARTER_LABEL } from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute'; import { TDS_AS_AT, TDS_FYS } from './tds-reports-data'; import { Tds26qSummary, TdsRemittanceRow, tds26qSummary, tdsRemittanceRows } from './tds-reports.compute'; @Component({ selector: 'mifosx-tds-remittance-report', templateUrl: './tds-remittance-report.component.html', styleUrls: ['./tds-reports.shared.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, FaIconComponent, FormsModule ] }) export class TdsRemittanceReportComponent { private scope = inject(BranchScopeService); readonly financialYears = TDS_FYS; readonly quarterLabel = QUARTER_LABEL; readonly asAt = TDS_AS_AT; fy = TDS_FYS[0]; exporting = false; /** * Provision and return references for the selected year. The quarterly return * became Form 140 under the 2025 Act, 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.fy]; return { section: cfg?.interestTdsSection ?? '—', statement: cfg?.tdsStatementForm ?? '—', frequency: cfg?.declarationStatementFrequency === 'MONTHLY' ? 'monthly' : 'quarterly' }; } get scopeLabel(): string { return this.scope.isHeadOffice ? 'Bank as a whole' : this.scope.officeName; } get rows(): TdsRemittanceRow[] { return tdsRemittanceRows(this.fy, this.asAt); } get summary(): Tds26qSummary { return tds26qSummary(this.fy, this.asAt); } /** * The control that matters. Tax deducted must equal tax deposited plus tax * still within its due date. Anything else means something has gone missing * and must be found before the return is filed. */ get reconciled(): boolean { return this.summary.difference === 0; } get totalDefaultInterest(): number { return this.summary.defaultInterest; } trackRow(_: number, row: TdsRemittanceRow): string { return `${row.fy}-${row.quarter}`; } async exportWorkbook(): Promise { this.exporting = true; try { const s = this.summary; const wb = new ExcelJS.Workbook(); const challan = wb.addWorksheet('Challans'); challan.addRow([`TDS remitted to Government — FY ${this.fy}`]); challan.addRow([`Scope: ${this.scopeLabel} · as at ${this.asAt}`]); challan.addRow([]); challan.addRow([ 'Quarter', 'Deduction month', 'Amount deducted', 'Due date', 'Deposited on', 'Challan no.', 'BSR code', 'Status', 'Delay (months)', 'Late-deposit interest', `${this.statute.statement} due` ]); for (const r of this.rows) { challan.addRow([ `Q${r.quarter}`, r.month, r.amount, r.dueDate, r.depositedOn ?? 'PENDING', r.challanNo ?? '', r.bsrCode ?? '', r.overdue ? 'OVERDUE' : r.late ? 'LATE' : r.depositedOn ? 'On time' : 'Within due date', r.delayMonths || '', r.defaultInterest || '', r.returnDueDate ]); } challan.getRow(1).font = { bold: true, size: 13 }; challan.getRow(4).font = { bold: true }; challan.columns.forEach((c) => (c.width = 18)); const recon = wb.addWorksheet('Return Summary'); recon.addRow([`${this.statute.statement} year-end position — FY ${this.fy}`]); recon.addRow([]); const lines: [string, number | string][] = [ [ 'Deductees', s.deductees ], [ 'Total interest paid on time deposits', s.totalInterestPaid ], [ ' — with tax deducted', s.interestWithDeduction ], [ ' — without deduction (declaration on record)', s.interestWithoutDeduction ], [ 'TDS at standard rate (PAN on record)', s.tdsAtStandardRate ], [ 'TDS at higher rate (no PAN)', s.tdsAtHigherRate ], [ 'Total TDS deducted', s.totalDeducted ], [ 'Total deposited with Government', s.totalDeposited ], [ 'Still within due date', s.pendingDeposit ], [ 'DIFFERENCE (must be zero)', s.difference ], [ 'Interest payable for late deposit', s.defaultInterest ], [ 'Challans deposited late', s.lateChallans ], [ 'Challans overdue', s.overdueChallans ] ]; for (const line of lines) { recon.addRow(line); } recon.getRow(1).font = { bold: true, size: 13 }; recon.columns.forEach((c) => (c.width = 42)); recon.getColumn(2).width = 20; 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-Remittance-FY${this.fy}.xlsx`; a.click(); URL.revokeObjectURL(url); } finally { this.exporting = false; } } }