PSA/docs/workflow/workflow-patterns.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

36 KiB

Common Workflow Patterns: Dynamic Workflow UI System

Overview

This document provides examples of common workflow patterns that can be implemented using the Dynamic Workflow UI System. These patterns serve as templates and best practices for building workflows that leverage the system's capabilities.

Important Note on Workflow Execution: Workflows are executed in response to events. When a workflow is triggered, the event that triggered it is passed as input to the workflow. The workflow does not wait for the initial event - the fact that the workflow is executing means the event has already occurred. This is reflected in the patterns below where each workflow receives its triggering event via context.input.triggerEvent.

Table of Contents

  1. Approval Workflows

  2. Request and Fulfillment Workflows

  3. Review and Feedback Workflows

  4. Onboarding and Provisioning Workflows

  5. Incident Management Workflows

Approval Workflows

Simple Approval

A basic workflow where a request is submitted and approved or rejected by a single approver.

Workflow Definition


/**
 * Simple Approval Workflow
 *
 * A basic workflow where a request is submitted and approved or rejected by a single approver.
 *
 * @param context The workflow context provided by the runtime
 */
async function simpleApprovalWorkflow(context): Promise<void> {
  const { actions, events, data, logger } = context;
  
  // Initial state - Processing
  context.setState('processing');
  
  // The workflow is triggered by a Submit event, which is passed as input
  const { triggerEvent } = context.input;
  logger.info(`Processing request submitted by ${triggerEvent.user_id}`);
  
  // Store request data
  data.set('requestData', triggerEvent.payload);
  data.set('requestor', triggerEvent.user_id);
  
  // Create approval task
  const { taskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Approve Request',
    description: `Please review and approve the request submitted by ${submitEvent.user_id}`,
    priority: 'medium',
    dueDate: '2 days',
    assignTo: {
      roles: ['manager']
    },
    contextData: {
      requestData: submitEvent.payload,
      requestor: submitEvent.user_id
    }
  });
  
  // Update state
  context.setState('pending_approval');
  
  // Wait for task completion
  const approvalEvent = await events.waitFor(`Task:${taskId}:Complete`);
  
  // Process approval decision
  const { approved, comments } = approvalEvent.payload;
  
  if (approved) {
    // Handle approval
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_approved',
      data: {
        comments
      }
    });
    
    context.setState('approved');
  } else {
    // Handle rejection
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        comments
      }
    });
    
    context.setState('rejected');
  }
  
  logger.info('Workflow completed');
}

Form Definition

// Approval form definition
const approvalForm = {
  formId: 'simple-approval-form',
  name: 'Simple Approval Form',
  description: 'Form for approving or rejecting a request',
  version: '1.0.0',
  category: 'approval',
  status: FormStatus.ACTIVE,
  jsonSchema: {
    type: 'object',
    required: ['approved'],
    properties: {
      approved: {
        type: 'boolean',
        title: 'Approve this request?',
        default: false
      },
      comments: {
        type: 'string',
        title: 'Comments',
        description: 'Provide any comments or feedback'
      }
    }
  },
  uiSchema: {
    approved: {
      'ui:widget': 'checkbox'
    },
    comments: {
      'ui:widget': 'textarea',
      'ui:options': {
        rows: 5
      }
    }
  }
};

Multi-Level Approval

A workflow where a request requires approval from multiple levels of management in sequence.

Workflow Definition


/**
 * Multi-Level Approval Workflow
 *
 * A workflow where a request requires approval from multiple levels of management in sequence.
 *
 * @param context The workflow context provided by the runtime
 */
