Adds an Entry Type field to Transaction Entry's GL-crediting rows (Receipt / Collection), fetched from the backend's GLEntryType code, so the counter can flag what a GL credit is actually for (General / Share Collection / DD Issue) instead of leaving every GL account picker throughout Share Advance Collection guessing from the whole chart of accounts. Apply Share / Additional Share screens: - GL Account picker narrowed to accounts that actually have an eligible receipt, and only auto-selected when exactly one candidate exists -- no more defaulting to whichever account sorts first (Cash at Vault). - Voucher tables show Description and Collected By, and a Detail view exposes the receipt's real Dr/Cr GL legs instead of the GL Account dropdown's current unrelated selection. - Additional Share funds from a single voucher (radio-style selection); ticking a receipt syncs the GL Account picker to its own actual account. - Checker's Additional Share Request review dialog gets the same Description + real GL entry detail, expandable per voucher, so approval is never a guess about what will post. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
232 lines
9.2 KiB
HTML
232 lines
9.2 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/.
|
|
-->
|
|
|
|
<h2 mat-dialog-title>Apply Share Advance Collection</h2>
|
|
|
|
<mat-dialog-content>
|
|
<p class="intro">
|
|
Tick the receipts that make up this payment to settle share account <b>{{ data.accountNo }}</b
|
|
>, then apply. Today's unapplied receipts for this member are listed below — tick more than one where they paid in
|
|
instalments. The amount moves out of Share Sundry into share capital, and any entry fee is posted to fee income.
|
|
</p>
|
|
|
|
<!-- What this account needs, up front — so the counter knows the full figure, and that it may take
|
|
a second receipt for the entry fee, before a search ever comes up short. -->
|
|
@if (requirement) {
|
|
<div class="requirement-summary">
|
|
<span class="requirement-line">
|
|
Required: <b>{{ requirement.shareAmount | number: '1.2-2' }}</b> (Shares)
|
|
@if (requirement.chargeAmount > 0) {
|
|
+ <b>{{ requirement.chargeAmount | number: '1.2-2' }}</b> (Entry Fee) =
|
|
<b>{{ requirement.required | number: '1.2-2' }}</b> Total
|
|
}
|
|
</span>
|
|
@if (selectedIds.length > 0) {
|
|
<span class="requirement-progress" [class.covered]="shortfall === 0">
|
|
@if (shortfall === 0) {
|
|
Share receipts covered
|
|
} @else {
|
|
{{ selectedTotal | number: '1.2-2' }} of {{ shareRequired | number: '1.2-2' }} collected —
|
|
{{ shortfall | number: '1.2-2' }} short
|
|
}
|
|
</span>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<!-- For a receipt the list does not carry — one taken on an earlier day, or a slip the member is
|
|
holding that is quicker to type than to find. Both the GL account the deposit was posted to
|
|
and the voucher number are required — never resolved from a share product's own accounting
|
|
setup, so a broken or missing product mapping can never make a genuine voucher unreachable. -->
|
|
<div class="voucher-section">
|
|
<div class="voucher-section-label">Share Receipt</div>
|
|
<div class="gl-select-field">
|
|
<select class="gl-select" [(ngModel)]="glAccountNo" [disabled]="lookingUp || submitting || loadingGlAccounts">
|
|
<option value="" disabled>{{ loadingGlAccounts ? 'Loading GL accounts…' : 'Select GL account…' }}</option>
|
|
@for (gl of glAccounts; track gl.id) {
|
|
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="voucher-lookup">
|
|
<input
|
|
type="text"
|
|
class="voucher-input"
|
|
placeholder="Voucher no. (e.g. CR/001/2608/0001)"
|
|
[(ngModel)]="voucherNo"
|
|
[disabled]="lookingUp || submitting"
|
|
(keyup.enter)="lookupVoucher()"
|
|
/>
|
|
<button
|
|
mat-stroked-button
|
|
type="button"
|
|
[disabled]="!voucherNo.trim() || !glAccountNo.trim() || lookingUp || submitting"
|
|
(click)="lookupVoucher()"
|
|
>
|
|
{{ lookingUp ? 'Finding…' : 'Find' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shown only when this account actually has an outstanding fee -- a member sometimes pays it as
|
|
a second receipt into a different GL account rather than folding it into the share-value
|
|
deposit. This search only ever verifies that receipt exists; it never moves its money -- that
|
|
stays exactly where the cashier already deposited it, unlike the share receipts above which
|
|
genuinely move out of Sundry. -->
|
|
@if (requirement && requirement.chargeAmount > 0) {
|
|
<div class="voucher-section">
|
|
<div class="voucher-section-label">Entry Fee Receipt — verification only, no GL entry is posted for it</div>
|
|
|
|
@if (chargeReceipt) {
|
|
<div class="charge-receipt-found" [class.short]="chargeShortfall > 0">
|
|
<mat-icon>{{ chargeShortfall > 0 ? 'error_outline' : 'check_circle_outline' }}</mat-icon>
|
|
<span class="charge-receipt-detail">
|
|
<b class="mono">{{ chargeReceipt.voucher_no || chargeReceipt.receipt_no }}</b>
|
|
— {{ chargeReceipt.amount | number: '1.2-2' }}
|
|
@if (chargeShortfall > 0) {
|
|
(short by {{ chargeShortfall | number: '1.2-2' }})
|
|
} @else {
|
|
— entry fee verified
|
|
}
|
|
</span>
|
|
<button mat-button type="button" [disabled]="submitting" (click)="clearChargeReceipt()">Change</button>
|
|
</div>
|
|
} @else {
|
|
<div class="gl-select-field">
|
|
<select
|
|
class="gl-select"
|
|
[(ngModel)]="chargeGlAccountNo"
|
|
[disabled]="lookingUpCharge || submitting || loadingGlAccounts"
|
|
>
|
|
<option value="" disabled>{{ loadingGlAccounts ? 'Loading GL accounts…' : 'Select GL account…' }}</option>
|
|
@for (gl of glAccounts; track gl.id) {
|
|
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="voucher-lookup">
|
|
<input
|
|
type="text"
|
|
class="voucher-input"
|
|
placeholder="Voucher no. (e.g. CR/001/2608/0002)"
|
|
[(ngModel)]="chargeVoucherNo"
|
|
[disabled]="lookingUpCharge || submitting"
|
|
(keyup.enter)="lookupChargeVoucher()"
|
|
/>
|
|
<button
|
|
mat-stroked-button
|
|
type="button"
|
|
[disabled]="!chargeVoucherNo.trim() || !chargeGlAccountNo.trim() || lookingUpCharge || submitting"
|
|
(click)="lookupChargeVoucher()"
|
|
>
|
|
{{ lookingUpCharge ? 'Finding…' : 'Find' }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="charge-receipt-hint">
|
|
Leave this blank if the fee was already paid as part of the share receipt above.
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (errorMsg) {
|
|
<div class="alert-error">
|
|
<mat-icon>error_outline</mat-icon>
|
|
<span>{{ errorMsg }}</span>
|
|
</div>
|
|
}
|
|
|
|
@if (loading) {
|
|
<div class="loading">
|
|
<mat-spinner diameter="28"></mat-spinner>
|
|
<span>Loading receipts…</span>
|
|
</div>
|
|
} @else if (receipts.length === 0) {
|
|
<div class="empty">
|
|
<mat-icon>info_outline</mat-icon>
|
|
<div>
|
|
<b>No unapplied receipt for this member today.</b>
|
|
<div class="empty-hint">
|
|
If the cash was taken on an earlier day, enter that voucher number above. Otherwise collect it first from
|
|
Cashier › Transaction Entry as a deposit to the Share Sundry GL — once a manager approves it, the
|
|
voucher appears here.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
} @else {
|
|
<table class="receipt-table">
|
|
<thead>
|
|
<tr>
|
|
<th class="col-tick"></th>
|
|
<th class="ta-left">Voucher / Receipt</th>
|
|
<th class="ta-left">Date</th>
|
|
<th class="ta-left">Description</th>
|
|
<th class="ta-left">Collected By</th>
|
|
<th class="ta-right">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (r of receipts; track r.id) {
|
|
<tr [class.selected]="isSelected(r.id)" (click)="toggle(r.id)">
|
|
<td class="ta-center col-tick">
|
|
<!-- Checkboxes, not radios: a member paying in instalments has several receipts
|
|
against one application, and all of them settle it together. -->
|
|
<input
|
|
type="checkbox"
|
|
[checked]="isSelected(r.id)"
|
|
(change)="toggle(r.id)"
|
|
(click)="$event.stopPropagation()"
|
|
/>
|
|
</td>
|
|
<td class="ta-left mono">{{ r.voucher_no || r.receipt_no }}</td>
|
|
<td class="ta-left">{{ r.collection_date }}</td>
|
|
<td class="ta-left" [title]="r.note">{{ r.note || '—' }}</td>
|
|
<td class="ta-left">{{ r.collected_by_name || '—' }}</td>
|
|
<td class="ta-right amount-cell">{{ r.amount | number: '1.2-2' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
<!-- Shown from the first tick rather than the second: the total against what the account needs
|
|
is what decides whether the settlement is accepted, so it should be on screen as soon as
|
|
there is anything to total. -->
|
|
@if (selectedIds.length > 0) {
|
|
<tfoot>
|
|
<tr class="total-row">
|
|
<td class="col-tick"></td>
|
|
<td class="ta-left" colspan="4">
|
|
{{ selectedIds.length }} {{ selectedIds.length === 1 ? 'receipt' : 'receipts' }} selected
|
|
</td>
|
|
<td class="ta-right amount-cell">{{ selectedTotal | number: '1.2-2' }}</td>
|
|
</tr>
|
|
</tfoot>
|
|
}
|
|
</table>
|
|
|
|
<div class="note">
|
|
Their total must cover the account's share value — plus any entry fee, unless it was verified as its own receipt
|
|
above. Anything paid over that is credited to the member's savings account.
|
|
</div>
|
|
}
|
|
</mat-dialog-content>
|
|
|
|
<mat-dialog-actions align="end">
|
|
<button mat-button [mat-dialog-close]="false" [disabled]="submitting">Cancel</button>
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
type="button"
|
|
[disabled]="selectedIds.length === 0 || chargeShortfall > 0 || submitting"
|
|
(click)="apply()"
|
|
>
|
|
{{ submitting ? 'Applying…' : 'Apply Collection' }}
|
|
</button>
|
|
</mat-dialog-actions>
|