diff --git a/src/app/fd-interest-posting-report/fd-interest-posting-report.component.html b/src/app/fd-interest-posting-report/fd-interest-posting-report.component.html index 6bbb1aa36..16942b226 100644 --- a/src/app/fd-interest-posting-report/fd-interest-posting-report.component.html +++ b/src/app/fd-interest-posting-report/fd-interest-posting-report.component.html @@ -25,6 +25,9 @@ + @@ -227,7 +230,7 @@ {{ fdTypeShort(r.fdType) }} - {{ r.interestPostingDate }} + {{ r.interestPeriodFrom || '—' }} → {{ r.interestPostingDate }} ₹{{ r.depositAmount | number: '1.2-2' }} {{ r.interestRate | number: '1.2-2' }} {{ r.maturityDate || '—' }} @@ -250,6 +253,129 @@ } + @if (showOutsideApproval) { +
+
+
+

Fixed deposit interest posted outside this report

+
+ A deposit that matured before its own posting cycle completed has its last period settled directly, the + moment it matures — before any report here could be generated or approved for it. Listed here so + nothing paid to a customer is invisible, approved or not. +
+
+
+ Total + ₹{{ outsideApprovalTotal | number: '1.2-2' }} + {{ outsideApprovalRows.length }} posting(s) +
+
+
+ + + + +
+ @if (outsideApprovalLoading) { +
Looking for deposit interest posted outside this report…
+ } @else { +
+ + + + + + + + + + + @for (g of outsideApprovalGroups; track g.productName) { + + + + + + + } @empty { + + + + } + +
ProductSchemeDepositsInterest
+ {{ g.productName }} + + {{ fdTypeShort(g.fdType) }} + {{ g.accounts }}₹{{ g.interest | number: '1.2-2' }}
+ No fixed deposit interest was posted outside this report in this period. +
+
+ + @if (outsideApprovalRows.length > 0) { +
+ + + + + + + + + + + + + + @for (r of outsideApprovalRows; track r.transactionId) { + + + + + + + + + + } + + + + + + + + +
Account noCustomerProductSchemeInterest forInterestPosted at
{{ r.accountNo }}{{ r.clientName }}{{ r.productName }} + {{ fdTypeShort(r.fdType) }} + {{ r.interestFor }}₹{{ r.interestAmount | number: '1.2-2' }}{{ r.postedAt | date: 'dd MMM yyyy, HH:mm' }}
Total + ₹{{ outsideApprovalTotal | number: '1.2-2' }} +
+
+ } + } +
+ } +

Reports{{ postingDate ? ' for ' + postingDate : '' }}

@@ -347,6 +473,7 @@

{{ selected.officeName }} · {{ selected.productName }} — {{ selected.postingDate }} {{ fdTypeShort(selected.fdType) }} + {{ selected.postingPeriodLabel }} {{ selected.status }}

