N
n8n Store
Workflow Market
Photo Receipt AI for Budget & Slack Alerts

Photo Receipt AI for Budget & Slack Alerts

by takuma0 views

Description

Categories

🤖 AI & Machine Learning

Nodes Used

n8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.cronn8n-nodes-base.slackn8n-nodes-base.slackn8n-nodes-base.webhookn8n-nodes-base.stickyNoten8n-nodes-base.stickyNoten8n-nodes-base.stickyNote
PriceGratuit
Views0
Last Updated11/28/2025
workflow.json
{
  "id": "tXIAVEIkPlrlzwO9",
  "meta": {
    "instanceId": "d66f4a8292e2201b622a3d783e2857e4d90c675c5e7bbd26eacc6af060964522"
  },
  "name": "Photo Receipt AI for Budget & Slack Alerts",
  "tags": [],
  "nodes": [
    {
      "id": "d9e934b1-3ddb-4312-8556-03e0bc75f001",
      "name": "Receipt Photo Upload",
      "type": "n8n-nodes-base.webhook",
      "position": [
        0,
        256
      ],
      "webhookId": "8d328615-3aeb-42ec-990a-1263815468b2",
      "parameters": {
        "path": "receipt Text",
        "options": {},
        "httpMethod": "POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "be58ef74-b716-4f12-875d-aa09572f9b3c",
      "name": "Add to Budget Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        800,
        256
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json[\"Date\"] }}",
            "Items": "={{ $json[\"Items\"] }}",
            "Store": "={{ $json[\"Store\"] }}",
            "Amount": "={{ $json[\"Amount\"] }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Store",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Store",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Items",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Items",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Amount",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Amount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Data"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit#gid=0",
          "cachedResultName": "Budget"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit?usp=drivesdk",
          "cachedResultName": "Budget"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "51956fcd-1422-4e1c-88fb-ea7a5f921fb2",
      "name": "Code in JavaScript2",
      "type": "n8n-nodes-base.code",
      "position": [
        1024,
        256
      ],
      "parameters": {
        "jsCode": "// Get all rows from the previous Google Sheets (Read) node\nconst items = $items();\n\n// If the sheet is empty, do nothing\nif (items.length === 0) {\n  return [{\n    json: {\n      message: 'No household budget data yet.'\n    }\n  }];\n}\n\n// Function to convert date strings \"YYYY/MM/DD\" or \"YYYY-MM-DD\" to Date objects\nfunction parseDate(str) {\n  if (!str) return null;\n  const parts = str.split(/[\\/\\-]/).map(Number);\n  if (parts.length < 3) return null;\n  const [y, m, d] = parts;\n  return new Date(y, m - 1, d);\n}\n\n// Assume the last row is the newly added record and aggregate by its month\nconst lastRow = items[items.length - 1].json;\nconst lastDateStr = lastRow['Date'];\nconst targetDate = parseDate(lastDateStr) || new Date();\n\n// Target Year-Month (e.g., \"2025-04\")\nconst targetYm = `${targetDate.getFullYear()}-${('0' + (targetDate.getMonth() + 1)).slice(-2)}`;\n\n// Calculate total for the current month\nlet total = 0;\nfor (const { json: row } of items) {\n  const ds = row['Date'];\n  const d = parseDate(ds);\n  if (!d) continue;\n\n  const ym = `${d.getFullYear()}-${('0' + (d.getMonth() + 1)).slice(-2)}`;\n  if (ym === targetYm) {\n    const price = Number(row['Amount'] || 0);\n    if (!isNaN(price)) {\n      total += price;\n    }\n  }\n}\n\n// ★ Monthly budget (change this to your desired amount)\nconst budget = 30000;\n\nconst remaining = budget - total;\n\n// Assemble message for Slack\nconst lines = [\n  `📊 Household Budget for ${targetYm}`,\n  `Budget: ${budget.toLocaleString()} JPY`,\n  `Total Spent: ${total.toLocaleString()} JPY`,\n  `Remaining Budget: ${remaining.toLocaleString()} JPY`,\n];\n\nif (remaining < 0) {\n  lines.push(`⚠️ You have exceeded your budget by ${Math.abs(remaining).toLocaleString()} JPY.`);\n}\n\nreturn [{\n  json: {\n    month: targetYm,\n    budget: budget,\n    totalSpent: total,\n    remainingBudget: remaining,\n    message: lines.join('\\n')\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8a6fa2ef-887e-4695-94d3-7275b5dd2410",
      "name": "Send a message",
      "type": "n8n-nodes-base.slack",
      "position": [
        1600,
        256
      ],
      "webhookId": "a82d6c2e-b15c-4795-b7ec-75b5296a487b",
      "parameters": {
        "text": "={{ $json.output }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09SMMZGXMM",
          "cachedResultName": ""
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "843ef5f2-cba2-4629-bde0-f19355739d93",
      "name": "OpenRouter Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        992,
        -64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "c0ea41d0-15e8-461f-8162-f6762444f75b",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        576,
        256
      ],
      "parameters": {
        "jsCode": "// Assuming AI Agent output is in $json.output\nlet raw = $json.output;\n\n// Convert to JSON if it's a string\nif (typeof raw === 'string') {\n  raw = raw.trim();\n\n  // Remove ```json ~ ``` if present\n  raw = raw.replace(/^```(?:json)?/i, '').replace(/```$/i, '').trim();\n\n  try {\n    raw = JSON.parse(raw);\n  } catch (error) {\n    throw new Error('Could not parse AI Agent output as JSON: ' + error.message + '\\nraw=' + raw);\n  }\n}\n\n// Ensure it's an array, even if only one item\nconst data = Array.isArray(raw) ? raw : [raw];\n\n// Format into n8n's items array structure\nconst items = data.map(entry => {\n  return {\n    json: {\n      Date: entry['Date'],\n      Store: entry['Store'],\n      Items: entry['Items'],\n      Amount: entry['Amount']\n    }\n  };\n});\n\nreturn items;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fe796bfc-a632-4d8c-9ba3-2c76b768655d",
      "name": "OpenRouter Chat Model2",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        296,
        480
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "a204a1cb-3cb6-4ea0-8e79-cbd6b301cd30",
      "name": "Parse Receipt",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        224,
        256
      ],
      "parameters": {
        "text": "={{ $json.body[''].text }}",
        "options": {
          "systemMessage": "You are an expert in receipt analysis. From the following text, accurately extract the date, store name, purchased items, and amount, and output them in the following format:\n\n[\n  {\n    \"Date\": \"YYYY/MM/DD\",\n    \"Store\": \"Store Name\",\n    \"Items\": \"Item Name\",\n    \"Amount\": Amount\n  }\n]\n\nIf there are multiple items, return multiple objects. Do not include any extra comments or sentences, return only JSON. Group items with the same date into a single entry."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "8515213f-2fe5-4334-bc8c-7f6b6739c85d",
      "name": "Report Budget",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1248,
        256
      ],
      "parameters": {
        "text": "={{ $json[\"message\"] }}",
        "options": {
          "systemMessage": "You are a specialist in drafting polished content for Slack. Summarize the total amount spent and the remaining budget. Example:\nCurrent spending: 1000 JPY\nRemaining budget this month: 10000 JPY"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "78cafaa5-7ef7-4441-affb-ca5494ce10eb",
      "name": "Daily Report Trigger",
      "type": "n8n-nodes-base.cron",
      "position": [
        240,
        -288
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "4baa9d88-2664-4dbf-b59c-d673c743564b",
      "name": "Get Budget Sheet (Daily)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        464,
        -288
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit#gid=0",
          "cachedResultName": "Budget"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1hzLc4ioe2H24wTG3TSQNMinFPhoi6LdlIJkyCb0yxNk/edit#gid=0",
          "cachedResultName": "Budget"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "a1e12212-03c5-4f11-bf73-4191b7a52c41",
      "name": "Monthly Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        -288
      ],
      "parameters": {
        "jsCode": "// All rows from the sheet\nconst items = $items();\nif (items.length === 0) {\n  return [{\n    json: {\n      message: 'No household budget data yet.'\n    }\n  }];\n}\n\n// Date parsing function\nfunction parseDate(str) {\n  if (!str) return null;\n  const parts = str.split(/[\\/\\-]/).map(Number);\n  if (parts.length < 3) return null;\n  const [y, m, d] = parts;\n  return new Date(y, m - 1, d);\n}\n\n// The month of the latest row's date will be the target month\nconst lastRow = items[items.length - 1].json;\nconst lastDate = parseDate(lastRow['Date']) || new Date();\nconst targetYm = `${lastDate.getFullYear()}-${('0' + (lastDate.getMonth() + 1)).slice(-2)}`;\n\n// For aggregation\nlet total = 0;\nconst byStore = {};  // Per store\nconst byItem = {};   // Per item\nconst byDate = {};   // Per date\n\nfor (const { json: row } of items) {\n  const d = parseDate(row['Date']);\n  if (!d) continue;\n\n  const ym = `${d.getFullYear()}-${('0' + (d.getMonth() + 1)).slice(-2)}`;\n  if (ym !== targetYm) continue; // Skip if not current month\n\n  const price = Number(row['Amount'] || 0);\n  if (isNaN(price)) continue;\n\n  const dateKey = row['Date'];\n  const store = row['Store'] || 'Unknown Store';\n  const item = row['Items'] || 'Unknown Item';\n\n  total += price;\n\n  byDate[dateKey] = (byDate[dateKey] || 0) + price;\n  byStore[store] = (byStore[store] || 0) + price;\n  byItem[item] = (byItem[item] || 0) + price;\n}\n\n// Daily average\nconst days = Object.keys(byDate).length || 1;\nconst avgPerDay = Math.round(total / days);\n\n// Top N ranking function\nfunction topN(map, n) {\n  return Object.entries(map)\n    .sort((a, b) => b[1] - a[1])\n    .slice(0, n)\n    .map(([key, value]) => ({\n      name: key,\n      amount: value\n    }));\n}\n\nconst topStores = topN(byStore, 3);\nconst topItems = topN(byItem, 3);\nconst topDays = topN(byDate, 3);\n\nreturn [{\n  json: {\n    month: targetYm,\n    monthlyTotal: total,\n    dailyAverage: avgPerDay,\n    top3Stores: topStores,\n    top3Items: topItems,\n    top3SpendingDays: topDays\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ca04ca63-5377-4f27-bc2f-87cfecee94d1",
      "name": "Monthly Report",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        912,
        -288
      ],
      "parameters": {
        "text": "={{`Create a report from this month's (${ $json[\"month\"] }) household budget data.\n\n- Total monthly expenditure: ${$json[\"monthlyTotal\"]} JPY\n- Daily average: ${$json[\"dailyAverage\"]} JPY\n- Top 3 stores by spending: ${JSON.stringify($json[\"top3Stores\"])}\n- Top 3 items by spending: ${JSON.stringify($json[\"top3Items\"])}\n- Top 3 days with most spending: ${JSON.stringify($json[\"top3SpendingDays\"])}\n\nSummarize this month's trends and provide simple advice in English, using bullet points and short comments. Keep it to a readable length and use a polite, informal tone.`}}",
        "options": {
          "systemMessage": "You are an assistant that analyzes household budget data and writes clear English reports. Based on the given numbers, describe the trends and offer simple advice. Keep it to a readable length and use a polite, informal tone."
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "6d340421-5082-4dc2-98e8-8aadb85acbc9",
      "name": "Send monthly report",
      "type": "n8n-nodes-base.slack",
      "position": [
        1264,
        -288
      ],
      "webhookId": "a82d6c2e-b15c-4795-b7ec-75b5296a487b",
      "parameters": {
        "text": "={{ $json.output }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09SMMZGXMM",
          "cachedResultName": ""
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "1e37b78a-f1ce-46e6-94c2-3efd52f140b1",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        1320,
        480
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "fc9c9473-198e-4d57-9968-f6f69030a385",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -656,
        -272
      ],
      "parameters": {
        "width": 496,
        "height": 592,
        "content": "## What it does\nThis workflow automates your household budget tracking in several steps:\n1.  **Receipt Photo Upload**: You upload a photo of your receipt to a webhook.\n2.  **Parse Receipt**: An AI agent extracts the date, store name, purchased items, and total amount from the receipt text.\n3.  **Add to Budget Sheet**: The extracted data is then appended to your designated Google Sheet.\n4.  **Daily Budget Report**: After each receipt entry, the workflow calculates your current month's spending, remaining budget, and sends a summary to Slack.\n5.  **Monthly Budget Report**: Once a day (triggered by a cron job), it reads all budget data for the current month from Google Sheets, performs an analysis (total spending, daily average, top stores, items, and spending days), and sends a comprehensive report to Slack.\n### Requirements\n-   **n8n Account**: Self-hosted or Cloud.\n-   **Google Sheets**: A Google Sheet set up with columns for \"Date\", \"Store\", \"Items\", and \"Amount\".\n-   **Slack Workspace**: A Slack channel where you want to receive budget updates.\n-   **OpenRouter Account**: An API key for OpenRouter to use their chat models."
      },
      "typeVersion": 1
    },
    {
      "id": "017435d3-43cb-4322-9697-024de9511297",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        48,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 1600,
        "height": 464,
        "content": "### Steps\n1.  **Google Sheets Setup**: Create/use a Google Sheet, share with n8n credentials, and select it in the workflow nodes.\n2.  **OpenRouter Credentials**: Get an API key from OpenRouter and configure n8n credentials for all chat model nodes.\n3.  **Slack Credentials**: Connect your Slack workspace in n8n and select your desired channels for messages.\n4.  **Webhook URL**: Copy the 'Receipt Photo Upload' webhook URL after activation.\n5.  **Monthly Budget Adjustment**: Edit `const budget = 30000;` in 'Code in JavaScript2' to set your budget."
      },
      "typeVersion": 1
    },
    {
      "id": "53e317a2-b71a-466d-a94c-d389dccf9b83",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -496
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 480,
        "content": "## How to customize the workflow\n\n-   **Daily Report Trigger**: Adjust the cron settings for frequency.\n-   **AI Model**: Change AI models in 'OpenRouter Chat Model' nodes to other LLMs.\n-   **Output Formatting**: Customize Slack message content and tone in 'Report Budget' and 'Monthly Report' nodes.\n-   **Additional Integrations**: Expand with more nodes for other accounting software, notifications, or advanced analysis."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "4e51c46b-3d65-4634-b5a6-ccd423cc3c4c",
  "connections": {
    "Parse Receipt": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Report Budget": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Monthly Report": {
      "main": [
        [
          {
            "node": "Send monthly report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Monthly Analysis": {
      "main": [
        [
          {
            "node": "Monthly Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Add to Budget Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to Budget Sheet": {
      "main": [
        [
          {
            "node": "Code in JavaScript2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript2": {
      "main": [
        [
          {
            "node": "Report Budget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Report Trigger": {
      "main": [
        [
          {
            "node": "Get Budget Sheet (Daily)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receipt Photo Upload": {
      "main": [
        [
          {
            "node": "Parse Receipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Report Budget",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Monthly Report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model2": {
      "ai_languageModel": [
        [
          {
            "node": "Parse Receipt",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get Budget Sheet (Daily)": {
      "main": [
        [
          {
            "node": "Monthly Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

相关工作流