Skip to content

Intelligent Expense Management Workflow

AI-powered expense processing with smart approval routing and fraud detection

typescript
import { cronflow } from 'cronflow';
import express from 'express';
import { z } from 'zod';

const app = express();
app.use(express.json());

// Types for our expense management system
interface ExpenseData {
  id: string;
  amount: number;
  description: string;
  category?: string;
  vendor?: string;
  date: string;
  receipt?: {
    url: string;
    text?: string;
    confidence?: number;
  };
  employee: {
    id: string;
    name: string;
    department: string;
    level: 'junior' | 'senior' | 'manager' | 'director';
  };
  project?: string;
  taxDeductible?: boolean;
  riskScore?: number;
}

interface ApprovalData {
  approved: boolean;
  reason?: string;
  approver?: string;
  timestamp: string;
  conditions?: string[];
}

interface Approver {
  id: string;
  name: string;
  role: string;
  required: boolean;
}

interface AIAnalysis {
  text: string;
  amount: number;
  vendor: string;
  date: string;
  category: string;
  confidence: number;
  taxDeductible: boolean;
}

// Mock AI services (in real implementation, these would call external APIs)
async function analyzeReceiptWithAI(receiptUrl: string): Promise<AIAnalysis> {
  // Simulate AI OCR and analysis
  return {
    text: 'RECEIPT - Restaurant ABC - $45.50 - Business Lunch',
    amount: 45.5,
    vendor: 'Restaurant ABC',
    date: new Date().toISOString(),
    category: 'meals',
    confidence: 0.95,
    taxDeductible: true,
  };
}

async function detectFraud(expense: ExpenseData): Promise<{
  riskScore: number;
  flags: string[];
  recommendation: 'approve' | 'review' | 'reject';
}> {
  const flags: string[] = [];
  let riskScore = 0;

  // Check for suspicious patterns
  if (expense.amount > 500) {
    riskScore += 0.3;
    flags.push('High amount');
  }

  if (expense.receipt?.confidence && expense.receipt.confidence < 0.8) {
    riskScore += 0.4;
    flags.push('Low OCR confidence');
  }

  // Weekend submission check
  const submissionDate = new Date(expense.date);
  if (submissionDate.getDay() === 0 || submissionDate.getDay() === 6) {
    riskScore += 0.2;
    flags.push('Weekend submission');
  }

  // Duplicate detection (simplified)
  if (Math.random() < 0.1) {
    // 10% chance of "duplicate"
    riskScore += 0.6;
    flags.push('Potential duplicate');
  }

  const recommendation =
    riskScore > 0.7 ? 'reject' : riskScore > 0.4 ? 'review' : 'approve';

  return { riskScore, flags, recommendation };
}

async function predictBudgetImpact(expense: ExpenseData): Promise<{
  departmentBudgetUsed: number;
  projectBudgetUsed?: number;
  monthlyTrend: 'increasing' | 'stable' | 'decreasing';
  recommendation: string;
}> {
  // Simulate budget analysis
  return {
    departmentBudgetUsed: 0.75, // 75% used
    projectBudgetUsed: expense.project ? 0.6 : undefined,
    monthlyTrend: 'increasing',
    recommendation:
      'Budget usage is trending high this month. Consider review.',
  };
}

async function getApprovalWorkflow(expense: ExpenseData): Promise<{
  approvers: Approver[];
  autoApprove: boolean;
  escalationRules: string[];
}> {
  const approvers: Approver[] = [];
  let autoApprove = false;
  const escalationRules: string[] = [];

  // Auto-approval for small amounts
  if (expense.amount < 50 && expense.riskScore! < 0.3) {
    autoApprove = true;
  } else {
    // Manager approval required
    approvers.push({
      id: 'manager_001',
      name: 'Department Manager',
      role: 'manager',
      required: true,
    });

    // Finance approval for high amounts
    if (expense.amount > 1000) {
      approvers.push({
        id: 'finance_001',
        name: 'Finance Director',
        role: 'finance',
        required: true,
      });
      escalationRules.push('Finance approval required for amounts > $1000');
    }

    // CEO approval for very high amounts
    if (expense.amount > 5000) {
      approvers.push({
        id: 'ceo_001',
        name: 'CEO',
        role: 'executive',
        required: true,
      });
      escalationRules.push('Executive approval required for amounts > $5000');
    }
  }

  return { approvers, autoApprove, escalationRules };
}

async function sendNotification(
  type: string,
  data: any,
  recipients: string[]
): Promise<void> {
  console.log(`📧 Sending ${type} notification to:`, recipients);
  console.log('Data:', data);
  // In real implementation, integrate with email/Slack/Teams
}