async function multiLevelApprovalWorkflow(context): Promise<void> {
  const { actions, events, data, logger } = context;
  
  // Initial state - Processing
  context.setState('processing');
  
  // The workflow is triggered by a Submit event, which is passed as input
  const { triggerEvent } = context.input;
  
  // Store request data
  data.set('requestData', triggerEvent.payload);
  data.set('requestor', triggerEvent.user_id);
  
  // First level approval (Team Lead)
  context.setState('pending_team_lead_approval');
  
  const { taskId: teamLeadTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Team Lead Approval',
    description: 'First level approval by Team Lead',
    priority: 'medium',
    assignTo: {
      roles: ['team_lead']
    },
    contextData: {
      requestData: submitEvent.payload,
      requestor: submitEvent.user_id
    }
  });
  
  // Wait for team lead decision
  const teamLeadEvent = await events.waitFor(`Task:${teamLeadTaskId}:Complete`);
  
  // If rejected by team lead, end workflow
  if (!teamLeadEvent.payload.approved) {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        level: 'Team Lead',
        comments: teamLeadEvent.payload.comments
      }
    });
    
    context.setState('rejected_by_team_lead');
    return;
  }
  
  // Store team lead approval
  data.set('teamLeadApproval', {
    approver: teamLeadEvent.user_id,
    timestamp: teamLeadEvent.timestamp,
    comments: teamLeadEvent.payload.comments
  });
  
  // Second level approval (Manager)
  context.setState('pending_manager_approval');
  
  const { taskId: managerTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Manager Approval',
    description: 'Second level approval by Manager',
    priority: 'medium',
    assignTo: {
      roles: ['manager']
    },
    contextData: {
      requestData: submitEvent.payload,
      requestor: submitEvent.user_id,
      teamLeadApproval: data.get('teamLeadApproval')
    }
  });
  
  // Wait for manager decision
  const managerEvent = await events.waitFor(`Task:${managerTaskId}:Complete`);
  
  // If rejected by manager, end workflow
  if (!managerEvent.payload.approved) {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        level: 'Manager',
        comments: managerEvent.payload.comments
      }
    });
    
    context.setState('rejected_by_manager');
    return;
  }
  
  // Store manager approval
  data.set('managerApproval', {
    approver: managerEvent.user_id,
    timestamp: managerEvent.timestamp,
    comments: managerEvent.payload.comments
  });
  
  // For high-value requests, require director approval
  if (submitEvent.payload.amount > 10000) {
    // Third level approval (Director)
    context.setState('pending_director_approval');
    
    const { taskId: directorTaskId } = await actions.createHumanTask({
      taskType: 'approval',
      title: 'Director Approval',
      description: 'Third level approval by Director (required for high-value requests)',
      priority: 'high',
      assignTo: {
        roles: ['director']
      },
      contextData: {
        requestData: submitEvent.payload,
        requestor: submitEvent.user_id,
        teamLeadApproval: data.get('teamLeadApproval'),
        managerApproval: data.get('managerApproval')
      }
    });
    
    // Wait for director decision
    const directorEvent = await events.waitFor(`Task:${directorTaskId}:Complete`);
    
    // If rejected by director, end workflow
    if (!directorEvent.payload.approved) {
      await actions.sendNotification({
        recipient: data.get('requestor'),
        template: 'request_rejected',
        data: {
          level: 'Director',
          comments: directorEvent.payload.comments
        }
      });
      
      context.setState('rejected_by_director');
      return;
    }
    
    // Store director approval
    data.set('directorApproval', {
      approver: directorEvent.user_id,
      timestamp: directorEvent.timestamp,
      comments: directorEvent.payload.comments
    });
  }
  
  // All required approvals received
  await actions.sendNotification({
    recipient: data.get('requestor'),
    template: 'request_approved',
    data: {
      approvals: [
        data.get('teamLeadApproval'),
        data.get('managerApproval'),
        data.get('directorApproval')
      ].filter(Boolean)
    }
  });
  
  // Execute the approved request
  await actions.executeApprovedRequest({
    requestData: data.get('requestData'),
    approvals: [
      data.get('teamLeadApproval'),
      data.get('managerApproval'),
      data.get('directorApproval')
    ].filter(Boolean)
  });
  
  context.setState('approved');
  logger.info('Multi-level approval workflow completed');
}

