import { Component, OnDestroy, OnInit } from '@angular/core'; import { Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { CurrencyService } from '@core/services/currency.service'; import { PolicyService } from '@core/services/policy.service'; import { SpinnerService } from '@core/services/spinner.service'; import { FooterState } from '@core/states/footer.state'; import { BudgetDetail, BudgetFundingSource, BudgetFundingSourceModalResponse, FundingSourceTypes } from '@core/typings/budget.typing'; import { ProcessingTypes } from '@core/typings/payment.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { Tab, TypeSafeFormBuilder, TypeSafeFormGroup } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ModalFactory } from '@yourcause/common/modals'; import { Subscription } from 'rxjs'; import { BudgetFundingSourceModalComponent } from '../budget-funding-source-modal/budget-funding-source-modal.component'; import { BudgetService } from '../budget.service'; interface BudgetGroup { name: string; description: string; accountNumber: string; } @Component({ selector: 'yc-new-edit-budgets-page', templateUrl: 'new-edit-budgets-page.component.html', styleUrls: ['./new-edit-budgets-page.component.scss'] }) export class NewEditBudgetsPageComponent implements OnInit, OnDestroy { id = this.activatedRoute.snapshot.params.id; isNew = this.activatedRoute.snapshot.params.id === 'new'; title: string; sub = new Subscription(); budget: BudgetDetail; formGroup: TypeSafeFormGroup; SOURCE_TYPES = FundingSourceTypes; ProcessingTypes = ProcessingTypes; originalBudgetFundingSources: BudgetFundingSource[]; totalAllocatedText = this.i18n.translate( 'GLOBAL:textTotalAllocated', {}, 'Total allocated' ); totalPaymentsText = this.i18n.translate( 'GLOBAL:textTotalPayments', {}, 'Total payments' ); totalUnavailableText = this.i18n.translate( 'GLOBAL:textTotalUnavailable', {}, 'Total unavailable' ); totalRemainingText = this.i18n.translate( 'BUDGET:textTotalRemaining', {}, 'Total remaining' ); totalReservedText = this.i18n.translate( 'BUDGET:textTotalReserved', {}, 'Total reserved' ); hasInsightsPermission = false; tabs: Tab[] = [{ link: `/management/program-setup/budgets/${this.id}/funding-sources`, labelKey: 'GLOBAL:textFundingSources', label: 'Funding Sources' }, !this.isNew ? { link: `/management/program-setup/budgets/${this.id}/audit-trail`, labelKey: 'GLOBAL:textAuditTrail', label: 'Audit Trail' } : null].filter((item) => !!item); constructor ( private router: Router, private activatedRoute: ActivatedRoute, private budgetService: BudgetService, private formBuilder: TypeSafeFormBuilder, private modalFactory: ModalFactory, private footerState: FooterState, private policyService: PolicyService, private i18n: I18nService, private currencyService: CurrencyService, private clientSettingsService: ClientSettingsService, private spinnerService: SpinnerService ) { this.sub.add( this.budgetService.changesTo$('budgetEditMap').subscribe(() => { this.budget = this.budgetService.get('budgetEditMap')[this.id]; this.determineValidity(); }) ); } get totalBudgetString () { return this.currencyService.formatMoney(this.totalAvailable); } get canReserveFunds () { return this.clientSettingsService.get('clientSettings').reserveFunds; } get totalUnavailable () { return this.budget.budgetFundingSources.reduce((acc, itr) => { return acc + itr.amountUnavailable; }, 0); } get totalAvailable () { return this.budget.budgetFundingSources.reduce((acc, itr) => { return acc + itr.totalAmount; }, 0); } get totalPayments () { return this.budget.budgetFundingSources.reduce((acc, itr) => { return acc + itr.totalAmountPayments; }, 0); } get totalRemaining () { return this.budget.budgetFundingSources.reduce((acc, itr) => { return acc + +itr.amountRemaining; }, 0); } get totalReserved () { return this.budget.budgetFundingSources.reduce((acc, itr) => { return itr.reservedAmount ? acc + +itr.reservedAmount : acc; }, 0); } ngOnInit () { this.hasInsightsPermission = this.policyService.insights.canViewBudgets(); this.originalBudgetFundingSources = [ ...this.budget.budgetFundingSources ]; this.formGroup = this.formBuilder.group({ name: [ this.budget.name, [Validators.required, Validators.maxLength(50)] ], description: [this.budget.description, Validators.required], accountNumber: this.budget.accountNumber }); this.setTitle(); this.setFooter(); } setTitle () { return this.title = this.isNew ? this.i18n.translate( 'BUDGET:hdrCreateBudget', {}, 'Create Budget' ) : ( (this.budget && this.budget.isClosed) ? this.i18n.translate( 'BUDGET:hdrViewBudgetDynamic', { budgetName: this.budget.name }, 'View Budget: __budgetName__' ) : this.i18n.translate( 'BUDGET:hdrEditBudgetDynamic', { budgetName: this.budget.name }, 'Edit Budget: __budgetName__' ) ); } setFooter () { if (this.budget && this.budget.isClosed) { this.footerState.set( 'footerActionLabel', this.i18n.translate('common:btnBack') ); this.footerState.set( 'footerAction', () => this.router.navigateByUrl('/management/program-setup/budgets') ); this.footerState.set( 'footerState', this.footerState.FOOTER_STATES.ACTION ); } else { this.footerState.set( 'footerActionLabel', this.i18n.translate('common:btnSave') ); this.footerState.set( 'footerAction', () => this.save() ); this.footerState.set( 'footerState', this.footerState.FOOTER_STATES.ACTION ); this.determineValidity(); this.sub.add(this.formGroup.statusChanges.subscribe(() => { this.determineValidity(); })); } this.footerState.setCancelLabel(this.i18n.translate('common:btnCancel')); this.footerState.setCancelAction(() => this.router.navigateByUrl('/management/program-setup/budgets')); } routeToBudgetInsights () { return this.router.navigateByUrl( `/management/insights/budgets/budget/${this.budget.id}` ); } async addEditFundingSource () { const response = await this.modalFactory.open( BudgetFundingSourceModalComponent, { existingType: this.budget.budgetFundingSources.length ? this.budget.budgetFundingSources[0].fundingSourceType : null, existingSources: this.budget.budgetFundingSources, existingSource: null, originalExistingSources: this.originalBudgetFundingSources || [] } ); if (response) { if (typeof response === 'string') { this.router.navigate([response]); } else { this.budget.budgetFundingSources = this.budgetService.handleBudgetFundingSourceModalResponse( response as BudgetFundingSourceModalResponse, this.budget ); this.budgetService.attachEditBudget({ ...this.budget }); } } } determineValidity () { const valid = (this.formGroup && this.formGroup.status === 'VALID') && (this.budget.budgetFundingSources.length > 0); this.footerState.set('footerActionDisabled', !valid); } async save () { this.footerState.set('footerActionDisabled', true); this.budget.name = this.formGroup.value.name; this.budget.description = this.formGroup.value.description; this.budget.accountNumber = this.formGroup.value.accountNumber; this.spinnerService.startSpinner(); await this.budgetService.saveBudget(this.budget); this.spinnerService.stopSpinner(); this.router.navigateByUrl('/management/program-setup/budgets'); } ngOnDestroy () { this.sub.unsubscribe(); this.footerState.clearAll(); } }