KYC management: stage-wise notices, pagination, RD base chart
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 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 { Component, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RbiCircularsService } from '../../../rbi-circulars/rbi-circulars.service';
|
||||
|
||||
/** A reserve account as the position screen lists it. */
|
||||
export interface TransferAccount {
|
||||
glAccountId: number;
|
||||
glCode: string;
|
||||
name: string;
|
||||
balance: number;
|
||||
item: string;
|
||||
head: string;
|
||||
}
|
||||
|
||||
/** What the dialog is opened with: the account clicked, and everything it may move to. */
|
||||
export interface ReserveTransferData {
|
||||
from: TransferAccount;
|
||||
accounts: TransferAccount[];
|
||||
/** The office the entry posts against, and the date it posts on. */
|
||||
officeId: number;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves money between the accounts the cash reserve is held in.
|
||||
*
|
||||
* The entry posts through the platform's own journal command, so it goes to maker-checker like any
|
||||
* other entry against the ledger. That is deliberate: the amounts settle a statutory requirement,
|
||||
* and a transfer that changes whether the bank is compliant is not something one person should be
|
||||
* able to post alone.
|
||||
*
|
||||
* It records the movement; it does not make it. The money reaches the other bank when the treasury
|
||||
* desk sends the NEFT or RTGS, and this entry is the bank's own record of having done so — which is
|
||||
* why the dialog says as much before it posts.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-reserve-transfer-dialog',
|
||||
standalone: true,
|
||||
templateUrl: './reserve-transfer-dialog.component.html',
|
||||
styleUrls: ['./reserve-transfer-dialog.component.scss'],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
MatDialogModule,
|
||||
MatButtonModule,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class ReserveTransferDialogComponent {
|
||||
private dialogRef = inject(MatDialogRef<ReserveTransferDialogComponent>);
|
||||
private rbi = inject(RbiCircularsService);
|
||||
|
||||
/** The account money leaves. Fixed — the dialog is opened from its row. */
|
||||
readonly from: TransferAccount;
|
||||
/** Everything it can move to: the other reserve accounts, never itself. */
|
||||
readonly destinations: TransferAccount[];
|
||||
|
||||
toAccountId: number | null = null;
|
||||
amount: number | null = null;
|
||||
reference = '';
|
||||
comments = '';
|
||||
|
||||
submitting = false;
|
||||
error = '';
|
||||
|
||||
readonly data = inject<ReserveTransferData>(MAT_DIALOG_DATA);
|
||||
|
||||
constructor() {
|
||||
this.from = this.data.from;
|
||||
this.destinations = (this.data.accounts ?? []).filter((a) => a.glAccountId !== this.data.from.glAccountId);
|
||||
}
|
||||
|
||||
/** The account chosen to receive the money, while one is chosen. */
|
||||
get to(): TransferAccount | null {
|
||||
return this.destinations.find((a) => a.glAccountId === this.toAccountId) ?? null;
|
||||
}
|
||||
|
||||
/** What the source would hold once this posts — the figure that decides whether it may. */
|
||||
get fromAfter(): number {
|
||||
return this.from.balance - (this.amount ?? 0);
|
||||
}
|
||||
|
||||
get toAfter(): number {
|
||||
return (this.to?.balance ?? 0) + (this.amount ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the amount can actually be sent.
|
||||
*
|
||||
* An account cannot send more than it holds: the ledger would carry a negative balance on a cash
|
||||
* or bank account, which is not a position that exists.
|
||||
*/
|
||||
get overdrawn(): boolean {
|
||||
return this.amount !== null && this.amount > this.from.balance;
|
||||
}
|
||||
|
||||
get canSubmit(): boolean {
|
||||
return !this.submitting && this.toAccountId !== null && (this.amount ?? 0) > 0 && !this.overdrawn;
|
||||
}
|
||||
|
||||
money(value: number | null | undefined): string {
|
||||
if (value === null || value === undefined) {
|
||||
return '—';
|
||||
}
|
||||
return `₹${value.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts the transfer as a single journal entry.
|
||||
*
|
||||
* These are asset accounts, so the one money LEAVES is credited and the one it arrives in is
|
||||
* debited — the opposite of how the screen words it, and deliberately so: a treasury user thinks
|
||||
* "send from here, receive there", and making them think in debits and credits to move their own
|
||||
* money would be asking them to do the ledger's job.
|
||||
*/
|
||||
submit(): void {
|
||||
if (!this.canSubmit || !this.to) {
|
||||
return;
|
||||
}
|
||||
this.submitting = true;
|
||||
this.error = '';
|
||||
|
||||
// The reserve endpoint, not the manual journal entry one: that is turned off at the HTTP edge
|
||||
// on this installation. The sides are named from and to, as a desk thinks of them; the debit
|
||||
// and credit are worked out on the server, where the accounts can also be checked.
|
||||
const transfer = {
|
||||
fromGlAccountId: this.from.glAccountId,
|
||||
toGlAccountId: this.to.glAccountId,
|
||||
amount: this.amount as number,
|
||||
officeId: this.data.officeId,
|
||||
reference: this.reference || undefined,
|
||||
comments:
|
||||
this.comments || `Reserve transfer: ${this.from.glCode} ${this.from.name} to ${this.to.glCode} ${this.to.name}`
|
||||
};
|
||||
|
||||
this.rbi.transferReserve(transfer).subscribe({
|
||||
next: () => this.dialogRef.close(true),
|
||||
error: (err: any) => {
|
||||
this.submitting = false;
|
||||
// The platform's own message, not a rewritten one: it names the rule that stopped the
|
||||
// entry — a closed accounting period, a disabled account — and a generic "could not post"
|
||||
// sends the user to look for a fault that has already been explained.
|
||||
this.error =
|
||||
err?.error?.developerMessage ||
|
||||
err?.error?.defaultUserMessage ||
|
||||
err?.error?.errors?.[0]?.defaultUserMessage ||
|
||||
'The transfer could not be posted.';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user