Parallel Approval

A workflow where multiple approvers must review a request simultaneously, and all must approve for the request to proceed.

Workflow Definition


/**
 * Parallel Approval Workflow
 *
 * A workflow where multiple approvers must review a request simultaneously,
 * and all must approve for the request to proceed.
 *
 * @param context The workflow context provided by the runtime
 */
async function parallelApprovalWorkflow(context): Promise<void> {
  const { actions, events, data, logger } = context;
  
  // Initial state - Processing
  context.setState('processing');
  
  // The workflow is triggered by a Submit event, which is passed as input
  const { triggerEvent } = context.input;
  
  // Store request data
  data.set('requestData', triggerEvent.payload);
  data.set('requestor', triggerEvent.user_id);
  
  // Create approval tasks for all required approvers
  context.setState('pending_approval');
  
  // Create financial approval task
  const { taskId: financialTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Financial Approval',
    description: 'Financial review and approval',
    priority: 'medium',
    assignTo: {
      roles: ['financial_approver']
    },
    contextData: {
      requestData: submitEvent.payload,
      requestor: submitEvent.user_id,
      approvalType: 'financial'
    }
  });
  
  // Create technical approval task
  const { taskId: technicalTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Technical Approval',
    description: 'Technical review and approval',
    priority: 'medium',
    assignTo: {
      roles: ['technical_approver']
    },
    contextData: {
      requestData: submitEvent.payload,
      requestor: submitEvent.user_id,
      approvalType: 'technical'
    }
  });
  
  // Create legal approval task
  const { taskId: legalTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Legal Approval',
    description: 'Legal review and approval',
    priority: 'medium',
    assignTo: {
      roles: ['legal_approver']
    },
    contextData: {
      requestData: submitEvent.payload,
      requestor: submitEvent.user_id,
      approvalType: 'legal'
    }
  });
  
  // Store task IDs
  data.set('approvalTasks', {
    financial: financialTaskId,
    technical: technicalTaskId,
    legal: legalTaskId
  });
  
  // Initialize approval status
  data.set('approvalStatus', {
    financial: null,
    technical: null,
    legal: null
  });
  
  // Wait for all approvals in parallel
  await Promise.all([
    (async () => {
      const financialEvent = await events.waitFor(`Task:${financialTaskId}:Complete`);
      const approved = financialEvent.payload.approved;
      
      // Update approval status
      const approvalStatus = data.get('approvalStatus');
      approvalStatus.financial = {
        approved,
        approver: financialEvent.user_id,
        timestamp: financialEvent.timestamp,
        comments: financialEvent.payload.comments
      };
      data.set('approvalStatus', approvalStatus);
      
      logger.info(`Financial approval: ${approved ? 'Approved' : 'Rejected'}`);
    })(),
    
    (async () => {
      const technicalEvent = await events.waitFor(`Task:${technicalTaskId}:Complete`);
      const approved = technicalEvent.payload.approved;
      
      // Update approval status
      const approvalStatus = data.get('approvalStatus');
      approvalStatus.technical = {
        approved,
        approver: technicalEvent.user_id,
        timestamp: technicalEvent.timestamp,
        comments: technicalEvent.payload.comments
      };
      data.set('approvalStatus', approvalStatus);
      
      logger.info(`Technical approval: ${approved ? 'Approved' : 'Rejected'}`);
    })(),
    
    (async () => {
      const legalEvent = await events.waitFor(`Task:${legalTaskId}:Complete`);
      const approved = legalEvent.payload.approved;
      
      // Update approval status
      const approvalStatus = data.get('approvalStatus');
      approvalStatus.legal = {
        approved,
        approver: legalEvent.user_id,
        timestamp: legalEvent.timestamp,
        comments: legalEvent.payload.comments
      };
      data.set('approvalStatus', approvalStatus);
      
      logger.info(`Legal approval: ${approved ? 'Approved' : 'Rejected'}`);
    })()
  ]);
  
  // Check if all approvals were received
  const approvalStatus = data.get('approvalStatus');
  const allApproved = Object.values(approvalStatus).every(status => status && status.approved);
  
  if (allApproved) {
    // All approvers approved
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_approved',
      data: {
        approvals: Object.values(approvalStatus)
      }
    });
    
    // Execute the approved request
    await actions.executeApprovedRequest({
      requestData: data.get('requestData'),
      approvals: Object.values(approvalStatus)
    });
    
    context.setState('approved');
  } else {
    // At least one approver rejected
    const rejections = Object.entries(approvalStatus)
      .filter(([_, status]) => status && !status.approved)
      .map(([type, status]) => ({
        type,
        ...status
      }));
    
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        rejections
      }
    });
    
    context.setState('rejected');
  }
  
  logger.info('Parallel approval workflow completed');
}

