238 lines
9.4 KiB
HTML
238 lines
9.4 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="pg">
|
|
<header class="pg-head vr-no-print">
|
|
<div>
|
|
<div class="pg-eyebrow">CASHIER · REPORT</div>
|
|
<h1 class="pg-title">{{ title }}</h1>
|
|
<p class="pg-sub">{{ subtitle }}</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>
|
|
@if (report?.canSeeAllUsers) {
|
|
<label class="pg-field">
|
|
<span>User</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="usernameFilter"
|
|
(keyup.enter)="load()"
|
|
placeholder="All users"
|
|
autocomplete="off"
|
|
/>
|
|
</label>
|
|
}
|
|
<button type="button" class="pg-btn pg-btn--primary" (click)="load()" [disabled]="loading">
|
|
<mat-icon>refresh</mat-icon> Run
|
|
</button>
|
|
@if (report && count > 0) {
|
|
<button type="button" class="pg-btn" (click)="print()"><mat-icon>print</mat-icon> Print</button>
|
|
}
|
|
</div>
|
|
</header>
|
|
|
|
@if (error) {
|
|
<div class="pg-alert pg-alert--error vr-no-print">{{ error }}</div>
|
|
}
|
|
|
|
@if (loading) {
|
|
<div class="pg-empty vr-no-print">Loading…</div>
|
|
}
|
|
|
|
@if (report && !loading) {
|
|
<div class="vr-period">
|
|
{{ report.from | date: 'dd MMM yyyy' }}
|
|
@if (report.from !== report.to) {
|
|
— {{ report.to | date: 'dd MMM yyyy' }}
|
|
}
|
|
· {{ count }} {{ noun }}{{ count === 1 ? '' : 's' }} · ₹{{ totalAmount | number: '1.2-2' }}
|
|
@if (report.username) {
|
|
· {{ report.username }}
|
|
}
|
|
<!-- Said plainly rather than left to be inferred from a missing filter: a cashier should know
|
|
this is their own work and not the branch's. -->
|
|
@if (!report.canSeeAllUsers) {
|
|
<span class="vr-scope-note">Your own vouchers</span>
|
|
}
|
|
<!-- The total on a cash register means something narrower than the whole voucher book, so the
|
|
screen says which one it is instead of leaving the figure ambiguous. -->
|
|
@if (cashOnly) {
|
|
<span class="vr-scope-note">Cash only</span>
|
|
}
|
|
</div>
|
|
|
|
<!-- A voucher with no CR/DR prefix belongs to neither report. Say so, so the two reports not
|
|
adding up to the day's total is explained rather than left to be puzzled over. -->
|
|
@if (unclassifiedCount > 0) {
|
|
<div class="pg-alert vr-no-print">
|
|
{{ unclassifiedCount }} voucher{{ unclassifiedCount === 1 ? '' : 's' }} in this period
|
|
{{ unclassifiedCount === 1 ? 'carries' : 'carry' }} no CR or DR prefix, so
|
|
{{ unclassifiedCount === 1 ? 'it appears' : 'they appear' }} on neither the Received nor the Payment report.
|
|
</div>
|
|
}
|
|
|
|
<!-- Which part of the total has notes and coins counted behind it. Shown on the cash register
|
|
as well as the full one: a voucher with no breakdown is still real money, so it stays in
|
|
the total, and what the drawer can be checked against is stated instead of assumed. -->
|
|
@if (uncountedCount > 0) {
|
|
<div class="pg-alert vr-no-print">
|
|
{{ uncountedCount }} of these {{ noun }}s {{ uncountedCount === 1 ? 'has' : 'have' }} no denomination breakdown
|
|
recorded, totalling ₹{{ uncountedTotal | number: '1.2-2' }}. Some registers pay out without counting notes
|
|
against the voucher, so these are still cash — ₹{{ countedTotal | number: '1.2-2' }} of the total has a
|
|
breakdown behind it.
|
|
</div>
|
|
}
|
|
|
|
<!-- Explains the blank Debit/Credit cells instead of leaving them looking like lost data. -->
|
|
@if (missingSidesCount > 0) {
|
|
<div class="pg-alert vr-no-print">
|
|
{{ missingSidesCount }} of these {{ noun }}s {{ missingSidesCount === 1 ? 'has' : 'have' }} no debit or credit
|
|
recorded. The sides are attached when a request is approved, so vouchers raised before that step was added keep
|
|
their number and amount but show no accounts. Newly raised {{ noun }}s carry both sides.
|
|
</div>
|
|
}
|
|
|
|
@if (count === 0) {
|
|
<div class="pg-empty">No {{ noun }}s were raised in this period.</div>
|
|
} @else {
|
|
<div class="vr-summaries">
|
|
<div class="pg-card">
|
|
<div class="pos-section-label">By type</div>
|
|
<table class="pg-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Type</th>
|
|
<th class="num">Count</th>
|
|
<th class="num">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (row of byType; track row.type) {
|
|
<tr>
|
|
<td>{{ row.type }}</td>
|
|
<td class="num">{{ row.count }}</td>
|
|
<td class="num">{{ row.total | number: '1.2-2' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Only worth showing to someone who can see more than one person's work. -->
|
|
@if (report.canSeeAllUsers) {
|
|
<div class="pg-card">
|
|
<div class="pos-section-label">By user</div>
|
|
<table class="pg-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th class="num">Count</th>
|
|
<th class="num">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (row of byUser; track row.username) {
|
|
<tr>
|
|
<td>{{ row.username }}</td>
|
|
<td class="num">{{ row.count }}</td>
|
|
<td class="num">{{ row.total | number: '1.2-2' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="pg-card">
|
|
<div class="pos-section-label">{{ isReceived ? 'Receipts' : 'Payments' }}</div>
|
|
<table class="pg-table vr-rows">
|
|
<thead>
|
|
<tr>
|
|
<th>Voucher No</th>
|
|
<th>Type</th>
|
|
<th>Debit</th>
|
|
<th>Credit</th>
|
|
<th class="num">Amount</th>
|
|
<th>User</th>
|
|
<!-- Two dates, not one, on the system register.
|
|
A voucher's timestamp is the minute it was written; the posting behind it carries
|
|
the business date. A nightly run finishing after midnight writes today's voucher
|
|
for yesterday's work, so a reader opening this screen today is looking at last
|
|
night's postings — and without the ledger date they would take the timestamp for
|
|
the day the money moved. -->
|
|
@if (systemOnly) {
|
|
<th>Ledger date</th>
|
|
}
|
|
<th>Time</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (row of rows; track row.voucherNo) {
|
|
<tr>
|
|
<td>
|
|
{{ row.voucherNo }}
|
|
@if (row.verificationStatus === 'EXCEPTION') {
|
|
<mat-icon class="vr-flag" title="An exception was raised against this voucher"
|
|
>error_outline</mat-icon
|
|
>
|
|
}
|
|
</td>
|
|
<td>{{ row.voucherType || '—' }}</td>
|
|
<!-- A voucher raised before its sides were recorded has neither. Say that once,
|
|
across both cells, rather than showing two dashes that read as "nowhere". -->
|
|
@if (!hasSides(row)) {
|
|
<td colspan="2" class="vr-nosides">Sides not recorded</td>
|
|
} @else {
|
|
<td>
|
|
<div class="vr-party">{{ row.debitParty || '—' }}</div>
|
|
@if (row.debitAccount) {
|
|
<div class="vr-acct">{{ row.debitAccount }}</div>
|
|
}
|
|
</td>
|
|
<td>
|
|
<div class="vr-party">{{ row.creditParty || '—' }}</div>
|
|
@if (row.creditAccount) {
|
|
<div class="vr-acct">{{ row.creditAccount }}</div>
|
|
}
|
|
<!-- A DD with charges, or a compound journal, has sides this row does not show. -->
|
|
@if (row.extraLines > 0) {
|
|
<div class="vr-more">+{{ row.extraLines }} more on voucher</div>
|
|
}
|
|
</td>
|
|
}
|
|
<td class="num">{{ row.amount | number: '1.2-2' }}</td>
|
|
<td>{{ row.username || '—' }}</td>
|
|
@if (systemOnly) {
|
|
<td class="vr-ledgerdate" [class.vr-ledgerdate--prior]="row.postedAfterClose">
|
|
{{ row.ledgerDate ? (row.ledgerDate | date: 'dd MMM yyyy') : '—' }}
|
|
@if (row.postedAfterClose) {
|
|
<span class="vr-priorflag" title="Posted after that day had closed">after close</span>
|
|
}
|
|
</td>
|
|
}
|
|
<td>{{ row.createdAt | date: 'dd MMM, HH:mm' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|