Files
cbsnexora/src/app/shares/shares-account-view/apply-collection-dialog/apply-collection-dialog.component.html
T
2026-09-23 15:51:10 +05:30

422 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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="dialog-wrapper">
<div class="dialog-header">
<div class="header-icon">
<mat-icon>account_balance_wallet</mat-icon>
</div>
<div class="header-text">
<h2>Apply Share Collection</h2>
<p>
Share account <strong class="mono">{{ data.accountNo }}</strong>
</p>
</div>
<button type="button" mat-icon-button class="header-close" [mat-dialog-close]="false" aria-label="Close">
<mat-icon>close</mat-icon>
</button>
</div>
<mat-dialog-content class="dialog-body">
@if (loading) {
<div class="state-block">
<mat-spinner diameter="30"></mat-spinner>
<span>Loading receipts…</span>
</div>
} @else {
@if (requirement) {
<div class="sc-card">
<!-- Share row -->
<div
class="sc-row"
[class.sc-ok]="shareReceipt && shareShortfall === 0"
[class.sc-bad]="shareReceipt && shareShortfall > 0"
>
<div class="sc-left">
<span class="sc-label">Share Collection</span>
<span class="sc-sub"
>Required: <b>{{ requirement.shareAmount | number: '1.2-2' }}</b></span
>
</div>
<div class="sc-right">
<span
class="sc-val"
[class.sc-val-ok]="shareReceipt && shareShortfall === 0"
[class.sc-val-bad]="shareReceipt && shareShortfall > 0"
>
{{ shareReceipt ? (shareReceipt.amount | number: '1.2-2') : '—' }}
</span>
@if (shareShortfall > 0) {
<span class="sc-tag sc-tag-bad">-{{ shareShortfall | number: '1.2-2' }}</span>
} @else if (shareReceipt && shareShortfall === 0) {
<span class="sc-tag sc-tag-ok">✓ Covered</span>
}
</div>
</div>
<!-- Entry Fee row -->
@if (hasFee) {
<div
class="sc-row sc-sep"
[class.sc-ok]="chargeReceipt && feeShortfall === 0"
[class.sc-bad]="chargeReceipt && feeShortfall > 0"
>
<div class="sc-left">
<span class="sc-label">Entry Fee</span>
<span class="sc-sub"
>Required: <b>{{ requirement.chargeAmount | number: '1.2-2' }}</b></span
>
</div>
<div class="sc-right">
<span
class="sc-val"
[class.sc-val-ok]="chargeReceipt && feeShortfall === 0"
[class.sc-val-bad]="chargeReceipt && feeShortfall > 0"
>
{{ chargeReceipt ? (chargeReceipt.amount | number: '1.2-2') : '—' }}
</span>
@if (feeShortfall > 0) {
<span class="sc-tag sc-tag-bad">-{{ feeShortfall | number: '1.2-2' }}</span>
} @else if (chargeReceipt && feeShortfall === 0) {
<span class="sc-tag sc-tag-ok">✓ Covered</span>
}
</div>
</div>
}
<!-- Total row -->
<div class="sc-total-row" [class.sc-total-ok]="canApply">
<span class="sc-total-lbl">Total Payable</span>
<div class="sc-total-nums">
<span class="sc-total-req mono">{{ requirement.required | number: '1.2-2' }}</span>
<span class="sc-arrow">→</span>
<span class="sc-total-got mono" [class.sc-total-got-ok]="canApply">
{{
(shareReceipt?.amount || 0) + (hasFee && chargeReceipt ? chargeReceipt.amount || 0 : 0)
| number: '1.2-2'
}}
</span>
<span class="sc-status-pill" [class.sc-pill-ok]="canApply" [class.sc-pill-pending]="!canApply">
{{ canApply ? '✓ Ready' : 'Pending' }}
</span>
</div>
</div>
</div>
}
<!-- ── Slot 1: the share receipt ─────────────────────────────────────────── -->
<section class="slot">
<header class="slot-head">
<span class="slot-step">1</span>
<div class="slot-title">
<h3>Share Collection Receipt</h3>
<p>Posts Dr Sundry / Cr Share Capital.</p>
</div>
<button type="button" class="slot-search" (click)="openSearch('share')">
<mat-icon>search</mat-icon>
<span>Find by voucher</span>
</button>
</header>
@if (shareReceipt) {
<div class="chosen" [class.chosen-short]="shareShortfall > 0">
<div class="chosen-badge">
<mat-icon>{{ shareShortfall > 0 ? 'warning' : 'check_circle' }}</mat-icon>
</div>
<div class="chosen-main">
<span class="chosen-voucher mono">{{ shareReceipt.voucherNo }}</span>
<span class="chosen-gl">{{ glLabel(shareReceipt) }}</span>
</div>
<span class="chosen-amount mono">{{ shareReceipt.amount | number: '1.2-2' }}</span>
<button type="button" class="chosen-clear" (click)="clear('share')" aria-label="Change receipt">
<mat-icon>close</mat-icon>
</button>
</div>
@if (shareShortfall > 0) {
<p class="slot-note note-bad">
Short by {{ shareShortfall | number: '1.2-2' }} — this receipt does not cover
{{ shareRequired | number: '1.2-2' }}.
</p>
} @else if (requirement) {
<p class="slot-note note-good">Covers the {{ shareRequired | number: '1.2-2' }} payable.</p>
}
} @else {
@if (optionsFor('share').length) {
<ul class="options">
@for (option of optionsFor('share'); track option.key) {
<li class="option" [class.option-busy]="attaching === option.key" (click)="pick('share', option)">
<span class="option-radio"></span>
<div class="option-main">
<span class="option-voucher mono">{{ option.voucherNo }}</span>
<span class="option-meta">{{ option.date }} · {{ glLabel(option) }}</span>
@if (option.description) {
<span class="option-desc">{{ option.description }}</span>
}
</div>
<span class="option-amount mono">{{ option.amount | number: '1.2-2' }}</span>
@if (attaching === option.key) {
<mat-spinner diameter="16"></mat-spinner>
} @else if (option.collectionId !== null) {
<button
type="button"
class="option-info"
(click)="openDetail(option, $event)"
aria-label="GL entries"
>
<mat-icon>receipt_long</mat-icon>
</button>
}
</li>
}
</ul>
} @else {
<p class="slot-empty">
No Share Collection receipt is waiting. Collect the cash at <em>Cashier › Transaction Entry</em> with
entry type <strong>Share Collection</strong>, or find an earlier voucher by number.
</p>
}
}
</section>
<!-- ── Slot 2: the entry fee receipt ─────────────────────────────────────── -->
@if (hasFee) {
<section class="slot">
<header class="slot-head">
<span class="slot-step">2</span>
<div class="slot-title">
<h3>Entry Fee Receipt</h3>
<p>Verification only — no GL entry is posted for it.</p>
</div>
<button type="button" class="slot-search" (click)="openSearch('fee')">
<mat-icon>search</mat-icon>
<span>Find by voucher</span>
</button>
</header>
@if (chargeReceipt) {
<div class="chosen" [class.chosen-short]="feeShortfall > 0">
<div class="chosen-badge">
<mat-icon>{{ feeShortfall > 0 ? 'warning' : 'check_circle' }}</mat-icon>
</div>
<div class="chosen-main">
<span class="chosen-voucher mono">{{ chargeReceipt.voucherNo }}</span>
<span class="chosen-gl">{{ glLabel(chargeReceipt) }}</span>
</div>
<span class="chosen-amount mono">{{ chargeReceipt.amount | number: '1.2-2' }}</span>
<button type="button" class="chosen-clear" (click)="clear('fee')" aria-label="Change receipt">
<mat-icon>close</mat-icon>
</button>
</div>
@if (feeShortfall > 0) {
<p class="slot-note note-bad">
Short by {{ feeShortfall | number: '1.2-2' }} — the fee is
{{ requirement!.chargeAmount | number: '1.2-2' }}.
</p>
} @else {
<p class="slot-note note-good">Entry fee verified.</p>
}
} @else {
@if (optionsFor('fee').length) {
<ul class="options">
@for (option of optionsFor('fee'); track option.key) {
<li class="option" [class.option-busy]="attaching === option.key" (click)="pick('fee', option)">
<span class="option-radio"></span>
<div class="option-main">
<span class="option-voucher mono">{{ option.voucherNo }}</span>
<span class="option-meta">{{ option.date }} · {{ glLabel(option) }}</span>
@if (option.description) {
<span class="option-desc">{{ option.description }}</span>
}
</div>
<span class="option-amount mono">{{ option.amount | number: '1.2-2' }}</span>
@if (attaching === option.key) {
<mat-spinner diameter="16"></mat-spinner>
} @else if (option.collectionId !== null) {
<button
type="button"
class="option-info"
(click)="openDetail(option, $event)"
aria-label="GL entries"
>
<mat-icon>receipt_long</mat-icon>
</button>
}
</li>
}
</ul>
} @else {
<p class="slot-empty">No separate fee receipt is waiting — find one by voucher number.</p>
}
<p class="slot-note note-muted">
Leave this empty if the fee was already collected inside the share receipt above.
</p>
}
</section>
}
@if (errorMsg) {
<div class="alert">
<mat-icon>error_outline</mat-icon>
<span>{{ errorMsg }}</span>
</div>
}
}
</mat-dialog-content>
<mat-dialog-actions class="dialog-actions">
<button type="button" mat-stroked-button [mat-dialog-close]="false" [disabled]="submitting">Cancel</button>
<button type="button" mat-flat-button color="primary" [disabled]="!canApply" (click)="apply()">
@if (submitting) {
<mat-spinner diameter="16"></mat-spinner>
<span>Applying…</span>
} @else {
<mat-icon>check</mat-icon>
<span>Apply Collection</span>
}
</button>
</mat-dialog-actions>
</div>
<!-- ── Find a voucher the list does not carry ───────────────────────────────── -->
@if (searchFor) {
<div class="overlay" (click)="closeSearch()">
<div class="popup" (click)="$event.stopPropagation()">
<header class="popup-head">
<h3>{{ searchFor === 'share' ? 'Find a share receipt' : 'Find an entry fee receipt' }}</h3>
<button type="button" mat-icon-button (click)="closeSearch()" aria-label="Close">
<mat-icon>close</mat-icon>
</button>
</header>
<div class="popup-body">
<label class="field">
<span>GL Account</span>
<select [(ngModel)]="searchGlAccountNo" [disabled]="loadingGlAccounts">
<option value="">{{ loadingGlAccounts ? 'Loading…' : 'Select the account it was paid into' }}</option>
@for (gl of glAccounts; track gl.id) {
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
}
</select>
</label>
<label class="field">
<span>Voucher No.</span>
<input
type="text"
[(ngModel)]="searchVoucherNo"
placeholder="CR/18092026/0004"
(keyup.enter)="runSearch()"
autocomplete="off"
/>
</label>
@if (errorMsg) {
<div class="alert">
<mat-icon>error_outline</mat-icon>
<span>{{ errorMsg }}</span>
</div>
}
</div>
<footer class="popup-foot">
<button type="button" mat-stroked-button (click)="closeSearch()">Cancel</button>
<button
type="button"
mat-flat-button
color="primary"
[disabled]="!searchVoucherNo.trim() || !searchGlAccountNo.trim() || searching"
(click)="runSearch()"
>
@if (searching) {
<mat-spinner diameter="16"></mat-spinner>
} @else {
<span>Find &amp; Use</span>
}
</button>
</footer>
</div>
</div>
}
<!-- ── A receipt's own GL entries ───────────────────────────────────────────── -->
@if (viewing) {
<div class="overlay" (click)="closeDetail()">
<div class="popup" (click)="$event.stopPropagation()">
<header class="popup-head">
<h3>
Voucher <span class="mono">{{ viewing.voucherNo }}</span>
</h3>
<button type="button" mat-icon-button (click)="closeDetail()" aria-label="Close">
<mat-icon>close</mat-icon>
</button>
</header>
<div class="popup-body">
<dl class="detail-list">
<div>
<dt>Date</dt>
<dd class="mono">{{ viewing.date }}</dd>
</div>
<div>
<dt>Amount</dt>
<dd class="mono">{{ viewing.amount | number: '1.2-2' }}</dd>
</div>
<div>
<dt>GL Account</dt>
<dd>{{ glLabel(viewing) }}</dd>
</div>
@if (viewing.cashierName) {
<div>
<dt>Collected By</dt>
<dd>{{ viewing.cashierName }}</dd>
</div>
}
@if (viewing.description) {
<div>
<dt>Description</dt>
<dd>{{ viewing.description }}</dd>
</div>
}
</dl>
<h4 class="detail-heading">Journal Entries</h4>
@if (loadingGlEntries) {
<div class="state-block"><mat-spinner diameter="22"></mat-spinner></div>
} @else if (viewingGlEntries?.length) {
<table class="gl-table">
<thead>
<tr>
<th>Account</th>
<th class="num">Debit</th>
<th class="num">Credit</th>
</tr>
</thead>
<tbody>
@for (entry of viewingGlEntries; track $index) {
<tr>
<td>
<span class="mono">{{ entry.glCode }}</span> {{ entry.accountName }}
</td>
<td class="num mono">{{ entry.drCr === 'Dr' ? (entry.amount | number: '1.2-2') : '' }}</td>
<td class="num mono">{{ entry.drCr === 'Cr' ? (entry.amount | number: '1.2-2') : '' }}</td>
</tr>
}
</tbody>
</table>
} @else {
<p class="slot-empty">No journal entries are recorded against this receipt.</p>
}
</div>
<footer class="popup-foot">
<button type="button" mat-stroked-button (click)="closeDetail()">Close</button>
</footer>
</div>
</div>
}