async function updateExpenseDatabase(
  expense: ExpenseData,
  status: string
): Promise<void> {
  console.log(`💾 Updating expense ${expense.id} with status: ${status}`);
  // In real implementation, update your database
}

// Define the main expense management workflow
const expenseWorkflow = cronflow.define({
  id: 'intelligent-expense-management',
  name: 'Intelligent Expense Management Workflow',
  description:
    'AI-powered expense processing with smart approval routing and fraud detection',
  hooks: {
    onSuccess: (ctx, stepId) => {
      if (!stepId) {
        console.log('🎉 Expense workflow completed successfully!');
        console.log('Final result:', ctx.last);
      }
    },
    onFailure: (ctx, stepId) => {
      console.log(`❌ Expense workflow failed at step: ${stepId}`);
      console.log('Error:', ctx.step_error || ctx.error);
    },
  },
});

// Webhook endpoint for expense submissions
expenseWorkflow
  .onWebhook('/api/expenses/submit', {
    method: 'POST',
    schema: z.object({
      amount: z.number().positive(),
      description: z.string().min(1),
      receiptUrl: z.string().url().optional(),
      category: z
        .enum(['meals', 'travel', 'supplies', 'software', 'training', 'other'])
        .optional(),
      employee: z.object({
        id: z.string(),
        name: z.string(),
        department: z.string(),
        level: z.enum(['junior', 'senior', 'manager', 'director']),
      }),
      project: z.string().optional(),
    }),
  })

  // Step 1: Initial data processing and receipt analysis
  .step('process-receipt', async ctx => {
    console.log('📄 Processing expense receipt...');

    const expenseId = `exp_${Date.now()}`;
    let aiAnalysis: AIAnalysis | null = null;

    if (ctx.payload.receiptUrl) {
      try {
        aiAnalysis = await analyzeReceiptWithAI(ctx.payload.receiptUrl);
        console.log('🤖 AI Receipt Analysis:', aiAnalysis);
      } catch (error) {
        console.log('⚠️ AI analysis failed, proceeding with manual data');
      }
    }

    const expense: ExpenseData = {
      id: expenseId,
      amount: aiAnalysis?.amount || ctx.payload.amount,
      description: ctx.payload.description,
      category: aiAnalysis?.category || ctx.payload.category,
      vendor: aiAnalysis?.vendor,
      date: aiAnalysis?.date || new Date().toISOString(),
      receipt: ctx.payload.receiptUrl
        ? {
            url: ctx.payload.receiptUrl,
            text: aiAnalysis?.text,
            confidence: aiAnalysis?.confidence,
          }
        : undefined,
      employee: ctx.payload.employee,
      project: ctx.payload.project,
      taxDeductible: aiAnalysis?.taxDeductible,
    };

    return { expense, aiAnalysis };
  })

  // Step 2: Fraud detection and risk analysis
  .step('fraud-detection', async ctx => {
    console.log('🔍 Running fraud detection...');

    const fraudAnalysis = await detectFraud(ctx.last.expense);
    console.log('🚨 Fraud Analysis:', fraudAnalysis);

    // Update expense with risk score
    const expense = {
      ...ctx.last.expense,
      riskScore: fraudAnalysis.riskScore,
    };

    return { expense, fraudAnalysis };
  })

  // Step 3: Budget impact analysis
  .step('budget-analysis', async ctx => {
    console.log('💰 Analyzing budget impact...');

    const budgetAnalysis = await predictBudgetImpact(ctx.last.expense);
    console.log('📊 Budget Analysis:', budgetAnalysis);

    return {
      expense: ctx.last.expense,
      fraudAnalysis: ctx.last.fraudAnalysis,
      budgetAnalysis,
    };
  })

  // Step 4: Determine approval workflow
  .step('approval-routing', async ctx => {
    console.log('📋 Determining approval workflow...');

    const approvalWorkflow = await getApprovalWorkflow(ctx.last.expense);
    console.log('👥 Approval Workflow:', approvalWorkflow);

    return {
      expense: ctx.last.expense,
      fraudAnalysis: ctx.last.fraudAnalysis,
      budgetAnalysis: ctx.last.budgetAnalysis,
      approvalWorkflow,
    };
  })

  // Conditional: Auto-approve or require manual approval
  .if('requires-approval', ctx => !ctx.last.approvalWorkflow.autoApprove)

  // Send approval notifications
  .action('send-approval-notifications', async ctx => {
    const approvers = ctx.last.approvalWorkflow.approvers.map(a => a.id);
    await sendNotification(
      'approval_request',
      {
        expense: ctx.last.expense,
        fraudAnalysis: ctx.last.fraudAnalysis,
        budgetAnalysis: ctx.last.budgetAnalysis,
      },
      approvers
    );
  })

  // Human in the loop for approval
  .humanInTheLoop({
    timeout: '48h', // 48 hours to approve
    description: 'Expense approval required',
    onPause: (ctx, token) => {
      console.log(`⏸️ Expense ${ctx.last.expense.id} awaiting approval`);
      console.log(`🔑 Approval Token: ${token}`);
      console.log(`💵 Amount: $${ctx.last.expense.amount}`);
      console.log(`👤 Employee: ${ctx.last.expense.employee.name}`);
      console.log(`🚨 Risk Score: ${ctx.last.fraudAnalysis.riskScore}`);

      // In real implementation, send this token via email/Slack
    },
  })

  // Process approval response
  .step('process-approval', async ctx => {
    console.log('✅ Processing approval response...');

    if (ctx.last.timedOut) {
      return {
        approved: false,
        reason: 'Approval timeout',
        status: 'rejected',
      };
    }

    const approval: ApprovalData = {
      approved: ctx.last.approved,
      reason: ctx.last.reason,
      approver: ctx.last.approver,
      timestamp: new Date().toISOString(),
      conditions: ctx.last.conditions,
    };

    return {
      approval,
      status: approval.approved ? 'approved' : 'rejected',
    };
  })

  .endIf()

  // Auto-approval branch
  .if('auto-approved', ctx => ctx.last.approvalWorkflow?.autoApprove)
  .step('auto-approve', async ctx => {
    console.log('⚡ Auto-approving expense...');

    const approval: ApprovalData = {
      approved: true,
      reason: 'Auto-approved based on policy',
      timestamp: new Date().toISOString(),
    };

    return {
      approval,
      status: 'approved',
    };
  })
  .endIf()

  // Step 5: Final processing and database update
  .step('finalize-expense', async ctx => {
    console.log('🏁 Finalizing expense processing...');

    const finalStatus = ctx.last.status || 'approved';
    const expense = ctx.last.expense;
    const approval = ctx.last.approval;

    // Update database
    await updateExpenseDatabase(expense, finalStatus);

    // Prepare final result
    const result = {
      expenseId: expense.id,
      status: finalStatus,
      amount: expense.amount,
      employee: expense.employee.name,
      approval,
      fraudAnalysis: ctx.last.fraudAnalysis,
      budgetAnalysis: ctx.last.budgetAnalysis,
      processedAt: new Date().toISOString(),
    };

    return result;
  })

  // Parallel background actions
  .parallel([
    // Tax optimization analysis
    async ctx => {
      if (ctx.last.expense?.taxDeductible) {
        console.log('📈 Running tax optimization analysis...');
        // Simulate tax calculation
        const taxSavings = ctx.last.expense.amount * 0.25; // 25% tax rate
        return { taxSavings, optimized: true };
      }
      return { taxSavings: 0, optimized: false };
    },

    // Update reporting dashboard
    async ctx => {
      console.log('📊 Updating expense analytics dashboard...');
      // Simulate dashboard update
      return { dashboardUpdated: true, timestamp: new Date().toISOString() };
    },

    // Integration with accounting system
    async ctx => {
      if (ctx.last.status === 'approved') {
        console.log('🔗 Syncing with accounting system...');
        // Simulate accounting integration
        return { accountingSync: true, referenceNumber: `ACC_${Date.now()}` };
      }
      return { accountingSync: false };
    },
  ])

  // Final notifications
  .action('send-final-notifications', async ctx => {
    const expense = ctx.last.expense;
    const status = ctx.last.status;

    // Notify employee
    await sendNotification(
      'expense_processed',
      {
        expenseId: expense.id,
        status,
        amount: expense.amount,
      },
      [expense.employee.id]
    );

    // Notify finance if approved
    if (status === 'approved') {
      await sendNotification('expense_approved', ctx.last, ['finance_team']);
    }
  });

