# Extensible Workflow Architecture Plan ## Supporting Multiple Template Types (Claim Management, Non-Templatized, Future Templates) ## Overview This document outlines how to design the backend architecture to support: 1. **Unified Request System**: All requests (templatized, non-templatized, claim management) use the same `workflow_requests` table 2. **Template Identification**: Distinguish between different workflow types 3. **Extensibility**: Easy addition of new templates by admins without code changes 4. **Unified Views**: All requests appear in "My Requests", "Open Requests", etc. automatically --- ## Architecture Principles ### 1. **Single Source of Truth: `workflow_requests` Table** All requests, regardless of type, are stored in the same table: ```sql workflow_requests { request_id UUID PK request_number VARCHAR(20) UK initiator_id UUID FK template_type VARCHAR(20) -- 'CUSTOM' | 'TEMPLATE' (high-level) workflow_type VARCHAR(50) -- 'NON_TEMPLATIZED' | 'CLAIM_MANAGEMENT' | 'DEALER_ONBOARDING' | etc. template_id UUID FK (nullable) -- Reference to workflow_templates if using admin template title VARCHAR(500) description TEXT status workflow_status current_level INTEGER total_levels INTEGER -- ... common fields } ``` **Key Fields:** - `template_type`: High-level classification ('CUSTOM' for user-created, 'TEMPLATE' for admin templates) - `workflow_type`: Specific workflow identifier (e.g., 'CLAIM_MANAGEMENT', 'NON_TEMPLATIZED') - `template_id`: Optional reference to `workflow_templates` table if using an admin-created template ### 2. **Template-Specific Data Storage** Each workflow type can have its own extension table for type-specific data: ```sql -- For Claim Management dealer_claim_details { claim_id UUID PK request_id UUID FK -> workflow_requests(request_id) activity_name VARCHAR(500) activity_type VARCHAR(100) dealer_code VARCHAR(50) dealer_name VARCHAR(200) dealer_email VARCHAR(255) dealer_phone VARCHAR(20) dealer_address TEXT activity_date DATE location VARCHAR(255) period_start_date DATE period_end_date DATE estimated_budget DECIMAL(15,2) closed_expenses DECIMAL(15,2) io_number VARCHAR(50) io_blocked_amount DECIMAL(15,2) sap_document_number VARCHAR(100) dms_number VARCHAR(100) e_invoice_number VARCHAR(100) credit_note_number VARCHAR(100) -- ... claim-specific fields } -- For Non-Templatized (if needed) non_templatized_details { detail_id UUID PK request_id UUID FK -> workflow_requests(request_id) custom_fields JSONB -- Flexible storage for any custom data -- ... any specific fields } -- For Future Templates -- Each new template can have its own extension table ``` ### 3. **Workflow Templates Table (Admin-Created Templates)** ```sql workflow_templates { template_id UUID PK template_name VARCHAR(200) -- Display name: "Claim Management", "Dealer Onboarding" template_code VARCHAR(50) UK -- Unique identifier: "CLAIM_MANAGEMENT", "DEALER_ONBOARDING" template_description TEXT template_category VARCHAR(100) -- "Dealer Operations", "HR", "Finance", etc. workflow_type VARCHAR(50) -- Maps to workflow_requests.workflow_type approval_levels_config JSONB -- Step definitions, TAT, roles, etc. default_tat_hours DECIMAL(10,2) form_fields_config JSONB -- Form field definitions for wizard is_active BOOLEAN is_system_template BOOLEAN -- True for built-in (Claim Management), False for admin-created created_by UUID FK created_at TIMESTAMP updated_at TIMESTAMP } ``` --- ## Database Schema Changes ### Migration: Add Workflow Type Support ```sql -- Migration: 20251210-add-workflow-type-support.ts -- 1. Add workflow_type column to workflow_requests ALTER TABLE workflow_requests ADD COLUMN IF NOT EXISTS workflow_type VARCHAR(50) DEFAULT 'NON_TEMPLATIZED'; -- 2. Add template_id column (nullable, for admin templates) ALTER TABLE workflow_requests ADD COLUMN IF NOT EXISTS template_id UUID REFERENCES workflow_templates(template_id); -- 3. Create index for workflow_type CREATE INDEX IF NOT EXISTS idx_workflow_requests_workflow_type ON workflow_requests(workflow_type); -- 4. Create index for template_id CREATE INDEX IF NOT EXISTS idx_workflow_requests_template_id ON workflow_requests(template_id); -- 5. Create dealer_claim_details table CREATE TABLE IF NOT EXISTS dealer_claim_details ( claim_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID NOT NULL UNIQUE REFERENCES workflow_requests(request_id) ON DELETE CASCADE, activity_name VARCHAR(500) NOT NULL, activity_type VARCHAR(100) NOT NULL, dealer_code VARCHAR(50) NOT NULL, dealer_name VARCHAR(200) NOT NULL, dealer_email VARCHAR(255), dealer_phone VARCHAR(20), dealer_address TEXT, activity_date DATE, location VARCHAR(255), period_start_date DATE, period_end_date DATE, estimated_budget DECIMAL(15,2), closed_expenses DECIMAL(15,2), io_number VARCHAR(50), io_available_balance DECIMAL(15,2), io_blocked_amount DECIMAL(15,2), io_remaining_balance DECIMAL(15,2), sap_document_number VARCHAR(100), dms_number VARCHAR(100), e_invoice_number VARCHAR(100), e_invoice_date DATE, credit_note_number VARCHAR(100), credit_note_date DATE, credit_note_amount DECIMAL(15,2), created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_dealer_claim_details_request_id ON dealer_claim_details(request_id); CREATE INDEX idx_dealer_claim_details_dealer_code ON dealer_claim_details(dealer_code); -- 6. Create proposal_details table (Step 1: Dealer Proposal) CREATE TABLE IF NOT EXISTS dealer_proposal_details ( proposal_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID NOT NULL REFERENCES workflow_requests(request_id) ON DELETE CASCADE, proposal_document_path VARCHAR(500), proposal_document_url VARCHAR(500), cost_breakup JSONB, -- Array of {description, amount} total_estimated_budget DECIMAL(15,2), timeline_mode VARCHAR(10), -- 'date' | 'days' expected_completion_date DATE, expected_completion_days INTEGER, dealer_comments TEXT, submitted_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_dealer_proposal_details_request_id ON dealer_proposal_details(request_id); -- 7. Create completion_documents table (Step 5: Dealer Completion) CREATE TABLE IF NOT EXISTS dealer_completion_details ( completion_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), request_id UUID NOT NULL REFERENCES workflow_requests(request_id) ON DELETE CASCADE, activity_completion_date DATE NOT NULL, number_of_participants INTEGER, closed_expenses JSONB, -- Array of {description, amount} total_closed_expenses DECIMAL(15,2), completion_documents JSONB, -- Array of document references activity_photos JSONB, -- Array of photo references submitted_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_dealer_completion_details_request_id ON dealer_completion_details(request_id); ``` --- ## Model Updates ### 1. Update WorkflowRequest Model ```typescript // Re_Backend/src/models/WorkflowRequest.ts interface WorkflowRequestAttributes { requestId: string; requestNumber: string; initiatorId: string; templateType: 'CUSTOM' | 'TEMPLATE'; workflowType: string; // NEW: 'NON_TEMPLATIZED' | 'CLAIM_MANAGEMENT' | etc. templateId?: string; // NEW: Reference to workflow_templates title: string; description: string; // ... existing fields } // Add association WorkflowRequest.hasOne(DealerClaimDetails, { as: 'claimDetails', foreignKey: 'requestId', sourceKey: 'requestId' }); ``` ### 2. Create DealerClaimDetails Model ```typescript // Re_Backend/src/models/DealerClaimDetails.ts import { DataTypes, Model } from 'sequelize'; import { sequelize } from '@config/database'; import { WorkflowRequest } from './WorkflowRequest'; interface DealerClaimDetailsAttributes { claimId: string; requestId: string; activityName: string; activityType: string; dealerCode: string; dealerName: string; // ... all claim-specific fields } class DealerClaimDetails extends Model { public claimId!: string; public requestId!: string; // ... fields } DealerClaimDetails.init({ claimId: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, field: 'claim_id' }, requestId: { type: DataTypes.UUID, allowNull: false, unique: true, field: 'request_id', references: { model: 'workflow_requests', key: 'request_id' } }, // ... all other fields }, { sequelize, modelName: 'DealerClaimDetails', tableName: 'dealer_claim_details', timestamps: true }); // Association DealerClaimDetails.belongsTo(WorkflowRequest, { as: 'workflowRequest', foreignKey: 'requestId', targetKey: 'requestId' }); export { DealerClaimDetails }; ``` --- ## Service Layer Pattern ### 1. Template-Aware Service Factory ```typescript // Re_Backend/src/services/templateService.factory.ts import { WorkflowRequest } from '../models/WorkflowRequest'; import { DealerClaimService } from './dealerClaim.service'; import { NonTemplatizedService } from './nonTemplatized.service'; export class TemplateServiceFactory { static getService(workflowType: string) { switch (workflowType) { case 'CLAIM_MANAGEMENT': return new DealerClaimService(); case 'NON_TEMPLATIZED': return new NonTemplatizedService(); default: // For future templates, use a generic service or throw error throw new Error(`Unsupported workflow type: ${workflowType}`); } } static async getRequestDetails(requestId: string) { const request = await WorkflowRequest.findByPk(requestId); if (!request) return null; const service = this.getService(request.workflowType); return service.getRequestDetails(request); } } ``` ### 2. Unified Workflow Service (No Changes Needed) The existing `WorkflowService.listMyRequests()` and `listOpenForMe()` methods will **automatically** include all request types because they query `workflow_requests` table without filtering by `workflow_type`. ```typescript // Existing code works as-is - no changes needed! async listMyRequests(userId: string, page: number, limit: number, filters?: {...}) { // This query automatically includes ALL workflow types const requests = await WorkflowRequest.findAll({ where: { initiatorId: userId, isDraft: false, // ... filters // NO workflow_type filter - includes everything! } }); return requests; } ``` --- ## API Endpoints ### 1. Create Claim Management Request ```typescript // Re_Backend/src/controllers/dealerClaim.controller.ts async createClaimRequest(req: AuthenticatedRequest, res: Response) { const userId = req.user?.userId; const { activityName, activityType, dealerCode, // ... claim-specific fields } = req.body; // 1. Create workflow request (common) const workflowRequest = await WorkflowRequest.create({ initiatorId: userId, templateType: 'CUSTOM', workflowType: 'CLAIM_MANAGEMENT', // Identify as claim title: `${activityName} - Claim Request`, description: req.body.requestDescription, totalLevels: 8, // Fixed 8-step workflow // ... other common fields }); // 2. Create claim-specific details const claimDetails = await DealerClaimDetails.create({ requestId: workflowRequest.requestId, activityName, activityType, dealerCode, // ... claim-specific fields }); // 3. Create approval levels (8 steps) await this.createClaimApprovalLevels(workflowRequest.requestId); return ResponseHandler.success(res, { request: workflowRequest, claimDetails }); } ``` ### 2. Get Request Details (Template-Aware) ```typescript async getRequestDetails(req: Request, res: Response) { const { requestId } = req.params; const request = await WorkflowRequest.findByPk(requestId, { include: [ { model: User, as: 'initiator' }, // Conditionally include template-specific data ...(request.workflowType === 'CLAIM_MANAGEMENT' ? [{ model: DealerClaimDetails, as: 'claimDetails' }] : []) ] }); // Use factory to get template-specific service const templateService = TemplateServiceFactory.getService(request.workflowType); const enrichedDetails = await templateService.enrichRequestDetails(request); return ResponseHandler.success(res, enrichedDetails); } ``` --- ## Frontend Integration ### 1. Request List Views (No Changes Needed) The existing "My Requests" and "Open Requests" pages will automatically show all request types because the backend doesn't filter by `workflow_type`. ```typescript // Frontend: MyRequests.tsx - No changes needed! const fetchMyRequests = async () => { const result = await workflowApi.listMyInitiatedWorkflows({ page, limit: itemsPerPage }); // Returns ALL request types automatically }; ``` ### 2. Request Detail Page (Template-Aware Rendering) ```typescript // Frontend: RequestDetail.tsx const RequestDetail = ({ requestId }) => { const request = useRequestDetails(requestId); // Render based on workflow type if (request.workflowType === 'CLAIM_MANAGEMENT') { return ; } else if (request.workflowType === 'NON_TEMPLATIZED') { return ; } else { // Future templates - use generic renderer or template config return ; } }; ``` --- ## Adding New Templates (Future) ### Step 1: Admin Creates Template in UI 1. Admin goes to "Template Management" page 2. Creates new template with: - Template name: "Vendor Payment" - Template code: "VENDOR_PAYMENT" - Approval levels configuration - Form fields configuration ### Step 2: Database Entry Created ```sql INSERT INTO workflow_templates ( template_name, template_code, workflow_type, approval_levels_config, form_fields_config, is_active, is_system_template ) VALUES ( 'Vendor Payment', 'VENDOR_PAYMENT', 'VENDOR_PAYMENT', '{"levels": [...], "tat": {...}}'::jsonb, '{"fields": [...]}'::jsonb, true, false -- Admin-created, not system template ); ``` ### Step 3: Create Extension Table (If Needed) ```sql CREATE TABLE vendor_payment_details ( payment_id UUID PRIMARY KEY, request_id UUID UNIQUE REFERENCES workflow_requests(request_id), vendor_code VARCHAR(50), invoice_number VARCHAR(100), payment_amount DECIMAL(15,2), -- ... vendor-specific fields ); ``` ### Step 4: Create Service (Optional - Can Use Generic Service) ```typescript // Re_Backend/src/services/vendorPayment.service.ts export class VendorPaymentService { async getRequestDetails(request: WorkflowRequest) { const paymentDetails = await VendorPaymentDetails.findOne({ where: { requestId: request.requestId } }); return { ...request.toJSON(), paymentDetails }; } } // Update factory TemplateServiceFactory.getService(workflowType: string) { switch (workflowType) { case 'VENDOR_PAYMENT': return new VendorPaymentService(); // ... existing cases } } ``` ### Step 5: Frontend Component (Optional) ```typescript // Frontend: components/VendorPaymentDetail.tsx export function VendorPaymentDetail({ request }) { // Render vendor payment specific UI } ``` --- ## Benefits of This Architecture 1. **Unified Data Model**: All requests in one table, easy to query 2. **Automatic Inclusion**: My Requests/Open Requests show all types automatically 3. **Extensibility**: Add new templates without modifying existing code 4. **Type Safety**: Template-specific data in separate tables 5. **Flexibility**: Support both system templates and admin-created templates 6. **Backward Compatible**: Existing non-templatized requests continue to work --- ## Migration Strategy 1. **Phase 1**: Add `workflow_type` column, set default to 'NON_TEMPLATIZED' for existing requests 2. **Phase 2**: Create `dealer_claim_details` table and models 3. **Phase 3**: Update claim management creation flow to use new structure 4. **Phase 4**: Update request detail endpoints to be template-aware 5. **Phase 5**: Frontend updates (if needed) for template-specific rendering --- ## Summary - **All requests** use `workflow_requests` table - **Template identification** via `workflow_type` field - **Template-specific data** in extension tables (e.g., `dealer_claim_details`) - **Unified views** automatically include all types - **Future templates** can be added by admins without code changes - **Existing functionality** remains unchanged This architecture ensures that: - ✅ Claim Management requests appear in My Requests/Open Requests - ✅ Non-templatized requests continue to work - ✅ Future templates can be added easily - ✅ No code duplication - ✅ Single source of truth for all requests