feat(bank-transfer): convert bank transfer to maker-checker approval flow

- submitBankTransfer() now calls submit() with requestType='BANK_TRANSFER'
  instead of the immediate-execution /banktransfer endpoint
- Confirm dialog updated: 'Submit for Approval' instead of 'Execute Transfer'
- disbursement-review: isBankTransferMode getter, separate DR/CR entry tables,
  'Approve Bank Transfer' button label, correct isCollectionMode guard
- Disbursement Requests checker inbox: Type badge (Bank Transfer/Collection/
  Disbursement) column, GL column shows 'Intra-bank transfer' for BT requests
- Checker inbox tab renamed to 'Disbursement & Transfers'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-17 14:46:26 +05:30
co-authored by Claude Sonnet 4.6
parent e661161eac
commit cbb2bd7c58
7 changed files with 401 additions and 85 deletions
@@ -13,7 +13,7 @@
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v20M2 12h20" />
</svg>
{{ isCollectionMode ? 'Collection Review' : 'Disbursement Review' }}
{{ isBankTransferMode ? 'Bank Transfer Review' : isCollectionMode ? 'Collection Review' : 'Disbursement Review' }}
</button>
<span class="cashier-nav__esc-hint" (click)="backToInbox()">← Back to Inbox</span>
</nav>
@@ -63,77 +63,189 @@
</div>
</div>
<!-- GL card — Source (disbursement) or Target (collection) -->
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>account_balance</mat-icon>
{{ isCollectionMode ? 'GL Credit (Target)' : 'GL Debit (Source)' }}
</div>
<div class="dri-detail-grid">
<div class="dri-detail-item">
<span class="dri-detail-label">GL Account No</span>
<span class="dri-detail-value cell-mono">{{ request.gl_account_no }}</span>
<!-- GL card — Source (disbursement) or Target (collection). Hidden for bank transfer. -->
@if (!isBankTransferMode) {
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>account_balance</mat-icon>
{{ isCollectionMode ? 'GL Credit (Target)' : 'GL Debit (Source)' }}
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">GL Account Name</span>
<span class="dri-detail-value">{{ request.gl_account_name }}</span>
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">{{ isCollectionMode ? 'CR Amount' : 'DR Amount' }}</span>
<span class="dri-detail-value dri-amount">₹{{ request.dr_amount | number: '1.2-2' }}</span>
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">Payment Mode</span>
<span class="dri-detail-value">{{ request.payment_type_name || 'Transfer' }}</span>
</div>
@if (request.description) {
<div class="dri-detail-item dri-detail-item--full">
<span class="dri-detail-label">Description</span>
<span class="dri-detail-value">{{ request.description }}</span>
<div class="dri-detail-grid">
<div class="dri-detail-item">
<span class="dri-detail-label">GL Account No</span>
<span class="dri-detail-value cell-mono">{{ request.gl_account_no }}</span>
</div>
}
<div class="dri-detail-item">
<span class="dri-detail-label">GL Account Name</span>
<span class="dri-detail-value">{{ request.gl_account_name }}</span>
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">{{ isCollectionMode ? 'CR Amount' : 'DR Amount' }}</span>
<span class="dri-detail-value dri-amount">₹{{ request.dr_amount | number: '1.2-2' }}</span>
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">Payment Mode</span>
<span class="dri-detail-value">{{ request.payment_type_name || 'Transfer' }}</span>
</div>
@if (request.description) {
<div class="dri-detail-item dri-detail-item--full">
<span class="dri-detail-label">Description</span>
<span class="dri-detail-value">{{ request.description }}</span>
</div>
}
</div>
</div>
</div>
}
<!-- Bank Transfer summary card (replaces GL card) -->
@if (isBankTransferMode) {
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>swap_horiz</mat-icon>
Bank Transfer Summary
</div>
<div class="dri-detail-grid">
<div class="dri-detail-item">
<span class="dri-detail-label">Total Amount</span>
<span class="dri-detail-value dri-amount">₹{{ request.dr_amount | number: '1.2-2' }}</span>
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">DR Accounts</span>
<span class="dri-detail-value"
>{{ drEntries.length }} account{{ drEntries.length !== 1 ? 's' : '' }}</span
>
</div>
<div class="dri-detail-item">
<span class="dri-detail-label">CR Accounts</span>
<span class="dri-detail-value"
>{{ crEntries.length }} account{{ crEntries.length !== 1 ? 's' : '' }}</span
>
</div>
@if (request.description) {
<div class="dri-detail-item dri-detail-item--full">
<span class="dri-detail-label">Description</span>
<span class="dri-detail-value">{{ request.description }}</span>
</div>
}
</div>
</div>
}
<!-- Entries table — DR savings (collection) or CR accounts (disbursement) -->
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>account_balance_wallet</mat-icon>
@if (isCollectionMode) {
Debit Entries ({{ drEntries.length }} account{{ drEntries.length !== 1 ? 's' : '' }} — collect from)
} @else {
Credit Entries ({{ crEntries.length }} account{{ crEntries.length !== 1 ? 's' : '' }})
}
</div>
<table class="dri-cr-table">
<thead>
<tr>
<th>#</th>
<th>Account No</th>
<th>Account Name</th>
<th>Type</th>
<th class="num">{{ isCollectionMode ? 'Amount (DR)' : 'Amount (CR)' }}</th>
</tr>
</thead>
<tbody>
@for (entry of isCollectionMode ? drEntries : crEntries; track entry.accountNo; let i = $index) {
<tr>
<td class="dri-cr-idx">{{ i + 1 }}</td>
<td class="cell-mono">{{ entry.accountNo }}</td>
<td>{{ entry.accountName }}</td>
<td>
<span class="dri-type-badge">{{ entry.accountType }}</span>
</td>
<td class="num dri-cr-amount">₹{{ entry.amount | number: '1.2-2' }}</td>
</tr>
@if (!isBankTransferMode) {
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>account_balance_wallet</mat-icon>
@if (isCollectionMode) {
Debit Entries ({{ drEntries.length }} account{{ drEntries.length !== 1 ? 's' : '' }} — collect from)
} @else {
Credit Entries ({{ crEntries.length }} account{{ crEntries.length !== 1 ? 's' : '' }})
}
<tr class="dri-cr-total-row">
<td colspan="4" class="dri-cr-total-label">{{ isCollectionMode ? 'Total Debit' : 'Total Credit' }}</td>
<td class="num dri-cr-total-amount">₹{{ totalCreditAmount() | number: '1.2-2' }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<table class="dri-cr-table">
<thead>
<tr>
<th>#</th>
<th>Account No</th>
<th>Account Name</th>
<th>Type</th>
<th class="num">{{ isCollectionMode ? 'Amount (DR)' : 'Amount (CR)' }}</th>
</tr>
</thead>
<tbody>
@for (entry of isCollectionMode ? drEntries : crEntries; track entry.accountNo; let i = $index) {
<tr>
<td class="dri-cr-idx">{{ i + 1 }}</td>
<td class="cell-mono">{{ entry.accountNo }}</td>
<td>{{ entry.accountName }}</td>
<td>
<span class="dri-type-badge">{{ entry.accountType }}</span>
</td>
<td class="num dri-cr-amount">₹{{ entry.amount | number: '1.2-2' }}</td>
</tr>
}
<tr class="dri-cr-total-row">
<td colspan="4" class="dri-cr-total-label">{{ isCollectionMode ? 'Total Debit' : 'Total Credit' }}</td>
<td class="num dri-cr-total-amount">₹{{ totalCreditAmount() | number: '1.2-2' }}</td>
</tr>
</tbody>
</table>
</div>
}
<!-- Bank Transfer: DR entries table -->
@if (isBankTransferMode) {
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>arrow_upward</mat-icon>
Debit Entries ({{ drEntries.length }} account{{ drEntries.length !== 1 ? 's' : '' }} — funds out)
</div>
<table class="dri-cr-table">
<thead>
<tr>
<th>#</th>
<th>Account No</th>
<th>Account Name</th>
<th>Type</th>
<th class="num">Amount (DR)</th>
</tr>
</thead>
<tbody>
@for (entry of drEntries; track entry.accountNo; let i = $index) {
<tr>
<td class="dri-cr-idx">{{ i + 1 }}</td>
<td class="cell-mono">{{ entry.accountNo }}</td>
<td>{{ entry.accountName }}</td>
<td>
<span class="dri-type-badge">{{ entry.accountType }}</span>
</td>
<td class="num dri-cr-amount">₹{{ entry.amount | number: '1.2-2' }}</td>
</tr>
}
<tr class="dri-cr-total-row">
<td colspan="4" class="dri-cr-total-label">Total Debit</td>
<td class="num dri-cr-total-amount">₹{{ totalDebitAmount() | number: '1.2-2' }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Bank Transfer: CR entries table -->
<div class="dri-card">
<div class="dri-section-title">
<mat-icon>arrow_downward</mat-icon>
Credit Entries ({{ crEntries.length }} account{{ crEntries.length !== 1 ? 's' : '' }} — funds in)
</div>
<table class="dri-cr-table">
<thead>
<tr>
<th>#</th>
<th>Account No</th>
<th>Account Name</th>
<th>Type</th>
<th class="num">Amount (CR)</th>
</tr>
</thead>
<tbody>
@for (entry of crEntries; track entry.accountNo; let i = $index) {
<tr>
<td class="dri-cr-idx">{{ i + 1 }}</td>
<td class="cell-mono">{{ entry.accountNo }}</td>
<td>{{ entry.accountName }}</td>
<td>
<span class="dri-type-badge">{{ entry.accountType }}</span>
</td>
<td class="num dri-cr-amount">₹{{ entry.amount | number: '1.2-2' }}</td>
</tr>
}
<tr class="dri-cr-total-row">
<td colspan="4" class="dri-cr-total-label">Total Credit</td>
<td class="num dri-cr-total-amount">₹{{ totalCreditAmount() | number: '1.2-2' }}</td>
</tr>
</tbody>
</table>
</div>
}
<!-- Processing indicator -->
@if (request.status === 'PROCESSING') {
@@ -204,7 +316,15 @@
@if (!showRejectForm) {
<button type="button" class="dri-btn dri-btn--approve" [disabled]="acting" (click)="approve()">
<mat-icon>check_circle</mat-icon>
{{ acting ? 'Processing…' : isCollectionMode ? 'Approve Collection' : 'Approve Disbursement' }}
{{
acting
? 'Processing…'
: isBankTransferMode
? 'Approve Bank Transfer'
: isCollectionMode
? 'Approve Collection'
: 'Approve Disbursement'
}}
</button>
<button type="button" class="dri-btn dri-btn--reject" [disabled]="acting" (click)="showRejectForm = true">
<mat-icon>cancel</mat-icon>
@@ -91,7 +91,10 @@ export class DisbursementReviewComponent implements OnInit {
if (res?.error) {
this.snackBar.open(res.error, 'Close', { duration: 5000 });
} else {
this.snackBar.open('Disbursement approved successfully.', 'Close', { duration: 3000 });
const msg = this.isBankTransferMode
? 'Bank transfer approved successfully.'
: 'Disbursement approved successfully.';
this.snackBar.open(msg, 'Close', { duration: 3000 });
this.router.navigate(['/checker-inbox-and-tasks/disbursement-requests']);
}
});
@@ -114,7 +117,12 @@ export class DisbursementReviewComponent implements OnInit {
});
}
get isBankTransferMode(): boolean {
return this.request?.request_type === 'BANK_TRANSFER';
}
get isCollectionMode(): boolean {
if (this.isBankTransferMode) return false;
return this.creditEntries.some((e) => e.dbCr === 'Dr');
}
@@ -131,6 +139,10 @@ export class DisbursementReviewComponent implements OnInit {
return entries.reduce((sum, e) => sum + (e.amount ?? 0), 0);
}
totalDebitAmount(): number {
return this.drEntries.reduce((sum, e) => sum + (e.amount ?? 0), 0);
}
statusClass(status: string): string {
const map: Record<string, string> = {
PENDING: 'dri-status--pending',