Conditional Approval

A workflow where the approval path depends on the request attributes, such as amount, category, or risk level.

Workflow Definition


/**
 * Conditional Approval Workflow
 *
 * A workflow where the approval path depends on the request attributes,
 * such as amount, category, or risk level.
 *
 * @param context The workflow context provided by the runtime
 */
async function conditionalApprovalWorkflow(context): Promise<void> {
  const { actions, events, data, logger } = context;
  
  // Initial state - Processing
  context.setState('processing');
  
  // The workflow is triggered by a Submit event, which is passed as input
  const { triggerEvent } = context.input;
  const requestData = triggerEvent.payload;
  
  // Store request data
  data.set('requestData', requestData);
  data.set('requestor', triggerEvent.user_id);
  
  // Determine approval path based on request attributes
  let approvalPath;
  
  if (requestData.amount <= 1000) {
    // Low value - requires only team lead approval
    approvalPath = 'team_lead_only';
  } else if (requestData.amount <= 10000) {
    // Medium value - requires team lead and manager approval
    approvalPath = 'team_lead_and_manager';
  } else {
    // High value - requires team lead, manager, and director approval
    approvalPath = 'full_approval_chain';
  }
  
  // Additional conditions
  if (requestData.category === 'legal') {
    // Legal requests always require legal review
    approvalPath = 'legal_review';
  } else if (requestData.risk_level === 'high') {
    // High risk requests always require full approval chain
    approvalPath = 'full_approval_chain';
  }
  
  // Store approval path
  data.set('approvalPath', approvalPath);
  logger.info(`Selected approval path: ${approvalPath}`);
  
  // Execute the selected approval path
  switch (approvalPath) {
    case 'team_lead_only':
      await executeTeamLeadOnlyPath(context);
      break;
    case 'team_lead_and_manager':
      await executeTeamLeadAndManagerPath(context);
      break;
    case 'legal_review':
      await executeLegalReviewPath(context);
      break;
    case 'full_approval_chain':
      await executeFullApprovalChainPath(context);
      break;
    default:
      throw new Error(`Unknown approval path: ${approvalPath}`);
  }
  
  logger.info('Conditional approval workflow completed');
}

// Helper functions for different approval paths

async function executeTeamLeadOnlyPath(context): Promise<void> {
  const { actions, events, data } = context;
  
  context.setState('pending_team_lead_approval');
  
  const { taskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Team Lead Approval',
    description: 'Approval for low-value request',
    priority: 'low',
    assignTo: {
      roles: ['team_lead']
    },
    contextData: {
      requestData: data.get('requestData'),
      requestor: data.get('requestor'),
      approvalPath: 'team_lead_only'
    }
  });
  
  const approvalEvent = await events.waitFor(`Task:${taskId}:Complete`);
  
  if (approvalEvent.payload.approved) {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_approved',
      data: {
        approver: approvalEvent.user_id,
        comments: approvalEvent.payload.comments
      }
    });
    
    await actions.executeApprovedRequest({
      requestData: data.get('requestData'),
      approval: {
        approver: approvalEvent.user_id,
        timestamp: approvalEvent.timestamp,
        comments: approvalEvent.payload.comments
      }
    });
    
    context.setState('approved');
  } else {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        approver: approvalEvent.user_id,
        comments: approvalEvent.payload.comments
      }
    });
    
    context.setState('rejected');
  }
}

