{
 "name": "PDF receipt/invoice upload to CSV log + phone alert (AutomateRecipes)",
 "nodes": [
  {
   "parameters": {
    "formTitle": "Upload a receipt or invoice (PDF)",
    "formDescription": "Text PDFs only (not photos or scans).",
    "formFields": {
     "values": [
      {
       "fieldLabel": "PDF file",
       "fieldType": "file",
       "acceptFileTypes": ".pdf",
       "multipleFiles": false,
       "requiredField": true
      }
     ]
    },
    "options": {},
    "path": "receipt-upload"
   },
   "id": "02d225e5-ce1b-4338-b19c-f0ce6b6d8a6f",
   "name": "Receipt upload form",
   "type": "n8n-nodes-base.formTrigger",
   "typeVersion": 2.2,
   "position": [
    0,
    0
   ],
   "webhookId": "9d96efc2-6e0a-4ad8-b36e-b4b3b30e0212"
  },
  {
   "parameters": {
    "jsCode": "// EDIT THESE SETTINGS\nreturn [{json:{...$input.first().json,\n \"ntfyTopic\": \"CHANGE-ME-to-a-long-random-topic\",\n \"cfAccountId\": \"YOUR_CLOUDFLARE_ACCOUNT_ID\",\n \"kvNamespaceId\": \"YOUR_KV_NAMESPACE_ID\",\n \"csvKey\": \"receipts.csv\"\n}}];"
   },
   "name": "Config",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    240,
    0
   ],
   "id": "2a96cae3-313d-445f-ae65-685d22a39783"
  },
  {
   "parameters": {
    "operation": "pdf",
    "options": {},
    "binaryPropertyName": "={{ Object.keys($binary)[0] }}"
   },
   "name": "Extract PDF text",
   "type": "n8n-nodes-base.extractFromFile",
   "typeVersion": 1,
   "position": [
    720,
    0
   ],
   "id": "05a793cf-313c-41c7-bfc7-40a68e0e4029"
  },
  {
   "parameters": {
    "jsCode": "const t=String($input.first().json.text||'');\nconst g=re=>{const m=t.match(re);return m?m[1].trim():''};\nconst f={invoice_number:g(/(?:Invoice|Receipt|Order) (?:number|no\\.?|#)\\s*[:#]?\\s*([A-Z0-9-]+)/i),supplier:g(/(?:Supplier|From|Vendor|Merchant|Store)\\s*:\\s*(.+)/i),invoice_date:g(/(?:Invoice |Receipt )?date\\s*:\\s*([0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9/.-]+)/i),due_date:g(/Due date\\s*:\\s*([0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9/.-]+)/i),subtotal:g(/Subtotal\\s*:\\s*([0-9.,]+)/i),vat:g(/VAT[^:]*:\\s*([0-9.,]+)/i),total:g(/\\bTotal\\s*:\\s*([0-9.,]+)/i),currency:g(/Currency\\s*:\\s*([A-Z]{3})/)};\nf.source='form upload';f.extracted_at=new Date().toISOString();\nf.needs_review=(!f.invoice_number||!f.total)?'yes':'no';\nreturn [{json:f}];"
   },
   "name": "Parse invoice fields",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    960,
    0
   ],
   "id": "2d936c72-db20-4717-9ea8-95e6e907540e"
  },
  {
   "parameters": {
    "url": "=https://api.cloudflare.com/client/v4/accounts/{{ $('Config').first().json.cfAccountId }}/storage/kv/namespaces/{{ $('Config').first().json.kvNamespaceId }}/values/{{ $('Config').first().json.csvKey }}",
    "authentication": "genericCredentialType",
    "genericAuthType": "httpHeaderAuth",
    "options": {
     "response": {
      "response": {
       "responseFormat": "text",
       "outputPropertyName": "raw",
       "neverError": true
      }
     }
    }
   },
   "name": "Read existing CSV",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    1200,
    0
   ],
   "id": "f762f468-a448-40ac-95b9-2ad1944d3c9d",
   "credentials": {
    "httpHeaderAuth": {
     "id": "REPLACE_WITH_YOUR_CREDENTIAL",
     "name": "Cloudflare API token"
    }
   }
  },
  {
   "parameters": {
    "jsCode": "const f=$('Parse invoice fields').first().json;let csv=String($input.first().json.raw||'');\nif(csv.includes('\"errors\"')||!csv.startsWith('invoice_number'))csv='';\nconst cols=['invoice_number','supplier','invoice_date','due_date','subtotal','vat','total','currency','needs_review','source','extracted_at'];\nconst q=v=>'\"'+String(v??'').replace(/\"/g,'\"\"')+'\"';\nif(!csv)csv=cols.join(',')+'\\n';\nconst dup=csv.split('\\n').some(l=>l.startsWith(q(f.invoice_number)+',')&&f.invoice_number);\nif(!dup)csv+=cols.map(c=>q(f[c])).join(',')+'\\n';\nreturn [{json:{csv,dup,rows:csv.trim().split('\\n').length-1}}];"
   },
   "name": "Append CSV row",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    1440,
    0
   ],
   "id": "43c5b512-0a55-4da8-a407-2342b691f7ed"
  },
  {
   "parameters": {
    "method": "PUT",
    "url": "=https://api.cloudflare.com/client/v4/accounts/{{ $('Config').first().json.cfAccountId }}/storage/kv/namespaces/{{ $('Config').first().json.kvNamespaceId }}/values/{{ $('Config').first().json.csvKey }}",
    "authentication": "genericCredentialType",
    "genericAuthType": "httpHeaderAuth",
    "sendBody": true,
    "contentType": "raw",
    "rawContentType": "text/plain",
    "body": "={{ $json.csv }}",
    "options": {}
   },
   "name": "Save CSV to Cloudflare KV",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    1680,
    0
   ],
   "id": "75c7d5e7-1cab-4d0c-ab77-387c21cab2a5",
   "credentials": {
    "httpHeaderAuth": {
     "id": "REPLACE_WITH_YOUR_CREDENTIAL",
     "name": "Cloudflare API token"
    }
   }
  },
  {
   "parameters": {
    "jsCode": "if($input.first().json.success!==true)throw new Error('KV write failed');const f=$('Parse invoice fields').first().json,a=$('Append CSV row').first().json;\nreturn [{json:{title:(a.dup?'Duplicate invoice skipped: ':'Invoice logged: ')+f.invoice_number,tags:f.needs_review=='yes'?'warning':'receipt',message:`${f.supplier}\\nTotal ${f.total} ${f.currency} (VAT ${f.vat}) due ${f.due_date}\\nNeeds review: ${f.needs_review} \u00b7 CSV rows: ${a.rows}`}}];"
   },
   "name": "Build alert",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    1920,
    0
   ],
   "id": "2c0f371b-9da3-4b72-af72-f755bba5de68"
  },
  {
   "parameters": {
    "method": "POST",
    "url": "=https://ntfy.sh/{{ $('Config').first().json.ntfyTopic }}",
    "sendHeaders": true,
    "headerParameters": {
     "parameters": [
      {
       "name": "Title",
       "value": "={{ $json.title }}"
      },
      {
       "name": "Tags",
       "value": "={{ $json.tags || 'bell' }}"
      }
     ]
    },
    "sendBody": true,
    "contentType": "raw",
    "rawContentType": "text/plain",
    "body": "={{ $json.message }}",
    "options": {}
   },
   "name": "Send ntfy alert",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    2160,
    0
   ],
   "id": "c0242553-49c5-4c32-b0ae-72e8e457f8e6"
  }
 ],
 "connections": {
  "Config": {
   "main": [
    [
     {
      "node": "Parse invoice fields",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Extract PDF text": {
   "main": [
    [
     {
      "node": "Config",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Parse invoice fields": {
   "main": [
    [
     {
      "node": "Read existing CSV",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Read existing CSV": {
   "main": [
    [
     {
      "node": "Append CSV row",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Append CSV row": {
   "main": [
    [
     {
      "node": "Save CSV to Cloudflare KV",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Save CSV to Cloudflare KV": {
   "main": [
    [
     {
      "node": "Build alert",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Build alert": {
   "main": [
    [
     {
      "node": "Send ntfy alert",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Receipt upload form": {
   "main": [
    [
     {
      "node": "Extract PDF text",
      "type": "main",
      "index": 0
     }
    ]
   ]
  }
 },
 "settings": {
  "executionOrder": "v1"
 },
 "active": false,
 "pinData": {}
}