Automated Invoice Processing Workflow

End-to-end invoice processing with OCR, validation, SAP integration, and exception handling

Back
Workflow Information

ID: invoice_processing_workflow

Namespace: finance

Version: 1.0

Created: 2025-07-15

Updated: 2025-07-16

Tasks: 6

Quick Actions
Manage Secrets
Inputs
Name Type Required Default
image_url_data string Required None
notification_email string Optional sarfraz@ampcome.com
Outputs
Name Type Source
processing_summary object generate_summary
invoice_data object validate_data
sap_result object post_to_sap
Tasks
ocr_processing
mcp

Extract text from scanned invoice image

extract_invoice_data
ai_agent

Extract key invoice fields from OCR text

validate_data
script

Validate and clean extracted invoice data

post_to_sap
script

Post invoice to SAP system

generate_summary
script

Create final processing summary

upload_to_supabase
script

Upload invoice processing data to Supabase bucket as CSV

Task Outputs: {'name': 'supabase_upload_result', 'value': 'Supabase upload operation completed'}, {'name': 'csv_file_info', 'value': 'CSV file information and public URL'}, {'name': 'upload_summary', 'value': 'Upload operation summary and status'}
Triggers
Manual Trigger: Manual Invoice Processing
Webhook Trigger: Invoice Upload
POST /webhook/invoice-upload
YAML Source
id: simple_invoice_processing_workflow_1
name: Simple Invoice Processing Workflow
retry:
  retryOn:
  - TEMPORARY_FAILURE
  - TIMEOUT
  - NETWORK_ERROR
  maxDelay: 60s
  maxAttempts: 3
  initialDelay: 5s
  backoffMultiplier: 2.0
tasks:
- id: ocr_processing
  name: OCR Invoice Processing
  type: mcp
  tool_name: ocr_image_url
  description: Extract text from scanned invoice image
  deployment_id: pod-tldususf
  tool_arguments:
    image_url: ${image_url_data}
  timeout_seconds: 120
- id: extract_invoice_data
  name: Extract Invoice Data
  type: ai_agent
  config:
    input_format: json
    output_format: json
    model_client_id: invoice_extractor
  depends_on:
  - ocr_processing
  description: Extract key invoice fields from OCR text
  user_message: 'Extract invoice data from this OCR text:

    ${ocr_processing}

    '
  previous_node: ocr_processing
  system_message: "Extract only these key fields from the OCR text. Return ONLY a\
    \ valid JSON object with **escaped characters where necessary**, especially inside\
    \ string values (e.g., escape double quotes within strings as `\\\\\\\"`). Ensure\
    \ all values are properly JSON-encoded.:\n{\n  \"vendor_name\": \"extracted vendor\
    \ name or null\",\n  \"invoice_number\": \"extracted invoice number or null\"\
    ,\n  \"date\": \"YYYY-MM-DD format or null\",\n  \"po_number\": \"extracted PO\
    \ number or null\", \n  \"amount\": \"numeric amount or null\",\n  \"line_items\"\
    : [\n    {\n      \"description\": \"item description\",\n      \"quantity\":\
    \ \"numeric quantity\",\n      \"price\": \"numeric price\",\n      \"total\"\
    : \"numeric total\"\n    }\n  ]\n}\n"
  timeout_seconds: 60
