204 lines
5.9 KiB
TypeScript
204 lines
5.9 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, 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<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;
|
|
}
|
|
}
|
|
}
|