251 lines
6.7 KiB
TypeScript
251 lines
6.7 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, 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<void> {
|
|
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;
|
|
}
|
|
}
|
|
}
|