205 lines
6.0 KiB
TypeScript
205 lines
6.0 KiB
TypeScript
/**
|
||
* 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, tdsFys } 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);
|
||
|
||
/** Filled by the route resolver from Organization › TDS Configuration. */
|
||
readonly financialYears = tdsFys();
|
||
readonly quarterLabel = QUARTER_LABEL;
|
||
readonly asAt = TDS_AS_AT;
|
||
|
||
fy = tdsFys()[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<void> {
|
||
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;
|
||
}
|
||
}
|
||
}
|