async function executeTeamLeadAndManagerPath(context): Promise<void> {
  const { actions, events, data } = context;
  
  // Team Lead approval
  context.setState('pending_team_lead_approval');
  
  const { taskId: teamLeadTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Team Lead Approval',
    description: 'First level approval for medium-value request',
    priority: 'medium',
    assignTo: {
      roles: ['team_lead']
    },
    contextData: {
      requestData: data.get('requestData'),
      requestor: data.get('requestor'),
      approvalPath: 'team_lead_and_manager'
    }
  });
  
  const teamLeadEvent = await events.waitFor(`Task:${teamLeadTaskId}:Complete`);
  
  if (!teamLeadEvent.payload.approved) {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        level: 'Team Lead',
        comments: teamLeadEvent.payload.comments
      }
    });
    
    context.setState('rejected_by_team_lead');
    return;
  }
  
  // Manager approval
  context.setState('pending_manager_approval');
  
  const { taskId: managerTaskId } = await actions.createHumanTask({
    taskType: 'approval',
    title: 'Manager Approval',
    description: 'Second level approval for medium-value request',
    priority: 'medium',
    assignTo: {
      roles: ['manager']
    },
    contextData: {
      requestData: data.get('requestData'),
      requestor: data.get('requestor'),
      teamLeadApproval: {
        approver: teamLeadEvent.user_id,
        timestamp: teamLeadEvent.timestamp,
        comments: teamLeadEvent.payload.comments
      }
    }
  });
  
  const managerEvent = await events.waitFor(`Task:${managerTaskId}:Complete`);
  
  if (managerEvent.payload.approved) {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_approved',
      data: {
        approvals: [
          {
            level: 'Team Lead',
            approver: teamLeadEvent.user_id,
            comments: teamLeadEvent.payload.comments
          },
          {
            level: 'Manager',
            approver: managerEvent.user_id,
            comments: managerEvent.payload.comments
          }
        ]
      }
    });
    
    await actions.executeApprovedRequest({
      requestData: data.get('requestData'),
      approvals: [
        {
          level: 'Team Lead',
          approver: teamLeadEvent.user_id,
          timestamp: teamLeadEvent.timestamp,
          comments: teamLeadEvent.payload.comments
        },
        {
          level: 'Manager',
          approver: managerEvent.user_id,
          timestamp: managerEvent.timestamp,
          comments: managerEvent.payload.comments
        }
      ]
    });
    
    context.setState('approved');
  } else {
    await actions.sendNotification({
      recipient: data.get('requestor'),
      template: 'request_rejected',
      data: {
        level: 'Manager',
        comments: managerEvent.payload.comments
      }
    });
    
    context.setState('rejected_by_manager');
  }
}

// Additional approval path implementations would follow the same pattern

Request and Fulfillment Workflows

Credit Reimbursement

A workflow for processing credit reimbursement requests.

Workflow Definition

/**
 * Credit Reimbursement Workflow
 *
 * A workflow for processing credit reimbursement requests.
 *
 * @param context The workflow context provided by the runtime
 */
