{
  "nodes": [
    {
      "parameters": {
        "content": "**1. Trigger**\nReceives photo message from Telegram bot and downloads the image file",
        "height": 228,
        "width": 264
      },
      "id": "0325534b-b937-4cf5-b361-c7193ab239c0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        2336
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "**2. OCR Processing**\nGemini Vision extracts all visible text from the invoice image",
        "height": 80,
        "width": 260
      },
      "id": "22222a5e-b863-4f15-b8b0-ecc08b00d18e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        336,
        2336
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "**3. Data Extraction**\nLLM parses OCR text into structured JSON with standard invoice fields",
        "height": 80,
        "width": 300
      },
      "id": "4f18f88f-ee17-48a8-a1ae-c934f807e1e2",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        2928
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "**4. Storage & Notify**\nWrites extracted data to Google Sheets and sends Telegram confirmation",
        "height": 80,
        "width": 300
      },
      "id": "8ee34964-3992-4791-a194-796608c900ca",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        2800
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "modelName": "models/gemini-2.5-flash-preview-05-20",
        "options": {}
      },
      "id": "433f85a1-a7e8-47de-8b41-062815be3bf1",
      "name": "Gemini Vision Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        384,
        2576
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        -320,
        2416
      ],
      "id": "c11933ac-16cb-42ea-b1ee-46b5c8d5c6eb",
      "name": "Receive Invoice Photo",
      "notes": "Chat-ID : YOUR_CHAT_ID_2"
    },
    {
      "parameters": {
        "resource": "file",
        "fileId": "={{ $json.message.photo[3].file_id }}",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        -96,
        2416
      ],
      "id": "20289c44-679e-49fe-b5da-52b1255b77fb",
      "name": "Download Photo File"
    },
    {
      "parameters": {
        "chatId": "YOUR_CHAT_ID_1",
        "text": "={{ $json.Message }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        496,
        2640
      ],
      "id": "fe5b1162-21b9-49a0-8448-35686ccb700a",
      "name": "Send Confirmation",
      "alwaysOutputData": false,
      "executeOnce": false,
      "retryOnFail": false
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "Generate a caption for this image.",
        "hasOutputParser": true,
        "messages": {
          "messageValues": [
            {
              "message": "=You are a Precision OCR Engine. Your task is to extract all visible and legible text from images with high accuracy and fidelity. Output only the exact text appearing in the image, maintaining its original case. Preserve the natural reading order (e.g., top-to-bottom, left-to-right). Do not infer, summarize, correct, or add any information not explicitly visible.\n\nIf text is partially visible/illegible, extract only what can be confidently read. \n\nMaintain original line breaks and significant structural spacing where present. If no readable text exists, respond solely with an empty string: \"\". \n\nYour primary objective is an accurate, faithful textual representation of the image."
            },
            {
              "type": "HumanMessagePromptTemplate",
              "messageType": "imageBinary"
            }
          ]
        }
      },
      "id": "3f5fc96c-0a78-47ba-9b7e-db768efbf415",
      "name": "OCR Extract Text",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        352,
        2416
      ],
      "typeVersion": 1.4
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=<parsed-invoice>\n{{ $json.text }}\n</parsed-invoice>",
        "messages": {
          "messageValues": [
            {
              "message": "You are an AI data extractor. Your task is to convert the provided invoice (<parsed-invoice>) text into a single, valid JSON object.\n\n**Key Instructions:**\n\n1.  **Output JSON Only:** Your entire response MUST be the JSON object. No explanations or any other text.\n2.  **Key Creation:**\n  * Keys must be **`snake_case`** and in **English**.\n\n  * **Prioritize these standard keys** (grouped for clarity):\n\n    * **Supplier & Recipient:**\n      * `supplier_name`, `supplier_address`, `supplier_contact_details`, `supplier_tax_id`, `supplier_vat_number`\n      * `recipient_business_name`, `recipient_business_address`\n    * **Invoice Core Details:**\n      * `invoice_number`, `invoice_date`, `purchase_order_number`, `internal_reference_number`\n    * **Payment & Financials:**\n      * `due_date`, `payment_terms`, `supplier_bank_account_details`\n      * `total_amount_due`, `currency_code`\n      * `subtotal_amount`, `tax_amount`, `discount_amount`\n    * **Internal Processing:**\n      * `date_received`, `department_id`, `cost_center_code`, `approver_id`, `approver_name`\n      * `date_paid`, `payment_method`, `payment_reference`, `invoice_status`\n    * **General:**\n      * `notes`\n\n  * If invoice text provides a label (e.g., \"Invoice \\#:\") not matching a standard key, use it for the key (e.g., `invoice_hash`).\n\n  * For unlabeled info not matching a standard key, **create a concise, descriptive, logical `snake_case` key.**\n\n  * If unsure which key to assign, prefix your best guess key with `uncertain_` (e.g., `uncertain_delivery_code`).\n3.  **Value Extraction:**\n  * Line items: array of objects under `line_items`. Item keys: `snake_case` (e.g., `item_description`, `quantity`, `unit_price`, `line_item_total`; also `product_code`, `item_tax_amount` if applicable).\n4.  **Missing Information:** Do not include keys for information not present. Do not invent data.\n\n<parsed-invoice\\>\n\n</parsed-invoice\\>\n\n"
            }
          ]
        }
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.6,
      "position": [
        -320,
        2640
      ],
      "id": "d976ad29-60c7-4eb7-81a2-c1c2fc4ff060",
      "name": "Parse Invoice to JSON"
    },
    {
      "parameters": {
        "modelName": "models/gemini-2.5-flash-preview-05-20",
        "options": {}
      },
      "id": "31a763a3-f491-4a7a-a69a-ba21268fb0c0",
      "name": "Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -272,
        2816
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "252dc06a-a11a-4bd5-b32d-18d700b2ab1c",
              "name": "text",
              "value": "={{ $json.text.replace(/^(```json\\n)?(.*?)(\\n```)?$/s, '$2')}}",
              "type": "object"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        48,
        2640
      ],
      "id": "782d098f-5643-4271-b517-e99d6a55683b",
      "name": "Clean JSON Response"
    },
    {
      "parameters": {
        "authentication": "serviceAccount",
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_BILLING_LEDGER_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Billing_Ledger",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_BILLING_LEDGER_SHEET_ID/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_BILLING_LEDGER_SHEET_ID/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "supplier_name": "={{ $json.text.supplier_name }}",
            "supplier_address": "={{ $json.text.supplier_address }}",
            "invoice_date": "{{ $json.text.invoice_date }}",
            "total_amount_due": "={{ $json.text.total_amount_due }}",
            "currency_code": "={{ $json.text.currency_code }}",
            "subtotal_amount": "={{ $json.text.total_amount_due }}",
            "recipient_business_name": "={{ $json.text.supplier_name }}",
            "payment_method": "={{ $json.text.payment_method }}",
            "date_paid": "={{ $json.text.transaction_date }}",
            "payment_reference": "={{ $json.text.payment_reference }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "invoice_number",
              "displayName": "invoice_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "supplier_name",
              "displayName": "supplier_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invoice_date",
              "displayName": "invoice_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "total_amount_due",
              "displayName": "total_amount_due",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "currency_code",
              "displayName": "currency_code",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "due_date",
              "displayName": "due_date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "invoice_status",
              "displayName": "invoice_status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "purchase_order_number",
              "displayName": "purchase_order_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "subtotal_amount",
              "displayName": "subtotal_amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "tax_amount",
              "displayName": "tax_amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "payment_terms",
              "displayName": "payment_terms",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "date_received",
              "displayName": "date_received",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "supplier_tax_id",
              "displayName": "supplier_tax_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "supplier_vat_number",
              "displayName": "supplier_vat_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "recipient_business_name",
              "displayName": "recipient_business_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "date_paid",
              "displayName": "date_paid",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "payment_method",
              "displayName": "payment_method",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "payment_reference",
              "displayName": "payment_reference",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "discount_amount",
              "displayName": "discount_amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "approver_id",
              "displayName": "approver_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "approver_name",
              "displayName": "approver_name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "department_id",
              "displayName": "department_id",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "cost_center_code",
              "displayName": "cost_center_code",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "supplier_address",
              "displayName": "supplier_address",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "supplier_contact_details",
              "displayName": "supplier_contact_details",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "supplier_bank_account_details",
              "displayName": "supplier_bank_account_details",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "internal_reference_number",
              "displayName": "internal_reference_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "recipient_business_address",
              "displayName": "recipient_business_address",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            },
            {
              "id": "notes",
              "displayName": "notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.6,
      "position": [
        288,
        2768
      ],
      "id": "6ccd2351-11d2-4a74-97e6-f28514662874",
      "name": "Write to Billing Ledger"
    },
    {
      "parameters": {
        "borderWidth": 1,
        "borderHeight": 1,
        "options": {}
      },
      "id": "fca064bc-819c-48d2-a704-533da62e739c",
      "name": "Process Image",
      "type": "n8n-nodes-base.editImage",
      "position": [
        128,
        2416
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1e51a7f2-4bda-4d41-895f-1bacac630ed9",
              "name": "Message",
              "value": "=---\n{{ $now }}\n---\n{{ $json.text.toJsonString().substr(0, 1000).replace(/,/g, ',\\n') }}\n",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        288,
        2656
      ],
      "id": "6ae485c5-042f-4dd0-95e1-f2dbcd85a648",
      "name": "Format Notification"
    },
    {
      "parameters": {
        "content": "## Extract Invoice Data from Telegram Image and Log to Google Sheet\n\n### How it works\n1. A Telegram user sends an image (e.g., a scanned invoice) to the bot.\n2. The workflow downloads the photo, runs a Gemini OCR model to extract raw text.\n3. An LLM chain formats the extracted text into a structured JSON invoice object.\n4. Key fields (supplier, date, amount, currency, etc.) are written to a Google Sheet ledger.\n5. A concise summary with a timestamp is sent back to the user via Telegram.\n\n### Setup\n- [ ] Connect your Telegram bot credentials.\n- [ ] Add a Google Service Account with Sheets write access.\n- [ ] Provide the target Google Sheet ID and sheet name.\n- [ ] Enable the Google Gemini API and add the API key.\n- [ ] Ensure the bot has permission to receive photos.\n- [ ] Test with a sample invoice image to verify JSON mapping.",
        "height": 768,
        "width": 400,
        "color": 7
      },
      "id": "486b429f-c2eb-415a-8e42-7ec82250ea0c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        2240
      ],
      "typeVersion": 1
    }
  ],
  "connections": {
    "Gemini Vision Model": {
      "ai_languageModel": [
        [
          {
            "node": "OCR Extract Text",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Receive Invoice Photo": {
      "main": [
        [
          {
            "node": "Download Photo File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Photo File": {
      "main": [
        [
          {
            "node": "Process Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OCR Extract Text": {
      "main": [
        [
          {
            "node": "Parse Invoice to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Invoice to JSON": {
      "main": [
        [
          {
            "node": "Clean JSON Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Parse Invoice to JSON",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Clean JSON Response": {
      "main": [
        [
          {
            "node": "Write to Billing Ledger",
            "type": "main",
            "index": 0
          },
          {
            "node": "Format Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Image": {
      "main": [
        [
          {
            "node": "OCR Extract Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Notification": {
      "main": [
        [
          {
            "node": "Send Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}