/** Angular Imports */ import { Component, OnInit, TemplateRef, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core'; import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormArray, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; /** Custom Services */ import { AccountingService } from '../accounting.service'; import { SettingsService } from 'app/settings/settings.service'; import { PopoverService } from '../../configuration-wizard/popover/popover.service'; import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service'; /** Custom Validators */ import { onlyOneOfTheFieldsIsRequiredValidator } from './only-one-of-the-fields-is-required.validator'; import { Dates } from 'app/core/utils/dates'; import { TranslateService } from '@ngx-translate/core'; import { CurrencyPipe } from '@angular/common'; import { GlAccountDisplayComponent } from '../../shared/accounting/gl-account-display/gl-account-display.component'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; /** * Migrate opening balances component. */ @Component({ selector: 'mifosx-migrate-opening-balances', templateUrl: './migrate-opening-balances.component.html', styleUrls: ['./migrate-opening-balances.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, GlAccountDisplayComponent, CurrencyPipe ] }) export class MigrateOpeningBalancesComponent implements OnInit, AfterViewInit { private formBuilder = inject(UntypedFormBuilder); private accountingService = inject(AccountingService); private settingsService = inject(SettingsService); private dateUtils = inject(Dates); private route = inject(ActivatedRoute); private router = inject(Router); private configurationWizardService = inject(ConfigurationWizardService); private popoverService = inject(PopoverService); private translateService = inject(TranslateService); /** Minimum opening balances date allowed. */ minDate = new Date(2000, 0, 1); /** Maximum opening balances date allowed. */ maxDate = new Date(); /** Opening balances form. */ openingBalancesForm: UntypedFormGroup; /** Opening balances data. */ openingBalancesData: any; /** Office data. */ officeData: any; /** Currency data. */ currencyData: any; currencyCode: string; /** Sum total of debits. */ debitsSum = 0; /** Sum total of credits. */ creditsSum = 0; /* Reference of search form */ @ViewChild('searchFormRef') searchFormRef: ElementRef; /* Template for popover on search form */ @ViewChild('templateSearchFormRef') templateSearchFormRef: TemplateRef; /** * Retrieves the offices and currencies from `resolve`. * @param {FormBuilder} formBuilder Form Builder. * @param {AccountingService} accountingService Accounting Service. * @param {SettingsService} settingsService Settings Service. * @param {ActivatedRoute} route Activated Route. * @param {Router} router Router for navigation. * @param {ConfigurationWizardService} configurationWizardService ConfigurationWizard Service. * @param {PopoverService} popoverService PopoverService. */ constructor() { this.route.data.subscribe((data: { offices: any; currencies: any }) => { this.officeData = data.offices; this.currencyData = data.currencies.selectedCurrencyOptions; }); } /** * Creates the opening balances form. (initially retrieves gl accounts on the basis of specified office) */ ngOnInit() { this.maxDate = this.settingsService.businessDate; this.createOpeningBalancesForm(); } /** * Creates the opening balances form. */ createOpeningBalancesForm() { this.openingBalancesForm = this.formBuilder.group({ officeId: [ '', Validators.required ], currencyCode: [ '', Validators.required ], transactionDate: [ '', Validators.required ], glAccountEntries: this.formBuilder.array([]) }); this.openingBalancesForm.controls.currencyCode.valueChanges.subscribe((value: string) => { this.currencyCode = value; }); } /** * Creates the gl account entry form. * @param glAccount GL Account for which form is returned. * @returns {FormGroup} GL Account entry form. */ createGLAccountEntryForm(glAccount: any): UntypedFormGroup { return this.formBuilder.group( { glAccountId: [glAccount.glAccountId], debit: [null], credit: [null] }, { validator: onlyOneOfTheFieldsIsRequiredValidator } ); } /** * Gets the gl account entries form array. * @returns {FormArray} GL Account entries form array. */ get glAccountEntries(): UntypedFormArray { return this.openingBalancesForm.get('glAccountEntries') as UntypedFormArray; } /** * Retrieves gl accounts on the basis of specified office. */ retrieveOpeningBalances() { this.accountingService .retrieveOpeningBalances(this.openingBalancesForm.value.officeId) .subscribe((openingBalancesData: any) => { const entry = this.openingBalancesForm.get('glAccountEntries') as UntypedFormArray; openingBalancesData.glAccounts = openingBalancesData.assetAccountOpeningBalances.concat( openingBalancesData.liabityAccountOpeningBalances, openingBalancesData.equityAccountOpeningBalances, openingBalancesData.incomeAccountOpeningBalances, openingBalancesData.expenseAccountOpeningBalances ); openingBalancesData.glAccounts.forEach((glAccount: any) => { entry.push(this.createGLAccountEntryForm(glAccount)); }); this.openingBalancesData = openingBalancesData; entry.valueChanges.subscribe(() => { this.debitsSum = 0; this.creditsSum = 0; entry.controls.forEach((value) => { this.debitsSum += value.value.debit; this.creditsSum += value.value.credit; }); }); }); } /** * Submits the opening balances form and defines opening balances, * if successful redirects to view created transaction. */ submit() { if (this.amountsAreOK()) { const openingBalances = this.openingBalancesForm.value; openingBalances.locale = this.settingsService.language.code; openingBalances.dateFormat = this.settingsService.dateFormat; if (openingBalances.transactionDate instanceof Date) { openingBalances.transactionDate = this.dateUtils.formatDate( openingBalances.transactionDate, this.settingsService.dateFormat ); } openingBalances.debits = []; openingBalances.credits = []; this.openingBalancesForm.value.glAccountEntries.forEach((entry: any) => { if (entry.debit) { openingBalances.debits.push({ glAccountId: entry.glAccountId, amount: entry.debit }); } if (entry.credit) { openingBalances.credits.push({ glAccountId: entry.glAccountId, amount: entry.credit }); } }); delete openingBalances.glAccountEntries; this.accountingService.defineOpeningBalances(openingBalances).subscribe((response: any) => { this.router.navigate([ '/accounting/journal-entries/transactions/view', response.transactionId ]); }); } } /** * Popover function * @param template TemplateRef. * @param target HTMLElement | ElementRef. * @param position String. * @param backdrop Boolean. */ showPopover( template: TemplateRef, target: HTMLElement | ElementRef, position: string, backdrop: boolean ): void { setTimeout(() => this.popoverService.open(template, target, position, backdrop, {}), 200); } /** * To show popover. */ ngAfterViewInit() { if (this.configurationWizardService.showMigrateOpeningBalances === true) { setTimeout(() => { this.showPopover(this.templateSearchFormRef, this.searchFormRef.nativeElement, 'bottom', true); }); } } /** * Next Step (Closing Entries Accounting Page) Configuration Wizard. */ nextStep() { this.configurationWizardService.showMigrateOpeningBalances = false; this.configurationWizardService.showClosingEntries = true; this.router.navigate(['/accounting']); } /** * Previous Step (Migrate Opening Balances Accounting Page) Configuration Wizard. */ previousStep() { this.router.navigate(['/accounting']); } amountsAreOK(): boolean { let debitsSum = 0; let creditsSum = 0; this.openingBalancesForm.value.glAccountEntries.forEach((entry: any) => { if (entry.debit) { debitsSum = debitsSum + entry.debit; } if (entry.credit) { creditsSum = creditsSum + entry.credit; } }); return debitsSum > 0 && debitsSum === creditsSum; } glAccountTypeLabel(accountType: string): string { return this.translateService.instant('labels.inputs.accounting.' + accountType); } }