feat(clerk): add collection flow — multi-DR savings to CR GL with maker-checker

- Transaction entry: detect collection mode (N DR savings → 1 CR GL) and disbursement mode (1 DR GL → N CR savings); build dbCr-tagged payload for backend
- Disbursement review: show 'GL Credit (Target)' / 'Debit Entries' labels for collection; fix double-count by summing only DR entries for collection totals; dynamic approve button text
- My disbursement requests: add DR/CR badge column in expanded credit-entries preview
- My requests / withdrawal-requests: minor consistency updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-16 15:28:27 +05:30
co-authored by Claude Sonnet 4.6
parent efb01d5441
commit 6cccb58c04
11 changed files with 345 additions and 102 deletions
@@ -127,7 +127,12 @@
</td>
<td class="num mdr-amount">₹{{ r.dr_amount | number: '1.2-2' }}</td>
<td>
<span class="mdr-credit-badge">{{ creditCount(r) }} account{{ creditCount(r) !== 1 ? 's' : '' }}</span>
<button type="button" class="mdr-credit-badge mdr-credit-badge--btn" (click)="toggleExpand(r.id)">
{{ creditCount(r) }} account{{ creditCount(r) !== 1 ? 's' : '' }}
<mat-icon class="mdr-credit-chevron" [class.mdr-credit-chevron--open]="expandedIds.has(r.id)">
expand_more
</mat-icon>
</button>
</td>
<td>{{ r.payment_type_name || 'Transfer' }}</td>
<td>{{ r.manager_name || r.manager_username || '—' }}</td>
@@ -136,6 +141,46 @@
<span class="mdr-status" [ngClass]="statusClass(r.status)">{{ statusLabel(r.status) }}</span>
</td>
</tr>
@if (expandedIds.has(r.id)) {
<tr class="mdr-expand-row">
<td colspan="8" class="mdr-expand-cell">
<div class="mdr-credit-preview">
<table class="mdr-credit-table">
<thead>
<tr>
<th>DR/CR</th>
<th>Account No</th>
<th>Account Name</th>
<th>Type</th>
<th class="num">Amount (₹)</th>
</tr>
</thead>
<tbody>
@for (ce of getCreditEntries(r); track ce.accountNo) {
<tr>
<td>
<span
class="mdr-actype"
[style.background]="ce.dbCr === 'Dr' ? '#fee2e2' : '#dcfce7'"
[style.color]="ce.dbCr === 'Dr' ? '#991b1b' : '#166534'"
>
{{ ce.dbCr || 'CR' }}
</span>
</td>
<td class="cell-mono">{{ ce.accountNo || '—' }}</td>
<td>{{ ce.accountName || '—' }}</td>
<td>
<span class="mdr-actype">{{ ce.accountType || '—' }}</span>
</td>
<td class="num mdr-ce-amount">{{ ce.amount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
</td>
</tr>
}
@if (r.status === 'REJECTED' && r.rejection_reason) {
<tr class="mdr-reason-row">
<td colspan="8" class="mdr-reason-cell">
@@ -341,6 +341,91 @@
color: #0369a1;
font-size: 11px;
font-weight: 600;
&--btn {
border: none;
cursor: pointer;
gap: 2px;
font-family: inherit;
transition: background 0.15s;
&:hover {
background: #bae6fd;
}
}
}
.mdr-credit-chevron {
font-size: 14px;
width: 14px;
height: 14px;
transition: transform 0.2s;
&--open {
transform: rotate(180deg);
}
}
.mdr-expand-row td {
padding: 0;
background: #f0f9ff;
border-bottom: 2px solid #bae6fd;
}
.mdr-expand-cell {
padding: 0 !important;
}
.mdr-credit-preview {
padding: 10px 24px 12px 40px;
}
.mdr-credit-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
th,
td {
padding: 6px 10px;
text-align: left;
border-bottom: 1px solid #e0f2fe;
}
th {
font-size: 10px;
font-weight: 700;
color: #0369a1;
text-transform: uppercase;
letter-spacing: 0.06em;
background: #e0f2fe;
white-space: nowrap;
}
.num {
text-align: right;
}
tr:last-child td {
border-bottom: none;
}
}
.mdr-ce-amount {
font-weight: 700;
color: #0f4f3c;
}
.mdr-actype {
display: inline-block;
padding: 1px 7px;
border-radius: 10px;
background: #f1f5f9;
color: #475569;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.mdr-reason-row td {
@@ -45,6 +45,7 @@ export class MyDisbursementRequestsComponent implements OnInit {
requests: any[] = [];
totalRecords = 0;
expandedIds = new Set<number>();
pageSize = 20;
pageIndex = 0;
pageSizeOptions = [
@@ -164,15 +165,27 @@ export class MyDisbursementRequestsComponent implements OnInit {
this.router.navigate(['/clerk/dashboard']);
}
creditCount(req: any): number {
toggleExpand(id: number): void {
if (this.expandedIds.has(id)) {
this.expandedIds.delete(id);
} else {
this.expandedIds.add(id);
}
}
getCreditEntries(req: any): any[] {
try {
const arr = typeof req.credit_entries === 'string' ? JSON.parse(req.credit_entries) : req.credit_entries;
return Array.isArray(arr) ? arr.length : 0;
return Array.isArray(arr) ? arr.map((e: any) => ({ ...e, amount: parseFloat(e.amount) || 0 })) : [];
} catch {
return 0;
return [];
}
}
creditCount(req: any): number {
return this.getCreditEntries(req).length;
}
statusClass(status: string): string {
const map: Record<string, string> = {
PENDING: 'mdr-status--pending',