- id: validate_data
  name: Validate Invoice Data
  type: script
  script: "import json\nimport os\n\n# Parse the AI response from extract_invoice_data\n\
    try:\n    ai_response = '''${extract_invoice_data.ai_response}'''\n    extracted_data\
    \ = json.loads(ai_response)\n    \n    print(f\"Using extracted data from AI response\"\
    )\n    print(f\"Vendor: {extracted_data.get('vendor_name')}\")\n    print(f\"\
    Invoice: {extracted_data.get('invoice_number')}\")\n    print(f\"Amount: {extracted_data.get('amount')}\"\
    )\n    \nexcept Exception as e:\n    print(f\"Error parsing AI response: {e}\"\
    )\n    # Fallback to basic structure if parsing fails\n    extracted_data = {\n\
    \        \"vendor_name\": \"Unknown Vendor\",\n        \"invoice_number\": \"\
    Unknown\",\n        \"date\": \"2024-07-15\",\n        \"po_number\": None,\n\
    \        \"amount\": 0.00,\n        \"line_items\": []\n    }\n\n# Simple validation\
    \ using actual extracted data\nvalidation_result = {\n    \"vendor_name\": extracted_data.get(\"\
    vendor_name\"),\n    \"invoice_number\": extracted_data.get(\"invoice_number\"\
    ),\n    \"date\": extracted_data.get(\"date\"),\n    \"po_number\": extracted_data.get(\"\
    po_number\"),\n    \"amount\": extracted_data.get(\"amount\"),\n    \"line_items\"\
    : extracted_data.get(\"line_items\", []),\n    \"validation_status\": \"passed\"\
    ,\n    \"total_items\": len(extracted_data.get(\"line_items\", []))\n}\n\nprint(f\"\
    Validation completed for invoice: {validation_result['invoice_number']}\")\nprint(f\"\
    Vendor: {validation_result['vendor_name']}\")\nprint(f\"Amount: ${validation_result['amount']}\"\
    )\nprint(f\"__OUTPUTS__ {json.dumps(validation_result)}\")\n"
  depends_on:
  - extract_invoice_data
  description: Validate and clean extracted invoice data
  previous_node: extract_invoice_data
  timeout_seconds: 30
- id: post_to_sap
  name: Post to SAP
  type: script
  script: "import json\nimport os\n\n# Parse the validated data from previous task\n\
    try:\n    # In a real workflow, this would get the actual output from validate_data\n\
    \    # For now, we'll parse the AI response from extract_invoice_data\n    ai_response\
    \ = '''${validate_data}'''\n    invoice_data = json.loads(ai_response)\n    \n\
    \    vendor_name = invoice_data.get(\"vendor_name\", \"Unknown Vendor\")\n   \
    \ invoice_number = invoice_data.get(\"invoice_number\", \"Unknown\")\n    amount\
    \ = invoice_data.get(\"amount\", 0.00)\n    \n    print(f\"Posting to SAP for\
    \ invoice: {invoice_number}\")\n    print(f\"Vendor: {vendor_name}\")\n    print(f\"\
    Amount: ${amount}\")\n    \nexcept Exception as e:\n    print(f\"Error parsing\
    \ invoice data: {e}\")\n    vendor_name = \"Unknown Vendor\"\n    invoice_number\
    \ = \"Unknown\"\n    amount = 0.00\n\n# SAP posting using actual invoice data\n\
    sap_result = {\n    \"posting_status\": \"success\",\n    \"sap_document_number\"\
    : f\"51{hash(invoice_number) % 1000000:06d}\",\n    \"vendor_code\": f\"V{hash(vendor_name)\
    \ % 100000:05d}\",\n    \"vendor_name\": vendor_name,\n    \"invoice_number\"\
    : invoice_number,\n    \"amount_posted\": amount,\n    \"posting_date\": \"2024-07-15\"\
    ,\n    \"document_type\": \"Invoice\"\n}\n\nprint(f\"SAP posting completed\")\n\
    print(f\"Document number: {sap_result['sap_document_number']}\")\nprint(f\"Amount:\
    \ ${sap_result['amount_posted']}\")\nprint(f\"__OUTPUTS__ {json.dumps(sap_result)}\"\
    )\n"
  depends_on:
  - validate_data
  description: Post invoice to SAP system
  previous_node: validate_data
  timeout_seconds: 60
