feat: KYC document update, FIU & AML recurring alerts integration, Sponsor DD revoke workflow, and TDS reports

This commit is contained in:
2026-09-14 11:43:26 +05:30
parent 7afa9c7a50
commit 2124bb4b76
40 changed files with 3028 additions and 929 deletions
@@ -0,0 +1,91 @@
/**
* 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/.
*/
/** Angular Imports */
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, map } from 'rxjs';
/** Custom Helpers */
import { toIsoDate } from '../core/utils/api-date';
import { TdsDeductee } from './tds-reports-data';
/**
* Deposit interest for the TDS reports.
*
* The platform returns the interest it actually credited — nothing else. A deposit is the
* customer's own money arriving and a fee is a charge, so neither is income and neither is sent.
* Working the figure out here from a balance and a rate would produce a number the passbook does
* not show.
*/
@Injectable({ providedIn: 'root' })
export class TdsReportsService {
private http = inject(HttpClient);
/**
* @param financialYear the opening calendar year — 2026 means 1 Apr 2026 to 31 Mar 2027.
* Omit to let the platform use the year the business date falls in.
*/
getDepositInterest(financialYear?: number): Observable<TdsDeductee[]> {
const params = financialYear ? new HttpParams().set('financialYear', String(financialYear)) : undefined;
return this.http
.get<any[]>('/tds/deposit-interest', { params })
.pipe(map((rows) => (rows || []).map((r) => this.toDeductee(r))));
}
/**
* Shapes one platform row into what the section 194A computation expects.
*
* Dates arrive as [y, M, d] arrays and are flattened to ISO here, because the computation
* compares them as strings when deciding which financial year a credit falls in.
*/
private toDeductee(row: any): TdsDeductee {
return {
clientId: row.clientId,
customerId: row.customerId || '',
name: row.name || '',
pan: row.pan || '',
dob: toIsoDate(row.dateOfBirth),
branchCode: row.branchName || '',
// A 15G/15H on the customer's KYC record covers the year it was accepted for. The platform
// carries the kind but not the acceptance date, so the year's start stands in — a
// declaration is operative for the year it was given, which is what the computation needs.
declarations: this.declarationsOf(row),
accounts: (row.accounts || []).map((a: any) => ({
id: a.id,
accountNo: a.accountNo || '',
depositTypeCode: a.depositTypeCode || '',
accountStatus: a.accountStatus || undefined,
clientId: a.clientId ?? row.clientId,
credits: (a.credits || []).map((c: any) => ({
date: toIsoDate(c.date),
amount: Number(c.amount) || 0,
accountNo: c.accountNo || a.accountNo || ''
}))
}))
};
}
private declarationsOf(row: any): Record<string, any> {
const kind = (row.form15Type || '').trim().toUpperCase();
if (kind !== '15G' && kind !== '15H') {
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 };
}
return out;
}
}