{
  "name": "Telegram Expense Tracker Template",
  "nodes": [
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json).filter(r => r.expense_row);\nif (rows.length === 0) throw new Error('No expenses to delete.');\nconst last = rows[rows.length - 1];\nconst urlMatch = String(last.receipt_url || '').match(/\\/d\\/([^/?]+)/);\nconst fileId = urlMatch ? urlMatch[1] : null;\nreturn [{\n  json: {\n    vendor: last.vendor,\n    expense_date: last.expense_date,\n    delete_row: parseInt(last.expense_row),\n    delete_file_id: fileId,\n    delete_sheet_id: $('Look Up User (Delete)').first().json.sheet_id\n  }\n}];"
      },
      "id": "d6ba2c9f-4736-4e0b-85e0-bcabbe60cf9e",
      "name": "Find Last Expense Row",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        16576,
        4256
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "temu-cond-delete",
              "leftValue": "={{ $json.message_type }}",
              "rightValue": "delete",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8bb3229f-611c-42f5-99b9-5ce7ca856cc1",
      "name": "Is Delete Command",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        16496,
        4016
      ]
    },
    {
      "parameters": {
        "operation": "delete",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.delete_sheet_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "expenses",
          "mode": "name"
        },
        "startIndex": "={{ parseInt($json.delete_row) }}\n"
      },
      "id": "7427e21a-1b7f-4b89-ba05-b59e337f847d",
      "name": "Delete Sheet Row",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        16336,
        4480
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "operation": "deleteFile",
        "fileId": {
          "__rl": true,
          "value": "={{ $('Find Last Expense Row').first().json.delete_file_id }}\n",
          "mode": "id"
        },
        "options": {}
      },
      "id": "1464a2a8-8909-4b1c-be57-cac041fc7363",
      "name": "Delete Drive File",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        16592,
        4480
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Extract Message Context').first().json.chat_id }}",
        "text": "={{ `🗑 Deleted: ${$('Find Last Expense Row').first().json.vendor} (${$('Find Last Expense Row').first().json.expense_date})` }}\n",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "a1d28c80-ea5a-4a84-aed5-bf1266fa73dc",
      "name": "Send Deleted Confirmation",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        16192,
        4704
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "id": "265eb587-239e-42ca-8f4c-b27cdca0eb2b",
      "name": "Telegram Receipt Trigger",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        16064,
        3664
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst message = item.json.message || {};\nconst text = String(message.text || '').trim();\nconst photoList = Array.isArray(message.photo) ? message.photo : [];\nconst largestPhoto = photoList.length ? photoList[photoList.length - 1] : null;\nconst documentFile = message.document || null;\nconst selectedFile = largestPhoto || documentFile;\nconst mimeType = String(documentFile?.mime_type || '').toLowerCase();\nconst isImageDocument = mimeType.startsWith('image/');\nconst isSupported = Boolean(largestPhoto) || Boolean(documentFile && isImageDocument);\nconst isStart = text === '/start' || text.startsWith('/start ');\nconst isReceipt = Boolean(selectedFile) && isSupported;\nconst isDelete = text.toLowerCase() === 'delete';\nconst setCurrencyMatch = text.match(/^set currency\\s+([A-Za-z]{3})$/i);\nconst isSetCurrency = Boolean(setCurrencyMatch);\nconst setCurrencyCode = setCurrencyMatch ? setCurrencyMatch[1].toUpperCase() : null;\n\nreturn [{\n  json: {\n    submitted_at: new Date().toISOString(),\n    chat_id: message.chat?.id || '',\n    message_id: message.message_id || '',\n    user_id: String(message.from?.id || ''),\n    username: message.from?.username || message.from?.first_name || '',\n    first_name: message.from?.first_name || '',\n    last_name: message.from?.last_name || '',\n    caption: String(message.caption || '').trim(),\n    message_type: isStart ? 'start' : isDelete ? 'delete' : isSetCurrency ? 'set_currency' : isReceipt ? 'receipt' : 'unknown',\n    set_currency_code: setCurrencyCode,\n    has_receipt: isReceipt,\n    mime_type: mimeType || 'image/jpeg',\n    source_file_name: String(documentFile?.file_name || ''),\n    telegram_file_id: String(selectedFile?.file_id || ''),\n    failure_reason: ''\n  }\n}];"
      },
      "id": "0b546721-983b-4970-81d9-0d43a63251f2",
      "name": "Extract Message Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        16080,
        3888
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "temu-cond-msgtype",
              "leftValue": "={{ $json.message_type }}",
              "rightValue": "start",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "51120661-7ece-4678-902f-b49584862a0e",
      "name": "Is Start Command",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        16496,
        3808
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "users",
          "mode": "name"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "user_id",
              "lookupValue": "={{ $json.user_id }}"
            }
          ]
        },
        "options": {}
      },
      "id": "59dd49ad-19ce-4695-bb84-8859bb4ff007",
      "name": "Check User Exists (Start)",
      "alwaysOutputData": true,
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        16688,
        3792
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "temu-cond-exists",
              "leftValue": "={{ $json.user_id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "65c18a06-9376-47ae-982c-8112a54b79ff",
      "name": "Already Registered",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        16864,
        3792
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $('Extract Message Context').first().json.chat_id }}",
        "text": "You're already set up! Just send me a photo of any receipt and I'll handle the rest.",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "8b8ce30d-4ba3-4a91-b27a-36872d54dda7",
      "name": "Send Already Registered",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        16864,
        3584
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const ctx = $('Extract Message Context').first().json;\n\nreturn [{\n  json: {\n    username: (ctx.username || 'user') + '_' + String(ctx.user_id),\n    user_id: String(ctx.user_id),\n    chat_id: String(ctx.chat_id),\n    submitted_at: ctx.submitted_at,\n    first_name: ctx.first_name || '',\n    last_name: ctx.last_name || ''\n  }\n}];"
      },
      "id": "cc4ed24e-7fa0-45fd-b0d3-058b9eaa5e71",
      "name": "Set User Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        17056,
        3792
      ]
    },
    {
      "parameters": {
        "resource": "folder",
        "name": "={{ $json.username }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "REPLACE_WITH_RECEIPTS_FOLDER_ID",
          "mode": "id"
        },
        "options": {}
      },
      "id": "bab48b62-6c30-46e1-8f0d-e244288bd430",
      "name": "Create User Receipt Folder",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        17248,
        3792
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "resource": "spreadsheet",
        "title": "={{ `${$('Set User Context').first().json.username} - Expense Tracker` }}",
        "sheetsUi": {
          "sheetValues": [
            {
              "title": "expenses"
            }
          ]
        },
        "options": {}
      },
      "id": "b14da15c-9bed-4a58-be36-96b5f6116201",
      "name": "Create User Expense Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        17440,
        3792
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Create User Expense Sheet').first().json.spreadsheetId }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "expenses",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "submitted_at": "submitted_at",
            "expense_date": "expense_date",
            "vendor": "vendor",
            "currency": "currency",
            "amount_gross": "amount_gross",
            "vat_amount": "vat_amount",
            "amount_net": "amount_net",
            "description": "description",
            "type": "type",
            "receipt_file_name": "receipt_file_name",
            "receipt_url": "receipt_url",
            "confidence": "confidence",
            "status": "status",
            "raw_json": "raw_json",
            "telegram_chat_id": "telegram_chat_id",
            "telegram_message_id": "telegram_message_id",
            "telegram_user_id": "telegram_user_id",
            "base_currency": "base_currency",
            "amount_gross_converted": "amount_gross_converted",
            "vat_amount_converted": "vat_amount_converted",
            "amount_net_converted": "amount_net_converted",
            "first_name": "first_name",
            "last_name": "last_name",
            "org_name": "org_name"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "44fdb7c8-5651-411d-b6ca-8fe6c40e72fc",
      "name": "Add Sheet Headers",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        18048,
        3536
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "users",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "user_id": "={{ $('Set User Context').first().json.user_id }}",
            "username": "={{ $('Set User Context').first().json.username }}",
            "sheet_id": "={{ $('Create User Expense Sheet').first().json.spreadsheetId }}",
            "folder_id": "={{ $('Create User Receipt Folder').first().json.id }}",
            "created_at": "={{ $('Set User Context').first().json.submitted_at }}",
            "first_name": "={{ $('Set User Context').first().json.first_name }}",
            "last_name": "={{ $('Set User Context').first().json.last_name }}"
          },
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "7c350dd6-a02e-441a-ab32-a2748fa95c53",
      "name": "Register User in Master Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        17824,
        3776
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Set User Context').first().json.chat_id }}",
        "text": "Welcome! 👋 I'm your expense tracker bot.\n\nJust send me a photo of any receipt and I'll automatically:\n• Extract the expense details\n• Save it to your personal spreadsheet\n• Store the receipt image\n\nYou're all set — send your first receipt whenever you're ready!",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "87b25293-371b-4ea3-9117-6ac7714b1e0b",
      "name": "Send Welcome Message",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        18064,
        3776
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "temu-cond-receipt",
              "leftValue": "={{ $json.has_receipt }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "0b6a73e5-c2ae-4522-802e-684eb4490a81",
      "name": "Has Receipt",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        16960,
        4032
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chat_id }}",
        "text": "Please send a photo of a receipt. If you're new, send /start first.",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "259d890d-5b44-4f8d-83d9-4099a7224c9d",
      "name": "Send Usage Guidance",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        16960,
        4224
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "users",
          "mode": "name"
        },
        "filtersUI": {
          "values": [
            {
              "lookupColumn": "user_id",
              "lookupValue": "={{ $json.user_id }}"
            }
          ]
        },
        "options": {}
      },
      "id": "5dbd9176-687b-4c37-85de-5f45d5b7821e",
      "name": "Look Up User",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        17152,
        4016
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "temu-cond-registered",
              "leftValue": "={{ $json.sheet_id }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "d1ea35a1-dd18-4f91-b4d1-84f5ec1b2352",
      "name": "Is Registered",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        17328,
        4016
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $('Extract Message Context').first().json.chat_id }}",
        "text": "You're not registered yet. Send /start to set up your account first.",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "fa261345-8fc3-45fe-947b-7c0fea634335",
      "name": "Send Not Registered",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        17328,
        4208
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "resource": "file",
        "fileId": "={{ $('Extract Message Context').first().json.telegram_file_id }}",
        "additionalFields": {}
      },
      "id": "5667a924-b315-4264-9754-6ca892194b29",
      "name": "Download Receipt File",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        17600,
        4000
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/files",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "purpose",
              "value": "vision"
            },
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "data"
            }
          ]
        },
        "options": {}
      },
      "id": "b553b8d2-4042-4546-a844-f7104edd775c",
      "name": "Upload Receipt to OpenAI",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        17824,
        4000
      ],
      "credentials": {
        "openAiApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst ctx = $('Extract Message Context').first().json;\nconst user = $('Look Up User').first().json;\n\nconst fileId = $('Upload Receipt to OpenAI').first().json.id;\nif (!fileId) {\n  throw new Error('OpenAI file upload did not return a file ID.');\n}\n\nconst prompt = [\n  'You are a receipt extraction engine.',\n  '',\n  'Task:',\n  'Extract expense data from the provided receipt image and return only JSON matching the supplied schema.',\n  '',\n  'Rules:',\n  '- Do not chat.',\n  '- Do not explain.',\n  '- Do not include any text outside the JSON schema response.',\n  '- Be conservative. If a field is unclear, return null.',\n  '- Never invent missing values.',\n  '- If the image is not a receipt, return null for all extractable fields, return an empty line_items array, set confidence to 0, and set requires_confirmation to true.',\n  '- Normalize expense_date to YYYY-MM-DD when the date is clear.',\n  '- vendor is the merchant or business name shown on the receipt.',\n  '- currency must be a 3-letter ISO currency code when clear; otherwise null.',\n  '- amount_gross is the total paid including VAT or tax.',\n  '- vat_amount is the VAT or tax amount only when explicitly shown or clearly derivable from the receipt.',\n  '- amount_net is the total excluding VAT or tax only when explicitly shown or safely derivable; otherwise null.',\n  '- description must be a short expense label such as Taxi, Lunch, Hotel, Office supplies, Software, or Parking.',\n  '- type should be the most specific useful expense class you can infer from the receipt.',\n  '- Set requires_confirmation to true if vendor, expense_date, currency, or amount_gross is unclear.',\n  '- Lower confidence when the image is blurry, cropped, low-resolution, partially visible, handwritten, or ambiguous.',\n  '- If line items are visible and legible, extract them. Otherwise return an empty array.',\n  '',\n  'Output contract:',\n  'Return valid JSON only, matching the schema exactly.'\n].join('\\n');\n\nconst schema = {\n  type: 'object',\n  additionalProperties: false,\n  properties: {\n    expense_date: { type: ['string', 'null'], description: 'Receipt date in YYYY-MM-DD format when clearly visible.' },\n    vendor: { type: ['string', 'null'], description: 'Merchant or business name printed on the receipt.' },\n    currency: { type: ['string', 'null'], description: 'Three-letter ISO currency code such as EUR, USD, or MYR when clear.' },\n    amount_gross: { type: ['number', 'null'], description: 'Total amount paid including VAT or tax.' },\n    vat_amount: { type: ['number', 'null'], description: 'VAT or tax amount only when explicitly shown or safely derivable.' },\n    amount_net: { type: ['number', 'null'], description: 'Net amount excluding VAT or tax only when explicit or safely derivable.' },\n    description: { type: ['string', 'null'], description: 'Short expense label for human review.' },\n    type: { type: ['string', 'null'], description: 'Useful expense category inferred from the receipt.' },\n    confidence: { type: 'number', description: 'Number from 0 to 1 representing extraction confidence.' },\n    requires_confirmation: { type: 'boolean', description: 'True when a user should confirm the extraction before saving.' },\n    review_reason: { type: ['string', 'null'], description: 'Short reason for low confidence or required confirmation.' },\n    line_items: {\n      type: 'array',\n      items: {\n        type: 'object',\n        additionalProperties: false,\n        properties: {\n          name: { type: ['string', 'null'], description: 'Item label exactly or closely matching the receipt.' },\n          quantity: { type: ['number', 'null'], description: 'Quantity when visible.' },\n          unit_price: { type: ['number', 'null'], description: 'Per-item price when visible.' },\n          line_total: { type: ['number', 'null'], description: 'Line total when visible.' }\n        },\n        required: ['name', 'quantity', 'unit_price', 'line_total']\n      }\n    }\n  },\n  required: ['expense_date', 'vendor', 'currency', 'amount_gross', 'vat_amount', 'amount_net', 'description', 'type', 'confidence', 'requires_confirmation', 'review_reason', 'line_items']\n};\n\nreturn [{\n  json: {\n    ...ctx,\n    sheet_id: user.sheet_id,\n    folder_id: user.folder_id,\n    openai_model: 'gpt-4.1-mini',\n    openai_input: [\n      {\n        role: 'system',\n        content: [{ type: 'input_text', text: prompt }]\n      },\n      {\n        role: 'user',\n        content: [\n          { type: 'input_text', text: `Caption: ${ctx.caption || '(none)'}` },\n          { type: 'input_image', file_id: fileId }\n        ]\n      }\n    ],\n    text_format: {\n      format: {\n        type: 'json_schema',\n        name: 'expense_receipt',\n        strict: true,\n        schema\n      }\n    }\n  },\n  binary: item.binary\n}];"
      },
      "id": "a6f1d1b4-62d2-4881-9275-466ef0594550",
      "name": "Prepare OpenAI Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        18000,
        4000
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/responses",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ ({ model: $json.openai_model, input: $json.openai_input, text: $json.text_format }) }}",
        "options": {}
      },
      "id": "cabae5a3-a38c-4cb6-88ec-0a7b63329035",
      "name": "Read Receipt with OpenAI",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        18240,
        4000
      ],
      "credentials": {
        "openAiApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const upstream = $('Prepare OpenAI Request').first();\nconst response = $input.first().json;\n\nfunction parseStructuredOutput(payload) {\n  if (typeof payload?.output_text === 'string' && payload.output_text.trim()) {\n    return JSON.parse(payload.output_text);\n  }\n  const output = Array.isArray(payload?.output) ? payload.output : [];\n  for (const item of output) {\n    const content = Array.isArray(item?.content) ? item.content : [];\n    for (const part of content) {\n      if (typeof part?.text === 'string' && part.text.trim()) {\n        return JSON.parse(part.text);\n      }\n    }\n  }\n  throw new Error('No structured output returned from OpenAI.');\n}\n\nlet parsed;\nlet status = 'ready_to_save';\nlet reviewReason = null;\n\ntry {\n  parsed = parseStructuredOutput(response);\n} catch (error) {\n  parsed = {\n    expense_date: null, vendor: null, currency: null, amount_gross: null,\n    vat_amount: null, amount_net: null, description: null, type: null,\n    confidence: 0, requires_confirmation: true,\n    review_reason: 'Could not read this receipt. Please resend a clearer image.',\n    line_items: []\n  };\n  status = 'needs_review';\n  reviewReason = String(error.message || error);\n}\n\nconst confidence = Number(parsed.confidence || 0);\nif (parsed.requires_confirmation || confidence < 0.85) status = 'needs_review';\n\nconst expenseDate = parsed.expense_date || upstream.json.submitted_at.slice(0, 10);\nconst vendor = parsed.vendor || 'Unknown vendor';\nconst baseCurrency = $('Look Up User').first().json.base_currency || null;\nconst currency = parsed.currency || baseCurrency || 'UNKNOWN';\nconst amountGross = parsed.amount_gross;\nconst vatAmount = parsed.vat_amount;\nconst amountNet = parsed.amount_net ?? amountGross;\nconst description = parsed.description || 'Receipt expense';\nconst expenseType = parsed.type || 'Unclassified';\nconst reason = parsed.review_reason || reviewReason || (status === 'needs_review' ? 'Low confidence extraction.' : '');\n\nreturn [{\n  json: {\n    ...upstream.json,\n    expense_date: expenseDate,\n    vendor, currency,\n    amount_gross: amountGross,\n    vat_amount: vatAmount,\n    amount_net: amountNet,\n    description,\n    type: expenseType,\n    confidence,\n    requires_confirmation: status === 'needs_review',\n    review_reason: reason,\n    line_items: parsed.line_items || [],\n    extraction_status: status,\n    raw_json: JSON.stringify(parsed)\n  },\n  binary: upstream.binary\n}];"
      },
      "id": "7f36c3ea-6c5d-45b2-9cda-e81fa73d0059",
      "name": "Parse Extraction Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        18464,
        4000
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Parse Extraction Result').first().json.sheet_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "expenses",
          "mode": "name"
        },
        "options": {}
      },
      "id": "d4065818-47e4-4d98-b775-d3d98d868d17",
      "name": "Read User Expense Sheet",
      "alwaysOutputData": true,
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        18672,
        4000
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const receipt = $('Parse Extraction Result').first();\nconst rows = $input.all().map(item => item.json);\nconst populatedRows = rows.filter(row => Object.values(row).some(value => String(value ?? '').trim() !== ''));\nconst nextRowNumber = populatedRows.length + 2;\n\nconst datePart = String(receipt.json.expense_date || receipt.json.submitted_at.slice(0, 10)).replace(/[^0-9-]/g, '').slice(0, 10) || 'undated';\nconst vendorSlug = String(receipt.json.vendor || 'unknown-vendor')\n  .toLowerCase()\n  .replace(/[^a-z0-9]+/g, '-')\n  .replace(/^-+|-+$/g, '')\n  .slice(0, 40) || 'unknown-vendor';\nconst extension = String(receipt.json.mime_type || '').split('/')[1] || 'jpg';\nconst fileName = `${datePart}_row-${nextRowNumber}_${vendorSlug}.${extension}`;\n\nreturn [{\n  json: {\n    ...receipt.json,\n    expense_row: nextRowNumber,\n    receipt_file_name: fileName\n  },\n  binary: $('Download Receipt File').first().binary\n}];"
      },
      "id": "a5c2ac57-3908-4e4b-bb19-8a09c611b5e5",
      "name": "Build Expense Record",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        18880,
        4000
      ]
    },
    {
      "parameters": {
        "name": "={{ $json.receipt_file_name }}",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "={{ $json.folder_id }}",
          "mode": "id"
        },
        "options": {}
      },
      "id": "a4fecdf6-d174-4ded-b416-4d00644f6aa9",
      "name": "Upload Receipt to Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        19104,
        4000
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const expense = $('Build Expense Record').first();\nconst drive = $input.first().json;\nconst driveUrl = drive.webViewLink || drive.webContentLink || '';\n\nconst baseCurrency = $('Look Up User').first().json.base_currency || null;\nconst receiptCurrency = expense.json.currency;\nlet convertedGross = null, convertedVat = null, convertedNet = null;\n\nif (baseCurrency && baseCurrency !== receiptCurrency) {\n  try {\n    const rates = $('Get Exchange Rate').first().json.rates || {};\n    const rate = rates[baseCurrency];\n    if (rate) {\n      convertedGross = expense.json.amount_gross != null ? Math.round(expense.json.amount_gross * rate * 100) / 100 : null;\n      convertedVat = expense.json.vat_amount != null ? Math.round(expense.json.vat_amount * rate * 100) / 100 : null;\n      convertedNet = expense.json.amount_net != null ? Math.round(expense.json.amount_net * rate * 100) / 100 : null;\n    }\n  } catch(e) {}\n}\n\nreturn [{\n  json: {\n    expense_row: expense.json.expense_row,\n    submitted_at: expense.json.submitted_at,\n    expense_date: expense.json.expense_date,\n    vendor: expense.json.vendor,\n    currency: expense.json.currency,\n    amount_gross: expense.json.amount_gross,\n    vat_amount: expense.json.vat_amount,\n    amount_net: expense.json.amount_net,\n    description: expense.json.description,\n    type: expense.json.type,\n    receipt_file_name: expense.json.receipt_file_name,\n    receipt_url: driveUrl,\n    drive_file_id: drive.id || '',\n    confidence: expense.json.confidence,\n    status: 'auto_saved',\n    raw_json: expense.json.raw_json,\n    telegram_chat_id: expense.json.chat_id,\n    telegram_message_id: expense.json.message_id,\n    telegram_user_id: expense.json.user_id,\n    sheet_id: expense.json.sheet_id,\n    folder_id: expense.json.folder_id,\n    first_name: $('Look Up User').first().json.first_name || '',\n    last_name: $('Look Up User').first().json.last_name || '',\n    org_name: $('Look Up User').first().json.org_name || '',\n    base_currency: baseCurrency,\n    amount_gross_converted: convertedGross,\n    vat_amount_converted: convertedVat,\n    amount_net_converted: convertedNet\n  },\n  binary: expense.binary\n}];"
      },
      "id": "f58b72db-2afc-4fd3-90b5-5c21a7939b9f",
      "name": "Finalize Sheet Row",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        19328,
        4000
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $json.sheet_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "expenses",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "c8bf0526-2bf1-44eb-96e1-f1cfe71b7cfd",
      "name": "Append Expense Row",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        19552,
        4000
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.telegram_chat_id }}",
        "text": "={{ `✅ Saved!\n\nDate: ${$json.expense_date || 'unclear'}\nVendor: ${$json.vendor || 'unclear'}\nAmount: ${$json.currency || ''} ${$json.amount_gross ?? 'unclear'}\nType: ${$json.type || 'unclear'}\n\nSend 'delete' to remove the last entry.` }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "00aa0046-82dc-4b82-b962-0403efd12132",
      "name": "Send Saved Summary",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        19760,
        4000
      ],
      "webhookId": "REPLACE_ME",
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "content": "## Telegram Expense Tracker — Multi User\n\n**Setup required:**\n1. Replace `REPLACE_WITH_MASTER_SHEET_ID` in all Google Sheets nodes with your actual master sheet ID\n2. Create the master sheet with a tab named `users` and columns: `user_id`, `username`, `sheet_id`, `folder_id`, `created_at`\n3. Set your Telegram bot token in the Telegram trigger credential\n\n**Folder IDs (pre-configured):**\n- User sheets → `REPLACE_WITH_PARENT_FOLDER_ID`\n- Receipt subfolders → `REPLACE_WITH_RECEIPTS_FOLDER_ID`\n\n**Flow:**\n- `/start` → creates personal sheet + receipt folder → registers user\n- Receipt photo → looks up user → extracts → saves to their sheet + folder",
        "height": 380,
        "width": 480,
        "color": 6
      },
      "id": "18779a2b-5ef4-4bb2-97d6-5170e11f4b09",
      "name": "Setup Notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        15328,
        3856
      ]
    },
    {
      "parameters": {
        "operation": "move",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.spreadsheetId }}",
          "mode": "id"
        },
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "folderId": {
          "__rl": true,
          "value": "REPLACE_WITH_PARENT_FOLDER_ID",
          "mode": "id"
        }
      },
      "id": "32fecc64-2658-4663-820b-008fac368691",
      "name": "Move Sheet to Folder",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        17632,
        3776
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "users",
          "mode": "name"
        },
        "filtersUI": {
          "values": []
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        15904,
        4256
      ],
      "id": "38a74df8-7518-40b6-8daf-c1dab9afd237",
      "name": "Look Up User (Delete)",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const userId = String($('Extract Message Context').first().json.user_id);\nconst match = $input.all().find(r => String(r.json.user_id) === userId);\nif (!match) throw new Error('User not registered.');\nreturn [{ json: match.json }];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        16112,
        4256
      ],
      "id": "d2073502-93ca-4c30-9fe9-fa58fe5afde4",
      "name": "Find User Row (Delete)"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $json.sheet_id }}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "expenses",
          "mode": "name"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        16352,
        4256
      ],
      "id": "debe71d6-9a68-4a12-b6d9-2e58a62048cb",
      "name": "Read Expense Rows (Delete)",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "temu-cond-setcurrency",
              "leftValue": "={{ $json.message_type }}",
              "rightValue": "set_currency",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "temu-new-0001",
      "name": "Is Set Currency",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        16496,
        4480
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "users",
          "mode": "name"
        },
        "filtersUI": {
          "values": []
        },
        "options": {}
      },
      "id": "temu-new-0002",
      "name": "Look Up User (Set Currency)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        16688,
        4480
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const userId = String($('Extract Message Context').first().json.user_id);\nconst match = $input.all().find(r => String(r.json.user_id) === userId);\nif (!match) throw new Error('User not registered. Please send /start first.');\nreturn [{ json: match.json }];"
      },
      "id": "temu-new-0003",
      "name": "Find User Row (Set Currency)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        16880,
        4480
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_GOOGLE_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "users",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "user_id": "={{ Number($('Extract Message Context').first().json.user_id) }}",
            "base_currency": "={{ $('Extract Message Context').first().json.set_currency_code }}"
          },
          "matchingColumns": [
            "user_id"
          ],
          "schema": []
        },
        "options": {}
      },
      "id": "temu-new-0004",
      "name": "Update User Currency",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        17072,
        4480
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('Extract Message Context').first().json.chat_id }}",
        "text": "={{ `✅ Base currency set to ${$('Extract Message Context').first().json.set_currency_code}. Future receipts will be converted automatically.` }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "id": "temu-new-0005",
      "name": "Send Currency Confirmation",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        17264,
        4480
      ],
      "credentials": {
        "telegramApi": {
          "id": "REPLACE_ME",
          "name": "Add your credential"
        }
      }
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ `https://api.frankfurter.app/latest?from=${$json.currency}` }}",
        "options": {}
      },
      "id": "temu-new-0006",
      "name": "Get Exchange Rate",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "continueOnFail": true,
      "position": [
        18568,
        4000
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Telegram Receipt Trigger": {
      "main": [
        [
          {
            "node": "Extract Message Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Message Context": {
      "main": [
        [
          {
            "node": "Is Start Command",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Start Command": {
      "main": [
        [
          {
            "node": "Check User Exists (Start)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Is Delete Command",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Delete Command": {
      "main": [
        [
          {
            "node": "Look Up User (Delete)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Is Set Currency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Sheet Row": {
      "main": [
        [
          {
            "node": "Delete Drive File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delete Drive File": {
      "main": [
        [
          {
            "node": "Send Deleted Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check User Exists (Start)": {
      "main": [
        [
          {
            "node": "Already Registered",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Already Registered": {
      "main": [
        [
          {
            "node": "Send Already Registered",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set User Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set User Context": {
      "main": [
        [
          {
            "node": "Create User Receipt Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create User Receipt Folder": {
      "main": [
        [
          {
            "node": "Create User Expense Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create User Expense Sheet": {
      "main": [
        [
          {
            "node": "Move Sheet to Folder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add Sheet Headers": {
      "main": [
        [
          {
            "node": "Register User in Master Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Register User in Master Sheet": {
      "main": [
        [
          {
            "node": "Send Welcome Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Receipt": {
      "main": [
        [
          {
            "node": "Look Up User",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Usage Guidance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Look Up User": {
      "main": [
        [
          {
            "node": "Is Registered",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Registered": {
      "main": [
        [
          {
            "node": "Download Receipt File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Not Registered",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Receipt File": {
      "main": [
        [
          {
            "node": "Upload Receipt to OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Receipt to OpenAI": {
      "main": [
        [
          {
            "node": "Prepare OpenAI Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare OpenAI Request": {
      "main": [
        [
          {
            "node": "Read Receipt with OpenAI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Receipt with OpenAI": {
      "main": [
        [
          {
            "node": "Parse Extraction Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Extraction Result": {
      "main": [
        [
          {
            "node": "Get Exchange Rate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read User Expense Sheet": {
      "main": [
        [
          {
            "node": "Build Expense Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Expense Record": {
      "main": [
        [
          {
            "node": "Upload Receipt to Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Receipt to Drive": {
      "main": [
        [
          {
            "node": "Finalize Sheet Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finalize Sheet Row": {
      "main": [
        [
          {
            "node": "Append Expense Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append Expense Row": {
      "main": [
        [
          {
            "node": "Send Saved Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Sheet to Folder": {
      "main": [
        [
          {
            "node": "Register User in Master Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Last Expense Row": {
      "main": [
        [
          {
            "node": "Delete Sheet Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Look Up User (Delete)": {
      "main": [
        [
          {
            "node": "Find User Row (Delete)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Expense Rows (Delete)": {
      "main": [
        [
          {
            "node": "Find Last Expense Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find User Row (Delete)": {
      "main": [
        [
          {
            "node": "Read Expense Rows (Delete)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Set Currency": {
      "main": [
        [
          {
            "node": "Look Up User (Set Currency)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Has Receipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Look Up User (Set Currency)": {
      "main": [
        [
          {
            "node": "Find User Row (Set Currency)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find User Row (Set Currency)": {
      "main": [
        [
          {
            "node": "Update User Currency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update User Currency": {
      "main": [
        [
          {
            "node": "Send Currency Confirmation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Exchange Rate": {
      "main": [
        [
          {
            "node": "Read User Expense Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "3621c4b6-ac5f-43a6-a27b-85959ee0e1d2",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "38c984ab3c887cf0a803e31f65e2a86ba2641bf95d08f3130b66e0f79af98e81"
  },
  "id": "grW0eWulT7ZznCpM",
  "tags": []
}
