feat(cashier): add end-of-day report, my work, and cashier updates
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
<!--
|
||||
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="pg">
|
||||
<header class="pg-head">
|
||||
<div>
|
||||
<div class="pg-eyebrow">MY WORK</div>
|
||||
<h1 class="pg-title">My Vouchers</h1>
|
||||
<p class="pg-sub">
|
||||
Everything you entered and everything you approved — receipts, payments and journals in one list.
|
||||
</p>
|
||||
</div>
|
||||
<div class="pg-toolbar">
|
||||
<label class="pg-field">
|
||||
<span>Period</span>
|
||||
<select [(ngModel)]="period" (change)="load()">
|
||||
<option value="DAY">Day</option>
|
||||
<option value="MONTH">Month</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pg-field">
|
||||
<span>{{ period === 'DAY' ? 'Date' : 'Month of' }}</span>
|
||||
<input type="date" [(ngModel)]="anchor" (change)="load()" />
|
||||
</label>
|
||||
<button type="button" class="pg-btn pg-btn--primary" (click)="load()" [disabled]="loading">
|
||||
<mat-icon>refresh</mat-icon> Run
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@if (error) {
|
||||
<div class="pg-alert pg-alert--error">{{ error }}</div>
|
||||
}
|
||||
|
||||
@if (loading) {
|
||||
<div class="pg-empty">Loading…</div>
|
||||
}
|
||||
|
||||
@if (report && !loading) {
|
||||
<div class="mw-who">
|
||||
<strong>{{ report.displayName || report.username }}</strong>
|
||||
<span class="mw-who__sep">·</span>
|
||||
{{ report.from | date: 'dd MMM yyyy' }}
|
||||
@if (report.from !== report.to) {
|
||||
<span> to {{ report.to | date: 'dd MMM yyyy' }}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- The day at a glance. Received and Payment are the two sides a branch balances; Journal is
|
||||
everything that moved between accounts without cash crossing the counter. -->
|
||||
<div class="mw-tiles">
|
||||
<div class="mw-tile">
|
||||
<div class="mw-tile__label">Received</div>
|
||||
<div class="mw-tile__value mw-tile__value--cr">₹{{ report.receivedTotal | number: '1.2-2' }}</div>
|
||||
</div>
|
||||
<div class="mw-tile">
|
||||
<div class="mw-tile__label">Payments</div>
|
||||
<div class="mw-tile__value mw-tile__value--dr">₹{{ report.paymentTotal | number: '1.2-2' }}</div>
|
||||
</div>
|
||||
<div class="mw-tile">
|
||||
<div class="mw-tile__label">Journal</div>
|
||||
<div class="mw-tile__value">₹{{ report.journalTotal | number: '1.2-2' }}</div>
|
||||
</div>
|
||||
<div class="mw-tile">
|
||||
<div class="mw-tile__label">Entered</div>
|
||||
<div class="mw-tile__value">{{ report.raisedCount }}</div>
|
||||
</div>
|
||||
<div class="mw-tile">
|
||||
<div class="mw-tile__label">Approved</div>
|
||||
<div class="mw-tile__value">{{ report.verifiedCount }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (report.count > 0) {
|
||||
<div class="mw-filters">
|
||||
<label class="pg-field">
|
||||
<span>Side</span>
|
||||
<select [(ngModel)]="sideFilter">
|
||||
<option value="">All</option>
|
||||
<option value="CR">Received</option>
|
||||
<option value="DR">Payment</option>
|
||||
<option value="JV">Journal</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="pg-field">
|
||||
<span>My part</span>
|
||||
<select [(ngModel)]="roleFilter">
|
||||
<option value="">All</option>
|
||||
<option value="RAISED">Entered</option>
|
||||
<option value="VERIFIED">Approved</option>
|
||||
<option value="RAISED_AND_VERIFIED">Entered & approved</option>
|
||||
</select>
|
||||
</label>
|
||||
@if (isFiltered) {
|
||||
<button type="button" class="pg-btn" (click)="clearFilters()"><mat-icon>clear</mat-icon> Clear</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (rows.length === 0) {
|
||||
<div class="pg-empty">
|
||||
@if (report.count === 0) {
|
||||
Nothing recorded against you for this period.
|
||||
} @else {
|
||||
No vouchers match these filters — {{ report.count }} hidden.
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="mw-count">
|
||||
Showing {{ rows.length }}
|
||||
@if (isFiltered) {
|
||||
<span>of {{ report.count }}</span>
|
||||
}
|
||||
{{ rows.length === 1 ? 'voucher' : 'vouchers' }}
|
||||
<span class="mw-count__total">Total ₹{{ filteredTotal | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- One table per part played, not one long list. Entering a request and passing somebody
|
||||
else's are different jobs answerable to different people, and each carries its own total
|
||||
so a maker's tally and a checker's can both be read off the page. -->
|
||||
@for (g of groups; track g.key) {
|
||||
<div class="pg-card mw-group" [class]="'mw-group--' + g.key">
|
||||
<div class="mw-group__head">
|
||||
<strong>{{ g.label }}</strong>
|
||||
<span class="mw-group__hint">{{ g.hint }}</span>
|
||||
<span class="mw-group__count">
|
||||
{{ g.rows.length }} {{ g.rows.length === 1 ? 'voucher' : 'vouchers' }}
|
||||
<span class="mw-group__total">₹{{ g.total | number: '1.2-2' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="pg-table-wrap">
|
||||
<table class="pg-table mw-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Voucher</th>
|
||||
<th>Side</th>
|
||||
<th>Type</th>
|
||||
<th>Debit</th>
|
||||
<th>Credit</th>
|
||||
<th class="num">Amount</th>
|
||||
<th>My part</th>
|
||||
<th>Status</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of g.rows; track r.voucherNo) {
|
||||
<!-- The left edge repeats the section's own colour, so a row still says which part it
|
||||
is when the heading has scrolled away. -->
|
||||
<tr [class]="'mw-row mw-row--' + r.myRole">
|
||||
<td class="mw-vno">{{ r.voucherNo }}</td>
|
||||
<td>
|
||||
<span class="mw-badge" [class]="'mw-badge--' + (r.side || 'JV')">{{ sideLabel(r.side) }}</span>
|
||||
</td>
|
||||
<td class="mw-type">{{ r.voucherType }}</td>
|
||||
<td>
|
||||
{{ r.debitParty || '—' }}
|
||||
@if (r.debitAccount) {
|
||||
<span class="mw-acct">{{ r.debitAccount }}</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{{ r.creditParty || '—' }}
|
||||
@if (r.creditAccount) {
|
||||
<span class="mw-acct">{{ r.creditAccount }}</span>
|
||||
}
|
||||
@if (r.extraLines > 0) {
|
||||
<span class="mw-more">+{{ r.extraLines }} more</span>
|
||||
}
|
||||
</td>
|
||||
<td class="num">₹{{ r.amount | number: '1.2-2' }}</td>
|
||||
<td>
|
||||
<span class="mw-part" [class]="'mw-part--' + r.myRole">
|
||||
<mat-icon class="mw-part__icon">{{ roleIcon(r.myRole) }}</mat-icon>
|
||||
{{ roleLabel(r.myRole) }}
|
||||
</span>
|
||||
<!-- Whose entry it was, when it was not this user's — that is the whole point of
|
||||
an approval row appearing here at all. -->
|
||||
@if (r.myRole === 'VERIFIED' && r.raisedBy) {
|
||||
<span class="mw-acct">by {{ r.raisedBy }}</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<span class="mw-status" [class]="'mw-status--' + statusClass(r.verificationStatus)">
|
||||
{{ statusLabel(r.verificationStatus) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="mw-time">{{ r.createdAt | date: 'HH:mm' }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
Reference in New Issue
Block a user