- Cash Transfer now raises a request ("Send for approval") instead of posting. A My Requests button in the
page header opens the caller's requests with their status, and a Cash Transfers approvals page (review
popup with the full denomination breakdown, approve, reject with a reason) lets a different manager release
the cash.
- Transaction Entry's Branch dropdown lists the installation's real offices instead of a fixed list. A login
that cannot read offices is offered only its own branch (read-only), so no "Unauthorized Request" popup.
- Approval Center header and tab bar made compact so more tabs fit on a 100% window.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
230 lines
8.0 KiB
HTML
230 lines
8.0 KiB
HTML
<!--
|
|
Copyright since 2025 Mifos Initiative
|
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
-->
|
|
|
|
<div class="cv-tab-content cta-tab">
|
|
<!-- ── TITLE BAR ── -->
|
|
<div class="cta-action-bar">
|
|
<div class="cta-action-bar__left">
|
|
<mat-icon class="cta-action-bar__icon">swap_horiz</mat-icon>
|
|
<div>
|
|
<div class="cta-action-bar__title">Cash Transfers</div>
|
|
<div class="cta-action-bar__sub">
|
|
Approve or reject an allocation of cash from the vault to a cashier. The cash leaves the vault only when it is
|
|
approved.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cta-counts">
|
|
<span class="cta-count"
|
|
><strong>{{ all.length }}</strong> waiting</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── FILTER BAR ── -->
|
|
<div class="cta-filter-bar">
|
|
<div class="cta-search-box">
|
|
<mat-icon>search</mat-icon>
|
|
<input type="text" placeholder="Search by cashier, vault, user or note" [(ngModel)]="searchTerm" />
|
|
@if (searchTerm) {
|
|
<button type="button" class="cta-search-box__clear" (click)="searchTerm = ''">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
}
|
|
</div>
|
|
<button type="button" class="cta-btn cta-btn--refresh" (click)="load()" [disabled]="loading">
|
|
<mat-icon>refresh</mat-icon>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- ── TABLE ── -->
|
|
@if (loading && !loaded) {
|
|
<div class="cta-empty"><p class="cta-empty__title">Loading…</p></div>
|
|
} @else if (rows.length === 0) {
|
|
<div class="cta-empty">
|
|
<mat-icon class="cta-empty__icon">inbox</mat-icon>
|
|
<p class="cta-empty__title">No cash transfer waiting</p>
|
|
<p class="cta-empty__text">There is no cash allocation waiting for approval.</p>
|
|
</div>
|
|
} @else {
|
|
<div class="cta-table-wrap">
|
|
<table class="cta-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>From</th>
|
|
<th>To (Cashier)</th>
|
|
<th class="num">Amount</th>
|
|
<th>Note</th>
|
|
<th>Raised By</th>
|
|
<th>Raised At</th>
|
|
<th class="cta-th-action">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (r of rows; track r.id) {
|
|
<tr class="cta-row">
|
|
<td class="cta-dt">{{ r.transfer_date || '—' }}</td>
|
|
<td>{{ r.from_party || '—' }}</td>
|
|
<td class="cta-strong">{{ r.to_party || '—' }}</td>
|
|
<td class="num cta-amount">₹{{ r.total_amount | number: '1.2-2' }}</td>
|
|
<td class="cta-reason" [title]="r.note || ''">{{ r.note || '—' }}</td>
|
|
<td>{{ r.maker_name || '—' }}</td>
|
|
<td class="cta-dt">{{ formatDateTime(r.maker_at) }}</td>
|
|
<td class="cta-action-cell">
|
|
<button type="button" class="cta-btn cta-btn--approve" (click)="review(r)">
|
|
<mat-icon>rate_review</mat-icon> Review
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- ─── Popup: review a request, every note and coin it asks for ───────────── -->
|
|
<ng-template #reviewDialog>
|
|
@if (reviewing) {
|
|
<div class="cta-dlg">
|
|
<header class="cta-dlg__head">
|
|
<mat-icon class="cta-dlg__ico cta-dlg__ico--ok">swap_horiz</mat-icon>
|
|
<div class="cta-dlg__titles">
|
|
<div class="cta-dlg__title">Review cash transfer</div>
|
|
<div class="cta-dlg__sub">
|
|
Raised by {{ reviewing.maker_name || '—' }} · {{ formatDateTime(reviewing.maker_at) }}
|
|
</div>
|
|
</div>
|
|
<button type="button" class="cta-dlg__x" aria-label="Close" (click)="closeReview()">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
</header>
|
|
|
|
<div class="cta-dlg__body">
|
|
<div class="cta-total">
|
|
<span>Amount to release</span>
|
|
<strong>₹{{ reviewing.total_amount | number: '1.2-2' }}</strong>
|
|
</div>
|
|
|
|
<div class="cta-grid">
|
|
<div class="cta-cell">
|
|
<div class="cta-label">Transfer date</div>
|
|
<div class="cta-value">{{ reviewing.transfer_date || '—' }}</div>
|
|
</div>
|
|
<div class="cta-cell">
|
|
<div class="cta-label">From</div>
|
|
<div class="cta-value">{{ reviewing.from_party || '—' }}</div>
|
|
</div>
|
|
<div class="cta-cell">
|
|
<div class="cta-label">To (cashier)</div>
|
|
<div class="cta-value">{{ reviewing.to_party || '—' }}</div>
|
|
</div>
|
|
<div class="cta-cell">
|
|
<div class="cta-label">Notes and coins</div>
|
|
<div class="cta-value">
|
|
{{ piecesOf(reviewing) }} pieces · {{ linesOf(reviewing).length }} denominations
|
|
</div>
|
|
</div>
|
|
@if (reviewing.note) {
|
|
<div class="cta-cell cta-cell--wide">
|
|
<div class="cta-label">Note</div>
|
|
<div class="cta-value">{{ reviewing.note }}</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="cta-label">Denomination breakdown</div>
|
|
<table class="cta-mini">
|
|
<thead>
|
|
<tr>
|
|
<th>Denomination</th>
|
|
<th class="num">Count</th>
|
|
<th class="num">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (l of linesOf(reviewing); track l.value) {
|
|
<tr>
|
|
<td>₹{{ l.value }}</td>
|
|
<td class="num">{{ l.count }}</td>
|
|
<td class="num">₹{{ l.amount | number: '1.2-2' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
<tfoot>
|
|
<tr>
|
|
<td>Total</td>
|
|
<td class="num">{{ piecesOf(reviewing) }}</td>
|
|
<td class="num">₹{{ reviewing.total_amount | number: '1.2-2' }}</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
|
|
<footer class="cta-dlg__foot">
|
|
<button type="button" class="cta-btn cta-btn--ghost" (click)="closeReview()">Close</button>
|
|
<button
|
|
type="button"
|
|
class="cta-btn cta-btn--reject"
|
|
[disabled]="busyId === reviewing.id"
|
|
(click)="rejectFromReview(reviewing)"
|
|
>
|
|
<mat-icon>close</mat-icon> Reject
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="cta-btn cta-btn--approve"
|
|
[disabled]="busyId === reviewing.id"
|
|
(click)="approve(reviewing)"
|
|
>
|
|
<mat-icon>check</mat-icon> Approve
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
}
|
|
</ng-template>
|
|
|
|
<!-- ─── Popup: reject with a reason ───────────────────────────────────────── -->
|
|
<ng-template #rejectDialog>
|
|
@if (rejecting) {
|
|
<div class="cta-dlg">
|
|
<header class="cta-dlg__head">
|
|
<mat-icon class="cta-dlg__ico">block</mat-icon>
|
|
<div class="cta-dlg__titles">
|
|
<div class="cta-dlg__title">Reject cash transfer</div>
|
|
<div class="cta-dlg__sub">₹{{ rejecting.total_amount | number: '1.2-2' }} to {{ rejecting.to_party }}</div>
|
|
</div>
|
|
<button type="button" class="cta-dlg__x" aria-label="Close" (click)="cancelReject()">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
</header>
|
|
<div class="cta-dlg__body">
|
|
<label class="cta-label" for="cta-reason">Reason (required)</label>
|
|
<textarea
|
|
id="cta-reason"
|
|
rows="3"
|
|
placeholder="Why is this being rejected?"
|
|
[(ngModel)]="rejectReason"
|
|
></textarea>
|
|
</div>
|
|
<footer class="cta-dlg__foot">
|
|
<button type="button" class="cta-btn cta-btn--ghost" (click)="cancelReject()">Cancel</button>
|
|
<button
|
|
type="button"
|
|
class="cta-btn cta-btn--reject-solid"
|
|
[disabled]="!rejectReason.trim() || busyId === rejecting.id"
|
|
(click)="confirmReject()"
|
|
>
|
|
Confirm reject
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
}
|
|
</ng-template>
|