- id: generate_summary
  name: Generate Processing Summary
  type: script
  script: "import json\nimport os\n\n# Parse the extracted invoice data\ntry:\n  \
    \  sap_response = '''${post_to_sap}'''\n    invoice_data = json.loads(sap_response)\n\
    \n    validated_data = '''${validate_data}'''\n    validate_data = json.loads(validated_data)\n\
    \    \n    vendor_name = invoice_data.get(\"vendor_name\", \"Unknown Vendor\"\
    )\n    invoice_number = invoice_data.get(\"invoice_number\", \"Unknown\")\n  \
    \  amount = invoice_data.get(\"amount_posted\", 0.00)\n    po_number = validate_data.get(\"\
    po_number\")\n    line_items_count = len(validate_data.get(\"line_items\", []))\n\
    \    \n    print(f\"Generating summary for invoice: {invoice_number}\")\n    print(f\"\
    Vendor: {vendor_name}\")\n    print(f\"Amount: ${amount}\")\n    \nexcept Exception\
    \ as e:\n    print(f\"Error parsing invoice data: {e}\")\n    vendor_name = \"\
    Unknown Vendor\"\n    invoice_number = \"Unknown\"\n    amount = 0.00\n    po_number\
    \ = None\n    line_items_count = 0\n\n# Generate summary using actual extracted\
    \ data\nsummary = {\n    \"processing_status\": \"completed\",\n    \"invoice_number\"\
    : invoice_number,\n    \"vendor_name\": vendor_name,\n    \"amount\": amount,\n\
    \    \"po_number\": po_number,\n    \"line_items_count\": line_items_count,\n\
    \    \"sap_document\": f\"51{hash(invoice_number) % 1000000:06d}\",\n    \"processing_time\"\
    : \"5 minutes\",\n    \"automation_level\": \"fully_automated\"\n}\n\nprint(f\"\
    Processing summary generated\")\nprint(f\"Status: {summary['processing_status']}\"\
    )\nprint(f\"Invoice: {summary['invoice_number']}\")\nprint(f\"__OUTPUTS__ {json.dumps(summary)}\"\
    )\n"
  depends_on:
  - post_to_sap
  - validate_data
  description: Create final processing summary
  previous_node: post_to_sap
  timeout_seconds: 30
