181 lines
5.9 KiB
TypeScript
181 lines
5.9 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 { Injectable, inject } from '@angular/core';
|
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
import { Observable } from 'rxjs';
|
|
|
|
export type InterestPostingReportStatus = 'GENERATED' | 'APPROVED' | 'POSTED' | 'SUPERSEDED';
|
|
|
|
/** Which branches a list covers: the user's own, or (head office only) every other branch. */
|
|
export type ReportScope = 'own' | 'others';
|
|
|
|
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;
|
|
/** Why not, where canAct is false. */
|
|
blockedReason: string | null;
|
|
}
|
|
|
|
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;
|
|
canAct: boolean;
|
|
blockedReason: string | null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
export interface InterestPreviewRow {
|
|
officeId: number;
|
|
officeName: string;
|
|
productId: number;
|
|
productName: string;
|
|
savingsAccountId: number;
|
|
accountNo: string;
|
|
clientName: string;
|
|
balance: number;
|
|
rate: number;
|
|
cycleStart: string;
|
|
postingDate: string;
|
|
asOf: string;
|
|
interest: number;
|
|
}
|
|
|
|
/**
|
|
* 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';
|
|
|
|
/** scope 'own' is the user's own branch; 'others' (head office only) every other branch. */
|
|
list(
|
|
postingDate?: string,
|
|
status?: string,
|
|
officeId?: number,
|
|
scope?: ReportScope
|
|
): Observable<InterestPostingReport[]> {
|
|
let params = new HttpParams();
|
|
if (scope) params = params.set('scope', scope);
|
|
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, scope?: ReportScope): Observable<InterestPostingDue[]> {
|
|
let params = new HttpParams();
|
|
if (scope) params = params.set('scope', scope);
|
|
if (postingDate) params = params.set('postingDate', postingDate);
|
|
return this.http.get<InterestPostingDue[]>(`${this.base}/due-without-report`, { params });
|
|
}
|
|
|
|
/** The signed-in user's branch, and whether it is the head office. */
|
|
context(): Observable<{ officeId: number; officeName: string; headOffice: boolean }> {
|
|
return this.http.get<{ officeId: number; officeName: string; headOffice: boolean }>(`${this.base}/context`);
|
|
}
|
|
|
|
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') }
|
|
);
|
|
}
|
|
|
|
/** Interest each account has earned so far this cycle, as if posted today. View only. */
|
|
preview(officeId?: number): Observable<InterestPreviewRow[]> {
|
|
let params = new HttpParams();
|
|
if (officeId) params = params.set('officeId', String(officeId));
|
|
return this.http.get<InterestPreviewRow[]>(`${this.base}/preview`, { params });
|
|
}
|
|
|
|
/** 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 });
|
|
}
|
|
}
|