async function creditReimbursementWorkflow(context): Promise<void> {
  const { actions, events, data, logger } = context;
    
    // Initial state
    context.setState('processing');
    
    // The workflow is triggered by a Submit event, which is passed as input
    const { triggerEvent } = context.input;
    const requestData = triggerEvent.payload;
    
    // Store request data
    data.set('requestData', requestData);
    data.set('requestor', triggerEvent.user_id);
    data.set('submissionDate', triggerEvent.timestamp);
    
    // Validate customer information
    context.setState('validating');
    
    const validationResult = await actions.validateCustomerInformation({
      customerId: requestData.customer,
      amount: requestData.amount
    });
    
    if (!validationResult.valid) {
      // Customer validation failed
      await actions.sendNotification({
        recipient: submitEvent.user_id,
        template: 'validation_failed',
        data: {
          reason: validationResult.reason
        }
      });
      
      context.setState('validation_failed');
      return;
    }
    
    // Store customer information
    data.set('customerInfo', validationResult.customerInfo);
    
    // Create approval task
    context.setState('pending_approval');
    
    const { taskId } = await actions.createHumanTask({
      taskType: 'credit_approval',
      title: 'Approve Credit Reimbursement',
      description: `Review and approve credit reimbursement request for ${requestData.customer}`,
      priority: requestData.amount > 1000 ? 'high' : 'medium',
      dueDate: '3 days',
      assignTo: {
        roles: ['finance_approver']
      },
      contextData: {
        requestData,
        customerInfo: validationResult.customerInfo
      }
    });
    
    // Wait for approval decision
    const approvalEvent = await events.waitFor(`Task:${taskId}:Complete`);
    const { approved, adjustedAmount, reason, comments } = approvalEvent.payload;
    
    if (!approved) {
      // Request rejected
      await actions.sendNotification({
        recipient: submitEvent.user_id,
        template: 'reimbursement_rejected',
        data: {
          reason,
          comments
        }
      });
      
      context.setState('rejected');
      return;
    }
    
    // Store approval information
    data.set('approvalInfo', {
      approver: approvalEvent.user_id,
      timestamp: approvalEvent.timestamp,
      adjustedAmount: adjustedAmount || requestData.amount,
      comments
    });
    
    // Process reimbursement
    context.setState('processing');
    
    const finalAmount = adjustedAmount || requestData.amount;
    
    const processingResult = await actions.processReimbursement({
      customerId: requestData.customer,
      amount: finalAmount,
      reason: requestData.reason,
      approver: approvalEvent.user_id,
      reference: `REIMB-${context.executionId}`
    });
    
    if (processingResult.success) {
      // Reimbursement processed successfully
      await actions.sendNotification({
        recipient: submitEvent.user_id,
        template: 'reimbursement_processed',
        data: {
          amount: finalAmount,
          transactionId: processingResult.transactionId,
          processingDate: processingResult.timestamp
        }
      });
      
      // Send notification to customer
      await actions.sendNotification({
        recipient: validationResult.customerInfo.email,
        template: 'customer_reimbursement_notification',
        data: {
          amount: finalAmount,
          reason: requestData.reason,
          processingDate: processingResult.timestamp
        }
      });
      
      // Update accounting records
      await actions.updateAccountingRecords({
        type: 'credit_reimbursement',
        customerId: requestData.customer,
        amount: finalAmount,
        transactionId: processingResult.transactionId,
        approver: approvalEvent.user_id
      });
      
      context.setState('completed');
    } else {
      // Reimbursement processing failed
      await actions.sendNotification({
        recipient: submitEvent.user_id,
        template: 'reimbursement_failed',
        data: {
          reason: processingResult.reason
        }
      });
      
      // Create manual intervention task
      await actions.createHumanTask({
        taskType: 'manual_intervention',
        title: 'Manual Reimbursement Processing Required',
        description: `Automated reimbursement processing failed for ${requestData.customer}. Manual intervention required.`,
        priority: 'high',
        dueDate: '1 day',
        assignTo: {
          roles: ['finance_operations']
        },
        contextData: {
          requestData,
          customerInfo: validationResult.customerInfo,
          approvalInfo: data.get('approvalInfo'),
          processingError: processingResult.reason
        }
      });
      
      context.setState('manual_intervention_required');
    }
    
    logger.info('Credit reimbursement workflow completed');
}

Form Definitions

