- Add Balance Sheet report and disabled-features accounting config - Point menu-access tenant header and env.js/environment.ts tenant identifier at env config instead of hardcoded 'default'/'lincpay' literals - Surface the real backend error (not a generic "no reference id") when an OTP send fails in contact-otp-block - Fix branch-day "₹null" display bug (null vs undefined) and add a guard blocking a branch's first-ever day open before cash denomination migration - Dynamically size the denomination count input and block re-opening an already-open business day from Migrate Cash Denomination - Lock the denomination-managed cash GL row in Migrate Opening Balances (read-only, with a link back to Migrate Cash Denomination) - Remove duplicate Cancel/Submit action bars on the Share Product and Savings Account creation preview steps; align Previous into the same row - Hide "Apply Collection" on a share account once its advance collection is already settled (further purchases go through Apply Additional Shares) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
234 lines
10 KiB
HTML
234 lines
10 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 mob">
|
|
<form [formGroup]="openingBalancesForm" (ngSubmit)="submit()">
|
|
<!-- Step 1: pick the branch whose old books are being carried into this GL. -->
|
|
<mat-card class="mob-card">
|
|
<mat-card-content>
|
|
<div class="mob-intro">
|
|
<mat-icon>info</mat-icon>
|
|
<span>
|
|
Bring a branch's existing balances into this system's ledger, one time, as a single balanced entry against
|
|
the Opening Balances Contra account below.
|
|
</span>
|
|
</div>
|
|
<div #searchFormRef class="mob-row">
|
|
<mat-form-field class="mob-office">
|
|
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
|
|
<mat-select required formControlName="officeId">
|
|
@for (office of officeData; track office) {
|
|
<mat-option [value]="office.id">
|
|
{{ office.name }}
|
|
</mat-option>
|
|
}
|
|
</mat-select>
|
|
@if (openingBalancesForm.controls.officeId.hasError('required')) {
|
|
<mat-error>
|
|
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
|
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
</mat-error>
|
|
}
|
|
</mat-form-field>
|
|
<button
|
|
type="button"
|
|
mat-raised-button
|
|
color="primary"
|
|
[disabled]="!openingBalancesForm.controls.officeId.value"
|
|
(click)="retrieveOpeningBalances()"
|
|
>
|
|
{{ 'labels.buttons.Retrieve' | translate }}
|
|
</button>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
@if (openingBalancesData) {
|
|
<!-- Step 2: the contra account this entry balances against, plus currency and date. -->
|
|
<mat-card class="mob-card">
|
|
<mat-card-content>
|
|
<div class="mob-contra">
|
|
<mat-icon>account_balance</mat-icon>
|
|
<div>
|
|
<div class="mob-contra__label">Opening Balances Contra Account</div>
|
|
<div class="mob-contra__value">
|
|
<mifosx-gl-account-display [glAccount]="openingBalancesData.contraAccount"></mifosx-gl-account-display>
|
|
</div>
|
|
<div class="mob-contra__hint">
|
|
Every debit and credit below posts against this one account, so the whole entry always balances to zero.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mob-row">
|
|
<mat-form-field class="mob-field">
|
|
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
|
<mat-select required formControlName="currencyCode">
|
|
@for (currency of currencyData; track currency) {
|
|
<mat-option [value]="currency.code">
|
|
{{ currency.displayLabel }}
|
|
</mat-option>
|
|
}
|
|
</mat-select>
|
|
@if (openingBalancesForm.controls.currencyCode.hasError('required')) {
|
|
<mat-error>
|
|
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
</mat-error>
|
|
}
|
|
</mat-form-field>
|
|
<mat-form-field class="mob-field" (click)="openingBalancesDatePicker.open()">
|
|
<mat-label>{{ 'labels.inputs.Opening Balances Date' | translate }}</mat-label>
|
|
<input
|
|
matInput
|
|
[min]="minDate"
|
|
[max]="maxDate"
|
|
[matDatepicker]="openingBalancesDatePicker"
|
|
required
|
|
formControlName="transactionDate"
|
|
/>
|
|
<mat-datepicker-toggle matSuffix [for]="openingBalancesDatePicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #openingBalancesDatePicker></mat-datepicker>
|
|
@if (openingBalancesForm.controls.transactionDate.hasError('required')) {
|
|
<mat-error>
|
|
{{ 'labels.inputs.Opening Balances Date' | translate }} {{ 'labels.commons.is' | translate }}
|
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
</mat-error>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<!-- The one number that matters: does the sheet balance yet. Pinned above the entry grid so it
|
|
never has to be scrolled to. -->
|
|
<div class="mob-balance" [class.mob-balance--ok]="debitsSum === creditsSum && debitsSum > 0">
|
|
<div class="mob-balance__figure">
|
|
<span class="mob-balance__label">{{ 'labels.inputs.Total Debit' | translate }}</span>
|
|
<span class="mob-balance__amt mob-balance__amt--dr">{{
|
|
debitsSum | currency: currencyCode : 'symbol-narrow' : '1.2-2'
|
|
}}</span>
|
|
</div>
|
|
<div class="mob-balance__figure">
|
|
<span class="mob-balance__label">{{ 'labels.inputs.Total Credit' | translate }}</span>
|
|
<span class="mob-balance__amt mob-balance__amt--cr">{{
|
|
creditsSum | currency: currencyCode : 'symbol-narrow' : '1.2-2'
|
|
}}</span>
|
|
</div>
|
|
<div class="mob-balance__status">
|
|
@if (debitsSum === creditsSum && debitsSum > 0) {
|
|
<mat-icon>check_circle</mat-icon>
|
|
<span>Balanced — ready to submit</span>
|
|
} @else if (debitsSum === 0 && creditsSum === 0) {
|
|
<mat-icon>edit_note</mat-icon>
|
|
<span>Enter each account's opening balance below</span>
|
|
} @else {
|
|
<mat-icon>error</mat-icon>
|
|
<span
|
|
>Out of balance by {{ debitsSum - creditsSum | currency: currencyCode : 'symbol-narrow' : '1.2-2' }}</span
|
|
>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: one row per GL account, grouped by account type, debit on one side and credit on
|
|
the other — a debit balance goes in the Debit box, a credit balance in the Credit box,
|
|
never both. -->
|
|
<mat-card class="mob-card mob-card--flush">
|
|
<mat-card-content>
|
|
<div class="mob-grid mob-grid--head">
|
|
<div class="mob-grid__code">{{ 'labels.inputs.GL Code' | translate }}</div>
|
|
<div class="mob-grid__name">
|
|
{{ 'labels.inputs.GL Name' | translate }} {{ 'labels.inputs.Account' | translate }}
|
|
</div>
|
|
<div class="mob-grid__amt">{{ 'labels.inputs.Debit' | translate }}</div>
|
|
<div class="mob-grid__amt">{{ 'labels.inputs.Credit' | translate }}</div>
|
|
</div>
|
|
|
|
@for (credit of glAccountEntries.controls; track credit; let i = $index) {
|
|
@if (
|
|
openingBalancesData.glAccounts[i].glAccountType.value !==
|
|
openingBalancesData.glAccounts[i - 1]?.glAccountType.value
|
|
) {
|
|
<div class="mob-grid__section">
|
|
{{ glAccountTypeLabel(openingBalancesData.glAccounts[i].glAccountType.value) }}
|
|
</div>
|
|
}
|
|
<div class="mob-grid mob-grid--data" formArrayName="glAccountEntries">
|
|
<div class="mob-grid__row" [formGroupName]="i">
|
|
<div class="mob-grid__code">{{ openingBalancesData.glAccounts[i].glAccountCode }}</div>
|
|
<div class="mob-grid__name">
|
|
{{ openingBalancesData.glAccounts[i].glAccountName }}
|
|
@if (isDenominationManaged(openingBalancesData.glAccounts[i].glAccountId)) {
|
|
<span
|
|
class="mob-grid__lock"
|
|
matTooltip="Set by Migrate Cash Denomination — use that screen to change it"
|
|
>
|
|
<mat-icon inline="true">lock</mat-icon>
|
|
<a [routerLink]="['/accounting/migrate-cash-denomination']">Migrate Cash Denomination</a>
|
|
</span>
|
|
}
|
|
</div>
|
|
<div class="mob-grid__amt">
|
|
<mat-form-field class="mob-grid__input">
|
|
<input matInput type="number" min="0" formControlName="debit" placeholder="0.00" />
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="mob-grid__amt">
|
|
<mat-form-field class="mob-grid__input">
|
|
<input matInput type="number" min="0" formControlName="credit" placeholder="0.00" />
|
|
</mat-form-field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</mat-card-content>
|
|
</mat-card>
|
|
|
|
<div class="mob-actions">
|
|
<button type="button" mat-stroked-button [routerLink]="['../']">
|
|
{{ 'labels.buttons.Cancel' | translate }}
|
|
</button>
|
|
<span class="mob-actions__spacer"></span>
|
|
@if (!(debitsSum === creditsSum && debitsSum > 0)) {
|
|
<span class="mob-actions__hint">Total Debit must equal Total Credit before this can be submitted</span>
|
|
}
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
[disabled]="!openingBalancesForm.valid || !(debitsSum === creditsSum && debitsSum > 0)"
|
|
*mifosxHasPermission="'DEFINEOPENINGBALANCE_JOURNALENTRY'"
|
|
>
|
|
{{ 'labels.buttons.Submit' | translate }}
|
|
</button>
|
|
</div>
|
|
}
|
|
</form>
|
|
</div>
|
|
|
|
<ng-template #templateSearchFormRef let-popover="popover">
|
|
<h2>{{ 'labels.heading.Migrate opening balances (Office-wise)' | translate }}</h2>
|
|
<p class="mw300">
|
|
{{ 'labels.text.Migrate Opening Balances' | translate }}
|
|
<a [href]="'migrateOpeningBalances' | documentationLink" target="_blank" rel="noopener noreferrer">{{
|
|
'labels.inputs.Migrate opening balances (Office-wise)' | translate
|
|
}}</a>
|
|
</p>
|
|
<div class="layout-row align-end gap-2px responsive-column">
|
|
<button mat-raised-button color="warn" (click)="popover.close(); configurationWizardService.closeConfigWizard()">
|
|
{{ 'labels.buttons.Close' | translate }}
|
|
</button>
|
|
<button mat-raised-button color="primary" (click)="popover.close(); popover.close(); previousStep()">
|
|
{{ 'labels.buttons.Back' | translate }}
|
|
</button>
|
|
<button mat-raised-button color="primary" (click)="popover.close(); popover.close(); nextStep()">
|
|
{{ 'labels.buttons.Next' | translate }}
|
|
</button>
|
|
</div>
|
|
</ng-template>
|