feat(rbi-reserve,savings): Form 1/Form 9 returns UI, branch-wise interest posting reports and summary
- Reports > Interest Posting Reports: per branch and product, generate/regenerate at any time, approve; branch column, HO-on-behalf marker, posted txn per account - Reports > Interest Posting Summary (branch-wise) for the head office, with Excel export - Day Close warns and disables Close Day while the branch's interest report is unapproved - Savings product accounting: Interest On Savings (expense), Savings Interest Payable and Paid (liability rows, required) - RBI Form 1 statutory and Form 9 return screens, reserve config updates - Interest posting report GETs exempted from the response cache Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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 { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export type InterestPostingReportStatus = 'GENERATED' | 'APPROVED' | 'POSTED' | 'SUPERSEDED';
|
||||
|
||||
export interface InterestPostingReport {
|
||||
id: number;
|
||||
productId: number;
|
||||
productName: string;
|
||||
officeId: number;
|
||||
officeName: string;
|
||||
postingDate: string;
|
||||
status: InterestPostingReportStatus;
|
||||
totalInterest: number;
|
||||
accountCount: number;
|
||||
generationMode: 'AUTO' | 'MANUAL';
|
||||
generatedAt: string;
|
||||
generatedBy: string | null;
|
||||
approvedAt: string | null;
|
||||
approvedBy: string | null;
|
||||
approvedOnBehalf: boolean;
|
||||
postedAt: string | null;
|
||||
failureReason: string | null;
|
||||
/** Whether the signed-in user may regenerate or approve it: their own branch, or the head office for a branch that did not open. */
|
||||
canAct: boolean;
|
||||
}
|
||||
|
||||
export interface InterestPostingReportLine {
|
||||
savingsAccountId: number;
|
||||
accountNo: string;
|
||||
clientName: string;
|
||||
interestAmount: number;
|
||||
balanceBefore: number;
|
||||
balanceAfter: number;
|
||||
savingsTransactionId: number | null;
|
||||
}
|
||||
|
||||
export interface InterestPostingReportDetail extends InterestPostingReport {
|
||||
payableGlCode: string | null;
|
||||
payableGlName: string | null;
|
||||
paidGlCode: string | null;
|
||||
paidGlName: string | null;
|
||||
expenseGlCode: string | null;
|
||||
expenseGlName: string | null;
|
||||
lines: InterestPostingReportLine[];
|
||||
}
|
||||
|
||||
export interface InterestPostingDue {
|
||||
productId: number;
|
||||
productName: string;
|
||||
officeId: number;
|
||||
officeName: string;
|
||||
postingDate: string;
|
||||
}
|
||||
|
||||
export interface InterestPostingSummaryRow {
|
||||
id: number;
|
||||
postingDate: string;
|
||||
officeId: number;
|
||||
officeName: string;
|
||||
productId: number;
|
||||
productName: string;
|
||||
accountCount: number;
|
||||
totalInterest: number;
|
||||
status: InterestPostingReportStatus;
|
||||
approvedBy: string | null;
|
||||
approvedOnBehalf: boolean;
|
||||
approvedAt: string | null;
|
||||
postedAt: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The savings interest posting reports, one per branch and product: generated at noon on a posting
|
||||
* date, approved by the branch, posted when the branch closes the day.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class InterestPostingReportService {
|
||||
private http = inject(HttpClient);
|
||||
private readonly base = '/savingsinterestpostingreports';
|
||||
|
||||
list(postingDate?: string, status?: string, officeId?: number): Observable<InterestPostingReport[]> {
|
||||
let params = new HttpParams();
|
||||
if (postingDate) params = params.set('postingDate', postingDate);
|
||||
if (status) params = params.set('status', status);
|
||||
if (officeId) params = params.set('officeId', String(officeId));
|
||||
return this.http.get<InterestPostingReport[]>(this.base, { params });
|
||||
}
|
||||
|
||||
/** Branch and product pairs with interest due by the date and no report yet, that this user may generate. */
|
||||
dueWithoutReport(postingDate?: string): Observable<InterestPostingDue[]> {
|
||||
let params = new HttpParams();
|
||||
if (postingDate) params = params.set('postingDate', postingDate);
|
||||
return this.http.get<InterestPostingDue[]>(`${this.base}/due-without-report`, { params });
|
||||
}
|
||||
|
||||
detail(reportId: number): Observable<InterestPostingReportDetail> {
|
||||
return this.http.get<InterestPostingReportDetail>(`${this.base}/${reportId}`);
|
||||
}
|
||||
|
||||
regenerate(
|
||||
productId: number,
|
||||
officeId: number,
|
||||
postingDate: string
|
||||
): Observable<{ reportId: number; status: string }> {
|
||||
return this.http.post<{ reportId: number; status: string }>(
|
||||
this.base,
|
||||
{ productId, officeId, postingDate },
|
||||
{ params: new HttpParams().set('command', 'regenerate') }
|
||||
);
|
||||
}
|
||||
|
||||
approve(reportId: number): Observable<{ reportId: number; status: string }> {
|
||||
return this.http.post<{ reportId: number; status: string }>(
|
||||
`${this.base}/${reportId}`,
|
||||
{},
|
||||
{ params: new HttpParams().set('command', 'approve') }
|
||||
);
|
||||
}
|
||||
|
||||
/** The head office's branch-wise view over a date range. */
|
||||
summary(fromDate?: string, toDate?: string, productId?: number): Observable<InterestPostingSummaryRow[]> {
|
||||
let params = new HttpParams();
|
||||
if (fromDate) params = params.set('fromDate', fromDate);
|
||||
if (toDate) params = params.set('toDate', toDate);
|
||||
if (productId) params = params.set('productId', String(productId));
|
||||
return this.http.get<InterestPostingSummaryRow[]>(`${this.base}/summary`, { params });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user