{{ fdTypeLabel(selected.fdType) }}
@@ -384,7 +511,7 @@ {{ l.accountNo }} {{ l.clientName }} - {{ l.interestPostingDate }} + {{ l.interestPeriodFrom || '—' }} → {{ l.interestPostingDate }} ₹{{ l.depositAmount | number: '1.2-2' }} {{ l.interestRate | number: '1.2-2' }} {{ l.maturityDate || '—' }} diff --git a/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts b/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts index 28d12d189..d77e345fb 100644 --- a/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts +++ b/src/app/fd-interest-posting-report/fd-interest-posting-report.component.ts @@ -18,6 +18,7 @@ import { FdInterestReport, FdInterestReportDetail, FdInterestPostingReportService, + FdOutsideApprovalRow, FdReportScope, FdType } from './fd-interest-posting-report.service'; @@ -478,6 +479,61 @@ export class FdInterestPostingReportComponent implements OnInit { ); } + // ── Outside approval: deposit interest posted without this report, chiefly at maturity ── + + showOutsideApproval = false; + outsideApprovalLoading = false; + outsideApprovalRows: FdOutsideApprovalRow[] = []; + outsideApprovalFrom = ''; + outsideApprovalTo = ''; + + openOutsideApproval(): void { + this.showOutsideApproval = true; + if (!this.outsideApprovalTo) { + this.outsideApprovalTo = this.businessDateIso(); + this.outsideApprovalFrom = `${this.outsideApprovalTo.slice(0, 7)}-01`; + } + this.loadOutsideApproval(); + } + + loadOutsideApproval(): void { + this.outsideApprovalLoading = true; + this.errorMessage = ''; + this.reportService + .outsideApproval(this.outsideApprovalFrom || undefined, this.outsideApprovalTo || undefined, undefined, this.tab) + .subscribe({ + next: (rows) => { + this.outsideApprovalRows = rows ?? []; + this.outsideApprovalLoading = false; + }, + error: (err) => { + this.outsideApprovalLoading = false; + this.outsideApprovalRows = []; + this.errorMessage = this.messageOf(err, 'Could not load deposit interest posted outside this report.'); + } + }); + } + + closeOutsideApproval(): void { + this.showOutsideApproval = false; + } + + get outsideApprovalTotal(): number { + return this.outsideApprovalRows.reduce((sum, r) => sum + Number(r.interestAmount || 0), 0); + } + + /** Totals by product and scheme, so a branch can see at a glance where its unapproved interest sits. */ + get outsideApprovalGroups(): { productName: string; fdType: string; accounts: number; interest: number }[] { + const map = new Map(); + this.outsideApprovalRows.forEach((r) => { + const g = map.get(r.productName) ?? { productName: r.productName, fdType: r.fdType, accounts: 0, interest: 0 }; + g.accounts += 1; + g.interest += Number(r.interestAmount || 0); + map.set(r.productName, g); + }); + return [...map.values()]; + } + private businessDateIso(): string { const businessDate = this.settingsService.businessDate as any; const parsed = businessDate ? new Date(businessDate) : new Date(); diff --git a/src/app/fd-interest-posting-report/fd-interest-posting-report.service.ts b/src/app/fd-interest-posting-report/fd-interest-posting-report.service.ts index 85d7a4b91..72d12f397 100644 --- a/src/app/fd-interest-posting-report/fd-interest-posting-report.service.ts +++ b/src/app/fd-interest-posting-report/fd-interest-posting-report.service.ts @@ -37,6 +37,8 @@ export interface FdInterestReport { approvedOnBehalf: boolean; postedAt: string | null; failureReason: string | null; + /** The product's posting cycle in words — Monthly, Quarterly, Half-yearly, Yearly. */ + postingPeriodLabel: string; /** Whether this user may regenerate it: their own branch, or the head office for a branch that did not open. */ canAct: boolean; blockedReason: string | null; @@ -49,6 +51,12 @@ export interface FdInterestReportLine { fdAccountId: number; accountNo: string; clientName: string; + /** + * The day after the deposit's last paid period ended, or its activation date for a first-ever + * posting — null for a line posted before this was tracked. Paired with interestPostingDate, it is + * the span the interest was earned over. + */ + interestPeriodFrom: string | null; /** * The period this interest is for, as the interest engine dated it — not the day the branch closes. * A deposit owed two unpaid periods appears on two lines, one per date. @@ -103,6 +111,28 @@ export interface FdInterestSummaryRow { approvedOnBehalf: boolean; approvedAt: string | null; postedAt: string | null; + postingPeriodLabel: string; +} + +/** + * One fixed deposit interest transaction that reached the customer's account without this report's + * approval — chiefly a deposit that matured before its own posting cycle completed, whose last period + * is settled directly by the maturity job. View only, for visibility. + */ +export interface FdOutsideApprovalRow { + transactionId: number; + fdAccountId: number; + accountNo: string; + clientName: string; + officeId: number; + officeName: string; + productId: number; + productName: string; + fdType: FdType; + interestFor: string; + interestAmount: number; + /** When the posting actually reached the account — not the period it is for. */ + postedAt: string; } export interface FdInterestPreviewRow { @@ -114,6 +144,8 @@ export interface FdInterestPreviewRow { productId: number; productName: string; fdType: FdType; + /** The day after the deposit's last paid period ended, or its activation date for the first one. */ + interestPeriodFrom: string | null; /** The period this interest belongs to, as the engine dated it. */ interestPostingDate: string; depositAmount: number | null; @@ -230,4 +262,19 @@ export class FdInterestPostingReportService { if (productId) params = params.set('productId', String(productId)); return this.http.get(`${this.base}/summary`, { params }); } + + /** Deposit interest posted without this report's approval — a deposit maturing mid-cycle, chiefly. */ + outsideApproval( + fromDate?: string, + toDate?: string, + officeId?: number, + scope?: FdReportScope + ): Observable { + let params = new HttpParams(); + if (scope) params = params.set('scope', scope); + if (fromDate) params = params.set('fromDate', fromDate); + if (toDate) params = params.set('toDate', toDate); + if (officeId) params = params.set('officeId', String(officeId)); + return this.http.get(`${this.base}/outside-approval`, { params }); + } }