- id: upload_to_supabase
  name: Upload Status to csv
  type: script
  script: "import json\nimport os\nimport pandas as pd\nfrom datetime import datetime\n\
    import io\nfrom supabase import create_client, Client\n\n# Supabase configuration\n\
    SUPABASE_URL = \"https://mbauzgvitqvxceqanzjw.supabase.co\"  # Replace with your\
    \ actual URL\nSUPABASE_KEY = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1iYXV6Z3ZpdHF2eGNlcWFuemp3Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0Mzg2MDEwOCwiZXhwIjoyMDU5NDM2MTA4fQ.R71cWZoLuq2GNojkLSvhcXXt9rAW9PJ5O9V4g7vXvC0\"\
    \  # Replace with your actual anon key\n\ntry:\n    # Initialize Supabase client\n\
    \    supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)\n    \n    #\
    \ Parse data from previous nodes\n    try:\n        # Get SAP posting results\n\
    \        sap_response = '''${post_to_sap}'''\n        sap_data = json.loads(sap_response)\n\
    \        \n        # Get validation results\n        validation_response = '''${validate_data}'''\n\
    \        validation_data = json.loads(validation_response)\n        \n       \
    \ # Get processing summary\n        summary_response = '''${generate_summary}'''\n\
    \        summary_data = json.loads(summary_response)\n        \n        print(\"\
    Successfully parsed data from previous nodes\")\n        \n    except Exception\
    \ as e:\n        print(f\"Error parsing previous node data: {e}\")\n        #\
    \ Fallback data structure\n        sap_data = {\"posting_status\": \"unknown\"\
    , \"sap_document_number\": \"unknown\"}\n        validation_data = {\"vendor_name\"\
    : \"unknown\", \"invoice_number\": \"unknown\", \"amount\": 0}\n        summary_data\
    \ = {\"processing_status\": \"unknown\"}\n    \n    # Create comprehensive data\
    \ structure for CSV\n    processing_data = {\n        \"processing_timestamp\"\
    : datetime.now().isoformat(),\n        \"workflow_id\": \"simple_invoice_processing_workflow\"\
    ,\n        \"workflow_version\": \"1.0\",\n        \n        # Invoice Information\n\
    \        \"invoice_number\": validation_data.get(\"invoice_number\", \"unknown\"\
    ),\n        \"vendor_name\": validation_data.get(\"vendor_name\", \"unknown\"\
    ),\n        \"invoice_date\": validation_data.get(\"date\", \"unknown\"),\n  \
    \      \"po_number\": validation_data.get(\"po_number\", \"\"),\n        \"invoice_amount\"\
    : validation_data.get(\"amount\", 0),\n        \"line_items_count\": validation_data.get(\"\
    total_items\", 0),\n        \n        # SAP Information\n        \"sap_posting_status\"\
    : sap_data.get(\"posting_status\", \"unknown\"),\n        \"sap_document_number\"\
    : sap_data.get(\"sap_document_number\", \"unknown\"),\n        \"vendor_code\"\
    : sap_data.get(\"vendor_code\", \"unknown\"),\n        \"amount_posted\": sap_data.get(\"\
    amount_posted\", 0),\n        \"sap_posting_date\": sap_data.get(\"posting_date\"\
    , \"unknown\"),\n        \"document_type\": sap_data.get(\"document_type\", \"\
    unknown\"),\n        \n        # Processing Summary\n        \"overall_processing_status\"\
    : summary_data.get(\"processing_status\", \"unknown\"),\n        \"processing_time\"\
    : summary_data.get(\"processing_time\", \"unknown\"),\n        \"automation_level\"\
    : summary_data.get(\"automation_level\", \"unknown\"),\n        \n        # Workflow\
    \ Metadata\n        \"image_url\": \"${image_url_data}\",\n        \"notification_email\"\
    : \"${notification_email}\",\n        \"processed_by\": \"automated_workflow\"\
    ,\n        \"processing_node\": \"supabase_upload\"\n    }\n    \n    # Create\
    \ DataFrame\n    df = pd.DataFrame([processing_data])\n    \n    # Generate CSV\
    \ content\n    csv_buffer = io.StringIO()\n    df.to_csv(csv_buffer, index=False)\n\
    \    csv_content = csv_buffer.getvalue()\n    \n    # Generate unique filename\n\
    \    timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n    invoice_num =\
    \ processing_data[\"invoice_number\"].replace(\"/\", \"_\").replace(\"\\\\\",\
    \ \"_\")\n    filename = f\"invoice_processing_{invoice_num}_{timestamp}.csv\"\
    \n    \n    print(f\"Generated CSV file: {filename}\")\n    print(f\"CSV content\
    \ size: {len(csv_content)} characters\")\n    print(f\"Data rows: {len(df)}\"\
    )\n    \n    # Upload to Supabase Storage\n    try:\n        # Upload file to\
    \ the ubspublic bucket with public access\n        storage_response = supabase.storage.from_(\"\
    ubspublic\").upload(\n            filename, \n            csv_content.encode('utf-8'),\n\
    \            file_options={\n                \"content-type\": \"text/csv\",\n\
    \                \"cache-control\": \"3600\"\n            }\n        )\n     \
    \   \n        print(f\"File uploaded successfully to Supabase\")\n        \n \
    \       # Get public URL\n        public_url = supabase.storage.from_(\"ubspublic\"\
    ).get_public_url(filename)\n        \n        upload_result = {\n            \"\
    upload_status\": \"success\",\n            \"bucket_name\": \"ubspublic\",\n \
    \           \"filename\": filename,\n            \"file_size_bytes\": len(csv_content.encode('utf-8')),\n\
    \            \"public_url\": public_url,\n            \"upload_timestamp\": datetime.now().isoformat(),\n\
    \            \"records_uploaded\": len(df),\n            \"data_summary\": {\n\
    \                \"invoice_number\": processing_data[\"invoice_number\"],\n  \
    \              \"vendor_name\": processing_data[\"vendor_name\"],\n          \
    \      \"amount\": processing_data[\"invoice_amount\"],\n                \"sap_document\"\
    : processing_data[\"sap_document_number\"],\n                \"processing_status\"\
    : processing_data[\"overall_processing_status\"]\n            },\n           \
    \ \"csv_structure\": {\n                \"columns\": list(df.columns),\n     \
    \           \"column_count\": len(df.columns),\n                \"row_count\"\
    : len(df)\n            }\n        }\n        \n        print(f\"Upload completed\
    \ successfully\")\n        print(f\"Public URL: {public_url}\")\n        print(f\"\
    Records uploaded: {len(df)}\")\n        \n    except Exception as upload_error:\n\
    \        print(f\"Error uploading to Supabase: {upload_error}\")\n        \n \
    \       # Return error result\n        upload_result = {\n            \"upload_status\"\
    : \"failed\",\n            \"error_message\": str(upload_error),\n           \
    \ \"bucket_name\": \"ubspublic\",\n            \"filename\": filename,\n     \
    \       \"attempted_upload_timestamp\": datetime.now().isoformat(),\n        \
    \    \"records_prepared\": len(df),\n            \"data_summary\": {\n       \
    \         \"invoice_number\": processing_data[\"invoice_number\"],\n         \
    \       \"vendor_name\": processing_data[\"vendor_name\"],\n                \"\
    amount\": processing_data[\"invoice_amount\"]\n            }\n        }\n\nexcept\
    \ Exception as e:\n    print(f\"Critical error in Supabase upload process: {e}\"\
    )\n    \n    # Return critical error result\n    upload_result = {\n        \"\
    upload_status\": \"critical_error\",\n        \"error_message\": str(e),\n   \
    \     \"bucket_name\": \"ubspublic\",\n        \"error_timestamp\": datetime.now().isoformat(),\n\
    \        \"workflow_id\": \"simple_invoice_processing_workflow\"\n    }\n\nprint(f\"\
    __OUTPUTS__ {json.dumps(upload_result)}\")\n"
  outputs:
  - name: supabase_upload_result
    value: Supabase upload operation completed
  - name: csv_file_info
    value: CSV file information and public URL
  - name: upload_summary
    value: Upload operation summary and status
  packages:
  - supabase==2.4.4
  - pandas==2.0.3
  - python-dotenv==1.0.0
  depends_on:
  - post_to_sap
  - generate_summary
  - validate_data
  description: Upload invoice processing data to Supabase bucket as CSV
  previous_node: generate_summary
  timeout_seconds: 120
