Re_Backend/src/services/WORKFLOW_EMAIL_SERVICE_GUIDE.md

5.7 KiB

Workflow Email Service Guide

Overview

This guide explains how to add new workflow-specific email services without breaking existing implementations. The architecture uses a factory pattern with interfaces to ensure isolation between different workflow types.

Architecture

┌─────────────────────────────────────┐
│   notification.service.ts           │
│   (Main Router)                      │
└──────────────┬──────────────────────┘
               │
               ▼
┌─────────────────────────────────────┐
│   workflowEmail.factory.ts          │
│   (Factory Pattern)                  │
└──────────────┬──────────────────────┘
               │
       ┌───────┴───────┐
       │               │
       ▼               ▼
┌──────────────┐  ┌──────────────┐
│ Dealer Claim │  │ Custom/      │
│ Email Service│  │ Default      │
└──────────────┘  └──────────────┘

Adding a New Workflow Type

Step 1: Create the Email Service

Create a new file: Re_Backend/src/services/[workflowType]Email.service.ts

Example: Re_Backend/src/services/vendorPaymentEmail.service.ts

import { ApprovalLevel } from '@models/ApprovalLevel';
import { User } from '@models/User';
import logger from '@utils/logger';
import { IWorkflowEmailService } from './workflowEmail.interface';
import { emailNotificationService } from './emailNotification.service';

export class VendorPaymentEmailService implements IWorkflowEmailService {
  async sendAssignmentEmail(
    requestData: any,
    approverUser: User,
    initiatorData: any,
    currentLevel: ApprovalLevel | null,
    allLevels: ApprovalLevel[]
  ): Promise<void> {
    try {
      // Your workflow-specific logic here
      // Example: Check if it's a vendor-specific step
      const levelName = currentLevel?.levelName || '';
      const isVendorStep = levelName.toLowerCase().includes('vendor');
      
      if (isVendorStep) {
        // Use vendor-specific template
        await this.sendVendorSpecificEmail(requestData, approverUser, initiatorData, currentLevel);
      } else {
        // Use standard template
        await this.sendStandardApprovalEmail(requestData, approverUser, initiatorData, currentLevel);
      }
    } catch (error) {
      logger.error(`[VendorPaymentEmail] Error sending assignment email:`, error);
      throw error;
    }
  }

  private async sendVendorSpecificEmail(
    requestData: any,
    vendorUser: User,
    initiatorData: any,
    currentLevel: ApprovalLevel | null
  ): Promise<void> {
    // Implementation for vendor-specific email
  }

  private async sendStandardApprovalEmail(
    requestData: any,
    approverUser: User,
    initiatorData: any,
    currentLevel: ApprovalLevel | null
  ): Promise<void> {
    // Implementation for standard approval email
  }
}

export const vendorPaymentEmailService = new VendorPaymentEmailService();

Step 2: Register in Factory

Update Re_Backend/src/services/workflowEmail.factory.ts:

import { vendorPaymentEmailService } from './vendorPaymentEmail.service';

class WorkflowEmailServiceFactory {
  getService(workflowType: string): IWorkflowEmailService {
    switch (workflowType) {
      case 'CLAIM_MANAGEMENT':
        return dealerClaimEmailService;
      
      case 'VENDOR_PAYMENT':  // Add your new workflow type
        return vendorPaymentEmailService;
      
      default:
        return null as any;
    }
  }

  hasDedicatedService(workflowType: string): boolean {
    return workflowType === 'CLAIM_MANAGEMENT'
        || workflowType === 'VENDOR_PAYMENT';  // Add your new workflow type
  }
}

Step 3: Create Email Templates (if needed)

If your workflow needs custom templates, create them in: Re_Backend/src/emailtemplates/[templateName].template.ts

Then add the send method to emailNotification.service.ts:

async sendVendorPaymentRequired(
  requestData: any,
  vendorData: any,
  initiatorData: any,
  paymentData?: any
): Promise<void> {
  // Implementation
}

Benefits of This Architecture

  1. Isolation: Each workflow type has its own service, preventing cross-workflow breakage
  2. Scalability: Easy to add new workflow types without modifying existing code
  3. Maintainability: Changes to one workflow don't affect others
  4. Type Safety: Interface ensures consistent implementation
  5. Testability: Each service can be tested independently

Best Practices

  1. Always implement IWorkflowEmailService: Ensures consistency
  2. Use levelName, not levelNumber: Handles additional approvers correctly
  3. Log workflow-specific actions: Helps with debugging
  4. Handle errors gracefully: Don't break the entire notification system
  5. Document workflow-specific logic: Makes it easier for others to understand

Example: Dealer Claim Service

See dealerClaimEmail.service.ts for a complete example of:

  • Dynamic step identification
  • Multiple template types (proposal, completion, standard)
  • Additional approver handling
  • Proper error handling

Testing

When adding a new workflow type:

  1. Test assignment emails for all steps
  2. Test with additional approvers
  3. Test with missing levelName (fallback to levelNumber)
  4. Test error handling
  5. Verify custom workflows still work (regression test)