import { Injectable } from '@angular/core'; import { SequenceItem } from '@core/components/sequence-modal/sequence-modal.component'; import { AutomationAPI } from '@core/typings/api/automation.typing'; import { WorkflowLevelAdvancementAPI } from '@core/typings/api/workflow-level-advancement.typing'; import { WorkflowLevelAutomationAPI } from '@core/typings/api/workflow-level-automation.typing'; import { Automation } from '@core/typings/ui/automation.typing'; import { FormLogicService } from '@features/formio/services/form-logic/form-logic.service'; import { ProgramService } from '@features/programs/program.service'; import { ReferenceFieldsService } from '@features/reference-fields/services/reference-fields.service'; import { RuleAutomationService } from '@features/rule-automation/rule-automation.service'; import { DebounceFactory, PaginationOptions, SimpleStringMap, TSwitch } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { NotifierService } from '@yourcause/common/notifier'; import { AttachYCState, BaseYCService } from '@yourcause/common/state'; import { uniq } from 'lodash'; import { WorkflowLevelAdvancementResources } from './workflow-level-advancement.resources'; import { DetailUnion, WorkflowLevelAutomationResources } from './workflow-level-automation.resources'; import { WorkflowLevelAutomationState } from './workflow-level-automation.state'; type WFLDetailSwitch = TSwitch; export type WFLSaveSwitch = TSwitch; @AttachYCState(WorkflowLevelAutomationState) @Injectable({ providedIn: 'root' }) export class WorkflowLevelAutomationService extends BaseYCService { constructor ( private logger: LogService, private workflowLevelAutomationResources: WorkflowLevelAutomationResources, private workflowLevelAdvancementAutomationResources: WorkflowLevelAdvancementResources, private i18n: I18nService, private automationService: RuleAutomationService, private formLogicService: FormLogicService, private programService: ProgramService, private notifier: NotifierService, private referenceFieldService: ReferenceFieldsService ) { super(); } get workflowForms () { return this.get('workflowForms'); } get workflowSequences () { return this.get('workflowSequences'); } get workflowAutomationDetail () { return this.get('workflowAutomationDetail'); } get workflowAdvancementDetail () { return this.get('workflowAdvancementDetail'); } get assignmentTableDataFactory () { return this.get('assignmentTableDataFactory'); } get advancementTableDataFactory () { return this.get('advancementTableDataFactory'); } get assignmentHistoryTableDataFactory () { return this.get('assignmentHistoryTableDataFactory'); } get routingHistoryTableDataFactory () { return this.get('routingHistoryTableDataFactory'); } resetWorkflowObjects (isAdvancement: boolean) { this.set('WorkflowObjects', undefined); this.getWorkflowObjects(isAdvancement); } resetViewRulesTableDataFactory (isAdvancement = false) { if (isAdvancement) { this.advancementTableDataFactory.reset.emit(); } else { this.assignmentTableDataFactory.reset.emit(); } } getWorkflowObjects (isAdvancement: boolean) { const prop = isAdvancement ? 'WorkflowAdvancementObjects' : 'WorkflowObjects'; if (!this.get(prop)) { const objects: Automation.ObjectConfig[] = [ this.automationService.getApplicationObject(isAdvancement), this.automationService.getApplicantObject(), this.automationService.getBudgetObject(), this.automationService.getOrganizationObject(), this.automationService.getEmployeeSsoObject(), isAdvancement ? this.automationService.getAggregateObject() : undefined, isAdvancement ? this.automationService.getAwardObject() : undefined, isAdvancement ? this.automationService.getDecisionObject() : undefined, isAdvancement ? this.automationService.getPaymentObject() : undefined ].filter((item) => !!item); this.set(prop, objects); } return this.get(prop); } async fetchWorkflowLevelForms ( workflowId: number ) { if (!this.workflowForms[workflowId]) { const forms = await this.workflowLevelAutomationResources.getFormsByWorkflowId( workflowId ); this.set('workflowForms', { ...this.workflowForms, [workflowId]: forms }); } } async fetchSequence ( workflowId: number, formId: number, workflowLevelId?: number ) { const sequenceKey = this.getSequenceKey(workflowId, formId, workflowLevelId); if (!this.workflowSequences[sequenceKey]) { await this.doSequenceFetch( workflowId, formId, workflowLevelId ); } return this.workflowSequences[sequenceKey]; } async doSequenceFetch ( workflowId: number, formId: number, workflowLevelId?: number ) { const sequenceKey = this.getSequenceKey(workflowId, formId, workflowLevelId); const sequenceResults = await (!workflowLevelId ? this.workflowLevelAutomationResources.getRulesFromWorkflowFormId( workflowId, formId ) : this.workflowLevelAdvancementAutomationResources.getRulesFromWorkflowLevelFormId( formId, workflowId, workflowLevelId )); this.set('workflowSequences', { ...this.workflowSequences, [sequenceKey]: sequenceResults.data.records }); } private getSequenceKey ( workflowId: number, formId: number, nextWorkflowLevelId: number ) { return '' + workflowId + '-' + formId + (nextWorkflowLevelId || ''); } async sequence ( workflowId: number, formId: number, results: SequenceItem[], nextWorkflowLevelId?: number ) { await this[this.getService(!!nextWorkflowLevelId)].sequence( workflowId, formId, results.map((result) => { return { sequence: result.sequence, [ !!nextWorkflowLevelId ? 'workflowLevelRoutingAutomationRuleSetId' : 'workflowLevelAutomationRuleSetId' ]: result.id }; }), nextWorkflowLevelId ); await this.doSequenceFetch(workflowId, formId, nextWorkflowLevelId); } private getService (nextWorkflowLevelId: boolean) { return nextWorkflowLevelId ? 'workflowLevelAdvancementAutomationResources' : 'workflowLevelAutomationResources'; } clearWorkflowLevelForms () { this.set('workflowForms', {}); } async fetchDetail ( id: number, isAdvancement: T ): Promise> { if (!this.workflowAutomationDetail[id]) { const detail = await this[this.getService(isAdvancement)].getRuleSet(id); const key = this.getDetailKey(isAdvancement); const existing: SimpleStringMap = this.get(key); this.set(key, { ...existing, [id]: detail }); await this.fetchWorkflowLevelForms(detail.workflowId); } return (isAdvancement ? this.workflowAdvancementDetail[id] : this.workflowAutomationDetail[id]) as WFLDetailSwitch; } private getDetailKey ( isAdvancement: boolean ): 'workflowAdvancementDetail' | 'workflowAutomationDetail' { return isAdvancement ? 'workflowAdvancementDetail' : 'workflowAutomationDetail'; } async fetchDetailForTable ( id: number, isAdvancement: T ): Promise>> { await this.fetchDetail(id, isAdvancement); const detail = (isAdvancement ? this.workflowAdvancementDetail[id] : this.workflowAutomationDetail[id]) as WFLDetailSwitch; await this.fetchWorkflowLevelForms(detail.workflowId); const formOption = this.workflowForms[detail.workflowId] .find(form => form.id === detail.formId); await this.formLogicService.fetchFormDetail(formOption.id, formOption.currentRevisionId); const formObject = await this.automationService.formToRuleObject( this.formLogicService.getFormDetail(formOption.id, formOption.currentRevisionId) ); const singleResponseRefIds = await this.getSingleResponseGmFields( detail.workflowId, detail.formId ); const gmFieldsObject = isAdvancement ? await this.referenceFieldService.adaptFieldIdsForAutomation( singleResponseRefIds ) : undefined; const workflowObjects = this.getWorkflowObjects(isAdvancement); return { detail, rules: this.automationService.mapAPIRulesToView( detail.rules, [ ...workflowObjects, formObject, gmFieldsObject ] ) }; } resetWorkflowAutomationDetail () { this.set('workflowAutomationDetail', {}); this.set('workflowAdvancementDetail', {}); } async saveDetail ( level: WFLSaveSwitch, isAdvancement: T ) { try { await ( isAdvancement ? this.workflowLevelAdvancementAutomationResources.saveLevel( level as WorkflowLevelAdvancementAPI.SaveWorkflowLevelRoutingAutomationRuleSet ) : this.workflowLevelAutomationResources.saveLevel( level as WorkflowLevelAutomationAPI.SaveWorkflowLevelAutomationRuleSet ) ); await this.doSequenceFetch( level.workflowId, level.formId, isAdvancement ? level.workflowLevelId : null ); const id = this.getId(level); this.handleLevelUpdate(id, isAdvancement); this.notifier.success(this.i18n.translate( 'CONFIG:notificationSuccessfullySavedRuleset', {}, 'Successfully saved ruleset' )); return true; } catch (e) { this.logger.error(e); this.notifier.error(this.i18n.translate( 'CONFIG:notificationErrorSavingRuleset', {}, 'There was an error saving your ruleset' )); return false; } } getId ( row: { workflowLevelAutomationRuleSetId: number } | { workflowLevelRoutingAutomationRuleSetId: number } ) { return 'workflowLevelAutomationRuleSetId' in row ? row.workflowLevelAutomationRuleSetId : row.workflowLevelRoutingAutomationRuleSetId; } async changeRuleSetStatus ( id: number, ruleSet: { workflowId: number; formId: number; routeToWorkflowId?: number; workflowLevelId?: number; }, status: AutomationAPI.AutomationRuleSetStatus, isAdvancement: boolean ) { await this[this.getService(isAdvancement)].changeRuleSetStatus(id, status); await this.doSequenceFetch( ruleSet.workflowId, ruleSet.formId, ruleSet.routeToWorkflowId ? ruleSet.routeToWorkflowId : ruleSet.workflowLevelId ); this.handleLevelUpdate(id, isAdvancement); } async deleteRuleSet ( id: number, ruleSet: { workflowId: number; formId: number; }, isAdvancement: boolean ) { await (isAdvancement ? this.workflowLevelAdvancementAutomationResources : this.workflowLevelAutomationResources ).deleteRuleSet( id ); await this.doSequenceFetch(ruleSet.workflowId, ruleSet.formId); this.resetViewRulesTableDataFactory(isAdvancement); } async rerouteApplication ( workflowId: number, applicationId: number, comments: string ) { await this.workflowLevelAutomationResources.rerouteApplication( workflowId, applicationId, comments ); this.assignmentHistoryTableDataFactory.reset.emit(); } private handleLevelUpdate ( ruleSetId: number, isAdvancement: boolean ) { if (ruleSetId) { const key = isAdvancement ? 'workflowAdvancementDetail' : 'workflowAutomationDetail'; this.set(key, { ...this[key], [ruleSetId]: null }); } this.resetViewRulesTableDataFactory(isAdvancement); } setAssignmentTableDataFactory (isAdvancement: boolean) { const factory = DebounceFactory.createSimple(async ( arg: PaginationOptions ) => { const programFilterColumn = arg.filterColumns .find(column => column.columnName === 'programId'); if (programFilterColumn) { arg.filterColumns = arg.filterColumns .filter(column => column !== programFilterColumn); const programId = programFilterColumn.filters[0].filterValue; let program = this.programService.get('programMap')[+programId]; if (!program) { await this.programService.getProgram('' + programId); program = this.programService.get('programMap')[+programId]; } arg.filterColumns = arg.filterColumns.concat([{ columnName: 'formId', filters: [{ filterType: 'eq', filterValue: program.defaultFormId }] }, { columnName: 'workflowId', filters: [{ filterType: 'eq', filterValue: program.workflowId }] }]); } return isAdvancement ? this.workflowLevelAdvancementAutomationResources.searchLevels(arg) : this.workflowLevelAutomationResources.searchLevels(arg); }); this.set(isAdvancement ? 'advancementTableDataFactory' : 'assignmentTableDataFactory', factory ); } setAssignmentHistoryTableDataFactory () { const factory = DebounceFactory.createSimple(( options: PaginationOptions ) => { return this.workflowLevelAutomationResources.searchHistory(options); }); this.set('assignmentHistoryTableDataFactory', factory as any); } setRoutingHistoryTableDataFactory () { const factory = DebounceFactory.createSimple(( options: PaginationOptions ) => { return this.workflowLevelAdvancementAutomationResources.searchHistory(options); }); this.set('routingHistoryTableDataFactory', factory as any); } async handleCopyInitialRule ( row: WorkflowLevelAutomationAPI.WorkflowLevelAutomationRuleSetModel ): Promise { try { const detail = await this.fetchDetail( row.workflowLevelAutomationRuleSetId, false ); const payload: WorkflowLevelAutomationAPI.SaveWorkflowLevelAutomationRuleSet = { workflowLevelAutomationRuleSetId: null, workflowId: detail.workflowId, workflowLevelId: detail.workflowLevelId, name: detail.name + ' ' + this.i18n.translate( 'common:textCopy', {}, 'Copy' ), description: detail.description, applyRulesWithOr: detail.applyRulesWithOr, formId: detail.formId, rules: detail.rules.map((rule) => { return { ...rule, isRemoved: false, automationRuleSetExpressionId: null }; }) }; const response = await this.workflowLevelAutomationResources.saveLevel( payload ); const id = response.workflowLevelAutomationRuleSetId; this.handleCopySuccess( id, payload.workflowId, payload.formId, payload.workflowLevelId ); return id; } catch (e) { this.logger.error(e); this.notifier.error(this.i18n.translate( 'CONFIG:textErrorCopyingTheRule', {}, 'There was an error copying the rule' )); return null; } } async handleCopyAdvancementRule ( row: WorkflowLevelAdvancementAPI.WorkflowLevelRoutingAutomationRuleSetModel ): Promise { try { const detail = await this.fetchDetail( row.workflowLevelRoutingAutomationRuleSetId, true ); const payload: WorkflowLevelAdvancementAPI.SaveWorkflowLevelRoutingAutomationRuleSet = { workflowLevelRoutingAutomationRuleSetId: null, workflowId: detail.workflowId, workflowLevelId: detail.workflowLevelId, routeToWorkflowLevelId: detail.routeToWorkflowLevelId, name: `${detail.name} - copy`, description: detail.description, applyRulesWithOr: detail.applyRulesWithOr, formId: detail.formId, rules: detail.rules.map((rule) => { return { ...rule, isRemoved: false, automationRuleSetExpressionId: null }; }) }; const response = await this.workflowLevelAdvancementAutomationResources.saveLevel( payload ); const id = (response as any).workflowLevelRoutingAutomationRuleSetId; this.handleCopySuccess( id, payload.workflowId, payload.formId, payload.workflowLevelId ); return id; } catch (e) { this.logger.error(e); this.notifier.error(this.i18n.translate( 'CONFIG:textErrorCopyingTheRule', {}, 'There was an error copying the rule' )); return null; } } async handleCopySuccess ( id: number, workflowId: number, formId: number, workflowLevelId: number ) { await this.doSequenceFetch( workflowId, formId, workflowLevelId ); this.handleLevelUpdate(id, true); this.notifier.success(this.i18n.translate( 'CONFIG:textSuccessCopyingTheRule', {}, 'Successfully copied the rule' )); } async getSingleResponseGmFields ( workflowId: number, defaultFormId: number ): Promise { if (workflowId && defaultFormId) { const ids = await this.workflowLevelAutomationResources.getSingleResponseRefFields( workflowId, defaultFormId ); return uniq(ids); } return []; } }