import { WorkflowTemplate } from '../models/WorkflowTemplate'; import { WorkflowRequest } from '../models/WorkflowRequest'; import { ApprovalLevel } from '../models/ApprovalLevel'; import { TemplateFieldResolver, FormStepConfig } from './templateFieldResolver.service'; import logger from '../utils/logger'; /** * Enhanced Template Service * Handles template-based workflow operations with dynamic form configuration */ export class EnhancedTemplateService { private fieldResolver = new TemplateFieldResolver(); /** * Get form configuration for a template with resolved user references */ async getFormConfig( templateId: string, requestId?: string, currentUserId?: string ): Promise { try { const template = await WorkflowTemplate.findByPk(templateId); if (!template) { throw new Error('Template not found'); } const stepsConfig = (template.formStepsConfig || []) as FormStepConfig[]; // If request exists, resolve user references if (requestId && currentUserId) { const request = await WorkflowRequest.findByPk(requestId, { include: [{ model: ApprovalLevel, as: 'approvalLevels' }] }); if (request) { return await this.resolveStepsWithUserData(stepsConfig, request, currentUserId); } } return stepsConfig; } catch (error) { logger.error('[EnhancedTemplateService] Error getting form config:', error); throw error; } } /** * Resolve user references in all steps */ private async resolveStepsWithUserData( steps: FormStepConfig[], request: WorkflowRequest, currentUserId: string ): Promise { try { // Get all approvers for context const approvers = await ApprovalLevel.findAll({ where: { requestId: request.requestId } }); const approverMap = new Map( approvers.map(a => [a.levelNumber, a]) ); const resolvedSteps = await Promise.all( steps.map(async (step) => { const resolvedFields = await this.fieldResolver.resolveUserReferences( step, request, currentUserId, { currentLevel: request.currentLevel, approvers: approverMap } ); // Merge resolved values into field defaults const enrichedFields = step.fields.map(field => ({ ...field, defaultValue: resolvedFields[field.fieldId] || field.defaultValue })); return { ...step, fields: enrichedFields }; }) ); return resolvedSteps; } catch (error) { logger.error('[EnhancedTemplateService] Error resolving steps:', error); return steps; // Return original steps on error } } /** * Validate and save form data for a step */ async saveStepData( templateId: string, requestId: string, stepNumber: number, formData: Record, userId: string ): Promise { try { const template = await WorkflowTemplate.findByPk(templateId); if (!template) { throw new Error('Template not found'); } const stepsConfig = (template.formStepsConfig || []) as FormStepConfig[]; const stepConfig = stepsConfig.find(s => s.stepNumber === stepNumber); if (!stepConfig) { throw new Error(`Step ${stepNumber} not found in template`); } // Validate required fields this.validateStepData(stepConfig, formData); // Save to template-specific storage await this.saveToTemplateStorage(template.workflowType, requestId, stepNumber, formData); } catch (error) { logger.error('[EnhancedTemplateService] Error saving step data:', error); throw error; } } /** * Validate step data against configuration */ private validateStepData(stepConfig: FormStepConfig, formData: Record): void { for (const field of stepConfig.fields) { if (field.required && !formData[field.fieldId]) { throw new Error(`Field ${field.label} is required`); } // Apply validation rules if (field.validation && formData[field.fieldId]) { const value = formData[field.fieldId]; if (field.validation.min !== undefined && value < field.validation.min) { throw new Error(`${field.label} must be at least ${field.validation.min}`); } if (field.validation.max !== undefined && value > field.validation.max) { throw new Error(`${field.label} must be at most ${field.validation.max}`); } if (field.validation.pattern) { const regex = new RegExp(field.validation.pattern); if (!regex.test(String(value))) { throw new Error(`${field.label} format is invalid`); } } } } } /** * Save to template-specific storage based on workflow type */ private async saveToTemplateStorage( workflowType: string, requestId: string, stepNumber: number, formData: Record ): Promise { switch (workflowType) { case 'CLAIM_MANAGEMENT': await this.saveClaimManagementStepData(requestId, stepNumber, formData); break; default: // Generic storage for custom templates logger.warn(`[EnhancedTemplateService] No specific storage handler for workflow type: ${workflowType}`); } } /** * Save claim management step data */ private async saveClaimManagementStepData( requestId: string, stepNumber: number, formData: Record ): Promise { const { DealerClaimDetails } = await import('../models/DealerClaimDetails'); const { DealerProposalDetails } = await import('../models/DealerProposalDetails'); const { DealerCompletionDetails } = await import('../models/DealerCompletionDetails'); switch (stepNumber) { case 1: // Save to dealer_claim_details await DealerClaimDetails.upsert({ requestId, activityName: formData.activity_name, activityType: formData.activity_type, dealerCode: formData.dealer_code, dealerName: formData.dealer_name, dealerEmail: formData.dealer_email, dealerPhone: formData.dealer_phone, dealerAddress: formData.dealer_address, activityDate: formData.activity_date, location: formData.location, periodStartDate: formData.period_start_date, periodEndDate: formData.period_end_date, estimatedBudget: formData.estimated_budget, }); break; case 2: // Save to dealer_proposal_details await DealerProposalDetails.upsert({ requestId, costBreakup: formData.cost_breakup, totalEstimatedBudget: formData.total_estimated_budget, timelineMode: formData.timeline_mode, expectedCompletionDate: formData.expected_completion_date, expectedCompletionDays: formData.expected_completion_days, dealerComments: formData.dealer_comments, proposalDocumentPath: formData.proposal_document_path, proposalDocumentUrl: formData.proposal_document_url, submittedAt: new Date(), }); break; case 5: // Save to dealer_completion_details await DealerCompletionDetails.upsert({ requestId, activityCompletionDate: formData.activity_completion_date, numberOfParticipants: formData.number_of_participants, closedExpenses: formData.closed_expenses, totalClosedExpenses: formData.total_closed_expenses, completionDocuments: formData.completion_documents, activityPhotos: formData.activity_photos, submittedAt: new Date(), }); break; default: logger.warn(`[EnhancedTemplateService] No storage handler for claim management step ${stepNumber}`); } } }