Files
cbsnexora/src/app/accounting/migrate-opening-balances/migrate-opening-balances.component.html
T
niteshandClaude Sonnet 5 7b77166b19 fix: tenant-config fixes, cashier/share/savings UI bugs, and menu-access hardcoding
- 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>
2026-09-13 15:19:21 +05:30

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>