// Credit reimbursement request form
const creditReimbursementRequestForm = {
  formId: 'credit-reimbursement-request',
  name: 'Credit Reimbursement Request',
  description: 'Form for requesting credit reimbursements',
  version: '1.0.0',
  category: 'finance',
  status: FormStatus.ACTIVE,
  jsonSchema: {
    type: 'object',
    required: ['customer', 'amount', 'reason'],
    properties: {
      customer: {
        type: 'string',
        title: 'Customer Name'
      },
      amount: {
        type: 'number',
        title: 'Amount',
        minimum: 0
      },
      reason: {
        type: 'string',
        title: 'Reason for Reimbursement'
      },
      date: {
        type: 'string',
        format: 'date',
        title: 'Date of Transaction'
      },
      orderNumber: {
        type: 'string',
        title: 'Order Number (if applicable)'
      }
    }
  },
  uiSchema: {
    customer: {
      'ui:widget': 'CompanyPickerWidget',
      'ui:autofocus': true
    },
    amount: {
      'ui:widget': 'currencyWidget'
    },
    reason: {
      'ui:widget': 'textarea'
    },
    date: {
      'ui:widget': 'date'
    }
  }
};

// Credit approval form
const creditApprovalForm = {
  formId: 'credit-approval-form',
  name: 'Credit Approval Form',
  description: 'Form for approving credit reimbursements',
  version: '1.0.0',
  category: 'finance',
  status: FormStatus.ACTIVE,
  jsonSchema: {
    type: 'object',
    required: ['approved'],
    properties: {
      approved: {
        type: 'boolean',
        title: 'Approve this reimbursement?',
        default: false
      },
      adjustedAmount: {
        type: 'number',
        title: 'Adjusted Amount (if different from requested amount)'
      },
      reason: {
        type: 'string',
        title: 'Reason for Adjustment/Rejection'
      },
      comments: {
        type: 'string',
        title: 'Comments'
      }
    }
  },
  uiSchema: {
    approved: {
      'ui:widget': 'checkbox'
    },
    adjustedAmount: {
      'ui:widget': 'currencyWidget',
      'ui:displayIf': {
        field: 'approved',
        value: true
      }
    },
    reason: {
      'ui:widget': 'textarea',
      'ui:displayIf': {
        or: [
          { field: 'approved', value: false },
          { field: 'adjustedAmount', not: null }
        ]
      }
    },
    comments: {
      'ui:widget': 'textarea'
    }
  }
};

Review and Feedback Workflows

Document Review

A workflow for reviewing and approving documents.

Workflow Definition

/**
 * Document Review Workflow
 *
 * A workflow for reviewing and approving documents.
 *
 * @param context The workflow context provided by the runtime
 */
