Compare commits
2
Commits
d90719ad8d
...
6442562df0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6442562df0 | ||
|
|
17942509de |
@@ -48,11 +48,11 @@ export class ClerkClosureRequestService {
|
||||
return this.http.get(`/clerk/closure-requests/${id}`);
|
||||
}
|
||||
|
||||
/** Preview of what the member would receive on closure. The backend dry-runs the very same
|
||||
* interest posting that closing the account will perform, then rolls it back — so the figure
|
||||
* matches the real payout to the paisa. Nothing is persisted. */
|
||||
preview(savingsAccountId: number): Observable<any> {
|
||||
return this.http.get(`/clerk/closure-requests/preview/${savingsAccountId}`);
|
||||
/** Posts every rupee of interest the member has earned up to today straight onto the account —
|
||||
* the same posting close() itself performs — and returns the resulting real balance. Not a dry
|
||||
* run: ticking "Close this account" is meant to credit the interest immediately, not estimate it. */
|
||||
postClosureInterest(savingsAccountId: number): Observable<any> {
|
||||
return this.http.post(`/clerk/closure-requests/post-interest/${savingsAccountId}`, {});
|
||||
}
|
||||
|
||||
approve(id: number): Observable<any> {
|
||||
|
||||
@@ -1804,7 +1804,7 @@
|
||||
|
||||
@if (closeAccountChecked) {
|
||||
@if (closurePreviewLoading) {
|
||||
<div class="te-aa-calc te-aa-calc--loading">Calculating interest to date…</div>
|
||||
<div class="te-aa-calc te-aa-calc--loading">Posting interest to the account…</div>
|
||||
} @else if (closurePreviewError) {
|
||||
<div class="te-aa-calc te-aa-calc--error">{{ closurePreviewError }}</div>
|
||||
} @else if (closurePreview) {
|
||||
@@ -1813,10 +1813,20 @@
|
||||
<span>Current Balance</span>
|
||||
<span>₹{{ closurePreview.balance | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="te-aa-calc__row">
|
||||
<span>Interest to date</span>
|
||||
<span>+ ₹{{ closurePreview.interest | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
@if (closurePreview.interest < 0) {
|
||||
<!-- Negative only when this run corrects interest that was over-posted earlier: the
|
||||
delta is against the stored balance, so calling it "interest" would read as the
|
||||
member being charged. The Payable figure below is the authoritative one either way. -->
|
||||
<div class="te-aa-calc__row">
|
||||
<span>Interest correction</span>
|
||||
<span>− ₹{{ -closurePreview.interest | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="te-aa-calc__row">
|
||||
<span>Interest to date</span>
|
||||
<span>+ ₹{{ closurePreview.interest | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="te-aa-calc__row te-aa-calc__row--total">
|
||||
<span>Payable on Closure</span>
|
||||
<span>₹{{ closurePreview.total | number: '1.2-2' }}</span>
|
||||
@@ -1828,8 +1838,8 @@
|
||||
</div>
|
||||
}
|
||||
<div class="te-aa-calc__note">
|
||||
Interest calculated to {{ closurePreview.asOfDate }} using the same calculation the closure itself
|
||||
performs. Cash is handed over rounded to whole rupees.
|
||||
Interest has been posted to the account as of {{ closurePreview.asOfDate }} — this is the account's real
|
||||
balance now, not an estimate. Cash is handed over rounded to whole rupees.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3746,9 +3746,11 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
this.showAccountActions = false;
|
||||
}
|
||||
|
||||
/** Ticking "Close this account" immediately previews what the member would receive — the balance
|
||||
* plus interest accrued to date — so the clerk sees the real figure before submitting. Nothing
|
||||
* is posted; the binding amount is computed by Fineract's own close command at approval time. */
|
||||
/** Ticking "Close this account" posts every rupee of interest the member has earned to date onto
|
||||
* the account for real — the same posting close() itself performs — and shows the resulting real
|
||||
* balance. This is not an estimate: the interest is credited the moment the box is ticked, exactly
|
||||
* as the user asked for ("jaise FD close/maturity ke time interest post hota tha"). Approval later
|
||||
* finds this same posting already sitting at the matching period end and adds nothing more. */
|
||||
onCloseAccountToggle(): void {
|
||||
if (!this.closeAccountChecked) {
|
||||
this.closurePreview = null;
|
||||
@@ -3761,13 +3763,13 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
this.closurePreviewLoading = true;
|
||||
this.closurePreviewError = '';
|
||||
this.closureRequestService
|
||||
.preview(accountId)
|
||||
.postClosureInterest(accountId)
|
||||
.pipe(
|
||||
catchError((err: any) => {
|
||||
this.closurePreviewError =
|
||||
err?.error?.message ??
|
||||
err?.error?.errors?.[0]?.defaultUserMessage ??
|
||||
'Could not calculate the closing amount. Please try again.';
|
||||
'Could not post interest to the account. Please try again.';
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
@@ -3864,13 +3866,25 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
if (!res) return;
|
||||
|
||||
const preview = this.closurePreview;
|
||||
// The server has now POSTED the interest and returns the account's real resulting balance.
|
||||
// That, not the popup's estimate, is what the closure pays out — so report it, and say so
|
||||
// outright if the two ever disagree rather than letting a paise drift pass unnoticed.
|
||||
const posted = res.payableAmount != null ? Number(res.payableAmount) : null;
|
||||
const rupees = (v: number) => `₹${v.toLocaleString('en-IN', { minimumFractionDigits: 2 })}`;
|
||||
const drifted = posted != null && preview != null && Math.abs(posted - preview.total) >= 0.005;
|
||||
|
||||
this.showAlert(
|
||||
'success',
|
||||
'Closure Request Submitted',
|
||||
[
|
||||
`Account ${this.accountDetails?.accountNo} has been sent to the Bank Manager for closure approval.`,
|
||||
preview
|
||||
? `Payout: ₹${preview.total.toLocaleString('en-IN', { minimumFractionDigits: 2 })} (balance ₹${preview.balance.toLocaleString('en-IN', { minimumFractionDigits: 2 })} + interest ₹${preview.interest.toLocaleString('en-IN', { minimumFractionDigits: 2 })}).`
|
||||
posted != null
|
||||
? `Interest has been posted to the account. Payout: ${rupees(posted)}.`
|
||||
: preview
|
||||
? `Payout: ${rupees(preview.total)}.`
|
||||
: '',
|
||||
drifted && preview
|
||||
? `Note: the posted figure ${rupees(posted!)} differs from the ${rupees(preview.total)} shown a moment ago — the posted figure is the one that will be paid.`
|
||||
: '',
|
||||
`Token No: ${row.tokNo?.trim()} — the cashier will hand over the cash against this token once approved.`,
|
||||
'Cash is handed over rounded to whole rupees; the paise difference goes to the Round Off Account.'
|
||||
@@ -4379,8 +4393,9 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Clerk raising a closure request: nothing moves yet, so the ordinary withdrawal gauntlet below
|
||||
// does not apply and actively rejects a valid closure —
|
||||
// · Balance: the payout is balance + interest accrued but NOT yet posted, so it is legitimately
|
||||
// MORE than today's withdrawable amount. close() posts that interest before withdrawing.
|
||||
// · Balance: the popup already posted the accrued interest onto the account before filling this
|
||||
// amount in (see onCloseAccountToggle -> postClosureInterest), so the account's own withdrawable
|
||||
// balance already covers it -- this bypass exists for the checks below it, not this one.
|
||||
// · Min balance: meaningless for an account that is being closed to zero.
|
||||
// · Withdrawal facility: a member closing their account is not making an ordinary withdrawal.
|
||||
// · Denomination: counted by the cashier at hand-over, not by the clerk here.
|
||||
|
||||
+54
-23
@@ -58,6 +58,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payout amount -- the interest is posted onto the account the moment the clerk raises the
|
||||
request (see clerk-closure-request.service#postClosureInterest), so this is the account's
|
||||
real, final balance, not an estimate. Approval re-confirms the identical figure; it does
|
||||
not recompute a different one. Shown first because it is what a manager reviews for. -->
|
||||
<div class="crv-card crv-card--approved">
|
||||
<div class="crv-section-title">
|
||||
<mat-icon>payments</mat-icon>
|
||||
Payout Amount
|
||||
</div>
|
||||
<div class="crv-detail-grid">
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Exact Closing Amount</span>
|
||||
<span class="crv-detail-value crv-amount">₹{{ request.closing_amount | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
@if (request.rounding_amount && request.rounding_amount !== 0) {
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Rounding ({{ request.rounding_amount > 0 ? 'up' : 'down' }})</span>
|
||||
<span class="crv-detail-value"
|
||||
>{{ request.rounding_amount > 0 ? '+' : '' }}₹{{ request.rounding_amount | number: '1.2-2' }}</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Cash to Hand Over</span>
|
||||
<span class="crv-detail-value crv-amount">₹{{ request.cashier_disbursed_amount | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
@if (request.voucher_no) {
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Voucher No</span>
|
||||
<span class="crv-detail-value cell-mono">{{ request.voucher_no }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Closure details -->
|
||||
<div class="crv-card">
|
||||
<div class="crv-section-title">
|
||||
@@ -77,6 +112,16 @@
|
||||
<span class="crv-detail-label">Token No</span>
|
||||
<span class="crv-detail-value cell-mono">{{ request.tok_no || '—' }}</span>
|
||||
</div>
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Interest Posted As On</span>
|
||||
<span class="crv-detail-value">{{ formatDate(request.closure_date) }}</span>
|
||||
</div>
|
||||
@if (request.txn_id) {
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Transaction Id</span>
|
||||
<span class="crv-detail-value cell-mono">{{ request.txn_id }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (request.description) {
|
||||
<div class="crv-detail-item crv-detail-item--full">
|
||||
<span class="crv-detail-label">Reason / Narration</span>
|
||||
@@ -86,36 +131,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Computed amounts (once approved) -->
|
||||
@if (request.closing_amount != null) {
|
||||
<div class="crv-card crv-card--approved">
|
||||
<!-- Cashier hand-over (once disbursed) -->
|
||||
@if (request.status === 'CLOSED' && request.disbursed_at) {
|
||||
<div class="crv-card">
|
||||
<div class="crv-section-title">
|
||||
<mat-icon>calculate</mat-icon>
|
||||
Computed at Approval
|
||||
<mat-icon>storefront</mat-icon>
|
||||
Handed Over By Cashier
|
||||
</div>
|
||||
<div class="crv-detail-grid">
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Exact Closing Amount</span>
|
||||
<span class="crv-detail-value crv-amount">₹{{ request.closing_amount | number: '1.2-2' }}</span>
|
||||
<span class="crv-detail-label">Cashier</span>
|
||||
<span class="crv-detail-value">{{ request.disbursed_by || '—' }}</span>
|
||||
</div>
|
||||
@if (request.rounding_amount && request.rounding_amount !== 0) {
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Rounding ({{ request.rounding_amount > 0 ? 'up' : 'down' }})</span>
|
||||
<span class="crv-detail-value"
|
||||
>{{ request.rounding_amount > 0 ? '+' : '' }}₹{{ request.rounding_amount | number: '1.2-2' }}</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Cash to Hand Over</span>
|
||||
<span class="crv-detail-value crv-amount">₹{{ request.cashier_disbursed_amount | number: '1.2-2' }}</span>
|
||||
<span class="crv-detail-label">Disbursed At</span>
|
||||
<span class="crv-detail-value">{{ formatDateTime(request.disbursed_at) }}</span>
|
||||
</div>
|
||||
@if (request.voucher_no) {
|
||||
<div class="crv-detail-item">
|
||||
<span class="crv-detail-label">Voucher No</span>
|
||||
<span class="crv-detail-value cell-mono">{{ request.voucher_no }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
+10
@@ -128,6 +128,16 @@ export class ClosureReviewDialogComponent implements OnInit {
|
||||
return n.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
formatDate(val: any): string {
|
||||
if (!val) return '—';
|
||||
try {
|
||||
const d = new Date(val);
|
||||
return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
} catch {
|
||||
return String(val);
|
||||
}
|
||||
}
|
||||
|
||||
formatDateTime(val: any): string {
|
||||
if (!val) return '—';
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user