// Additional webhook for approval responses (if using external approval system)
const approvalResponseWorkflow = cronflow.define({
  id: 'expense-approval-response',
  name: 'Expense Approval Response Handler',
  description: 'Handle external approval responses',
});

approvalResponseWorkflow
  .onWebhook('/api/expenses/approve', {
    method: 'POST',
    schema: z.object({
      token: z.string(),
      approved: z.boolean(),
      reason: z.string().optional(),
      approver: z.string(),
      conditions: z.array(z.string()).optional(),
    }),
  })
  .step('process-external-approval', async ctx => {
    console.log('📥 Processing external approval response...');

    try {
      // Resume the paused workflow
      await cronflow.resume(ctx.payload.token, {
        approved: ctx.payload.approved,
        reason: ctx.payload.reason,
        approver: ctx.payload.approver,
        conditions: ctx.payload.conditions,
      });

      return { success: true, message: 'Approval processed successfully' };
    } catch (error) {
      console.error('Failed to process approval:', error);
      return { success: false, error: (error as Error).message };
    }
  });

// Utility workflow for expense reporting and analytics
const expenseReportingWorkflow = cronflow.define({
  id: 'expense-reporting',
  name: 'Expense Reporting and Analytics',
  description: 'Generate expense reports and analytics',
});