inputs:
- name: image_url_data
  type: string
  required: true
  description: URL or path to the scanned invoice image
- name: notification_email
  type: string
  default: sarfraz@ampcome.com
  required: false
  description: Email for notifications
outputs:
- name: processing_summary
  type: object
  source: generate_summary
  description: Final processing summary
- name: invoice_data
  type: object
  source: validate_data
  description: Extracted invoice data
- name: sap_result
  type: object
  source: post_to_sap
  description: SAP posting results
version: '1.0'
metadata:
  author: Finance Team
  version: '1.0'
  environment: production
  last_updated: '2024-07-15'
triggers:
- name: Manual Invoice Processing
  type: manual
  description: Manually trigger invoice processing
- name: Invoice Upload
  path: /webhook/invoice-upload
  type: webhook
  config:
    method: POST
    headers:
    - name: Content-Type
      value: application/json
      required: true
    authentication: none
  description: Trigger on invoice upload
  input_mapping:
  - webhook_field: image_url
    workflow_input: image_url_data
namespace: finance
description: Streamlined invoice processing with OCR, validation, and SAP posting
model_clients:
- id: invoice_extractor
  config:
    model: gpt-4o-mini
    api_key: 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
    max_tokens: 2000
    temperature: 0.1
  provider: openai
timeout_seconds: 1200
Execution ID Status Started Duration Actions
b327355a... COMPLETED 2025-07-16
07:18:37
N/A View
f00f9c04... COMPLETED 2025-07-16
06:58:12
N/A View
f0b0ef37... COMPLETED 2025-07-16
04:44:13
N/A View
ea84b02a... COMPLETED 2025-07-16
04:42:17
N/A View
53e36be0... COMPLETED 2025-07-16
04:38:55
N/A View
b2a1eb46... COMPLETED 2025-07-16
04:36:46
N/A View
4db394da... COMPLETED 2025-07-16
04:32:10
N/A View
ad0450f3... COMPLETED 2025-07-16
04:23:21
N/A View
de79fa17... COMPLETED 2025-07-16
04:21:16
N/A View
08885492... COMPLETED 2025-07-16
04:18:33
N/A View
73910621... COMPLETED 2025-07-16
04:13:47
N/A View
8b420e50... COMPLETED 2025-07-16
04:06:42
N/A View
82c2b750... COMPLETED 2025-07-16
03:59:24
N/A View
b61d1112... COMPLETED 2025-07-16
03:54:27
N/A View
c8328a65... COMPLETED 2025-07-16
03:49:29
N/A View
c333eced... COMPLETED 2025-07-16
03:46:41
N/A View
f9d88412... COMPLETED 2025-07-16
03:40:28
N/A View
5b20c32b... COMPLETED 2025-07-16
03:38:52
N/A View
23303289... COMPLETED 2025-07-15
21:10:11
N/A View
ddaa9ad9... COMPLETED 2025-07-15
21:05:36
N/A View