- Replace TYPE dropdown options with Withdrawal Slip, Cheque, Disbursement, Enter Bank Transfer, DD - Move TXN TP. (payment type) into Classification row between BRANCH and AC. TP. - Add GL and LA account types; fix SHR endpoint to /accounts/share - Auto-search A/C NO. on 4+ digits with 400ms debounce - Auto-fill ECS A/C NO. from savingsAccountNumber after account load - Accept suffix/partial accountNo match from list API - Disable broken global /search fallback across all account types - Increase field widths: TYPE 160px, TXN TP. 160px, CHEQUE NO. 165px, TOK. NO. 120px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
376 lines
14 KiB
HTML
376 lines
14 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 ── -->
|
||
<header class="pg-head">
|
||
<div>
|
||
<div class="pg-eyebrow">CASHIER · SHARE COLLECTION</div>
|
||
<h1 class="pg-title">Share Collection</h1>
|
||
<p class="pg-sub">
|
||
Collect cash from member for share purchase — amounts are held in Sundry GL until the account is activated.
|
||
</p>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ── SEARCH BAR ── -->
|
||
@if (pageState !== 'success') {
|
||
<div class="sc-search-bar">
|
||
<div class="sc-search-bar__input-wrap">
|
||
<mat-icon class="sc-search-bar__icon">search</mat-icon>
|
||
<input
|
||
class="sc-search-bar__input"
|
||
type="text"
|
||
placeholder="Enter Share Account ID or Account No."
|
||
[(ngModel)]="accountInput"
|
||
(keyup.enter)="searchAccount()"
|
||
/>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--primary"
|
||
[disabled]="pageState === 'searching' || !accountInput.trim()"
|
||
(click)="searchAccount()"
|
||
>
|
||
<mat-icon>manage_search</mat-icon>
|
||
{{ pageState === 'searching' ? 'Searching…' : 'Search' }}
|
||
</button>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── ERROR ── -->
|
||
@if (errorMsg) {
|
||
<div class="sc-alert sc-alert--error">
|
||
<mat-icon>error_outline</mat-icon>
|
||
<span>{{ errorMsg }}</span>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── SEARCHING SPINNER ── -->
|
||
@if (pageState === 'searching') {
|
||
<div class="pg-empty">
|
||
<mat-icon class="sc-spin">hourglass_empty</mat-icon>
|
||
<p>Looking up account…</p>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── ACCOUNT LOADED ── -->
|
||
@if (pageState === 'loaded') {
|
||
<!-- Member Card -->
|
||
<div class="sc-member-card">
|
||
<div class="sc-member-card__avatar">
|
||
<mat-icon>person</mat-icon>
|
||
</div>
|
||
<div class="sc-member-card__info">
|
||
<div class="sc-member-card__name">{{ account.clientName }}</div>
|
||
<div class="sc-member-card__meta">
|
||
<span class="sc-badge sc-badge--account">{{ account.accountNo }}</span>
|
||
<span class="sc-badge sc-badge--product">{{ account.productName }}</span>
|
||
<span
|
||
class="sc-badge"
|
||
[class.sc-badge--pending]="account.status?.code?.includes('pending')"
|
||
[class.sc-badge--active]="account.status?.code?.includes('active')"
|
||
>{{ account.status?.value }}</span
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Editable Shares + Price -->
|
||
<div class="sc-member-card__shares">
|
||
<div class="sc-shares-inputs">
|
||
<div class="sc-shares-field">
|
||
<label class="sc-shares-label">No. of Shares</label>
|
||
<input
|
||
class="sc-shares-input"
|
||
type="number"
|
||
min="0"
|
||
[(ngModel)]="requestedSharesInput"
|
||
(input)="recalc()"
|
||
placeholder="0"
|
||
/>
|
||
</div>
|
||
<div class="sc-shares-sep">×</div>
|
||
<div class="sc-shares-field">
|
||
<label class="sc-shares-label">Price / Share (₹)</label>
|
||
<input
|
||
class="sc-shares-input"
|
||
type="number"
|
||
min="0"
|
||
[(ngModel)]="pricePerShare"
|
||
(input)="recalc()"
|
||
placeholder="0.00"
|
||
/>
|
||
</div>
|
||
<div class="sc-shares-sep">=</div>
|
||
<div class="sc-shares-result">
|
||
<div class="sc-shares-label">Share Amount</div>
|
||
<div class="sc-shares-total">{{ currencySymbol }}{{ shareAmount | number: '1.2-2' }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Amount Breakdown + GL Preview -->
|
||
<div class="sc-split">
|
||
<!-- LEFT: Amount Breakdown -->
|
||
<div class="pg-card sc-breakdown">
|
||
<div class="sc-breakdown__header">
|
||
<mat-icon>receipt_long</mat-icon>
|
||
<span>Amount Breakdown</span>
|
||
</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Description</th>
|
||
<th class="num">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
Share Amount
|
||
<span class="sc-dim">
|
||
({{ requestedSharesInput }} shares × {{ currencySymbol }}{{ pricePerShare | number: '1.2-2' }})
|
||
</span>
|
||
</td>
|
||
<td class="num">{{ currencySymbol }}{{ shareAmount | number: '1.2-2' }}</td>
|
||
</tr>
|
||
@for (charge of charges; track charge.id) {
|
||
<tr>
|
||
<td>{{ charge.name }}</td>
|
||
<td class="num sc-charge-amount">
|
||
<input class="sc-charge-input" type="number" min="0" [(ngModel)]="charge.amount" (input)="recalc()" />
|
||
</td>
|
||
</tr>
|
||
}
|
||
@if (charges.length === 0) {
|
||
<tr>
|
||
<td colspan="2">
|
||
<span class="sc-dim">No charges applicable</span>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="sc-total-row">
|
||
<td><strong>Total Payable</strong></td>
|
||
<td class="num sc-total-amount">
|
||
<strong>{{ currencySymbol }}{{ totalAmount | number: '1.2-2' }}</strong>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- RIGHT: GL Preview -->
|
||
<div class="pg-card sc-gl-preview">
|
||
<div class="sc-breakdown__header">
|
||
<mat-icon>account_tree</mat-icon>
|
||
<span>GL Entries Preview</span>
|
||
</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Account</th>
|
||
<th>Dr / Cr</th>
|
||
<th class="num">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="gl-row--dr">
|
||
<td class="gl-acname">Cash Vault GL</td>
|
||
<td><span class="sc-dr-badge">Dr</span></td>
|
||
<td class="num gl-amt--dr">{{ currencySymbol }}{{ totalAmount | number: '1.2-2' }}</td>
|
||
</tr>
|
||
<tr class="gl-row--cr">
|
||
<td class="gl-acname">Share Sundry GL</td>
|
||
<td><span class="sc-cr-badge">Cr</span></td>
|
||
<td class="num gl-amt--cr">{{ currencySymbol }}{{ shareAmount | number: '1.2-2' }}</td>
|
||
</tr>
|
||
@if (totalChargeAmount > 0) {
|
||
<tr class="gl-row--cr">
|
||
<td class="gl-acname">Charge Receivable GL</td>
|
||
<td><span class="sc-cr-badge">Cr</span></td>
|
||
<td class="num gl-amt--cr">{{ currencySymbol }}{{ totalChargeAmount | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
<div class="sc-gl-note">
|
||
<mat-icon>info_outline</mat-icon>
|
||
Funds move to Share GL & Income GL when account is <strong>activated</strong>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── DENOMINATION (Cash Only) ── -->
|
||
@if (isCashPayment && allDenominations.length > 0) {
|
||
<div class="pg-card sc-denom">
|
||
<div class="sc-breakdown__header">
|
||
<mat-icon>payments</mat-icon>
|
||
<span>Denomination Entry</span>
|
||
@if (denominationTotal > 0) {
|
||
<span
|
||
class="sc-denom-badge"
|
||
[class.sc-denom-badge--match]="denomMatchesTotal"
|
||
[class.sc-denom-badge--mismatch]="!denomMatchesTotal"
|
||
>
|
||
₹{{ denominationTotal | number: '1.2-2' }}
|
||
{{ denomMatchesTotal ? '✓ Matches' : '≠ Mismatch' }}
|
||
</span>
|
||
}
|
||
@if (denominationTotal === 0) {
|
||
<span class="sc-denom-required">⚠ Required for Cash</span>
|
||
}
|
||
</div>
|
||
<div class="sc-denom-body">
|
||
<table class="pg-table sc-denom-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th class="num">Count</th>
|
||
<th class="num">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (d of allDenominations; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="sc-denom-chip" [class.sc-denom-chip--coin]="d.kind === 'coin'">{{ d.label }}</span>
|
||
</td>
|
||
<td class="num">
|
||
<div class="sc-denom-stepper">
|
||
<button type="button" (click)="bumpDenom(d, -1)" [disabled]="countFor(d) === 0">−</button>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
[value]="countFor(d) || ''"
|
||
placeholder="0"
|
||
(input)="onDenomChange(d, $any($event.target).value)"
|
||
/>
|
||
<button type="button" (click)="bumpDenom(d, 1)">+</button>
|
||
</div>
|
||
</td>
|
||
<td class="num">{{ subtotalFor(d) > 0 ? (subtotalFor(d) | number: '1.2-2') : '—' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td><strong>Total</strong></td>
|
||
<td></td>
|
||
<td class="num">
|
||
<strong>₹{{ denominationTotal | number: '1.2-2' }}</strong>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
@if (!denomMatchesTotal && denominationTotal > 0) {
|
||
<div class="sc-denom-mismatch">
|
||
<mat-icon>warning</mat-icon>
|
||
Denomination ₹{{ denominationTotal | number: '1.2-2' }} must equal Total Payable ₹{{
|
||
totalAmount | number: '1.2-2'
|
||
}}.
|
||
</div>
|
||
}
|
||
@if (denominationTotal > 0) {
|
||
<div class="sc-denom-actions">
|
||
<button type="button" class="pg-btn pg-btn--ghost" (click)="clearDenominations()">
|
||
<mat-icon>clear</mat-icon> Clear
|
||
</button>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Transaction Details -->
|
||
<div class="pg-toolbar pg-toolbar--mat">
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||
<mat-label>Transaction Date *</mat-label>
|
||
<input matInput type="date" [(ngModel)]="transactionDate" />
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||
<mat-label>Payment Mode</mat-label>
|
||
<mat-select [(ngModel)]="paymentTypeId">
|
||
<mat-option [value]="null">— Cash (Default) —</mat-option>
|
||
@for (pt of paymentTypes; track pt.id) {
|
||
<mat-option [value]="pt.id">{{ pt.name }}</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="pg-note-field">
|
||
<mat-label>Note (Optional)</mat-label>
|
||
<input matInput type="text" [(ngModel)]="note" placeholder="e.g. Share purchase for 100 shares" />
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<!-- Footer Submit Bar -->
|
||
<div class="pg-total-bar sc-footer">
|
||
<div class="sc-footer__summary">
|
||
<span class="sc-footer__label">Total to Collect</span>
|
||
<span class="sc-footer__amount">{{ currencySymbol }}{{ totalAmount | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="sc-footer__actions">
|
||
<button type="button" class="pg-btn pg-btn--ghost" (click)="reset()"><mat-icon>close</mat-icon> Cancel</button>
|
||
<button type="button" class="pg-btn pg-btn--primary" [disabled]="!isValid || saving" (click)="submit()">
|
||
<mat-icon>payments</mat-icon>
|
||
{{ saving ? 'Posting…' : 'Collect & Post' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── SUCCESS ── -->
|
||
@if (pageState === 'success' && collectionResult) {
|
||
<div class="sc-success">
|
||
<div class="sc-success__icon-wrap">
|
||
<mat-icon>check_circle</mat-icon>
|
||
</div>
|
||
<h2 class="sc-success__title">Collection Posted Successfully</h2>
|
||
<p class="sc-success__sub">
|
||
Cash of
|
||
<strong>{{ collectionResult.currencyCode }} {{ collectionResult.totalAmount | number: '1.2-2' }}</strong>
|
||
collected from <strong>{{ collectionResult.clientName }}</strong> on
|
||
<strong>{{ collectionResult.collectionDate }}</strong> by <strong>{{ collectionResult.collectedBy }}</strong
|
||
>.
|
||
</p>
|
||
|
||
<!-- Transaction reference -->
|
||
<div class="sc-success__ref">
|
||
<span class="sc-success__ref-label">Transaction ID</span>
|
||
<span class="sc-success__ref-val">{{ collectionResult.transactionId }}</span>
|
||
</div>
|
||
|
||
<!-- GL entries from backend -->
|
||
<div class="sc-success__gl">
|
||
@for (entry of collectionResult.glEntries; track entry.account) {
|
||
<div
|
||
class="sc-success__gl-row"
|
||
[class.sc-success__gl-row--dr]="entry.type === 'Dr'"
|
||
[class.sc-success__gl-row--cr]="entry.type === 'Cr'"
|
||
>
|
||
<span>{{ entry.type }}. {{ entry.account }}</span>
|
||
<span>{{ entry.currency }} {{ entry.amount | number: '1.2-2' }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<p class="sc-success__note">
|
||
Funds are held in Sundry GL. They will move to Share Equity GL when the account is activated.
|
||
</p>
|
||
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="reset()">
|
||
<mat-icon>add</mat-icon> New Collection
|
||
</button>
|
||
</div>
|
||
}
|
||
</div>
|