feat(shares): route dividend cash claims through cashier token disbursement
Cash claims now require a token at claim submission; a cashier redeems it at Transaction Entry to record physical hand-over, mirroring the existing clerk-withdrawal-request flow -- no double-disbursement, and the manager never hands over cash directly at approval time. Also: dividend-claim tokens fall back through the same TOK. NO. lookup used for withdrawal requests, silenced against the global error interceptor since the caller already handles every outcome; the account summary card now shows "Dividend Claim" instead of a misleading "A/c Savings" label when resolving one; and cash amounts are floored to whole rupees client-side to match cash's whole-rupee denominations. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,9 @@
|
||||
*/
|
||||
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpContext } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SILENT_HTTP_ERROR } from '../core/http/silent-error.token';
|
||||
|
||||
/**
|
||||
* HTTP service for the maker-checker "claim my dividend early" workflow — a clerk submits a claim
|
||||
@@ -27,6 +28,7 @@ export class DividendClaimRequestService {
|
||||
savingsAccountId?: number;
|
||||
savingsApplyAmount?: number;
|
||||
cashApplyAmount?: number;
|
||||
tokNo?: string;
|
||||
}
|
||||
): Observable<any> {
|
||||
const body: any = { lockId };
|
||||
@@ -45,9 +47,27 @@ export class DividendClaimRequestService {
|
||||
if (breakdown.cashApplyAmount != null) {
|
||||
body.cashApplyAmount = String(breakdown.cashApplyAmount);
|
||||
}
|
||||
if (breakdown.tokNo != null) {
|
||||
body.tokNo = breakdown.tokNo;
|
||||
}
|
||||
return this.http.post('/clerk/dividend-claim-requests', body);
|
||||
}
|
||||
|
||||
// Silenced: onTokNoBlur() in Transaction Entry probes this only after the withdrawal-request
|
||||
// token pool 404s, and shows its own tailored message either way.
|
||||
getByToken(token: string): Observable<any> {
|
||||
return this.http.get(`/clerk/dividend-claim-requests/by-token/${encodeURIComponent(token)}`, {
|
||||
context: new HttpContext().set(SILENT_HTTP_ERROR, true)
|
||||
});
|
||||
}
|
||||
|
||||
disburse(id: number, denominationData: string | null, transactionAmount?: number): Observable<any> {
|
||||
return this.http.post(`/clerk/dividend-claim-requests/${id}/disburse`, {
|
||||
denominationData,
|
||||
transactionAmount: transactionAmount != null ? String(transactionAmount) : null
|
||||
});
|
||||
}
|
||||
|
||||
getOverdueLoans(clientId: number): Observable<any> {
|
||||
return this.http.get(`/clerk/dividend-claim-requests/overdue-loans/${clientId}`);
|
||||
}
|
||||
|
||||
@@ -283,6 +283,12 @@
|
||||
<span>Cash</span>
|
||||
<input type="number" [(ngModel)]="claimCashAmount[l.lockId]" min="0" step="0.01" />
|
||||
</label>
|
||||
@if (claimCashAmount[l.lockId] > 0) {
|
||||
<label class="inv-form-field">
|
||||
<span>Token No. (for cashier hand-over)</span>
|
||||
<input type="text" [(ngModel)]="claimTokNo[l.lockId]" />
|
||||
</label>
|
||||
}
|
||||
<div class="dd-apply-panel-total">
|
||||
Total: {{ claimTotal(l) | number: '1.2-2' }} / {{ l.amount | number: '1.2-2' }}
|
||||
</div>
|
||||
@@ -353,7 +359,12 @@
|
||||
}}
|
||||
</td>
|
||||
<td class="num">{{ req.savings_apply_amount > 0 ? (req.savings_apply_amount | number: '1.2-2') : '—' }}</td>
|
||||
<td class="num">{{ req.cash_apply_amount > 0 ? (req.cash_apply_amount | number: '1.2-2') : '—' }}</td>
|
||||
<td class="num">
|
||||
{{ req.cash_apply_amount > 0 ? (req.cash_apply_amount | number: '1.2-2') : '—' }}
|
||||
@if (req.cash_apply_amount > 0 && req.tok_no) {
|
||||
<div class="pg-empty-inline">Tok: {{ req.tok_no }}</div>
|
||||
}
|
||||
</td>
|
||||
<td class="num">{{ req.locked_amount | number: '1.2-2' }}</td>
|
||||
<td>
|
||||
@if (req.has_overdue_loan) {
|
||||
|
||||
@@ -68,6 +68,7 @@ export class DividendDashboardComponent implements OnInit {
|
||||
claimSavingsAmount: Record<number, number | null> = {};
|
||||
claimSavingsAccountOverride: Record<number, number | null> = {};
|
||||
claimCashAmount: Record<number, number | null> = {};
|
||||
claimTokNo: Record<number, string> = {};
|
||||
claimSubmitting = false;
|
||||
claimError: string | null = null;
|
||||
|
||||
@@ -99,6 +100,7 @@ export class DividendDashboardComponent implements OnInit {
|
||||
this.claimSavingsAmount[lock.lockId] = Number(lock.amount);
|
||||
this.claimSavingsAccountOverride[lock.lockId] = null;
|
||||
this.claimCashAmount[lock.lockId] = null;
|
||||
this.claimTokNo[lock.lockId] = '';
|
||||
this.claimRequestService
|
||||
.getOverdueLoans(lock.clientId)
|
||||
.pipe(catchError(() => of([])))
|
||||
@@ -117,14 +119,17 @@ export class DividendDashboardComponent implements OnInit {
|
||||
return (
|
||||
Number(this.claimLoanAmount[lock.lockId] || 0) +
|
||||
Number(this.claimSavingsAmount[lock.lockId] || 0) +
|
||||
Number(this.claimCashAmount[lock.lockId] || 0)
|
||||
Math.floor(Number(this.claimCashAmount[lock.lockId] || 0))
|
||||
);
|
||||
}
|
||||
|
||||
submitClaim(lock: any): void {
|
||||
const loanAmount = Number(this.claimLoanAmount[lock.lockId] || 0);
|
||||
const savingsAmount = Number(this.claimSavingsAmount[lock.lockId] || 0);
|
||||
const cashAmount = Number(this.claimCashAmount[lock.lockId] || 0);
|
||||
// Cash can only be handed over in whole-rupee notes/coins (no denomination below ₹1), while a
|
||||
// dividend share often works out to paise — floor to the nearest rupee so the denomination
|
||||
// total the cashier counts can always match exactly; any leftover paise stays unclaimed.
|
||||
const cashAmount = Math.floor(Number(this.claimCashAmount[lock.lockId] || 0));
|
||||
const total = loanAmount + savingsAmount + cashAmount;
|
||||
if (total <= 0) {
|
||||
this.claimError = 'Enter at least one amount (loan, savings, or cash), greater than zero.';
|
||||
@@ -142,6 +147,11 @@ export class DividendDashboardComponent implements OnInit {
|
||||
this.claimError = 'This share account has no linked savings account — enter a Savings Account ID to credit.';
|
||||
return;
|
||||
}
|
||||
const tokNo = this.claimTokNo[lock.lockId]?.trim();
|
||||
if (cashAmount > 0 && !tokNo) {
|
||||
this.claimError = 'Enter a Token No. for the cash portion — the cashier will use it to hand over the cash.';
|
||||
return;
|
||||
}
|
||||
this.claimSubmitting = true;
|
||||
this.claimError = null;
|
||||
this.claimRequestService
|
||||
@@ -151,7 +161,8 @@ export class DividendDashboardComponent implements OnInit {
|
||||
savingsAccountId:
|
||||
savingsAmount > 0 && !lock.savingsAccountId ? this.claimSavingsAccountOverride[lock.lockId] : undefined,
|
||||
savingsApplyAmount: savingsAmount > 0 ? savingsAmount : undefined,
|
||||
cashApplyAmount: cashAmount > 0 ? cashAmount : undefined
|
||||
cashApplyAmount: cashAmount > 0 ? cashAmount : undefined,
|
||||
tokNo: cashAmount > 0 ? tokNo : undefined
|
||||
})
|
||||
.subscribe({
|
||||
next: () => {
|
||||
|
||||
Reference in New Issue
Block a user