[
  {
    "id": "docExtract01",
    "name": "Automated data extraction from PDF invoices",
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "extract-invoice",
          "responseMode": "responseNode",
          "options": {}
        },
        "id": "d1",
        "name": "Receive document",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          0
        ],
        "webhookId": "extract-hook"
      },
      {
        "parameters": {
          "operation": "pdf",
          "binaryPropertyName": "file",
          "options": {}
        },
        "id": "d2",
        "name": "Read PDF text",
        "type": "n8n-nodes-base.extractFromFile",
        "typeVersion": 1.1,
        "position": [
          250,
          0
        ]
      },
      {
        "parameters": {
          "jsCode": "// Turn the text of a PDF into structured fields, and say what is missing\nconst file = $('Receive document').item.binary.file;\nconst text = String($json.text || '');\nconst find = re => { const m = text.match(re); return m ? m[1].trim() : ''; };\n\n// A PDF with almost no text is usually a scan and needs OCR first\nif (text.trim().length < 30) {\n  return [{ json: { file_name: file.fileName, status: 'needs_ocr', missing: ['all fields'] } }];\n}\n\nconst lines = text.split(/\\r?\\n/).map(l => l.trim()).filter(Boolean);\nconst vendor = lines[0] || '';\nconst invoice_no = find(/invoice\\s*(?:no\\.?|number|#)\\s*[:#]?\\s*([A-Z0-9][A-Z0-9-]{2,})/i) || find(/\\binvoice\\s*[:#]\\s*([A-Z0-9-]{3,})/i);\nconst rawDate = find(/(?:invoice\\s*date|date\\s*of\\s*issue|date)\\s*[:]?\\s*(\\d{4}-\\d{2}-\\d{2}|[A-Za-z]{3,9}\\.?\\s+\\d{1,2},\\s*\\d{4}|\\d{1,2}\\/\\d{1,2}\\/\\d{4})/i);\nconst totals = [...text.matchAll(/\\b(?:total\\s*due|amount\\s*due|balance\\s*due|grand\\s*total|total)\\s*[:]?\\s*(?:USD|EUR|GBP)?\\s*[$€£]?\\s*([\\d,]+\\.\\d{2})/gi)].map(m => m[1]);\nconst total = totals.length ? Number(totals[totals.length - 1].replace(/,/g, '')) : null;   // the last \"total\" is the grand total\nconst parsed = rawDate ? new Date(rawDate + (/^\\d{4}-/.test(rawDate) ? 'T00:00:00Z' : ' UTC')) : null;\nconst date = parsed && !isNaN(parsed) ? parsed.toISOString().slice(0, 10) : '';\nconst currency = /USD|\\$/.test(text) ? 'USD' : /EUR|€/.test(text) ? 'EUR' : /GBP|£/.test(text) ? 'GBP' : '';\n\nconst missing = [];\nif (!invoice_no) missing.push('invoice number');\nif (!date) missing.push('date');\nif (total === null) missing.push('total');\nreturn [{ json: { file_name: file.fileName, vendor, invoice_no, date, total, currency, missing, status: missing.length ? 'needs_review' : 'extracted' } }];"
        },
        "id": "d3",
        "name": "Pull out the fields",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          500,
          0
        ]
      },
      {
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ $json }}",
          "options": {}
        },
        "id": "d4",
        "name": "Respond with the result",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.4,
        "position": [
          750,
          0
        ]
      },
      {
        "parameters": {
          "rules": {
            "values": [
              {
                "conditions": {
                  "options": {
                    "caseSensitive": true,
                    "leftValue": "",
                    "typeValidation": "strict",
                    "version": 2
                  },
                  "conditions": [
                    {
                      "id": "x1",
                      "leftValue": "={{ $json.status }}",
                      "rightValue": "extracted",
                      "operator": {
                        "type": "string",
                        "operation": "equals"
                      }
                    }
                  ],
                  "combinator": "and"
                },
                "renameOutput": true,
                "outputKey": "All fields found"
              }
            ]
          },
          "options": {
            "fallbackOutput": "extra",
            "renameFallbackOutput": "Needs review"
          }
        },
        "id": "d5",
        "name": "Complete?",
        "type": "n8n-nodes-base.switch",
        "typeVersion": 3.2,
        "position": [
          1000,
          0
        ]
      },
      {
        "parameters": {
          "fromEmail": "extractor@360automate.test",
          "toEmail": "ap@360automate.test",
          "subject": "=Extracted: {{ $json.invoice_no }} from {{ $json.vendor }} ({{ $json.currency }} {{ $json.total }})",
          "emailFormat": "text",
          "text": "=Vendor: {{ $json.vendor }}\nInvoice number: {{ $json.invoice_no }}\nDate: {{ $json.date }}\nTotal: {{ $json.currency }} {{ $json.total }}\nFile: {{ $json.file_name }}",
          "options": {
            "appendAttribution": false
          }
        },
        "id": "d6",
        "name": "Send fields to accounts",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1300,
          -110
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      },
      {
        "parameters": {
          "fromEmail": "extractor@360automate.test",
          "toEmail": "review@360automate.test",
          "subject": "=Needs review: {{ $json.file_name }}",
          "emailFormat": "text",
          "text": "=We could not read everything in {{ $json.file_name }}.\nStatus: {{ $json.status }}\nMissing: {{ $json.missing.join(', ') }}\n\nWhat we did find:\nVendor: {{ $json.vendor || '-' }}\nInvoice number: {{ $json.invoice_no || '-' }}\nDate: {{ $json.date || '-' }}\nTotal: {{ $json.total ?? '-' }}",
          "options": {
            "appendAttribution": false
          }
        },
        "id": "d7",
        "name": "Ask a person to check it",
        "type": "n8n-nodes-base.emailSend",
        "typeVersion": 2.1,
        "position": [
          1300,
          110
        ],
        "credentials": {
          "smtp": {
            "id": "mailpitSmtp01",
            "name": "Local Mailpit SMTP"
          }
        }
      }
    ],
    "connections": {
      "Receive document": {
        "main": [
          [
            {
              "node": "Read PDF text",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Read PDF text": {
        "main": [
          [
            {
              "node": "Pull out the fields",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Pull out the fields": {
        "main": [
          [
            {
              "node": "Respond with the result",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respond with the result": {
        "main": [
          [
            {
              "node": "Complete?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Complete?": {
        "main": [
          [
            {
              "node": "Send fields to accounts",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Ask a person to check it",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "settings": {
      "executionOrder": "v1"
    },
    "active": false
  }
]