- Savings account creation shows a required "Default Share Account" picker once Share Linking is enabled, populated from the client's active share accounts; submits shareAccountId alongside it. - Fixes a real data-loss bug on Share Account creation: the Charges step re-seeded its table from the product's default charges on every ngOnChanges firing (e.g. stepping back to Details and forward again), silently discarding a charge the clerk had already added, edited, or removed - a pristine guard existed for exactly this but wasn't checked in ngOnChanges. Now it is. - Removes the hard "required" on Share Account creation's Default Savings Account field, matching the backend now accepting a share account with no linked savings account. - New /accounting/migrate-cash-denomination screen: counts a branch's opening cash into notes and coins, checks it against (or defines, if unset) the Cash GL account's balance, and opens the branch's first business day with that same count - the vault-side counterpart to Migrate Opening Balances, kept under Accounting rather than Branch Ops/Cashier so it isn't reachable by mistake. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
168 lines
6.1 KiB
HTML
168 lines
6.1 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="container mcd">
|
|
<!-- Step 1: which branch's vault is being counted. -->
|
|
<mat-card class="mcd-card">
|
|
<mat-card-content>
|
|
<div class="mcd-intro">
|
|
<mat-icon>inventory_2</mat-icon>
|
|
<span>
|
|
Count the branch's opening cash into notes and coins. If the Cash GL account has no balance yet, this count
|
|
defines it; if it already carries one, the count must match it exactly. Either way, this also opens the
|
|
branch's first business day with the same count.
|
|
</span>
|
|
</div>
|
|
<div class="mcd-row">
|
|
<mat-form-field class="mcd-office">
|
|
<mat-label>Branch</mat-label>
|
|
<mat-select required [(ngModel)]="officeId" (selectionChange)="onOfficeChange()">
|
|
@for (office of officeData; track office) {
|
|
<mat-option [value]="office.id">{{ office.name }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
<mat-form-field class="mcd-field" (click)="datePicker.open()">
|
|
<mat-label>Business Date</mat-label>
|
|
<input matInput [max]="maxDate" [matDatepicker]="datePicker" [(ngModel)]="businessDate" readonly />
|
|
<mat-datepicker-toggle matSuffix [for]="datePicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #datePicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
@if (glLoading) {
|
|
<div class="mcd-loading">Checking the cash GL account…</div>
|
|
}
|
|
|
|
@if (glError) {
|
|
<div class="mcd-gl mcd-gl--error">
|
|
<mat-icon>error</mat-icon>
|
|
<div>{{ glError }}</div>
|
|
</div>
|
|
}
|
|
|
|
@if (glStatus && !glError) {
|
|
<!-- Step 2: what the ledger already says, so the count is counted against something concrete. -->
|
|
<div class="mcd-gl" [class.mcd-gl--fresh]="!glHasBalance">
|
|
<mat-icon>account_balance</mat-icon>
|
|
<div>
|
|
<div class="mcd-gl__label">Cash GL Account</div>
|
|
<div class="mcd-gl__value">({{ glStatus.glCode }}) {{ glStatus.glAccountName }}</div>
|
|
@if (glHasBalance) {
|
|
<div class="mcd-gl__hint">
|
|
Already holds
|
|
<strong>₹{{ glTarget.toLocaleString('en-IN') }}</strong>
|
|
— count the drawer to exactly this amount.
|
|
</div>
|
|
} @else {
|
|
<div class="mcd-gl__hint">
|
|
No opening balance posted yet — whatever you count below becomes this account's opening balance.
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: the actual count. -->
|
|
<mat-card class="mcd-card mcd-card--flush">
|
|
<mat-card-content>
|
|
<div class="mcd-grid mcd-grid--head">
|
|
<div class="mcd-grid__denom">Denomination</div>
|
|
<div class="mcd-grid__count">Count</div>
|
|
<div class="mcd-grid__amt">Amount</div>
|
|
</div>
|
|
@for (d of allDenoms; track d.id) {
|
|
<div class="mcd-grid mcd-grid--row">
|
|
<div class="mcd-grid__denom">
|
|
{{ d.label }} <span class="mcd-grid__kind">{{ d.kind }}</span>
|
|
</div>
|
|
<div class="mcd-grid__count">
|
|
<button type="button" mat-icon-button (click)="bumpCount(d, -1)"><mat-icon>remove</mat-icon></button>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
class="mcd-grid__input"
|
|
[ngModel]="countFor(d)"
|
|
(ngModelChange)="onCountChange(d, $event)"
|
|
/>
|
|
<button type="button" mat-icon-button (click)="bumpCount(d, 1)"><mat-icon>add</mat-icon></button>
|
|
</div>
|
|
<div class="mcd-grid__amt">₹{{ subtotalFor(d).toLocaleString('en-IN') }}</div>
|
|
</div>
|
|
}
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<!-- The one number that matters. -->
|
|
<div class="mcd-balance" [class.mcd-balance--ok]="isReconciled">
|
|
<div class="mcd-balance__figure">
|
|
<span class="mcd-balance__label">Counted Total</span>
|
|
<span class="mcd-balance__amt">₹{{ countedTotal.toLocaleString('en-IN') }}</span>
|
|
</div>
|
|
@if (glHasBalance) {
|
|
<div class="mcd-balance__figure">
|
|
<span class="mcd-balance__label">GL Target</span>
|
|
<span class="mcd-balance__amt">₹{{ glTarget.toLocaleString('en-IN') }}</span>
|
|
</div>
|
|
}
|
|
<div class="mcd-balance__status">
|
|
@if (countedTotal <= 0) {
|
|
<mat-icon>edit_note</mat-icon>
|
|
<span>Count each denomination above</span>
|
|
} @else if (isReconciled) {
|
|
<mat-icon>check_circle</mat-icon>
|
|
<span>{{ glHasBalance ? 'Matches the GL balance' : 'Will define the GL opening balance' }}</span>
|
|
} @else {
|
|
<mat-icon>error</mat-icon>
|
|
<span
|
|
>Off by ₹{{
|
|
(countedTotal - glTarget < 0 ? glTarget - countedTotal : countedTotal - glTarget).toLocaleString('en-IN')
|
|
}}</span
|
|
>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@if (needsDifferenceReason) {
|
|
<mat-card class="mcd-card">
|
|
<mat-card-content>
|
|
<mat-form-field class="mcd-reason">
|
|
<mat-label>Why does the count differ from the GL balance?</mat-label>
|
|
<input matInput [(ngModel)]="differenceReason" />
|
|
</mat-form-field>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
}
|
|
|
|
@if (errorMsg) {
|
|
<div class="mcd-error">{{ errorMsg }}</div>
|
|
}
|
|
|
|
@if (successMsg) {
|
|
<div class="mcd-success">
|
|
<mat-icon>check_circle</mat-icon>
|
|
<div>
|
|
{{ successMsg }}
|
|
<div class="mcd-success__actions">
|
|
<button type="button" mat-stroked-button (click)="goToBranchDay()">Go to Day Register</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@if (!successMsg) {
|
|
<div class="mcd-actions">
|
|
<button mat-raised-button color="primary" [disabled]="!canSubmit" (click)="submit()">
|
|
{{ submitting ? 'Submitting…' : 'Migrate & Open Business Day' }}
|
|
</button>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|