AutomationFlowsAI & RAG › AI Voice Expense Tracker via Telegram

AI Voice Expense Tracker via Telegram

Original n8n title: Universal Expense Tracker

Universal Expense tracker. Uses telegram, httpRequest, openAi, googleSheets. Webhook trigger; 33 nodes.

Webhook trigger★★★★★ complexityAI-powered33 nodesTelegramHTTP RequestOpenAIGoogle SheetsOpenAI ChatMemory Buffer WindowTool CalculatorAgent
AI & RAG Trigger: Webhook Nodes: 33 Complexity: ★★★★★ AI nodes: yes Added:

This workflow follows the Agent → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Universal Expense tracker",
  "nodes": [
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.message.from.id }}",
              "operation": "contains",
              "value2": "=YOUR_USER_ID"
            }
          ]
        }
      },
      "name": "Check Authorization",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        -240,
        2944
      ],
      "id": "70a90cf8-029b-406c-b4e1-c356d07cd561"
    },
    {
      "parameters": {
        "chatId": "={{ $json.message.chat.id }}",
        "text": "\u26d4 Unauthorized access. This bot is private.",
        "additionalFields": {}
      },
      "name": "Send Unauthorized Message",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1,
      "position": [
        -448,
        3184
      ],
      "id": "54382802-c08b-4665-af45-fadc58f3bb68",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { \"status\": \"unauthorized\" } }}",
        "options": {}
      },
      "name": "Respond Unauthorized",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        -272,
        3184
      ],
      "id": "d5b6074c-0986-4869-839d-b5027a422185"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.message.voice }}",
              "operation": "isNotEmpty"
            }
          ]
        }
      },
      "id": "02c88b77-b046-4b4c-b33d-9427dc99eb5a",
      "name": "Is Voice Message?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        160,
        2928
      ]
    },
    {
      "parameters": {
        "url": "=https://api.telegram.org/botYOUR_TELEGRAM_BOT_TOKEN/getFile",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "file_id",
              "value": "={{ $json.message.voice.file_id }}"
            }
          ]
        },
        "options": {}
      },
      "id": "794afebd-97ef-46d3-b739-91c31c0e0547",
      "name": "Get Voice File URL",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        -32,
        2512
      ]
    },
    {
      "parameters": {
        "url": "=https://api.telegram.org/file/botYOUR_TELEGRAM_BOT_TOKEN/{{ $json.result.file_path }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "a47174ad-65f4-4290-884f-bb8f905eaae7",
      "name": "Download Voice File",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [
        160,
        2512
      ]
    },
    {
      "parameters": {
        "resource": "audio",
        "operation": "transcribe",
        "binaryPropertyName": "=data",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [
        368,
        2512
      ],
      "id": "72c2a3d2-6d25-4aaf-a84c-269a46a83150",
      "name": "Transcribe a recording",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst output = [];\n\nfor (const item of items) {\n  let messageText = '';\n  let chatId = '';\n  let userId = '';\n  let username = '';\n\n  if (item.json.text && !item.json.message) {\n    // Voice transcription - get chat info from the input item's metadata\n    messageText = item.json.text;\n    // Get from the original trigger data passed through\n    try {\n      const allItems = $input.all();\n      for (const i of allItems) {\n        if (i.json.message) {\n          chatId = i.json.message.chat.id;\n          userId = i.json.message.from.id;\n          username = i.json.message.from.username || i.json.message.from.first_name;\n          break;\n        }\n      }\n    } catch(e) {}\n    if (!chatId) chatId = 'YOUR_USER_ID';\n  } else if (item.json.message) {\n    messageText = item.json.message.text || '';\n    chatId = item.json.message.chat.id;\n    userId = item.json.message.from.id;\n    username = item.json.message.from.username || item.json.message.from.first_name;\n  }\n\n  output.push({\n    json: {\n      messageText,\n      chatId,\n      userId,\n      username,\n      timestamp: new Date().toISOString()\n    }\n  });\n}\n\nreturn output;"
      },
      "id": "3553c4a8-711a-4fea-84a2-e7cdef85a0fe",
      "name": "Merge & Format Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        384,
        3008
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Expense Tracker Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "options": {}
      },
      "name": "Read Expense Data",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        608,
        3088
      ],
      "id": "8905a844-9252-455c-adca-f19ddb4d49ab",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "numberInputs": 4
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        864,
        3040
      ],
      "id": "5f9cf389-ac10-4ddf-bf69-93dc47976b5a",
      "name": "Merge"
    },
    {
      "parameters": {
        "jsCode": "const allItems = $input.all();\n\nlet userMsg = null;\nconst sheetRows = [];\nconst employeeRows = [];\nconst salaryLogRows = [];\n\nconst MY_CHAT_ID = 'YOUR_CHAT_ID';\n\nfor (const item of allItems) {\n  if (item.json.messageText !== undefined) {\n    userMsg = item.json;\n  } else if (item.json['Monthly Salary'] !== undefined && item.json.Name) {\n    employeeRows.push(item.json);\n  } else if (item.json.Type !== undefined && item.json.Name && (item.json.Type === 'Advance' || item.json.Type === 'Paid')) {\n    salaryLogRows.push(item.json);\n  } else if (item.json.Date && item.json.Date !== '') {\n    sheetRows.push(item.json);\n  }\n}\n\nconst timezone = 'Asia/Kolkata';\nfunction getCurrentIST() {\n  return new Date(new Date().toLocaleString('en-US', { timeZone: timezone }));\n}\n\nfunction parseSheetDate(dateStr) {\n  if (!dateStr) return null;\n  const parts = dateStr.split('-');\n  if (parts.length !== 3) return null;\n  const months = {Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11};\n  const day = parseInt(parts[0]);\n  const month = months[parts[1]];\n  const year = parseInt(parts[2]);\n  if (month === undefined) return null;\n  return new Date(year, month, day);\n}\n\nfunction formatSheetDate(date) {\n  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n  return date.getDate() + '-' + months[date.getMonth()] + '-' + date.getFullYear();\n}\n\nfunction formatMonthYear(date) {\n  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n  return months[date.getMonth()] + '-' + date.getFullYear();\n}\n\nfunction formatDateTime(date) {\n  let hours = date.getHours();\n  const minutes = String(date.getMinutes()).padStart(2, '0');\n  const ampm = hours >= 12 ? 'PM' : 'AM';\n  hours = hours % 12 || 12;\n  return hours + ':' + minutes + ' ' + ampm + ' IST';\n}\n\nconst now = getCurrentIST();\nconst todayStr = formatSheetDate(now);\nconst currentMonthYear = formatMonthYear(now);\nconst currentMonthShort = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][now.getMonth()];\n\nconst expenseColumns = [\n  'Tea', 'Repair', 'Customer Courier', 'IJ Expenses', 'Beta',\n  'Juice', 'Water Cane', 'Sri Ram Courier', 'Generator Diesel',\n  'Pooja Oil', 'Tea Cup', 'A4 papper', 'Bovonto',\n  'Shiprocket Recharge', 'EB bill', 'Influencers Payment',\n  'Employee Salary', 'Social Media Ads Payment', 'Others',\n  'Travel Expense'\n];\n\nfunction buildDaySummary(row) {\n  const items = [];\n  let total = 0;\n  const skipCols = ['Date', 'Total Expense', 'row_number'];\n  for (const col of Object.keys(row)) {\n    if (skipCols.includes(col)) continue;\n    const val = parseFloat(row[col]);\n    if (val && val > 0) {\n      items.push({ category: col, amount: val });\n      total += val;\n    }\n  }\n  return { date: row.Date, items, total, sheetTotal: parseFloat(row['Total Expense']) || 0 };\n}\n\nfunction buildSalarySummary(empName, month) {\n  const emp = employeeRows.find(e => e.Name && e.Name.toLowerCase() === empName.toLowerCase());\n  if (!emp) return null;\n\n  const logs = salaryLogRows.filter(l => \n    l.Name && l.Name.toLowerCase() === empName.toLowerCase() && \n    l.Month === month\n  );\n\n  const advances = logs.filter(l => l.Type === 'Advance');\n  const payments = logs.filter(l => l.Type === 'Paid');\n  const totalAdvance = advances.reduce((sum, l) => sum + (parseFloat(l.Amount) || 0), 0);\n  const totalPaid = payments.reduce((sum, l) => sum + (parseFloat(l.Amount) || 0), 0);\n  const monthlySalary = parseFloat(emp['Monthly Salary']) || 0;\n  const balance = monthlySalary - totalAdvance - totalPaid;\n\n  return {\n    name: emp.Name, position: emp.Position, monthlySalary, totalAdvance, totalPaid, balance,\n    advances: advances.map(a => ({ date: a.Date, amount: parseFloat(a.Amount) || 0 })),\n    payments: payments.map(p => ({ date: p.Date, amount: parseFloat(p.Amount) || 0 })),\n    month\n  };\n}\n\nfunction buildAllSalarySummary(month) {\n  return employeeRows.map(emp => {\n    const summary = buildSalarySummary(emp.Name, month);\n    return summary || { name: emp.Name, position: emp.Position, monthlySalary: parseFloat(emp['Monthly Salary']) || 0, totalAdvance: 0, totalPaid: 0, balance: parseFloat(emp['Monthly Salary']) || 0, advances: [], payments: [], month };\n  });\n}\n\nconst employeeNames = employeeRows.map(e => e.Name ? e.Name.toLowerCase() : '');\n\nfunction isExpenseColumnMatch(msg) {\n  const lower = msg.toLowerCase();\n  return expenseColumns.some(col => lower.includes(col.toLowerCase()));\n}\n\n// \u2550\u2550\u2550 IMPROVED SALARY DETECTION \u2550\u2550\u2550\nfunction isSalaryRelated(msg) {\n  const lower = msg.toLowerCase();\n  const hasEmployee = employeeNames.some(name => name && lower.includes(name));\n  \n  const salaryKeywords = [\n    'advance', 'advantage', 'salary', '/salary', 'all salary', 'salary report',\n    'balance', 'pending', 'how much', 'remaining', 'deduct', 'deduction',\n    'how many employee', 'how many staff', 'total employee', 'total staff',\n    'employee list', 'staff list', 'all employee', 'all staff'\n  ];\n  const hasSalaryKeyword = salaryKeywords.some(kw => lower.includes(kw));\n  \n  const staffWords = [\n    'employee', 'employees', 'staff', 'worker', 'workers',\n    'team', 'people', 'everyone'\n  ];\n  const hasStaffWord = staffWords.some(w => lower.includes(w));\n  \n  if (hasEmployee && hasSalaryKeyword) return true;\n  if (hasEmployee && !isExpenseColumnMatch(msg)) return true;\n  if (hasSalaryKeyword && !isExpenseColumnMatch(msg)) return true;\n  if (hasStaffWord && (lower.includes('salary') || lower.includes('advance') || lower.includes('pending') || lower.includes('balance') || lower.includes('taken') || lower.includes('how much') || lower.includes('remaining') || lower.includes('how many') || lower.includes('total') || lower.includes('list') || lower.includes('company'))) return true;\n  if (hasStaffWord && !isExpenseColumnMatch(msg)) return true;\n  \n  return false;\n}\n\nfunction getEmployeeFromMsg(msg) {\n  const lower = msg.toLowerCase();\n  for (const emp of employeeRows) {\n    if (emp.Name && lower.includes(emp.Name.toLowerCase())) {\n      return emp.Name;\n    }\n  }\n  return null;\n}\n\nfunction getSalaryAction(msg) {\n  const lower = msg.toLowerCase();\n  if (lower.includes('advance') || lower.includes('advantage')) return 'advance';\n  if (lower.includes('paid') || lower.includes('pay ')) return 'paid';\n  if (lower.includes('balance') || lower.includes('pending') || lower.includes('remaining') || lower.includes('how much')) return 'query';\n  if (lower.includes('/salary') || lower.includes('all salary') || lower.includes('salary report')) return 'report';\n  if (lower.includes('salary')) return 'query';\n  if (lower.includes('how many') || lower.includes('taken') || lower.includes('everyone') || lower.includes('total') || lower.includes('list') || lower.includes('company')) return 'report';\n  return 'query';\n}\n\n// \u2550\u2550\u2550 SCHEDULE TRIGGER \u2550\u2550\u2550\nif (!userMsg) {\n  const hour = now.getHours();\n  const dayOfMonth = now.getDate();\n\n  if (dayOfMonth <= 1 && hour >= 19 && hour <= 21) {\n    const lastMonth = now.getMonth() === 0 ? 'Dec' : ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][now.getMonth() - 1];\n    const monthRows = sheetRows.filter(r => r.Date && r.Date.includes('-' + lastMonth + '-'));\n    const summaries = monthRows.map(r => buildDaySummary(r));\n    return [{ json: { actionType: 'report', reportType: 'monthly', requestLabel: lastMonth + ' ' + now.getFullYear(), reportGeneratedAt: formatDateTime(now), dataAvailable: summaries.length > 0, userQuery: 'Auto monthly report', user: { chatId: MY_CHAT_ID }, dailySummaries: summaries } }];\n  }\n\n  if (hour >= 9 && hour <= 11) {\n    const weekAgo = new Date(now); weekAgo.setDate(weekAgo.getDate() - 7);\n    const weekRows = sheetRows.filter(r => { const d = parseSheetDate(r.Date); return d && d >= weekAgo && d <= now; });\n    const summaries = weekRows.map(r => buildDaySummary(r));\n    return [{ json: { actionType: 'report', reportType: 'weekly', requestLabel: 'this week', reportGeneratedAt: formatDateTime(now), dataAvailable: summaries.length > 0, userQuery: 'Auto weekly report', user: { chatId: MY_CHAT_ID }, dailySummaries: summaries } }];\n  }\n\n  const todayRow = sheetRows.find(r => r.Date === todayStr);\n  const summary = todayRow ? buildDaySummary(todayRow) : null;\n  return [{ json: { actionType: 'report', reportType: 'daily', requestLabel: 'today', requestedDate: todayStr, reportGeneratedAt: formatDateTime(now), dataAvailable: !!todayRow, userQuery: 'Auto daily report', user: { chatId: MY_CHAT_ID }, daySummary: summary } }];\n}\n\n// \u2550\u2550\u2550 TELEGRAM MESSAGE \u2550\u2550\u2550\nconst msg = userMsg.messageText.toLowerCase().trim();\nconst originalMsg = userMsg.messageText.trim();\nconst chatId = userMsg.chatId || MY_CHAT_ID;\nconst userId = userMsg.userId;\nconst username = userMsg.username;\n\n// Greetings\nconst greetings = ['hai','hello','hi','hey','namaste','good morning','good evening','good night','vanakkam','thanks','thank you','ok','okay','start','/start'];\nif (greetings.some(g => msg === g || msg === g + '!')) {\n  return [{ json: { actionType: 'chat', userQuery: originalMsg, user: { chatId, userId, username } } }];\n}\n\nconst isSlashToday = (msg === '/today');\nconst isSlashWeek = (msg === '/week');\nconst isSlashMonth = (msg === '/month');\nconst isSlashSalary = (msg === '/salary');\n\n// \u2550\u2550\u2550 EXPENSE COLUMN WITH NUMBER \u2192 ALWAYS EXPENSE (before salary check) \u2550\u2550\u2550\nconst hasNumber = /[\\d]/.test(originalMsg);\nif (isExpenseColumnMatch(msg) && hasNumber && !msg.includes('salary') && !msg.includes('advance')) {\n  const todayRow = sheetRows.find(r => r.Date === todayStr);\n  return [{ json: { actionType: 'expense', userQuery: originalMsg, currentDate: todayStr, todaySummary: todayRow ? buildDaySummary(todayRow) : null, instruction: 'IMPORTANT: For unknown items NOT in known columns, use the item name itself as the column name. Do NOT map to Others. If amount is negative (e.g. Tea -100), return negative amount in JSON to subtract from existing value.', user: { chatId, userId, username } } }];\n}\n\n// \u2550\u2550\u2550 SALARY DETECTION \u2550\u2550\u2550\nif (isSlashSalary || isSalaryRelated(msg)) {\n  const employeeName = getEmployeeFromMsg(msg);\n  const salaryAction = getSalaryAction(msg);\n  const amountMatch = originalMsg.match(/(\\d[\\d,]*\\.?\\d*)/);\n  const amount = amountMatch ? parseFloat(amountMatch[1].replace(/,/g, '')) : 0;\n\n  let salaryData = null;\n  if (salaryAction === 'report' || !employeeName) {\n    salaryData = buildAllSalarySummary(currentMonthYear);\n  } else if (employeeName) {\n    salaryData = buildSalarySummary(employeeName, currentMonthYear);\n  }\n\n  return [{ json: {\n    actionType: 'salary',\n    salaryAction: salaryAction,\n    employeeName: employeeName,\n    amount: amount,\n    currentDate: todayStr,\n    currentMonth: currentMonthYear,\n    userQuery: originalMsg,\n    salaryData: salaryData,\n    employeeCount: employeeRows.length,\n    user: { chatId, userId, username }\n  } }];\n}\n\n// Has number \u2192 EXPENSE\nconst hasAmount = /\\d{2,}/.test(originalMsg);\nif (hasAmount && !isSlashToday && !isSlashWeek && !isSlashMonth) {\n  const todayRow = sheetRows.find(r => r.Date === todayStr);\n  return [{ json: { actionType: 'expense', userQuery: originalMsg, currentDate: todayStr, todaySummary: todayRow ? buildDaySummary(todayRow) : null, instruction: 'IMPORTANT: For unknown items NOT in known columns, use the item name itself as the column name. Do NOT map to Others. If amount is negative (e.g. Tea -100), return negative amount in JSON to subtract from existing value.', user: { chatId, userId, username } } }];\n}\n\n// Reports\nif (isSlashToday || msg.includes('today')) {\n  const todayRow = sheetRows.find(r => r.Date === todayStr);\n  return [{ json: { actionType: 'report', reportType: 'daily', requestLabel: 'today', requestedDate: todayStr, reportGeneratedAt: formatDateTime(now), dataAvailable: !!todayRow, userQuery: originalMsg, user: { chatId, userId, username }, daySummary: todayRow ? buildDaySummary(todayRow) : null } }];\n}\n\nif (msg.includes('yesterday')) {\n  const yday = new Date(now); yday.setDate(yday.getDate() - 1);\n  const ydayStr = formatSheetDate(yday);\n  const ydayRow = sheetRows.find(r => r.Date === ydayStr);\n  return [{ json: { actionType: 'report', reportType: 'daily', requestLabel: 'yesterday', requestedDate: ydayStr, reportGeneratedAt: formatDateTime(now), dataAvailable: !!ydayRow, userQuery: originalMsg, user: { chatId, userId, username }, daySummary: ydayRow ? buildDaySummary(ydayRow) : null } }];\n}\n\nif (isSlashWeek || msg.includes('this week') || msg.includes('weekly') || msg.includes('week report')) {\n  const weekAgo = new Date(now); weekAgo.setDate(weekAgo.getDate() - 7);\n  const weekRows = sheetRows.filter(r => { const d = parseSheetDate(r.Date); return d && d >= weekAgo && d <= now; });\n  const summaries = weekRows.map(r => buildDaySummary(r));\n  return [{ json: { actionType: 'report', reportType: 'weekly', requestLabel: 'this week', reportGeneratedAt: formatDateTime(now), dataAvailable: summaries.length > 0, dataCount: summaries.length, userQuery: originalMsg, user: { chatId, userId, username }, dailySummaries: summaries } }];\n}\n\nif (msg.includes('last month') || msg.includes('previous month')) {\n  const lastMonth = now.getMonth() === 0 ? 'Dec' : ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][now.getMonth() - 1];\n  const monthRows = sheetRows.filter(r => r.Date && r.Date.includes('-' + lastMonth + '-'));\n  const summaries = monthRows.map(r => buildDaySummary(r));\n  return [{ json: { actionType: 'report', reportType: 'monthly', requestLabel: lastMonth, reportGeneratedAt: formatDateTime(now), dataAvailable: summaries.length > 0, userQuery: originalMsg, user: { chatId, userId, username }, dailySummaries: summaries } }];\n}\n\nif (isSlashMonth || msg.includes('this month') || msg.includes('monthly') || msg.includes('month report')) {\n  const monthRows = sheetRows.filter(r => r.Date && r.Date.includes('-' + currentMonthShort + '-'));\n  const summaries = monthRows.map(r => buildDaySummary(r));\n  return [{ json: { actionType: 'report', reportType: 'monthly', requestLabel: currentMonthShort + ' ' + now.getFullYear(), reportGeneratedAt: formatDateTime(now), dataAvailable: summaries.length > 0, userQuery: originalMsg, user: { chatId, userId, username }, dailySummaries: summaries } }];\n}\n\n// Default\nif (/\\d+/.test(originalMsg)) {\n  return [{ json: { actionType: 'expense', userQuery: originalMsg, currentDate: todayStr, instruction: 'IMPORTANT: For unknown items NOT in known columns, use the item name itself as the column name. Do NOT map to Others. If amount is negative, return negative in JSON.', user: { chatId, userId, username } } }];\n}\n\nreturn [{ json: { actionType: 'chat', userQuery: originalMsg, user: { chatId, userId, username } } }];"
      },
      "name": "Prepare Data for AI",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        3040
      ],
      "id": "e8889d0d-8908-4746-9149-93645b67c662"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4.1",
          "mode": "list",
          "cachedResultName": "gpt-4.1"
        },
        "builtInTools": {},
        "options": {
          "temperature": 0.2
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        1024,
        3264
      ],
      "id": "04faa8b3-baf1-4539-9860-07a25cd0478e",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "=jewellery_v8_{{ $json.user.chatId }}",
        "contextWindowLength": 10
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        1152,
        3264
      ],
      "id": "050a83b6-d96d-41a3-9011-c8727d0922c2",
      "name": "Simple Memory"
    },
    {
      "parameters": {},
      "type": "@n8n/n8n-nodes-langchain.toolCalculator",
      "typeVersion": 1,
      "position": [
        1264,
        3264
      ],
      "id": "3afc9456-0422-4b6e-9cd5-6fbfb33417cd",
      "name": "Calculator"
    },
    {
      "parameters": {
        "jsCode": "// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n// PROCESS AI RESPONSE \u2014 v11\n// \n// Supports: add, subtract (negative amounts), salary\n// Tea 50 \u2192 adds 50\n// Tea -100 \u2192 subtracts 100 (minimum 0)\n// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nconst firstItem = $input.first().json;\nconst aiOutput = firstItem.output || firstItem.text || '';\n\nlet chatId = 'YOUR_CHAT_ID';\ntry {\n  const fmt = $('Merge & Format Message').first().json;\n  if (fmt && fmt.chatId) chatId = String(fmt.chatId);\n} catch(e) {}\n\nlet todayDate = '';\nlet todaySummary = null;\nlet actionType = '';\nlet salaryAction = '';\nlet employeeName = '';\nlet salaryAmount = 0;\nlet currentMonth = '';\n\ntry {\n  const pd = $('Prepare Data for AI').first().json;\n  todayDate = pd.currentDate || '';\n  todaySummary = pd.todaySummary || null;\n  actionType = pd.actionType || '';\n  salaryAction = pd.salaryAction || '';\n  employeeName = pd.employeeName || '';\n  salaryAmount = pd.amount || 0;\n  currentMonth = pd.currentMonth || '';\n} catch(e) {\n  const now = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Kolkata' }));\n  const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n  todayDate = now.getDate() + '-' + months[now.getMonth()] + '-' + now.getFullYear();\n  currentMonth = months[now.getMonth()] + '-' + now.getFullYear();\n}\n\n// \u2550\u2550\u2550 SALARY ACTION \u2550\u2550\u2550\nif (actionType === 'salary') {\n  if ((salaryAction === 'advance' || salaryAction === 'paid') && employeeName && salaryAmount > 0) {\n    const type = salaryAction === 'advance' ? 'Advance' : 'Paid';\n    const cleanText = '\u2705 *' + type + ' Recorded!*\\n\ud83d\udc64 ' + employeeName + '\\n\ud83d\udcb0 *\u20b9' + salaryAmount.toLocaleString('en-IN') + '*\\n\ud83d\udcc5 ' + todayDate + '\\n\\n_Powered By VelzenLabs_';\n\n    return [{\n      json: {\n        responseText: cleanText,\n        chatId: chatId,\n        shouldLog: false,\n        isSalary: true,\n        salaryLog: {\n          Date: todayDate,\n          Name: employeeName,\n          Type: type,\n          Amount: salaryAmount,\n          Month: currentMonth\n        }\n      }\n    }];\n  }\n\n  let displayText = aiOutput.replace(/```json[\\s\\S]*?```/g, '').replace(/```[\\s\\S]*?```/g, '').trim();\n  if (!displayText) displayText = aiOutput;\n  \n  return [{\n    json: {\n      responseText: displayText,\n      chatId: chatId,\n      shouldLog: false,\n      isSalary: false\n    }\n  }];\n}\n\n// \u2550\u2550\u2550 EXPENSE HANDLING \u2550\u2550\u2550\nconst existingValues = {};\nif (todaySummary && todaySummary.items) {\n  for (const item of todaySummary.items) {\n    existingValues[item.category] = item.amount;\n  }\n}\n\nconst KNOWN_COLUMNS = [\n  'Tea', 'Repair', 'Customer Courier', 'IJ Expenses', 'Beta',\n  'Juice', 'Water Cane', 'Sri Ram Courier', 'Generator Diesel',\n  'Pooja Oil', 'Tea Cup', 'A4 papper', 'Bovonto',\n  'Shiprocket Recharge', 'EB bill', 'Influencers Payment',\n  'Employee Salary', 'Social Media Ads Payment', 'Others',\n  'Travel Expense'\n];\n\nfunction matchColumn(name) {\n  if (!name) return null;\n  const lower = name.toLowerCase().trim();\n  if ((lower === 'tea' || lower === 'chai') && !lower.includes('cup')) return 'Tea';\n  if (lower.includes('tea cup') || lower === 'cups') return 'Tea Cup';\n  if (lower.includes('repair') || lower.includes('fix')) return 'Repair';\n  if (lower.includes('customer') || (lower.includes('courier') && !lower.includes('sri') && !lower.includes('ram'))) return 'Customer Courier';\n  if (lower.includes('ij')) return 'IJ Expenses';\n  if (lower === 'beta') return 'Beta';\n  if (lower.includes('juice')) return 'Juice';\n  if (lower.includes('water') || lower.includes('cane')) return 'Water Cane';\n  if (lower.includes('sri') || lower.includes('sriram')) return 'Sri Ram Courier';\n  if (lower.includes('diesel') || lower.includes('generator')) return 'Generator Diesel';\n  if (lower.includes('pooja') || lower.includes('puja')) return 'Pooja Oil';\n  if (lower.includes('a4') || lower.includes('paper')) return 'A4 papper';\n  if (lower.includes('bovonto')) return 'Bovonto';\n  if (lower.includes('ship') || lower.includes('rocket')) return 'Shiprocket Recharge';\n  if (lower.includes('eb') || lower.includes('electri') || lower.includes('current bill')) return 'EB bill';\n  if (lower.includes('influencer')) return 'Influencers Payment';\n  if (lower.includes('salary') || lower.includes('employee')) return 'Employee Salary';\n  if (lower.includes('social media') || lower.includes('ads pay')) return 'Social Media Ads Payment';\n  if (lower === 'others' || lower === 'other') return 'Others';\n  if (lower.includes('travel') || lower.includes('auto') || lower.includes('petrol')) return 'Travel Expense';\n  return null;\n}\n\nlet expenseMap = {};\ntry {\n  let cleanJson = aiOutput;\n  const codeBlock = aiOutput.match(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/);\n  if (codeBlock) cleanJson = codeBlock[1].trim();\n\n  const arrayMatch = cleanJson.match(/\\[[\\s\\S]*\\]/);\n  if (arrayMatch) {\n    const arr = JSON.parse(arrayMatch[0]);\n    if (Array.isArray(arr)) {\n      for (const item of arr) {\n        const rawName = item.column || item.category || '';\n        const mapped = matchColumn(rawName);\n        const col = mapped || rawName;\n        const amt = parseFloat(item.amount);\n        // Allow BOTH positive and negative amounts\n        if (col && amt !== 0 && !isNaN(amt)) expenseMap[col] = amt;\n      }\n    }\n  }\n\n  if (Object.keys(expenseMap).length === 0) {\n    const objMatch = cleanJson.match(/\\{[^{}]*(?:\"column\"|\"category\")[^{}]*\\}/g);\n    if (objMatch) {\n      for (const m of objMatch) {\n        try {\n          const obj = JSON.parse(m);\n          const rawName = obj.column || obj.category || '';\n          const mapped = matchColumn(rawName);\n          const col = mapped || rawName;\n          const amt = parseFloat(obj.amount);\n          if (col && amt !== 0 && !isNaN(amt)) expenseMap[col] = amt;\n        } catch(e) {}\n      }\n    }\n  }\n} catch(e) {}\n\nconst hasExpenses = Object.keys(expenseMap).length > 0;\n\nif (hasExpenses) {\n  const finalValues = {};\n  const addedDetails = [];\n\n  for (const [col, newAmt] of Object.entries(expenseMap)) {\n    const oldAmt = existingValues[col] || 0;\n    // Add or subtract, but never go below 0\n    const totalAmt = Math.max(0, oldAmt + newAmt);\n    finalValues[col] = totalAmt;\n\n    if (newAmt < 0) {\n      // Subtraction\n      addedDetails.push('\ud83d\udd3b ' + col + ': \u20b9' + oldAmt.toLocaleString('en-IN') + ' - \u20b9' + Math.abs(newAmt).toLocaleString('en-IN') + ' = *\u20b9' + totalAmt.toLocaleString('en-IN') + '*');\n    } else if (oldAmt > 0) {\n      // Addition to existing\n      addedDetails.push(col + ': \u20b9' + oldAmt.toLocaleString('en-IN') + ' + \u20b9' + newAmt.toLocaleString('en-IN') + ' = *\u20b9' + totalAmt.toLocaleString('en-IN') + '*');\n    } else {\n      // New entry\n      addedDetails.push(col + ': *\u20b9' + totalAmt.toLocaleString('en-IN') + '*');\n    }\n  }\n\n  const cleanText = '\u2705 *Expense Updated!*\\n\ud83d\udcc5 ' + todayDate + '\\n' + addedDetails.join('\\n');\n  const output = { 'Date': todayDate };\n  for (const [col, amt] of Object.entries(finalValues)) {\n    output[col] = amt;\n  }\n  output.responseText = cleanText;\n  output.chatId = chatId;\n  output.shouldLog = true;\n  return [{ json: output }];\n}\n\nlet displayText = aiOutput.replace(/```json[\\s\\S]*?```/g, '').replace(/```[\\s\\S]*?```/g, '').trim();\nif (!displayText) displayText = aiOutput;\nreturn [{ json: { responseText: displayText, chatId: chatId, shouldLog: false } }];"
      },
      "name": "Process AI Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1552,
        3024
      ],
      "id": "8c1a69e4-28d2-46ce-a31c-449bacdc9b4f"
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.shouldLog }}",
              "value2": true
            }
          ]
        }
      },
      "name": "Should Log?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1760,
        3024
      ],
      "id": "9a70844c-01fd-4624-949c-8872578db51c"
    },
    {
      "parameters": {
        "chatId": "=YOUR_USER_ID",
        "text": "={{ $('Process AI Response').item.json.responseText }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "299c1496-0458-4377-8d22-24091d6f28fd",
      "name": "Send Text",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2368,
        2784
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "=YOUR_USER_ID",
        "text": "={{ $json.responseText }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "name": "Send Report Text",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2160,
        3296
      ],
      "id": "af645664-bf44-4b03-93e2-9eef94b135d4",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.user.query }}\n\n{{ JSON.stringify($json) }}",
        "options": {
          "systemMessage": "You are the friendly Expense Manager for a Jewellery Shop's daily operations.\n\nCRITICAL: ALWAYS respond in ENGLISH ONLY.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83c\udfaf MODE SELECTION\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nCheck input JSON:\n- actionType \"chat\" \u2192 Friendly greeting\n- actionType \"report\" \u2192 Expense report from data\n- actionType \"expense\" \u2192 Parse & return JSON\n- actionType \"salary\" \u2192 Handle salary operations\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83d\ude0a CHAT MODE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\"Hello! \ud83d\ude0a Send expenses like: Tea 50, Juice 80. For reports: /today, /week, /month. For salary: /salary or [name] salary\"\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83d\udcca REPORT MODE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83d\udca1 SMART RECOMMENDATIONS (add at end of EVERY report):\n\nAnalyze the data and give 2-3 actionable suggestions:\n\n- If any single category is >30% of total \u2192 \"\u26a0\ufe0f [Category] is [X]% of total. Consider reducing or negotiating better rates.\"\n- If daily average is increasing week over week \u2192 \"\ud83d\udcc8 Spending trend is rising. Review non-essential expenses.\"\n- If Tea/Juice/Bovonto combined >10% \u2192 \"\u2615 Refreshment costs are high. Consider bulk buying or fixed monthly deals.\"\n- If Generator Diesel is frequent \u2192 \"\u26fd Diesel costs are recurring. Check generator efficiency or consider solar backup.\"\n- If EB bill is very high \u2192 \"\ud83d\udca1 Electricity bill is significant. Consider energy-saving measures.\"\n- If Repair costs are frequent \u2192 \"\ud83d\udd27 Frequent repairs detected. Consider preventive maintenance.\"\n- Compare with previous week/month if data available \u2192 \"\ud83d\udcca Compared to last week: spending [increased/decreased] by [X]%\"\n- Suggest where to save \u2192 \"\ud83d\udcb0 Potential savings: [specific suggestion based on data]\"\n\nFormat recommendations as:\n\ud83d\udca1 *Smart Suggestions:*\n1. [suggestion]\n2. [suggestion]\n3. [suggestion]\n\n- End EVERY report with recommendations then: \\n\\n_Powered By VelzenLabs_\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83d\udcb0 EXPENSE MODE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nParse user's message into column name + amount.\n\nKNOWN COLUMNS: Tea, Repair, Customer Courier, IJ Expenses, Beta, Juice, Water Cane, Sri Ram Courier, Generator Diesel, Pooja Oil, Tea Cup, A4 papper, Bovonto, Shiprocket Recharge, EB bill, Influencers Payment, Employee Salary, Social Media Ads Payment, Others, Travel Expense\n\nFOR UNKNOWN ITEMS: Use the item name as column name (capitalized).\n\nRESPOND WITH JSON ARRAY:\n```json\n[{\"column\": \"exact name\", \"amount\": number}]\n```\n\nThen confirmation:\n\u2705 *Expense Updated!*\n\ud83d\udcc5 [currentDate]\n\ud83d\udcc2 [columns]\n\ud83d\udcb0 [amounts]\n\nNEGATIVE AMOUNTS (corrections):\nIf user sends negative amount to correct/remove a wrong entry, return negative in JSON:\n- \"Tea -100\" \u2192 [{\"column\": \"Tea\", \"amount\": -100}]\n- \"Juice -50\" \u2192 [{\"column\": \"Juice\", \"amount\": -50}]\n- \"Influencers Payment -3007\" \u2192 [{\"column\": \"Influencers Payment\", \"amount\": -3007}]\nThe system will subtract from the existing value (minimum 0).\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83d\udc65 SALARY MODE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nWhen actionType is \"salary\":\n\nIMPORTANT: Use ONLY the salaryData provided in the input JSON. This data comes directly from Google Sheets. Do NOT guess or use memory.\n\nSALARY ACTIONS (check salaryAction field):\n\n1. salaryAction \"advance\" \u2192 Employee took advance\n   Response: \"\u2705 Advance Recorded! \ud83d\udc64 [name] \ud83d\udcb0 \u20b9[amount] \ud83d\udcc5 [date]\"\n\n2. salaryAction \"paid\" \u2192 Salary payment made\n   Response: \"\u2705 Payment Recorded! \ud83d\udc64 [name] \ud83d\udcb0 \u20b9[amount] \ud83d\udcc5 [date]\"\n\n3. salaryAction \"query\" \u2192 Check specific employee salary\n   Use salaryData to show:\n   \ud83d\udc64 *[name]* ([position])\n   \ud83d\udcb0 Monthly Salary: \u20b9[monthlySalary]\n   \ud83d\udd3b Total Advance: \u20b9[totalAdvance]\n   \u2705 Total Paid: \u20b9[totalPaid]\n   \ud83d\udcb5 *Balance: \u20b9[balance]*\n\n   Advance Details:\n   [For each advance in salaryData.advances:]\n   \u2022 \u20b9[amount] on [date]\n\n   Payment Details:\n   [For each payment in salaryData.payments:]\n   \u2022 \u20b9[amount] on [date]\n\n4. salaryAction \"report\" \u2192 All employees salary report\n   Use salaryData (array of all employees) to show:\n   [For each employee:]\n   \ud83d\udc64 *[name]* ([position])\n   \ud83d\udcb0 \u20b9[monthlySalary] | Adv: \u20b9[totalAdvance] | Paid: \u20b9[totalPaid] | *Bal: \u20b9[balance]*\n\n   Then grand total at bottom.\n\nIf employee not found in salaryData: \"\u274c Employee not found. Check the name and try again.\"\n\nEnd salary reports with: \\n\\n_Powered By VelzenLabs_\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\ud83d\udcd0 RULES\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n- Indian format \u20b91,50,000\n- Telegram Markdown (*bold*)\n- Always English\n- ONLY use data from salaryData, never guess\n- Reports max 35 lines"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        1248,
        3024
      ],
      "id": "b2542748-6f4c-477a-b9b1-77de0392acc7",
      "name": "AI Agent1"
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Expense Tracker Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "Date"
          ],
          "schema": [
            {
              "id": "Date",
              "displayName": "Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Tea",
              "displayName": "Tea",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Repair",
              "displayName": "Repair",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Customer Courier",
              "displayName": "Customer Courier",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "IJ Expenses",
              "displayName": "IJ Expenses",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Beta",
              "displayName": "Beta",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Juice",
              "displayName": "Juice",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Water Cane",
              "displayName": "Water Cane",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Sri Ram Courier",
              "displayName": "Sri Ram Courier",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Generator Diesel",
              "displayName": "Generator Diesel",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Pooja Oil",
              "displayName": "Pooja Oil",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Tea Cup",
              "displayName": "Tea Cup",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "A4 papper",
              "displayName": "A4 papper",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Bovonto",
              "displayName": "Bovonto",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Shiprocket Recharge",
              "displayName": "Shiprocket Recharge",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "EB bill",
              "displayName": "EB bill",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Influencers Payment",
              "displayName": "Influencers Payment",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Social Media Ads Payment",
              "displayName": "Social Media Ads Payment",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Others",
              "displayName": "Others",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Travel Expense",
              "displayName": "Travel Expense",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Total Expense",
              "displayName": "Total Expense",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Pamphlet Print",
              "displayName": "Pamphlet Print",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "New Printer",
              "displayName": "New Printer",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Laptop",
              "displayName": "Laptop",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "name": "Log to Google Sheets1",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2192,
        2784
      ],
      "id": "a63231c1-99ae-461a-a01b-6676d2162be4",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtHour": 10
            }
          ]
        }
      },
      "id": "f7235a1d-c3de-4226-84a3-c1bb0c292e16",
      "name": "weekly Schedule 10 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -16,
        3376
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months",
              "triggerAtHour": 20
            }
          ]
        }
      },
      "id": "72bff68b-ef3c-4a74-9490-465ca011d0c2",
      "name": "Monthly Schedule 8 PM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -16,
        3536
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 18,
              "triggerAtMinute": 3
            }
          ]
        }
      },
      "id": "5ae5eb2b-0c56-4b7c-8674-d9c3bd7cf5b7",
      "name": "Daily Schedule 10 PM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -32,
        3680
      ]
    },
    {
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.2,
      "position": [
        -512,
        2944
      ],
      "id": "467d92e0-3658-46ee-a3cc-88a6e30cc73a",
      "name": "Telegram Trigger",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst clean = {};\n\nconst BLOCKED = ['responseText', 'chatId', 'shouldLog'];\n\nfor (const key of Object.keys(input)) {\n  if (!BLOCKED.includes(key)) {\n    clean[key] = input[key];\n  }\n}\n\nreturn [{ json: clean }];\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2016,
        2784
      ],
      "id": "62a23346-aa5f-43aa-9fa9-b4aa494dd42a",
      "name": "Clean for Sheets"
    },
    {
      "parameters": {
        "sendTo": "ragul.kmkoil@gmail.com",
        "subject": "Expense Report",
        "emailType": "text",
        "message": "={{ $json.result.text }}",
        "options": {
          "appendAttribution": false
        }
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        2496,
        3296
      ],
      "id": "aed9d72d-0a12-42a7-965d-e02ff6f631e3",
      "name": "Send a message",
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const actionType = $('Prepare Data for AI').first().json.actionType;\n\nif (actionType === 'report') {\n  return [{ json: $input.first().json }];\n}\n\nreturn [];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2320,
        3296
      ],
      "id": "2f99a219-e9b3-49ad-8c8f-34cc7ab14998",
      "name": "Report Code"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "793b9f7a-685e-490a-98f6-993678c795f5",
              "leftValue": "={{ $json.isSalary }}",
              "rightValue": "True",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1936,
        3232
      ],
      "id": "33e943f7-a3de-43c8-bbe7-081934bc5703",
      "name": "If"
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Expense Tracker Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": 872664692,
          "mode": "list",
          "cachedResultName": "Salary Log",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {
            "Date": "=={{ $json.salaryLog.Date }}",
            "Name": "=={{ $json.salaryLog.Name }}",
            "Type": "=={{ $json.salaryLog.Type }}",
            "Amount": "=={{ $json.salaryLog.Amount }}",
            "Month": "=={{ $json.salaryLog.Month }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Date",
              "displayName": "Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "displayName": "Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Type",
              "displayName": "Type",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "displayName": "Amount",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Month",
              "displayName": "Month",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "name": "Log to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2304,
        3040
      ],
      "id": "56b8bcc1-7245-4113-b789-bf7baac1f9ab",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "=YOUR_USER_ID",
        "text": "=={{ $('Process AI Response').first().json.responseText }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "name": "Send Report Text1",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2480,
        3040
      ],
      "id": "fb2c8443-f2c3-4eb5-be66-b6b214e690b4",
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst log = input.salaryLog || {};\n\nreturn [{\n  json: {\n    Date: log.Date || '',\n    Name: log.Name || '',\n    Type: log.Type || '',\n    Amount: log.Amount || 0,\n    Month: log.Month || ''\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2128,
        3040
      ],
      "id": "4adc81d2-a680-41d2-8cf1-17a5dec1d4e7",
      "name": "Clean for Salary"
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Expense Tracker Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": 1452010791,
          "mode": "list",
          "cachedResultName": "Employees",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        608,
        3456
      ],
      "id": "c743501b-7c22-4c8f-8409-8e1c92895d4b",
      "name": "Read Employess",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Expense Tracker Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": 872664692,
          "mode": "list",
          "cachedResultName": "Salary Log",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        608,
        3280
      ],
      "id": "95925b16-730a-458d-93ce-6b0539ae3f92",
      "name": "Read Salary Log",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Check Authorization": {
      "main": [
        [
          {
            "node": "Is Voice Message?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Unauthorized Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Unauthorized Message": {
      "main": [
        [
          {
            "node": "Respond Unauthorized",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Voice Message?": {
      "main": [
        [
          {
            "node": "Get Voice File URL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge & Format Message",
            "type": "main",
            "index

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Universal Expense tracker. Uses telegram, httpRequest, openAi, googleSheets. Webhook trigger; 33 nodes.

Source: https://github.com/RagulRajan/n8n-ai-expense-tracker/blob/main/Universal_Expense_Tracker.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

🧠 Gwen – The AI Voice Marketing Agent Gwen is your intelligent voice-powered marketing assistant built in n8n. She combines the power of OpenAI, ElevenLabs, and automation workflows to handle content

Tool Workflow, Memory Buffer Window, Agent +10
AI & RAG

This suite automates distinct aspects of real estate operations: incoming web lead qualification, scheduled/manual data research and content generation, and automated voice call outreach with lead qua

Agent, HTTP Request, OpenAI Chat +6
AI & RAG

This workflow turns your WhatsApp Business number into a 24/7 AI-powered customer assistant — without any third-party chatbot platform. It receives incoming WhatsApp messages via Evolution API, unders

OpenAI, Information Extractor, Anthropic Chat +7
AI & RAG

This workflow transforms WhatsApp into a powerful personal AI using n8n + Green-API. Send text or voice messages — the assistant understands intent and handles daily tasks automatically. 💰 Expense & i

Tool Calculator, Google Sheets Tool, OpenAI Chat +10
AI & RAG

This workflow is designed for professionals, companies, and agencies that want to automate competitive analysis based on public reviews and opinions found on the Internet. It is especially useful for:

Agent, OpenAI Chat, OpenAI +4