expenseReportingWorkflow
  .onWebhook('/api/expenses/report', {
    method: 'GET',
    schema: z.object({
      department: z.string().optional(),
      dateRange: z
        .object({
          start: z.string(),
          end: z.string(),
        })
        .optional(),
      employeeId: z.string().optional(),
    }),
  })
  .step('generate-report', async ctx => {
    console.log('📋 Generating expense report...');

    // Simulate report generation
    const report = {
      totalExpenses: 15420.5,
      approvedExpenses: 14890.3,
      pendingExpenses: 530.2,
      rejectedExpenses: 0,
      topCategories: [
        { category: 'meals', amount: 5420.5 },
        { category: 'travel', amount: 4890.3 },
        { category: 'supplies', amount: 3120.4 },
      ],
      fraudDetected: 2,
      avgProcessingTime: '4.2 hours',
      generatedAt: new Date().toISOString(),
    };

    return { report, success: true };
  });

// Express Routes
app.get('/', (req, res) => {
  res.json({
    service: 'Intelligent Expense Management',
    status: 'ACTIVE',
    features: [
      'AI-powered receipt analysis and OCR',
      'Smart fraud detection and risk scoring',
      'Automated approval routing based on policies',
      'Budget impact analysis and forecasting',
      'Real-time expense tracking and reporting',
    ],
    advantages: [
      'Reduces manual processing by 80%',
      'Catches fraud before it costs money',
      'Ensures policy compliance automatically',
      'Provides real-time expense insights',
      'Integrates with existing accounting systems',
    ],
  });
});

// Submit expense endpoint
app.post('/api/expenses/submit', async (req, res) => {
  try {
    const expenseData = {
      amount: req.body.amount,
      description: req.body.description,
      receiptUrl: req.body.receiptUrl,
      category: req.body.category,
      employee: req.body.employee,
      project: req.body.project,
    };

    console.log('\n📄 EXPENSE SUBMITTED');
    console.log(`   Amount: $${expenseData.amount}`);
    console.log(`   Employee: ${expenseData.employee.name}`);
    console.log(`   Category: ${expenseData.category || 'Not specified'}`);

    const runId = await cronflow.trigger(
      'intelligent-expense-management',
      expenseData
    );

    res.json({
      success: true,
      expenseId: `exp_${Date.now()}`,
      workflowRunId: runId,
      message: 'Expense submitted for processing',
      estimatedProcessingTime: '2-5 minutes',
      trackingUrl: `/api/expenses/${runId}/status`,
    });
  } catch (error) {
    console.error('Expense submission error:', error);
    res.status(400).json({
      success: false,
      error: (error as Error).message || 'Failed to submit expense',
    });
  }
});

// Approve expense endpoint
app.post('/api/expenses/approve', async (req, res) => {
  try {
    const approvalData = {
      token: req.body.token,
      approved: req.body.approved,
      reason: req.body.reason,
      approver: req.body.approver,
      conditions: req.body.conditions,
    };

    console.log('\n✅ EXPENSE APPROVAL RECEIVED');
    console.log(`   Token: ${approvalData.token}`);
    console.log(`   Approved: ${approvalData.approved}`);
    console.log(`   Approver: ${approvalData.approver}`);

    const runId = await cronflow.trigger(
      'expense-approval-response',
      approvalData
    );

    res.json({
      success: true,
      message: 'Approval processed successfully',
      workflowRunId: runId,
    });
  } catch (error) {
    console.error('Approval processing error:', error);
    res.status(400).json({
      success: false,
      error: (error as Error).message || 'Failed to process approval',
    });
  }
});

