import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { SequenceModalComponent } from '@core/components/sequence-modal/sequence-modal.component'; import { SpinnerService } from '@core/services/spinner.service'; import { TranslationService } from '@core/services/translation.service'; import { AutomationAPI } from '@core/typings/api/automation.typing'; import { Automation } from '@core/typings/ui/automation.typing'; import { BudgetService } from '@features/budgets/budget.service'; import { CyclesService } from '@features/cycles/cycles.service'; import { ProgramService } from '@features/programs/program.service'; import { RuleAutomationService } from '@features/rule-automation/rule-automation.service'; import { ALL_SKIP_FILTER, ArrayHelpersService, DEFAULT_DOUBLE_VALUE, TopLevelFilter } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { ConfirmationModalComponent, ModalFactory } from '@yourcause/common/modals'; import { NotifierService } from '@yourcause/common/notifier'; import { BudgetAssignmentsService } from '../budget-assignments.service'; import { BudgetAssignmentRuleSetDetail, SimpleBudgetAssignmentRuleSet } from '../budget-assignments.typing'; @Component({ selector: 'gc-budget-assignment-page', templateUrl: './budget-assignment-page.component.html', styleUrls: ['./budget-assignment-page.component.scss'] }) export class BudgetAssignmentPageComponent { tableDataFactory = this.budgetAssignmentService.ruleSetDataFactory; drilldownRule: Automation.RuleSetForDisplay & { cycles: { cycleName: string; budgetName: string }[] }; drilldownError: boolean; budgetOptions = this.budgetService.get('allBudgetOptions'); fundingSourceOptions = this.budgetService.get('allSourceOptions'); options = [{ display: this.i18n.translate('CONFIG:textAllRules', {}, 'All rules'), value: DEFAULT_DOUBLE_VALUE, dependentOptions: [{ display: '', value: ALL_SKIP_FILTER }] }, { display: this.i18n.translate('common:lblBudget', {}, 'Budget'), value: 'budgetId', dependentOptions: [{ display: this.i18n.translate('CONFIG:textAllBudgets', {}, 'All budgets'), value: ALL_SKIP_FILTER }].concat(this.arrayHelper.sort( this.budgetService.get('allBudgetOptions').map(budget => { return { display: budget.label, value: budget.value }; }), 'display')) }, { display: this.i18n.translate('common:lblProgram', {}, 'Program'), value: 'grantProgramId', dependentOptions: [{ display: this.i18n.translate('common:textAllPrograms', {}, 'All programs'), value: ALL_SKIP_FILTER }].concat(this.arrayHelper.sort( this.programService.allPublishedActivePrograms.map(program => ({ display: program.grantProgramName, value: program.grantProgramId })), 'display')) }]; programNameMap = this.programService.allPrograms .reduce>((acc, program) => ({ ...acc, [program.grantProgramId]: program.grantProgramName }), {}); budgetNameMap = this.budgetService.get('allBudgetOptions') .reduce>((acc, budgetOption) => ({ ...acc, [budgetOption.value]: budgetOption.label }), {}); fundingSourceNameMap = this.budgetService.get('allSourceOptions') .reduce>((acc, fsOption) => ({ ...acc, [fsOption.value]: fsOption.label }), {}); topLevelFilters = [ new TopLevelFilter( 'doubleDropdownEquals', 'ruleProgramFormWorkflowFilter', null, null, { selectOptions: this.options }, this.i18n.translate( 'GLOBAL:textSearchRulesByBudgetOrProgram', {}, 'Search rules by budget or program' ), null ), new TopLevelFilter( 'typeaheadSingleEquals', 'isDisabledOrInActive', ALL_SKIP_FILTER, null, { selectOptions: [{ value: ALL_SKIP_FILTER, display: this.i18n.translate('common:lblAllCap') }, { display: this.i18n.translate('common:textActive', {}, 'Active'), value: false }, { display: this.i18n.translate('common:textInactive', {}, 'Inactive'), value: true }] }, this.i18n.translate( 'common:textActive', {}, 'Active' ) ) ]; constructor ( private logger: LogService, private translationService: TranslationService, private automationService: RuleAutomationService, private budgetAssignmentService: BudgetAssignmentsService, private i18n: I18nService, private programService: ProgramService, private budgetService: BudgetService, private arrayHelper: ArrayHelpersService, private spinnerService: SpinnerService, private modalFactory: ModalFactory, private notifier: NotifierService, private router: Router, private cycleService: CyclesService ) { } async handleDrilldown ( row: SimpleBudgetAssignmentRuleSet ) { this.drilldownError = false; try { const id = row.budgetAssignmentAutomationRuleSetId; const detail = await this.budgetAssignmentService.fetchDetail(id); const program = await this.programService.getProgram('' + row.grantProgramId); const { currentCycle } = this.cycleService.getCycleDateHelpers( program.cycles ); this.drilldownRule = { cycles: program.cycles.filter((cycle) => { return cycle.id === currentCycle?.id; }).map(cycle => { return { cycleName: this.translationService.viewTranslations?.Grant_Program_Cycle?.[cycle.id]?.Name ?? cycle.name, budgetName: this.budgetService.get('cashBudgetOptions') .find(option => { return option.value === cycle.defaultCashBudgetId; })?.label }; }), detail, rules: this.automationService.mapAPIRulesToView( detail.rules, await this.budgetAssignmentService.getRuleObjectsByProgramId(detail.grantProgramId) ) }; } catch (e) { this.logger.error(e); this.drilldownError = true; } } async copyRule ( row: SimpleBudgetAssignmentRuleSet ) { await this.handleDrilldown(row); if (this.drilldownError) { return this.notifier.error(this.i18n.translate( 'CONFIG:textTheSelectedRuleInvalidCantBeCopied', {}, 'The selected rule is invalid and cannot be copied. Please edit the rule to fix validation before copying.' )); } const response = await this.modalFactory.open( ConfirmationModalComponent, { modalHeader: this.i18n.translate( 'CONFIG:hdrCopyRule', {}, 'Copy Rule' ), modalSubHeader: row.name, confirmButtonText: this.i18n.translate( 'common:textCopy', {}, 'Copy' ), confirmText: this.i18n.translate( 'CONFIG:textAreYouSureCopyRule', {}, 'Are you sure you want to copy the rule? A new rule will be created with the same configuration.' ) } ); if (response) { this.spinnerService.startSpinner(); const id = await this.budgetAssignmentService.handleCopyRule( row.budgetAssignmentAutomationRuleSetId ); this.spinnerService.stopSpinner(); if (id) { this.router.navigate([ '/management/program-setup/budgets/assignments/' + id ]); } } } async sequence (row: SimpleBudgetAssignmentRuleSet) { const { grantProgramId } = row; this.spinnerService.startSpinner(); const results = await this.budgetAssignmentService.getRulesByProgramId(grantProgramId); this.spinnerService.stopSpinner(); const sequencedResults = await this.modalFactory.open( SequenceModalComponent, { modalHeader: this.i18n.translate( 'CONFIG:hdrSequenceAssignments', {}, 'Sequence Assignments' ), nameHeader: this.i18n.translate( 'CONFIG:lblBudgetAssignment', {}, 'Budget Assignment' ), description: `${this.i18n.translate( 'CONFIG:textBudgetSequenceRulesDesc1', {}, 'Budget assignment rulesets are grouped by the application program and cycle.' )}

${this.i18n.translate( 'CONFIG:textBudgetSequenceRulesDesc2', {}, 'The sequence below is the order each application will be evaluated to determine the ruleset it will use. Applications that do not satisfy any rule will be assigned the default cash budget on the cycle.' )}
${this.i18n.translate( 'common:lblProgram', {}, 'Program' )}: ${this.programNameMap[grantProgramId]}
`, items: results.map((result) => { return { id: result.budgetAssignmentAutomationRuleSetId, name: result.name, isDisabledOrInActive: result.isDisabledOrInActive, sequence: result.sequence }; }) } ); if (sequencedResults) { this.spinnerService.startSpinner(); await this.budgetAssignmentService.sequenceRules({ grantProgramId, ruleSets: sequencedResults.map((result) => { return { budgetAssignmentAutomationRuleSetId: result.id, sequence: result.sequence }; }) }); this.spinnerService.stopSpinner(); } } async toggleStatus ( row: SimpleBudgetAssignmentRuleSet ) { const currentStatus = row.status; let confirmText: string; let modalHeader: string; let confirmButtonText: string; let nextStatus: AutomationAPI.AutomationRuleSetStatus; if (currentStatus === AutomationAPI.AutomationRuleSetStatus.Enabled) { nextStatus = AutomationAPI.AutomationRuleSetStatus.Disabled; confirmText = this.i18n.translate( 'CONFIG:textDeactivateBudgetAssignmentConfirm', {}, `Deactivating this assignment will prevent it from being used when evaluating future submissions. Deactivated assignments can be reactivated by accessing the dropdown on the Budget Assignments tab.` ); modalHeader = this.i18n.translate( 'CONFIG:hdrDeactivateAssignment', {}, 'Deactivate Assignment' ); confirmButtonText = this.i18n.translate( 'GLOBAL:btnDeactivate', {}, 'Deactivate' ); } else { nextStatus = AutomationAPI.AutomationRuleSetStatus.Enabled; confirmText = this.i18n.translate( 'CONFIG:textActivateRulesConfirmAssignTextAssignment', {}, 'Activating this assignment will assign applications when they meet the rules’ criteria.' ); modalHeader = this.i18n.translate( 'CONFIG:hdrActivateAssignment', {}, 'Activate Assignment' ); confirmButtonText = this.i18n.translate( 'GLOBAL:textActivate', {}, 'Activate' ); } const result = await this.modalFactory.open( ConfirmationModalComponent, { confirmButtonText, modalHeader, confirmText, modalSubHeader: row.name } ); if (result && nextStatus) { this.spinnerService.startSpinner(); await this.budgetAssignmentService.changeStatus( row.budgetAssignmentAutomationRuleSetId, nextStatus ); this.spinnerService.stopSpinner(); } } async removeRule ( rule: SimpleBudgetAssignmentRuleSet ) { const result = await this.modalFactory.open( ConfirmationModalComponent, { modalHeader: this.i18n.translate( 'CONFIG:hdrDeleteBudgetAssignment', {}, 'Delete Budget Assignment' ), confirmText: this.i18n.translate( 'CONFIG:textConfirmDeleteAssignment', {}, `The assignment will no longer be available after it is deleted. This action cannot be undone. Are you sure you want to continue?` ), confirmButtonText: this.i18n.translate( 'common:btnDelete', {}, 'Delete' ) }); if (result) { this.spinnerService.startSpinner(); await this.budgetAssignmentService.deleteRuleSet(rule.budgetAssignmentAutomationRuleSetId); this.spinnerService.stopSpinner(); } } }