Transaction Entry (clerk): - Route a two-row Fund Transfer of Dr SHR / Cr GL to the share-withdrawal request with settlementMode GL, so a clerk can settle a share withdrawal into any GL account instead of the member's linked savings account. - Offer GL as an account type for Demand Draft funding, and submit it as a GL debit (no client or savings/current account is involved). My Work: - Add a voucher detail dialog so a clerk can open a raised voucher from the request list and see its GL lines. Closing Cash Report: - Fix the total row so it reflects the corrected opening balance. DD Leaflet Registry and DD Inventory: - Redesign both pages onto the shared usr-* visual language: gradient header badge, status filter chips with counts, card-wrapped sticky-header table, and one primary action per row with the rest behind an overflow menu. Filtering, pagination and every action condition are unchanged. - Register faHandPaper, needed by the new Stop Payment menu item. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
217 lines
8.5 KiB
HTML
217 lines
8.5 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">
|
|
<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, read off the same rows the table below shows: narrow "My part" to
|
|
Entered or Approved and these five follow, instead of staying pinned to the combined
|
|
day total regardless of the filter. -->
|
|
<div class="mw-tiles">
|
|
<div class="mw-tile">
|
|
<div class="mw-tile__label">Received</div>
|
|
<div class="mw-tile__value mw-tile__value--cr">₹{{ 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">₹{{ paymentTotal | number: '1.2-2' }}</div>
|
|
</div>
|
|
<div class="mw-tile">
|
|
<div class="mw-tile__label">Journal</div>
|
|
<div class="mw-tile__value">₹{{ journalTotal | number: '1.2-2' }}</div>
|
|
</div>
|
|
<div class="mw-tile">
|
|
<div class="mw-tile__label">Entered</div>
|
|
<div class="mw-tile__value">{{ raisedCount }}</div>
|
|
</div>
|
|
<div class="mw-tile">
|
|
<div class="mw-tile__label">Approved</div>
|
|
<div class="mw-tile__value">{{ 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. Clicking anywhere on the row opens the full
|
|
slip -- every line, not just the first debit and first credit this table has room
|
|
to show. -->
|
|
<tr
|
|
[class]="'mw-row mw-row--' + r.myRole"
|
|
class="mw-row--clickable"
|
|
(click)="openDetail(r.voucherNo)"
|
|
[attr.aria-busy]="loadingDetailFor === r.voucherNo"
|
|
>
|
|
<td class="mw-vno">
|
|
{{ r.voucherNo }}
|
|
@if (loadingDetailFor === r.voucherNo) {
|
|
<mat-icon class="mw-vno__spinner" inline>hourglass_empty</mat-icon>
|
|
}
|
|
</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" title="Click the row to see every line">+{{ 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>
|