// package/src/services/workflow-automation-engine.service.ts import { Injectable, Inject } from '@nestjs/common'; import { WorkflowAutomationService } from './workflow-automation.service'; import { FilterEvaluatorService } from '../../filter/service/filter-evaluator.service'; import { Action } from '../interface/action.interface'; import { ActionCategory } from 'src/module/workflow/entity/action-category.entity'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; @Injectable() export class WorkflowAutomationEngineService { private readonly actions = new Map(); constructor( @Inject('WorkflowAutomationService') private readonly wfService: WorkflowAutomationService, private readonly filterEvaluator: FilterEvaluatorService, @InjectRepository(ActionCategory) private readonly actionCateRepo: Repository, ) {} registerAction(actionName: string, actionInstance: Action) { this.actions.set(actionName, actionInstance); console.log( `āš™ļø [WorkflowAutomationEngine] Registered action: ${actionName}`, ); } /** * Called from entity hooks (CREATE / UPDATE / DELETE) * @param preUpdateStates optional, used for UPDATE events to pass pre-update criteria results */ async handleEntityEvent( entityType: string, eventType: 'CREATE' | 'UPDATE' | 'DELETE', newEntity: any, user: any, preUpdateStates?: Record | null, ) { console.log( `🟢 [WorkflowAutomationEngine] Handling entity event: ${eventType} for entityType: ${entityType}`, { entityId: newEntity?.id, preUpdateStates }, ); const workflows = await this.wfService.getActiveRules( entityType, eventType, user, ); console.log( `šŸ“‹ [WorkflowAutomationEngine] Found ${workflows.length} active workflows for entityType=${entityType}, eventType=${eventType}`, ); for (const wf of workflows) { console.log( `\nšŸ”¹ [WorkflowAutomationEngine] Processing Workflow ID: ${wf.id}`, { workflow_name: wf.name, applicable_entity_type: wf.applicable_entity_type, mapped_entity_type: wf.mapped_entity_type, condition_filter_code: wf.condition_filter_code, criteria_filter_code: wf.criteria_filter_code, }, ); // Step 1ļøāƒ£ Condition / Trigger evaluation let triggerMatched = false; if (eventType === 'CREATE') { console.log( `āž”ļø [WorkflowAutomationEngine] Evaluating trigger (CREATE) for entity ${newEntity.id} using filter_code=${wf.condition_filter_code}`, ); triggerMatched = await this.filterEvaluator.evaluateCriteria( wf.applicable_entity_type, wf.condition_filter_code, newEntity.id, user, ); } else if (eventType === 'UPDATE' && preUpdateStates) { const before = preUpdateStates[wf.id] ?? false; console.log( `āž”ļø [WorkflowAutomationEngine] Evaluating trigger (UPDATE) for entity ${newEntity.id} using filter_code=${wf.condition_filter_code}, previous=${before}`, ); const after = await this.filterEvaluator.evaluateCriteria( wf.applicable_entity_type, wf.condition_filter_code, newEntity.id, user, ); triggerMatched = before !== after && after === true; console.log( `šŸ” [WorkflowAutomationEngine] Trigger state changed: before=${before}, after=${after}, triggerMatched=${triggerMatched}`, ); } // šŸ” Log Step 1 result console.log( `🧩 [WorkflowAutomationEngine] Step 1 - Trigger matched for WF ${wf.id}: ${triggerMatched}`, ); if (!triggerMatched) { console.log( `ā© [WorkflowAutomationEngine] Skipping workflow ${wf.id} as trigger did not match.`, ); continue; } // Step 2ļøāƒ£ Final criteria evaluation const entityIdToUse = wf.mapped_entity_type === wf.applicable_entity_type ? newEntity.id : newEntity.parent_id; console.log( `āš™ļø [WorkflowAutomationEngine] Evaluating final criteria for mapped_entity_type=${wf.mapped_entity_type}, entityId=${entityIdToUse}, criteria_filter_code=${wf.criteria_filter_code}`, ); const criteriaMatched = await this.filterEvaluator.evaluateCriteria( wf.mapped_entity_type, wf.criteria_filter_code, entityIdToUse, user, ); // šŸ” Log Step 2 result console.log( `āš–ļø [WorkflowAutomationEngine] Step 2 - Criteria matched for WF ${wf.id}: ${criteriaMatched}`, ); if (!criteriaMatched) { console.log( `ā© [WorkflowAutomationEngine] Skipping workflow ${wf.id} as criteria did not match.`, ); continue; } // Step 3ļøāƒ£ Execute workflow actions console.log( `šŸš€ [WorkflowAutomationEngine] Executing actions for Workflow ID: ${wf.id}`, ); await this.executeActions(wf.id, newEntity, user); } console.log( `āœ… [WorkflowAutomationEngine] Completed processing all workflows for ${entityType} (${eventType})`, ); } async executeActions( workflow_automation_id: number, entity: any, user: any, ) { // 1ļø Get actions from workflow automation const actions = await this.wfService.getActionsForRule( workflow_automation_id, ); if (!actions.length) { console.warn( `āš ļø [WorkflowAutomationEngine] No actions found for workflow ${workflow_automation_id}`, ); return; } for (const action of actions) { try { // 2 Resolve action_decorator using action_category_id const category = await this.actionCateRepo.findOne({ where: { id: action.action_category_id, }, select: ['action_decorator'], }); if (!category?.action_decorator) { console.warn( `āš ļø [WorkflowAutomationEngine] No action_decorator found for category_id=${action.action_category_id}`, ); continue; } const decorator = String(category.action_decorator); // 3ļø Get implementation from the registered action map const impl = this.actions.get(decorator); if (!impl) { console.warn( `āš ļø [WorkflowAutomationEngine] No implementation found for action: ${decorator}`, ); continue; } console.log( `šŸš€ [WorkflowAutomationEngine] Executing action "${decorator}" for entity ${entity.id} (WF ${workflow_automation_id})`, ); // 4ļø Execute action with required context await impl.execute({ entity, user, config: action.payload, // from frm_wf_action table }); console.log( `āœ… [WorkflowAutomationEngine] Action "${decorator}" executed successfully (WF ${workflow_automation_id})`, ); } catch (err) { console.error( `āŒ [WorkflowAutomationEngine] Error executing action (category_id=${action.action_category_id}):`, err, ); } } } }