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:
co-authored by
Claude Sonnet 4.6
parent
e661161eac
commit
cbb2bd7c58
@@ -45,4 +45,8 @@ export class ClerkDisbursementService {
|
||||
reject(id: number, reason: string): Observable<any> {
|
||||
return this.http.post(`/clerk/disbursement-requests/${id}/reject`, { reason });
|
||||
}
|
||||
|
||||
bankTransfer(data: any): Observable<any> {
|
||||
return this.http.post('/clerk/disbursement-requests/banktransfer', data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,12 @@
|
||||
class="ws-f__ctl"
|
||||
[(ngModel)]="row.acTyp"
|
||||
(click)="$event.stopPropagation()"
|
||||
(change)="row.acTyp === 'GL' && row.typ !== 'Disbursement' && (row.dbCr = 'Cr')"
|
||||
(change)="
|
||||
row.acTyp === 'GL' &&
|
||||
row.typ !== 'Disbursement' &&
|
||||
row.typ !== 'Enter Bank Transfer' &&
|
||||
(row.dbCr = 'Cr')
|
||||
"
|
||||
>
|
||||
@for (a of acTypOptions; track a) {
|
||||
<option [value]="a">{{ a }}</option>
|
||||
@@ -192,7 +197,9 @@
|
||||
type="button"
|
||||
class="ws-seg__btn ws-seg__btn--dr"
|
||||
[class.ws-seg__btn--active-dr]="row.dbCr === 'Dr'"
|
||||
[disabled]="row.acTyp === 'GL' && row.typ !== 'Disbursement'"
|
||||
[disabled]="
|
||||
row.acTyp === 'GL' && row.typ !== 'Disbursement' && row.typ !== 'Enter Bank Transfer'
|
||||
"
|
||||
(click)="row.dbCr = 'Dr'"
|
||||
>
|
||||
DR
|
||||
@@ -1194,7 +1201,12 @@
|
||||
<select
|
||||
class="ws-f__ctl"
|
||||
[(ngModel)]="row.acTyp"
|
||||
(change)="row.acTyp === 'GL' && row.typ !== 'Disbursement' && (row.dbCr = 'Cr')"
|
||||
(change)="
|
||||
row.acTyp === 'GL' &&
|
||||
row.typ !== 'Disbursement' &&
|
||||
row.typ !== 'Enter Bank Transfer' &&
|
||||
(row.dbCr = 'Cr')
|
||||
"
|
||||
>
|
||||
@for (a of acTypOptions; track a) {
|
||||
<option [value]="a">{{ a }}</option>
|
||||
@@ -1232,7 +1244,9 @@
|
||||
type="button"
|
||||
class="ws-seg__btn ws-seg__btn--dr"
|
||||
[class.ws-seg__btn--active-dr]="row.dbCr === 'Dr'"
|
||||
[disabled]="row.acTyp === 'GL'"
|
||||
[disabled]="
|
||||
row.acTyp === 'GL' && row.typ !== 'Disbursement' && row.typ !== 'Enter Bank Transfer'
|
||||
"
|
||||
(click)="$event.stopPropagation(); row.dbCr = 'Dr'"
|
||||
>
|
||||
DR
|
||||
|
||||
@@ -1065,10 +1065,10 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
|
||||
addRow(): void {
|
||||
const active = this.selectedRow;
|
||||
// Disbursement: auto-add mirror CR row (same type/txntp, opposite DR/CR)
|
||||
if (active?.typ === 'Disbursement') {
|
||||
// Disbursement/Bank Transfer: auto-add mirror CR row when active row is DR
|
||||
if ((active?.typ === 'Disbursement' || active?.typ === 'Enter Bank Transfer') && active.dbCr === 'Dr') {
|
||||
const mirror: TxnRow = {
|
||||
typ: 'Disbursement',
|
||||
typ: active.typ,
|
||||
br: active.br ?? 1,
|
||||
trTp: '',
|
||||
acTyp: 'SD',
|
||||
@@ -1125,6 +1125,10 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
if (!row.dbCr) row.dbCr = 'Dr';
|
||||
row.paymentTypeId = this.findTransferPaymentTypeId();
|
||||
break;
|
||||
case 'Enter Bank Transfer':
|
||||
if (!row.dbCr) row.dbCr = 'Dr';
|
||||
row.paymentTypeId = this.findTransferPaymentTypeId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1278,6 +1282,69 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/** Submits an intra-bank book transfer for maker-checker approval. */
|
||||
private submitBankTransfer(): void {
|
||||
const drRows = this.rows.filter((r) => r.dbCr === 'Dr');
|
||||
const crRows = this.rows.filter((r) => r.dbCr === 'Cr');
|
||||
const firstRow = drRows[0] ?? crRows[0];
|
||||
|
||||
const toEntry = (r: TxnRow, dbCr: 'Dr' | 'Cr') => ({
|
||||
dbCr,
|
||||
accountNo: r.acNo,
|
||||
accountName: this.rowAccountNames[r.acNo] ?? r.acNo,
|
||||
accountType: r.acTyp === 'CA' || r.acTyp === 'CB' ? 'CURRENT' : 'SAVINGS',
|
||||
savingsAccountId: this.rowAccountIds[r.acNo],
|
||||
amount: String(r.amount),
|
||||
description: r.description ?? null
|
||||
});
|
||||
|
||||
const allEntries = [
|
||||
...drRows.map((r) => toEntry(r, 'Dr')),
|
||||
...crRows.map((r) => toEntry(r, 'Cr'))
|
||||
];
|
||||
|
||||
const payload = {
|
||||
requestType: 'BANK_TRANSFER',
|
||||
officeId: firstRow?.br ?? 1,
|
||||
currencyCode: 'INR',
|
||||
description: firstRow?.description || undefined,
|
||||
creditEntries: JSON.stringify(allEntries)
|
||||
};
|
||||
|
||||
this.saving = true;
|
||||
this.clerkDisbursementService
|
||||
.submit(payload)
|
||||
.pipe(
|
||||
catchError((err: any) => {
|
||||
const msg =
|
||||
err?.error?.errors?.[0]?.defaultUserMessage ??
|
||||
err?.error?.message ??
|
||||
'Bank transfer submission failed. Please try again.';
|
||||
this.showAlert('error', 'Submission Failed', [msg]);
|
||||
this.saving = false;
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
.subscribe((res: any) => {
|
||||
this.saving = false;
|
||||
if (res) {
|
||||
const totalDr = drRows.reduce((s, r) => s + (r.amount ?? 0), 0);
|
||||
this.showAlert('success', 'Bank Transfer Submitted', [
|
||||
`Voucher No: ${res.voucherNo}`,
|
||||
`Total: ₹${totalDr.toLocaleString('en-IN', { minimumFractionDigits: 2 })} · DR: ${drRows.length} account(s) → CR: ${crRows.length} account(s)`,
|
||||
'Pending Manager approval. Track status in "My Disbursement Requests".'
|
||||
]);
|
||||
this.rows = [];
|
||||
this.rowAccountIds = {};
|
||||
this.rowAccountNames = {};
|
||||
this.addRow();
|
||||
this.selectedRow = this.rows[0];
|
||||
this.accountDetails = null;
|
||||
this.clientDetails = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Cashier enters a token number → look up the APPROVED withdrawal request for auto-fill. */
|
||||
onTokNoBlur(row: TxnRow): void {
|
||||
if (this.isClerk || !row.tokNo?.trim()) return;
|
||||
@@ -2217,6 +2284,80 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onSave(): void {
|
||||
// Clerk bank transfer — N DR savings/CA + N CR savings/CA, immediate execution
|
||||
if (this.isClerk && this.rows.length >= 2 && this.rows.every((r) => r.typ === 'Enter Bank Transfer')) {
|
||||
const drRows = this.rows.filter((r) => r.dbCr === 'Dr');
|
||||
const crRows = this.rows.filter((r) => r.dbCr === 'Cr');
|
||||
|
||||
if (drRows.length === 0) {
|
||||
this.showAlert('error', 'DR Row Missing', ['At least one DR row (debit account) is required.']);
|
||||
return;
|
||||
}
|
||||
if (crRows.length === 0) {
|
||||
this.showAlert('error', 'CR Row Missing', ['At least one CR row (credit account) is required.']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bank transfer: only savings (SD) or current (CA/CB) accounts allowed
|
||||
const allowedTypes = new Set([
|
||||
'SD',
|
||||
'CA',
|
||||
'CB'
|
||||
]);
|
||||
const invalidAccounts = [
|
||||
...drRows,
|
||||
...crRows
|
||||
].filter((r) => !allowedTypes.has(r.acTyp));
|
||||
if (invalidAccounts.length > 0) {
|
||||
this.showAlert('error', 'Invalid Account Type', [
|
||||
`Account type(s) not allowed in Bank Transfer: ${invalidAccounts.map((r) => r.acTyp + ' (' + r.acNo + ')').join(', ')}`,
|
||||
'Only Savings (SD) and Current (CA) accounts are permitted.',
|
||||
'GL, FD, RD, SHR accounts are not allowed — use Disbursement/Collection instead.'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
const totalDr = drRows.reduce((s, r) => s + (r.amount ?? 0), 0);
|
||||
const totalCr = crRows.reduce((s, r) => s + (r.amount ?? 0), 0);
|
||||
|
||||
if (Math.abs(totalDr - totalCr) > 0.005) {
|
||||
this.showAlert('error', 'Unbalanced Entry', [
|
||||
`DR ₹${totalDr.toLocaleString('en-IN', { minimumFractionDigits: 2 })} ≠ CR ₹${totalCr.toLocaleString('en-IN', { minimumFractionDigits: 2 })}`,
|
||||
'Total DR must equal total CR for a bank transfer.'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// All accounts must be loaded
|
||||
const unloaded = [
|
||||
...drRows,
|
||||
...crRows
|
||||
].filter((r) => !this.rowAccountIds[r.acNo]);
|
||||
if (unloaded.length > 0) {
|
||||
this.showAlert('error', 'Account Not Loaded', [
|
||||
`Please load account details for: ${unloaded.map((r) => r.acNo).join(', ')}`
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmLines = [
|
||||
`DR (${drRows.length} account${drRows.length !== 1 ? 's' : ''}):`,
|
||||
...drRows.map(
|
||||
(r) => ` [${r.acTyp}] ${r.acNo} ₹${(r.amount ?? 0).toLocaleString('en-IN', { minimumFractionDigits: 2 })}`
|
||||
),
|
||||
`CR (${crRows.length} account${crRows.length !== 1 ? 's' : ''}):`,
|
||||
...crRows.map(
|
||||
(r) => ` [${r.acTyp}] ${r.acNo} ₹${(r.amount ?? 0).toLocaleString('en-IN', { minimumFractionDigits: 2 })}`
|
||||
),
|
||||
`Total: ₹${totalDr.toLocaleString('en-IN', { minimumFractionDigits: 2 })}`,
|
||||
'Transfer requires Manager approval before execution.'
|
||||
];
|
||||
this.showConfirmAlert('info', 'Confirm Bank Transfer', confirmLines, 'Submit for Approval', () =>
|
||||
this.submitBankTransfer()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clerk multi-row disbursement — handle before single-row guard (selected row may be incomplete)
|
||||
if (this.isClerk && this.rows.length >= 2 && this.rows.every((r) => r.typ === 'Disbursement')) {
|
||||
const drRows = this.rows.filter((r) => r.dbCr === 'Dr');
|
||||
|
||||
@@ -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',
|
||||
|
||||
+34
-9
@@ -12,8 +12,10 @@
|
||||
<div class="wri-action-bar__left">
|
||||
<mat-icon class="wri-action-bar__icon">swap_horiz</mat-icon>
|
||||
<div>
|
||||
<div class="wri-action-bar__title">Disbursement Requests</div>
|
||||
<div class="wri-action-bar__sub">Review, approve or reject clerk GL disbursement requests.</div>
|
||||
<div class="wri-action-bar__title">Disbursement & Transfer Requests</div>
|
||||
<div class="wri-action-bar__sub">
|
||||
Review, approve or reject clerk disbursement, collection, and bank transfer requests.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,10 +108,10 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="wri-th-id">Voucher No</th>
|
||||
<th>GL Account</th>
|
||||
<th class="num">DR Amount</th>
|
||||
<th>Credit Entries</th>
|
||||
<th>Payment Mode</th>
|
||||
<th>Type</th>
|
||||
<th>GL Account / Details</th>
|
||||
<th class="num">Amount</th>
|
||||
<th>Entries</th>
|
||||
<th>Clerk</th>
|
||||
<th>Reviewed By</th>
|
||||
<th>Submitted At</th>
|
||||
@@ -122,14 +124,37 @@
|
||||
<tr class="wri-row" (click)="review(r)">
|
||||
<td class="cell-mono wri-voucher">{{ r.voucher_no || '—' }}</td>
|
||||
<td>
|
||||
<span class="cell-mono">{{ r.gl_account_no }}</span>
|
||||
<span class="wri-gl-name">{{ r.gl_account_name }}</span>
|
||||
<span
|
||||
class="wri-credit-badge"
|
||||
[style.background]="
|
||||
r.request_type === 'BANK_TRANSFER'
|
||||
? '#e3f2fd'
|
||||
: r.request_type === 'COLLECTION'
|
||||
? '#fce4ec'
|
||||
: '#e8f5e9'
|
||||
"
|
||||
>
|
||||
{{
|
||||
r.request_type === 'BANK_TRANSFER'
|
||||
? 'Bank Transfer'
|
||||
: r.request_type === 'COLLECTION'
|
||||
? 'Collection'
|
||||
: 'Disbursement'
|
||||
}}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (r.request_type === 'BANK_TRANSFER') {
|
||||
<span class="wri-gl-name">Intra-bank transfer</span>
|
||||
} @else {
|
||||
<span class="cell-mono">{{ r.gl_account_no }}</span>
|
||||
<span class="wri-gl-name">{{ r.gl_account_name }}</span>
|
||||
}
|
||||
</td>
|
||||
<td class="num wri-amount">₹{{ r.dr_amount | number: '1.2-2' }}</td>
|
||||
<td class="wri-credit-count">
|
||||
<span class="wri-credit-badge">{{ creditCount(r) }} account{{ creditCount(r) !== 1 ? 's' : '' }}</span>
|
||||
</td>
|
||||
<td>{{ r.payment_type_name || 'Transfer' }}</td>
|
||||
<td>{{ r.clerk_name }}</td>
|
||||
<td>{{ r.manager_name || r.manager_username || '—' }}</td>
|
||||
<td class="wri-dt">{{ formatDateTime(r.submitted_at) }}</td>
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="money-bill-wave" class="ac-tab__ic"></fa-icon>
|
||||
<span>Disbursement Requests</span>
|
||||
<span>Disbursement & Transfers</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user