feat: update TDS config, RBI reserve returns, current products, and dormancy reports
This commit is contained in:
@@ -24,6 +24,13 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (loadError) {
|
||||
<div class="tdsr__banner tdsr__banner--warn">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||||
<div>{{ loadError }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Summary -->
|
||||
<div class="tdsr__kpis">
|
||||
<div class="tdsr__kpi">
|
||||
|
||||
@@ -15,7 +15,7 @@ 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 { setTdsDeductees, TDS_FYS } from './tds-reports-data';
|
||||
import { setTdsDeductees, tdsFys } from './tds-reports-data';
|
||||
import { TdsReportsService } from './tds-reports.service';
|
||||
import {
|
||||
NEAR_THRESHOLD_BAND,
|
||||
@@ -41,7 +41,8 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
private tdsService = inject(TdsReportsService);
|
||||
|
||||
readonly branches = GL_BRANCHES.filter((b) => b.code !== 'HO');
|
||||
readonly financialYears = TDS_FYS;
|
||||
/** Filled by the route resolver from Organization › TDS Configuration. */
|
||||
readonly financialYears = tdsFys();
|
||||
readonly quarterLabel = QUARTER_LABEL;
|
||||
readonly nearBand = NEAR_THRESHOLD_BAND;
|
||||
readonly quarters: Quarter[] = [
|
||||
@@ -52,7 +53,7 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
];
|
||||
|
||||
filter: TdsReportFilter = {
|
||||
fy: TDS_FYS[0],
|
||||
fy: tdsFys()[0] ?? '',
|
||||
branchCode: '',
|
||||
quarter: 0,
|
||||
band: '',
|
||||
@@ -93,6 +94,10 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
loadError = '';
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.financialYears.length) {
|
||||
this.loadError = 'No financial year is configured for TDS. Set one up under Organization › TDS Configuration.';
|
||||
return;
|
||||
}
|
||||
// A branch login sees only its own book and cannot widen the scope.
|
||||
const own = this.scope.ownBranchCode;
|
||||
if (own) {
|
||||
@@ -116,20 +121,29 @@ export class TdsInterestReportComponent implements OnInit {
|
||||
next: (deductees) => {
|
||||
this.loading = false;
|
||||
setTdsDeductees(deductees);
|
||||
this.summaryCacheKey = '';
|
||||
this.cachedSummary = null;
|
||||
this.invalidateCaches();
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.loading = false;
|
||||
setTdsDeductees([]);
|
||||
this.summaryCacheKey = '';
|
||||
this.cachedSummary = null;
|
||||
this.invalidateCaches();
|
||||
this.loadError =
|
||||
err?.error?.defaultUserMessage || 'Could not load deposit interest. The figures below cannot be shown.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Both caches are keyed on the filter alone, so a fresh load must drop them together. Clearing
|
||||
* only the summary left the KPIs counting the new data while the tables kept the empty list
|
||||
* cached on the first render, before the load returned.
|
||||
*/
|
||||
private invalidateCaches(): void {
|
||||
this.rowsCacheKey = '';
|
||||
this.summaryCacheKey = '';
|
||||
this.cachedSummary = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A different year needs a different set of interest credits, so the year re-reads from the
|
||||
* platform rather than re-filtering the set already in hand.
|
||||
|
||||
@@ -14,7 +14,7 @@ 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 { TDS_AS_AT, tdsFys } from './tds-reports-data';
|
||||
import { Tds26qSummary, TdsRemittanceRow, tds26qSummary, tdsRemittanceRows } from './tds-reports.compute';
|
||||
|
||||
@Component({
|
||||
@@ -30,11 +30,12 @@ import { Tds26qSummary, TdsRemittanceRow, tds26qSummary, tdsRemittanceRows } fro
|
||||
export class TdsRemittanceReportComponent {
|
||||
private scope = inject(BranchScopeService);
|
||||
|
||||
readonly financialYears = TDS_FYS;
|
||||
/** Filled by the route resolver from Organization › TDS Configuration. */
|
||||
readonly financialYears = tdsFys();
|
||||
readonly quarterLabel = QUARTER_LABEL;
|
||||
readonly asAt = TDS_AS_AT;
|
||||
|
||||
fy = TDS_FYS[0];
|
||||
fy = tdsFys()[0] ?? '';
|
||||
exporting = false;
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,17 +20,17 @@
|
||||
|
||||
import { GL_BRANCHES } from '../gl-reports/gl-reports-data';
|
||||
import {
|
||||
configuredFys,
|
||||
Declaration,
|
||||
Form15Kind,
|
||||
InterestCredit,
|
||||
TdsAccountInput
|
||||
} from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute';
|
||||
|
||||
/** The years the seed covers. 2025-26 is complete; 2026-27 is in progress. */
|
||||
export const TDS_FYS = [
|
||||
'2026-27',
|
||||
'2025-26'
|
||||
];
|
||||
/** The years the reports can show: every year configured on Organization › TDS Configuration. */
|
||||
export function tdsFys(): string[] {
|
||||
return configuredFys();
|
||||
}
|
||||
|
||||
/** Business date the book is drawn to — Q1 of 2026-27 is closed, Q2 is running. */
|
||||
export const TDS_AS_AT = '2026-08-01';
|
||||
@@ -206,7 +206,7 @@ export function tdsChallans(deductedByQuarter: Record<string, Record<number, num
|
||||
}
|
||||
const out: Record<string, TdsChallan[]> = {};
|
||||
|
||||
for (const fy of TDS_FYS) {
|
||||
for (const fy of Object.keys(deductedByQuarter)) {
|
||||
const y = Number(fy.slice(0, 4));
|
||||
const months: Record<number, string> = {
|
||||
1: `${y}-06`,
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
import { isTdsApplicable, tdsDeducteeOf } from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute';
|
||||
import { TDS_AS_AT, TDS_FYS, depositDueDate, tdsDeductees } from './tds-reports-data';
|
||||
import { installTdsConfigFixture } from '../letter-mgmt/certificates/form-15gh/tds-config.fixture';
|
||||
import { TDS_AS_AT, depositDueDate, tdsDeductees, tdsFys } from './tds-reports-data';
|
||||
import {
|
||||
NEAR_THRESHOLD_BAND,
|
||||
TdsReportFilter,
|
||||
@@ -19,6 +20,8 @@ import {
|
||||
tdsRemittanceRows
|
||||
} from './tds-reports.compute';
|
||||
|
||||
installTdsConfigFixture();
|
||||
|
||||
/** The complete year in the seed — the one a return would be filed for. */
|
||||
const CLOSED_FY = '2025-26';
|
||||
|
||||
@@ -75,7 +78,7 @@ describe('the reports follow the Act that governed each year', () => {
|
||||
});
|
||||
|
||||
it('keeps the age-based threshold band in both years', () => {
|
||||
for (const fy of TDS_FYS) {
|
||||
for (const fy of tdsFys()) {
|
||||
const rows = tdsInterestRowsForFy(fy);
|
||||
expect(rows.some((r) => r.band === 'SENIOR')).toBe(true);
|
||||
expect(rows.some((r) => r.band === 'GENERAL')).toBe(true);
|
||||
@@ -240,7 +243,7 @@ describe('remittance schedule', () => {
|
||||
});
|
||||
|
||||
describe('the 26Q reconciliation control', () => {
|
||||
it.each(TDS_FYS)('balances for FY %s', (fy) => {
|
||||
it.each(tdsFys())('balances for FY %s', (fy) => {
|
||||
const s = tds26qSummary(fy, TDS_AS_AT);
|
||||
// Deducted must equal deposited plus what is still within its due date.
|
||||
// Anything else means tax has gone missing between the ledger and the
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
declarationFormFor,
|
||||
formKindFor,
|
||||
fyOf,
|
||||
fyTaxConfigVersion,
|
||||
isSeniorInFy,
|
||||
isTdsApplicable,
|
||||
isWellFormedPan,
|
||||
@@ -118,7 +119,8 @@ let rowCache: TdsInterestRow[] = [];
|
||||
export function tdsInterestRowsForFy(fy: string): TdsInterestRow[] {
|
||||
// Keyed on the data set as well as the year: the rows are memoised, and a fresh load would
|
||||
// otherwise keep serving the previous customer's figures under the same financial year.
|
||||
const key = fy + '|' + tdsDeducteesVersion;
|
||||
// The tax configuration too: an administrator's save must not be hidden behind rows built on the old rates.
|
||||
const key = fy + '|' + tdsDeducteesVersion + '|' + fyTaxConfigVersion;
|
||||
if (rowCacheKey === key) {
|
||||
return rowCache;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Observable, map } from 'rxjs';
|
||||
|
||||
/** Custom Helpers */
|
||||
import { toIsoDate } from '../core/utils/api-date';
|
||||
import { configuredFys } from '../letter-mgmt/certificates/form-15gh/tds-15gh.compute';
|
||||
import { TdsDeductee } from './tds-reports-data';
|
||||
|
||||
/**
|
||||
@@ -77,14 +78,11 @@ export class TdsReportsService {
|
||||
return {};
|
||||
}
|
||||
const out: Record<string, any> = {};
|
||||
// Applied to the years the reports can show. Without an acceptance date on the record the
|
||||
// declaration is treated as operative from the start of each year rather than mid-year.
|
||||
for (const year of [
|
||||
2025,
|
||||
2026
|
||||
]) {
|
||||
const fy = `FY ${year}-${String(year + 1).slice(2)}`;
|
||||
out[fy] = { kind, acceptedOn: `${year}-04-01`, fy };
|
||||
// Applied to every configured year. Without an acceptance date on the record the declaration is
|
||||
// treated as operative from the start of each year rather than mid-year. Keyed '2026-27', the
|
||||
// form the computation looks it up by - an 'FY 2026-27' key was never found.
|
||||
for (const fy of configuredFys()) {
|
||||
out[fy] = { kind, acceptedOn: `${fy.slice(0, 4)}-04-01`, fy };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user