// Get expense report endpoint
app.get('/api/expenses/report', async (req, res) => {
  try {
    const reportParams = {
      department: req.query.department as string,
      dateRange:
        req.query.dateRange && typeof req.query.dateRange === 'object'
          ? {
              start: (req.query.dateRange as any).start as string,
              end: (req.query.dateRange as any).end as string,
            }
          : undefined,
      employeeId: req.query.employeeId as string,
    };

    console.log('\n📊 EXPENSE REPORT REQUESTED');
    console.log(`   Department: ${reportParams.department || 'All'}`);
    console.log(
      `   Date Range: ${reportParams.dateRange ? `${reportParams.dateRange.start} to ${reportParams.dateRange.end}` : 'All time'}`
    );

    const runId = await cronflow.trigger('expense-reporting', reportParams);

    res.json({
      success: true,
      message: 'Report generation started',
      workflowRunId: runId,
      estimatedCompletion: '30-60 seconds',
    });
  } catch (error) {
    console.error('Report generation error:', error);
    res.status(400).json({
      success: false,
      error: (error as Error).message || 'Failed to generate report',
    });
  }
});

// Get expense status endpoint
app.get('/api/expenses/:runId/status', (req, res) => {
  const { runId } = req.params;

  // Mock expense status
  const status = {
    runId,
    status: 'COMPLETED',
    expenseId: `exp_${Date.now()}`,
    amount: 245.5,
    employee: 'John Doe',
    category: 'meals',
    approvalStatus: 'APPROVED',
    fraudScore: 0.15,
    processingTime: '3.2 minutes',
    lastUpdated: new Date().toISOString(),
    nextSteps: 'Payment will be processed within 2 business days',
  };

  res.json({
    success: true,
    status,
  });
});

// Health check endpoint
app.get('/api/health', (req, res) => {
  res.json({
    status: 'HEALTHY',
    timestamp: new Date().toISOString(),
    services: {
      cronflow: 'ACTIVE',
      expense_processing: 'READY',
      ai_analysis: 'AVAILABLE',
      fraud_detection: 'OPERATIONAL',
    },
    performance: {
      activeExpenses: 12,
      avgProcessingTime: '3.2 minutes',
      approvalRate: '94.2%',
      fraudDetectionRate: '98.7%',
      uptime: '99.96%',
    },
  });
});

// Start server
app.listen(3001, async () => {
  console.log('\n💰 Intelligent Expense Management Starting...');
  console.log('⚡ Server running on port 3001');

  await cronflow.start();

  console.log('\n✅ Intelligent Expense Management - READY!');
  console.log('\n🎯 Key Features:');
  console.log('   ✅ AI-powered receipt analysis and OCR');
  console.log('   ✅ Smart fraud detection and risk scoring');
  console.log('   ✅ Automated approval routing based on policies');
  console.log('   ✅ Budget impact analysis and forecasting');
  console.log('   ✅ Real-time expense tracking and reporting');
  console.log('\n📋 Endpoints:');
  console.log('   POST /api/expenses/submit - Submit new expense');
  console.log('   POST /api/expenses/approve - Approve expense');
  console.log('   GET /api/expenses/report - Generate expense report');
  console.log('   GET /api/expenses/:id/status - Check expense status');
  console.log('\n💪 Ready for intelligent expense management!');
});

// Export the workflows
export { expenseWorkflow, approvalResponseWorkflow, expenseReportingWorkflow };

/* 
USAGE EXAMPLES:

1. Submit an expense:
curl -X POST http://localhost:3001/api/expenses/submit \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 245.50,
    "description": "Business lunch with client",
    "receiptUrl": "https://example.com/receipt.jpg",
    "category": "meals",
    "employee": {
      "id": "emp_123",
      "name": "John Doe",
      "department": "Sales",
      "level": "senior"
    },
    "project": "Client Acquisition"
  }'

2. Approve an expense:
curl -X POST http://localhost:3001/api/expenses/approve \
  -H "Content-Type: application/json" \
  -d '{
    "token": "approval_token_123",
    "approved": true,
    "reason": "Valid business expense",
    "approver": "manager_001"
  }'

3. Generate expense report:
curl "http://localhost:3001/api/expenses/report?department=Sales&dateRange[start]=2025-01-01&dateRange[end]=2025-01-31"

FEATURES:
✅ AI-powered receipt analysis and OCR
✅ Smart fraud detection and risk scoring
✅ Automated approval routing based on policies
✅ Budget impact analysis and forecasting
✅ Real-time expense tracking and reporting
✅ Human-in-the-loop approval system
✅ Tax optimization analysis
✅ Integration with accounting systems
✅ Comprehensive expense analytics
✅ Type-safe configuration with Zod validation
*/

Released under the Apache 2.0 License.