Visual Workflow

    JSON Code

    {
      "id": "4zHWtizvsvvFg5By",
      "meta": {
        "instanceId": "0023d4f5dfb57fec37ca9d230f69e0e19118190dca2bfc4d821fffd957cc37e7",
        "templateCredsSetupCompleted": true
      },
      "name": "Smart Receipt Organizer",
      "tags": [],
      "nodes": [
        {
          "id": "b092b3b3-989e-4ac3-abc2-f785293eae72",
          "name": "Telegram Trigger",
          "type": "n8n-nodes-base.telegramTrigger",
          "position": [
            -576,
            -416
          ],
          "webhookId": "auto-generated",
          "parameters": {
            "updates": [
              "message",
              "channel_post"
            ],
            "additionalFields": {}
          },
          "credentials": {
            "telegramApi": {
              "id": "xuFTbOQ62YpwhOpY",
              "name": "Telegram account"
            }
          },
          "typeVersion": 1.1
        },
        {
          "id": "867168cc-7ee7-4a42-af40-2e5bd1158f05",
          "name": "Backup to Drive",
          "type": "n8n-nodes-base.googleDrive",
          "position": [
            544,
            -416
          ],
          "parameters": {
            "name": "={{ $json.newFileName }}",
            "driveId": {
              "__rl": true,
              "mode": "list",
              "value": "My Drive"
            },
            "options": {},
            "folderId": {
              "__rl": true,
              "mode": "id",
              "value": "={{ $json.driveFolderId }}"
            }
          },
          "credentials": {
            "googleDriveOAuth2Api": {
              "id": "EkoZTEdJjBuNNGjR",
              "name": "Google Drive account"
            }
          },
          "typeVersion": 3
        },
        {
          "id": "715fa366-277c-4081-b1df-e426a45d195c",
          "name": "Format Data",
          "type": "n8n-nodes-base.code",
          "position": [
            304,
            -416
          ],
          "parameters": {
            "jsCode": "// --------------------------------------------------------\n// CONFIGURATION: PASTE YOUR GOOGLE DRIVE FOLDER IDS HERE\n// --------------------------------------------------------\nconst FOLDER_IDS = {\n  \"Eating Out\":    \"1g9DjOYM9DZIYWziVI5L5MzmLxMgGHB55\",\n  \"Groceries\":     \"16DmSj7kAA2H4PDLy_sOG3C_7a5ufNcUw\",\n  \"Utilities\":     \"1cR6bubHvq_QcP1SGTfmYZ-83k07vPHA4\",\n  \"Transport\":     \"1zr7kNNV030t3a912Q3ZL44cqFWId8sDr\",\n  \"Shopping\":      \"1ekTo1jjZJRccu-SRNouKq2Yl2pEdnvtP\",\n  \"Subscriptions\": \"1xd6SzHSjm197h9SgQTgVhFy02UV-oC4l\",\n  \"Tech & Office\": \"1_ucFcwTlQWeT0zbRH-CJQBnBnhTbt3ox\",\n  \"Medical\":       \"1z-dHiIqLLfI1c_HWsKo7XPloQGBinwj6\",\n  \"Miscellaneous\": \"1edg_WVd9edu1UeynSSyn865vV72VgmtW\" \n};\n\n// --------------------------------------------------------\n// 1. EXTRACT AI RESPONSE\n// --------------------------------------------------------\nconst item = $input.first();\nconst json = item.json;\n\n// Handle Google Gemini's deeply nested structure\nlet aiOutput = json.candidates?.[0]?.content?.parts?.[0]?.text || json.text || json.response;\n\nif (!aiOutput) {\n  throw new Error(\"Could not find text in AI response. Check input structure.\");\n}\n\n// --------------------------------------------------------\n// 2. CLEAN & PARSE JSON\n// --------------------------------------------------------\nif (aiOutput.includes('```')) {\n  aiOutput = aiOutput.split('```')[1];\n  aiOutput = aiOutput.replace(/^json\\s*/i, '');\n}\n\nlet receiptData = {};\ntry {\n  receiptData = JSON.parse(aiOutput.trim());\n} catch (e) {\n  receiptData = { \n    summary: aiOutput, \n    vendor: 'Parse Error', \n    amount: 0, \n    currency: 'CAD',\n    category: 'Miscellaneous' \n  }; \n}\n\n// --------------------------------------------------------\n// 3. LOGIC: FOLDERS, RENAMING & CURRENCY\n// --------------------------------------------------------\n\nconst category = receiptData.category || 'Miscellaneous';\nconst targetFolderId = FOLDER_IDS[category] || FOLDER_IDS['Miscellaneous'];\n\n// --- FILE EXTENSION FIX (PDF/JPG) ---\nconst binaryData = $('Get File').first().binary;\nconst binaryKey = Object.keys(binaryData)[0]; \nconst fileExtension = binaryData[binaryKey].fileExtension || 'jpg';\n\nconst date = receiptData.date || new Date().toISOString().split('T')[0];\nconst safeVendor = (receiptData.vendor || 'Unknown').replace(/[^a-zA-Z0-9]/g, ''); \nconst amount = receiptData.amount || 0;\n\n// Use the detected currency, or default to CAD if AI missed it\nconst currency = (receiptData.currency || 'CAD').toUpperCase();\n\n// Filename includes currency now for clarity: \"2025-11-28_GitHub_20_USD.pdf\"\nconst newFileName = `${date}_${safeVendor}_${amount}_${currency}.${fileExtension}`;\n\n// --------------------------------------------------------\n// 4. RETURN DATA\n// --------------------------------------------------------\nreturn {\n  json: {\n    vendor: receiptData.vendor || 'Unknown',\n    date: date,\n    amount: parseFloat(amount),\n    // Pass the dynamic currency to Sheets\n    currency: currency, \n    category: category,\n    summary: receiptData.summary || '',\n    driveFolderId: targetFolderId,\n    newFileName: newFileName\n  },\n  binary: binaryData\n};"
          },
          "typeVersion": 2
        },
        {
          "id": "ffc80004-d07a-4d8e-8893-f484ea57a232",
          "name": "Save to Sheets",
          "type": "n8n-nodes-base.googleSheets",
          "position": [
            736,
            -416
          ],
          "parameters": {
            "columns": {
              "value": {
                "Date": "={{ $('Format Data').first().json.date }}",
                "Link": "={{ $json.webViewLink }}",
                "Amount": "={{ $('Format Data').first().json.amount }}",
                "Vendor": "={{ $('Format Data').first().json.vendor }}",
                "Category": "={{ $('Format Data').first().json.category }}",
                "Currency": "={{ $('Format Data').first().json.currency }}"
              },
              "schema": [
                {
                  "id": "Date",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "required": false,
                  "displayName": "Date",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                },
                {
                  "id": "Vendor",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "required": false,
                  "displayName": "Vendor",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                },
                {
                  "id": "Amount",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "required": false,
                  "displayName": "Amount",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                },
                {
                  "id": "Currency",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "required": false,
                  "displayName": "Currency",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                },
                {
                  "id": "Category",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "required": false,
                  "displayName": "Category",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                },
                {
                  "id": "Link",
                  "type": "string",
                  "display": true,
                  "removed": false,
                  "required": false,
                  "displayName": "Link",
                  "defaultMatch": false,
                  "canBeUsedToMatch": true
                }
              ],
              "mappingMode": "defineBelow",
              "matchingColumns": [],
              "attemptToConvertTypes": false,
              "convertFieldsToString": false
            },
            "options": {},
            "operation": "append",
            "sheetName": {
              "__rl": true,
              "mode": "name",
              "value": "Sheet1"
            },
            "documentId": {
              "__rl": true,
              "mode": "list",
              "value": "1zdHPccdCdOzoV6TRQwddHdtZ4P-I91fnANsvQs4cvDU",
              "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1zdHPccdCdOzoV6TRQwddHdtZ4P-I91fnANsvQs4cvDU/edit?usp=drivesdk",
              "cachedResultName": "receipt_hub"
            }
          },
          "credentials": {
            "googleSheetsOAuth2Api": {
              "id": "Epz7c0im8OMtFCM4",
              "name": "Google Sheets account 2"
            }
          },
          "typeVersion": 4.5
        },
        {
          "id": "416a7789-a767-41ba-a055-3cbec823c3f1",
          "name": "Send Confirmation",
          "type": "n8n-nodes-base.telegram",
          "position": [
            960,
            -416
          ],
          "webhookId": "a7e3681e-27de-44be-be8d-7c9fce3f8031",
          "parameters": {
            "text": "=āœ… <b>Receipt saved!</b>\n\nšŸ’° Amount: ${{ $('Format Data').first().json.amount }} {{ $('Format Data').first().json.currency }}\nšŸŖ Vendor: {{ $('Format Data').first().json.vendor }}\nšŸ“ Category: {{ $('Format Data').first().json.category }}\nšŸ“… Date: {{ $('Format Data').first().json.date }}\n\nšŸ”— <a href=\"{{ $('Backup to Drive').first().json.webViewLink }}\">View in Drive</a>",
            "chatId": "={{ $('Telegram Trigger').first().json.message.chat.id }}",
            "additionalFields": {
              "parse_mode": "HTML"
            }
          },
          "credentials": {
            "telegramApi": {
              "id": "xuFTbOQ62YpwhOpY",
              "name": "Telegram account"
            }
          },
          "typeVersion": 1.2
        },
        {
          "id": "21949401-5686-4352-9526-c239036a4d4d",
          "name": "Analyze an image",
          "type": "@n8n/n8n-nodes-langchain.googleGemini",
          "position": [
            64,
            -576
          ],
          "parameters": {
            "text": "Analyze this receipt and return ONLY a raw JSON object (no markdown) with fields: \n- vendor\n- date (YYYY-MM-DD)\n- amount (number)\n- currency: Return the 3-letter ISO code (e.g. 'USD', 'CAD', 'EUR'). If the currency symbol is generic ($) and no address is found, assume 'CAD'.\n- summary\n- category: This MUST be exactly one of the following strings: 'Eating Out', 'Groceries', 'Utilities', 'Transport', 'Shopping', 'Subscriptions', 'Tech & Office', 'Medical', 'Miscellaneous'. If you are unsure, use 'Miscellaneous'.",
            "modelId": {
              "__rl": true,
              "mode": "list",
              "value": "models/gemini-2.5-flash",
              "cachedResultName": "models/gemini-2.5-flash"
            },
            "options": {},
            "resource": "image",
            "simplify": false,
            "inputType": "binary",
            "operation": "analyze"
          },
          "credentials": {
            "googlePalmApi": {
              "id": "p84bEFwG3jhgIwIp",
              "name": "Google Gemini(PaLM) Api account"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "795539c8-d668-4341-ba38-5c39eeb8ac0d",
          "name": "If",
          "type": "n8n-nodes-base.if",
          "position": [
            -144,
            -416
          ],
          "parameters": {
            "options": {},
            "conditions": {
              "options": {
                "version": 2,
                "leftValue": "",
                "caseSensitive": true,
                "typeValidation": "strict"
              },
              "combinator": "and",
              "conditions": [
                {
                  "id": "39bc76ff-01b1-4699-8db3-00faa20e5a82",
                  "operator": {
                    "type": "string",
                    "operation": "contains"
                  },
                  "leftValue": "={{ $input.first().binary.data.mimeType }}",
                  "rightValue": "image"
                }
              ]
            }
          },
          "typeVersion": 2.2
        },
        {
          "id": "29857232-9d49-48c4-9dbf-2edb6717deff",
          "name": "Analyze a file",
          "type": "@n8n/n8n-nodes-langchain.googleGemini",
          "position": [
            64,
            -304
          ],
          "parameters": {
            "text": "Analyze this receipt and return ONLY a raw JSON object (no markdown) with fields: \n- vendor\n- date (YYYY-MM-DD)\n- amount (number)\n- currency: Return the 3-letter ISO code (e.g. 'USD', 'CAD', 'EUR'). If the currency symbol is generic ($) and no address is found, assume 'CAD'.\n- summary\n- category: This MUST be exactly one of the following strings: 'Eating Out', 'Groceries', 'Utilities', 'Transport', 'Shopping', 'Subscriptions', 'Tech & Office', 'Medical', 'Miscellaneous'. If you are unsure, use 'Miscellaneous'.",
            "modelId": {
              "__rl": true,
              "mode": "list",
              "value": "models/gemini-2.5-flash",
              "cachedResultName": "models/gemini-2.5-flash"
            },
            "options": {},
            "resource": "document",
            "simplify": false,
            "inputType": "binary"
          },
          "credentials": {
            "googlePalmApi": {
              "id": "p84bEFwG3jhgIwIp",
              "name": "Google Gemini(PaLM) Api account"
            }
          },
          "typeVersion": 1
        },
        {
          "id": "4402927e-3d54-4282-8ba7-ea1e84fcfb2c",
          "name": "Get File",
          "type": "n8n-nodes-base.telegram",
          "position": [
            -368,
            -416
          ],
          "webhookId": "cbe76ba7-fcb6-445a-894a-9c7088f8822f",
          "parameters": {
            "fileId": "={{ $json.message.photo ? $json.message.photo[$json.message.photo.length - 1].file_id : $json.message.document.file_id }}",
            "resource": "file",
            "additionalFields": {}
          },
          "credentials": {
            "telegramApi": {
              "id": "xuFTbOQ62YpwhOpY",
              "name": "Telegram account"
            }
          },
          "typeVersion": 1.2
        }
      ],
      "active": true,
      "pinData": {},
      "settings": {
        "executionOrder": "v1"
      },
      "versionId": "91550b15-bca1-4aec-a1ea-6779c9ef35ed",
      "connections": {
        "If": {
          "main": [
            [
              {
                "node": "Analyze an image",
                "type": "main",
                "index": 0
              }
            ],
            [
              {
                "node": "Analyze a file",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Get File": {
          "main": [
            [
              {
                "node": "If",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Format Data": {
          "main": [
            [
              {
                "node": "Backup to Drive",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Analyze a file": {
          "main": [
            [
              {
                "node": "Format Data",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Save to Sheets": {
          "main": [
            [
              {
                "node": "Send Confirmation",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Backup to Drive": {
          "main": [
            [
              {
                "node": "Save to Sheets",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Analyze an image": {
          "main": [
            [
              {
                "node": "Format Data",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Telegram Trigger": {
          "main": [
            [
              {
                "node": "Get File",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      }
    }