async function documentReviewWorkflow(context): Promise<void> {
  const { actions, events, data, logger } = context;
    
    // Initial state
    context.setState('processing');
    
    // The workflow is triggered by a Submit event, which is passed as input
    const { triggerEvent } = context.input;
    const documentData = triggerEvent.payload;
    
    // Store document data
    data.set('documentData', documentData);
    data.set('author', triggerEvent.user_id);
    data.set('version', '1.0');
    
    // Determine reviewers based on document type
    let reviewers;
    
    switch (documentData.type) {
      case 'technical':
        reviewers = ['technical_reviewer'];
        break;
      case 'legal':
        reviewers = ['legal_reviewer'];
        break;
      case 'financial':
        reviewers = ['financial_reviewer'];
        break;
      case 'marketing':
        reviewers = ['marketing_reviewer'];
        break;
      default:
        reviewers = ['general_reviewer'];
    }
    
    // Add additional reviewers for sensitive documents
    if (documentData.sensitivity === 'high') {
      reviewers.push('compliance_reviewer');
    }
    
    // Store reviewers
    data.set('reviewers', reviewers);
    
    // Create review tasks for all reviewers
    context.setState('in_review');
    
    const reviewTasks = [];
    
    for (const reviewer of reviewers) {
      const { taskId } = await actions.createHumanTask({
        taskType: 'document_review',
        title: `Review ${documentData.title}`,
        description: `Please review the document: ${documentData.description}`,
        priority: documentData.priority || 'medium',
        dueDate: documentData.dueDate || '5 days',
        assignTo: {
          roles: [reviewer]
        },
        contextData: {
          documentData,
          author: submitEvent.user_id,
          documentUrl: documentData.url
        }
      });
      
      reviewTasks.push({
        taskId,
        reviewer
      });
    }
    
    // Store review tasks
    data.set('reviewTasks', reviewTasks);
    
    // Initialize review results
    data.set('reviewResults', []);
    
    // Wait for all reviews to complete
    for (const task of reviewTasks) {
      const reviewEvent = await events.waitFor(`Task:${task.taskId}:Complete`);
      
      // Store review result
      const reviewResults = data.get('reviewResults');
      reviewResults.push({
        reviewer: task.reviewer,
        reviewerId: reviewEvent.user_id,
        timestamp: reviewEvent.timestamp,
        approved: reviewEvent.payload.approved,
        comments: reviewEvent.payload.comments,
        changes: reviewEvent.payload.changes
      });
      data.set('reviewResults', reviewResults);
      
      logger.info(`Review completed by ${task.reviewer}: ${reviewEvent.payload.approved ? 'Approved' : 'Changes requested'}`);
    }
    
    // Check if all reviewers approved
    const reviewResults = data.get('reviewResults');
    const allApproved = reviewResults.every(result => result.approved);
    
    if (allApproved) {
      // All reviewers approved
      await actions.sendNotification({
        recipient: data.get('author'),
        template: 'document_approved',
        data: {
          document: documentData.title,
          reviewers: reviewResults.map(r => r.reviewerId)
        }
      });
      
      // Update document status
      await actions.updateDocumentStatus({
        documentId: documentData.id,
        status: 'approved',
        version: data.get('version'),
        approvers: reviewResults.map(r => r.reviewerId)
      });
      
      context.setState('approved');
    } else {
      // Changes requested by at least one reviewer
      const changesRequested = reviewResults.filter(result => !result.approved);
      
      await actions.sendNotification({
        recipient: data.get('author'),
        template: 'document_changes_requested',
        data: {
          document: documentData.title,
          changesRequested
        }
      });
      
      // Update document status
      await actions.updateDocumentStatus({
        documentId: documentData.id,
        status: 'changes_requested',
        version: data.get('version'),
        changesRequested
      });
      
      context.setState('changes_requested');
      
      // The workflow will be re-triggered when a DocumentRevision event occurs
      // This would be a separate workflow execution with the revision event as input
      logger.info('Waiting for document revision');
      
      // Note: In a real implementation, we would end this workflow here
      // and start a new workflow instance when the DocumentRevision event occurs
      
      // Restart review process with the same reviewers
      // This could be implemented as a recursive call or by jumping back to the review creation step
    }
    
    logger.info('Document review workflow completed');
}

Conclusion

These workflow patterns demonstrate the flexibility and power of the Dynamic Workflow UI System. By leveraging the system's components, you can implement a wide variety of workflows to meet your business needs.

Key benefits of using these patterns:

  1. Consistency: Standardized approach to common workflow scenarios
  2. Reusability: Patterns can be reused across different business processes
  3. Maintainability: Clear separation of concerns makes workflows easier to maintain
  4. Flexibility: Patterns can be customized to meet specific requirements
  5. Scalability: Patterns can be extended to handle more complex scenarios

When implementing these patterns, consider the following best practices:

  1. Start Simple: Begin with the simplest pattern that meets your needs
  2. Modularize: Break complex workflows into smaller, reusable components
  3. Handle Exceptions: Include error handling and exception paths
  4. Monitor Performance: Ensure workflows perform well under load
  5. Document Decisions: Document the reasoning behind workflow design decisions
  6. Test Thoroughly: Test workflows with various scenarios and edge cases