This workflow follows the Agent → Google Sheets 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 →
{
"createdAt": "2025-11-22T02:45:09.052Z",
"updatedAt": "2026-01-30T06:41:50.000Z",
"id": "rZjkLEVp0IM9xKfh",
"name": "PAY",
"active": true,
"isArchived": false,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "PAY-HISTORY",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-1504,
-272
],
"id": "8be8aa6e-80cf-4217-bbee-92e7783dc2ee",
"name": "Webhook"
},
{
"parameters": {
"promptType": "define",
"text": "Analyze this image and extract data",
"options": {
"systemMessage": "You are an expert Thai Accountant and OCR Specialist AI. Your task is to extract data from Thai bank transfer slips (GH Bank, KPlus, SCB, etc.) with 100% precision and classify the transaction.\n\n### CRITICAL INSTRUCTION: REFERENCE ID (ref_id)\n1. **L vs 1 Ambiguity:** The font used in bank slips often makes the lowercase letter \"l\" (India-Lima) and the number \"1\" look identical. You must analyze the visual context carefully.\n2. **Format:** The `ref_id` must be a single continuous string. **REMOVE all spaces and line breaks** immediately.\n\n### USER IDENTITY (The Owner)\nThe user is **\"Teerayut Yeerahem\"** (Thai: \u0e19\u0e32\u0e22 \u0e18\u0e35\u0e23\u0e22\u0e38\u0e17\u0e18 \u0e2b\u0e22\u0e35\u0e23\u0e32\u0e40\u0e2b\u0e21).\nMatches include variations: \"Teerayut\", \"Theerayut\", \"\u0e18\u0e35\u0e23\u0e22\u0e38\u0e17\u0e18\", \"Mr. Teerayut\", \"Teerayut Y\".\n\n### CLASSIFICATION LOGIC (Strict Order)\nAnalyze `Sender` and `Receiver` names to determine `transaction_type`:\n\n1. **\"transfer\" (Internal Move):**\n - IF `Sender` matches User Identity AND `Receiver` matches User Identity.\n - (Meaning: Moving money between own accounts).\n\n2. **\"expense\" (Spending):**\n - IF `Sender` matches User Identity AND `Receiver` is SOMEONE ELSE.\n\n3. **\"income\" (Receiving):**\n - IF `Sender` is SOMEONE ELSE AND `Receiver` matches User Identity.\n\n4. **\"unknown\":** If neither matches or names are illegible.\n\n### DATA EXTRACTION RULES\n1. **Date:** Convert to ISO format **YYYY-MM-DD**.\n - **Crucial:** Convert Buddhist Year to AD correctly (e.g., BE 2568 = AD 2025, BE 2569 = AD 2026).\n2. **Time:** Format as **HH:mm** (24-hour format).\n3. **Amount:** Numeric value only (remove commas, remove \"THB\").\n4. **Category:**\n - If type is \"transfer\" -> \"Internal Move\".\n - If type is \"expense\" -> Guess category based on Receiver (e.g., \"Food\", \"Shopping\", \"Utility\"). Default to \"Uncategorized\" if unsure.\n - If type is \"income\" -> \"Revenue\".\n\n### OUTPUT FORMAT\nReturn **ONLY** a raw JSON object (no markdown code blocks, no explanation).\n\n{\n \"transaction_data\": {\n \"date\": \"YYYY-MM-DD\",\n \"time\": \"HH:mm\",\n \"amount\": Number,\n \"sender_name\": \"String\",\n \"sender_bank\": \"String\",\n \"receiver_name\": \"String\",\n \"receiver_bank\": \"String\",\n \"ref_id\": \"String\",\n \"transaction_type\": \"String\",\n \"category\": \"String\"\n }\n}",
"passthroughBinaryImages": true
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [
624,
400
],
"id": "6fe5f860-b3b7-4766-9b9c-160013a772a3",
"name": "AI Agent"
},
{
"parameters": {
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1,
"position": [
592,
624
],
"id": "61267499-1863-47ee-a357-825f737580c7",
"name": "Google Gemini Chat Model",
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "faa0762e-6483-4684-9754-e84ce1bb0bf2",
"leftValue": "={{ $json.body.events[0].message.type }}",
"rightValue": "image",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
96,
400
],
"id": "283b34b1-4389-4ad1-828c-0b2c8b9fda6b",
"name": "Filter"
},
{
"parameters": {
"url": "=https://api-data.line.me/v2/bot/message/{{ $json.body.events[0].message.id }}/content",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
352,
400
],
"id": "bca842c1-f508-419e-86ac-e5b0fb1e3b4d",
"name": "HTTP Request",
"retryOnFail": true,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const aiResponse = $input.item.json.output || $input.item.json.text;\nconst cleanJson = aiResponse.replace(/```json/g, '').replace(/```/g, '').trim();\n\ntry {\n const data = JSON.parse(cleanJson);\n const tx = data.transaction_data;\n\n // normalize\n tx.ref_id = String(tx.ref_id || '').trim(); // \u274c \u0e44\u0e21\u0e48\u0e15\u0e49\u0e2d\u0e07 toUpperCase\n\n return tx;\n} catch (e) {\n return { error: true, message: \"\u0e23\u0e39\u0e1b\u0e41\u0e1a\u0e1a JSON \u0e1c\u0e34\u0e14\u0e1e\u0e25\u0e32\u0e14\" };\n}\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
976,
400
],
"id": "cf6b8e37-aef3-41fe-9adf-6f9eb0327aa1",
"name": "Code in JavaScript"
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds",
"mode": "list",
"cachedResultName": "Pay History",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"date": "={{ $('Code in JavaScript').item.json.date }}",
"time": "={{ $('Code in JavaScript').item.json.time }}",
"amount": "={{ $('Code in JavaScript').item.json.amount }}",
"category": "={{ $('Code in JavaScript').item.json.category }}",
"sender_name": "={{ $('Code in JavaScript').item.json.sender_name }}",
"sender_bank": "={{ $('Code in JavaScript').item.json.sender_bank }}",
"receiver_name": "={{ $('Code in JavaScript').item.json.receiver_name }}",
"receiver_bank": "={{ $('Code in JavaScript').item.json.receiver_bank }}",
"ref_id": "={{ $('Code in JavaScript').item.json.ref_id }}",
"transaction_type (income/expense/transfer)": "={{ $('Code in JavaScript').item.json.transaction_type }}"
},
"matchingColumns": [],
"schema": [
{
"id": "date",
"displayName": "date",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "time",
"displayName": "time",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "transaction_type (income/expense/transfer)",
"displayName": "transaction_type (income/expense/transfer)",
"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": "category",
"displayName": "category",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "sender_name",
"displayName": "sender_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "sender_bank",
"displayName": "sender_bank",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "receiver_name",
"displayName": "receiver_name",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "receiver_bank",
"displayName": "receiver_bank",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ref_id",
"displayName": "ref_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
2080,
576
],
"id": "78b9b698-7ca6-4960-b6b2-1cbab2f49cfe",
"name": "Append row in sheet",
"retryOnFail": true,
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.line.me/v2/bot/message/reply",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"replyToken\": \"{{ $('Webhook').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"flex\",\n \"altText\": \"\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e2a\u0e33\u0e40\u0e23\u0e47\u0e08\",\n \"contents\": {\n \"type\": \"bubble\",\n \"body\": {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u2705 \u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e2a\u0e33\u0e40\u0e23\u0e47\u0e08\",\n \"weight\": \"bold\",\n \"size\": \"xl\",\n \"color\": \"#1DB446\"\n },\n {\n \"type\": \"separator\",\n \"margin\": \"md\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"margin\": \"md\",\n \"spacing\": \"sm\",\n \"contents\": [\n {\n \"type\": \"box\",\n \"layout\": \"baseline\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e1b\u0e23\u0e30\u0e40\u0e20\u0e17\",\n \"color\": \"#aaaaaa\",\n \"size\": \"sm\",\n \"flex\": 1\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('Append row in sheet').item.json.transaction_type || '-' }}\",\n \"wrap\": true,\n \"color\": \"#666666\",\n \"size\": \"sm\",\n \"flex\": 2\n }\n ]\n },\n {\n \"type\": \"box\",\n \"layout\": \"baseline\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\",\n \"color\": \"#aaaaaa\",\n \"size\": \"sm\",\n \"flex\": 1\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('Append row in sheet').item.json.receiver_name || '\u0e44\u0e21\u0e48\u0e23\u0e30\u0e1a\u0e38' }}\",\n \"wrap\": true,\n \"color\": \"#666666\",\n \"size\": \"sm\",\n \"flex\": 2\n }\n ]\n },\n {\n \"type\": \"box\",\n \"layout\": \"baseline\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e22\u0e2d\u0e14\u0e40\u0e07\u0e34\u0e19\",\n \"color\": \"#aaaaaa\",\n \"size\": \"sm\",\n \"flex\": 1\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('Append row in sheet').item.json.amount }} \u0e1a\u0e32\u0e17\",\n \"wrap\": true,\n \"color\": \"#333333\",\n \"size\": \"lg\",\n \"weight\": \"bold\",\n \"flex\": 2\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2336,
576
],
"id": "04d4a598-9f51-4cd2-babd-3fafb571dad6",
"name": "HTTP Request1",
"retryOnFail": true,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds",
"mode": "list",
"cachedResultName": "Pay History",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit#gid=0"
},
"filtersUI": {
"values": [
{
"lookupColumn": "date",
"lookupValue": "={{ $json.date }}"
},
{
"lookupColumn": "time",
"lookupValue": "={{ $json.time }}"
},
{
"lookupColumn": "amount",
"lookupValue": "={{ $json.amount }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
1248,
400
],
"id": "3f42e9e8-8255-4359-9ebe-598d5ae2a448",
"name": "Get row(s) in sheet",
"alwaysOutputData": true,
"retryOnFail": true,
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "d68ced39-8a95-437f-aa9e-6811ca74d120",
"leftValue": "={{ $json.ref_id }}",
"rightValue": 0,
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1552,
400
],
"id": "650fef6d-e18d-49d0-9277-907a32ca62ba",
"name": "If"
},
{
"parameters": {
"method": "POST",
"url": "https://api.line.me/v2/bot/message/reply",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"replyToken\": \"{{ $('Webhook').first().json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"text\",\n \"text\": \"\u26a0\ufe0f \u0e2a\u0e25\u0e34\u0e1b\u0e19\u0e35\u0e49\u0e1a\u0e31\u0e19\u0e17\u0e36\u0e01\u0e44\u0e1b\u0e41\u0e25\u0e49\u0e27\u0e04\u0e23\u0e31\u0e1a (Ref: {{ $('Code in JavaScript').first().json.transaction_data.ref_id }})\"\n }\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1824,
384
],
"id": "d9177044-d72b-409c-b5a8-52c974af2c08",
"name": "HTTP Request2",
"executeOnce": true,
"retryOnFail": true,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"amount": 10
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
1840,
576
],
"id": "4bb65bd4-b9d3-4f7d-9474-afb848d6f6e4",
"name": "Wait"
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtHour": 23
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
-1120,
768
],
"id": "dae57c55-8523-4495-b77f-506376b199c9",
"name": "Schedule Trigger"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds",
"mode": "list",
"cachedResultName": "Pay History",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-816,
768
],
"id": "9ca290b6-efa1-4e2e-9e14-629d7581067b",
"name": "Get row(s) in sheet1",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// 1. \u0e23\u0e31\u0e1a\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\nconst rows = $input.all();\n\nlet totalMonthlyIncome = 0; // \u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a\u0e17\u0e31\u0e49\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19\nlet totalMonthlyExpense = 0; // [\u0e40\u0e1e\u0e34\u0e48\u0e21] \u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\u0e17\u0e31\u0e49\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19\nlet totalWeeklyExpense = 0; // \u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22 7 \u0e27\u0e31\u0e19\nlet countWeekly = 0; // \u0e19\u0e31\u0e1a\u0e08\u0e33\u0e19\u0e27\u0e19\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23 7 \u0e27\u0e31\u0e19\nlet catMap = {}; // \u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22 7 \u0e27\u0e31\u0e19\n\n// 2. \u0e01\u0e33\u0e2b\u0e19\u0e14\u0e40\u0e27\u0e25\u0e32\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19\nconst today = new Date();\ntoday.setHours(23, 59, 59, 999);\n\n// 2.1 \u0e01\u0e33\u0e2b\u0e19\u0e14\u0e0a\u0e48\u0e27\u0e07\u0e40\u0e27\u0e25\u0e32 \"\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e19\u0e35\u0e49\" (7 \u0e27\u0e31\u0e19\u0e22\u0e49\u0e2d\u0e19\u0e2b\u0e25\u0e31\u0e07)\nconst sevenDaysAgo = new Date();\nsevenDaysAgo.setDate(today.getDate() - 7);\nsevenDaysAgo.setHours(0, 0, 0, 0);\n\n// 2.2 \u0e01\u0e33\u0e2b\u0e19\u0e14\u0e0a\u0e48\u0e27\u0e07\u0e40\u0e27\u0e25\u0e32 \"\u0e40\u0e14\u0e37\u0e2d\u0e19\u0e19\u0e35\u0e49\" (\u0e27\u0e31\u0e19\u0e17\u0e35\u0e48 1 \u0e02\u0e2d\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19\u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19)\nconst startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);\nstartOfMonth.setHours(0, 0, 0, 0);\n\n// 3. \u0e27\u0e19\u0e25\u0e39\u0e1b\u0e40\u0e0a\u0e47\u0e04\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\nfor (const item of rows) {\n const row = item.json;\n if (row.date) {\n const rowDate = new Date(row.date);\n const amount = parseFloat(row.amount) || 0;\n \n // \u0e40\u0e0a\u0e47\u0e04\u0e1b\u0e23\u0e30\u0e40\u0e20\u0e17 (Income/Expense)\n const typeRaw = row['transaction_type (income/expense/transfer)'] || row['type'] || row['transaction_type'] || \"\";\n const type = typeRaw.toString().toLowerCase();\n const category = row['category'] || '\u0e2d\u0e37\u0e48\u0e19\u0e46';\n\n // --- Logic 1: \u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e23\u0e30\u0e14\u0e31\u0e1a \"\u0e40\u0e14\u0e37\u0e2d\u0e19\u0e19\u0e35\u0e49\" (\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a + \u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22) ---\n if (rowDate >= startOfMonth && rowDate <= today) {\n // 1.1 \u0e04\u0e34\u0e14\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a\n if (type.includes('income') || type.includes('\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a')) {\n totalMonthlyIncome += amount;\n }\n // 1.2 [\u0e40\u0e1e\u0e34\u0e48\u0e21] \u0e04\u0e34\u0e14\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\u0e17\u0e31\u0e49\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19\n if (type.includes('expense') || type.includes('\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22')) {\n totalMonthlyExpense += amount;\n }\n }\n\n // --- Logic 2: \u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e23\u0e30\u0e14\u0e31\u0e1a \"\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e19\u0e35\u0e49\" (7 \u0e27\u0e31\u0e19\u0e22\u0e49\u0e2d\u0e19\u0e2b\u0e25\u0e31\u0e07) ---\n if (rowDate >= sevenDaysAgo && rowDate <= today) {\n if (type.includes('expense') || type.includes('\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22')) {\n totalWeeklyExpense += amount;\n \n // \u0e40\u0e01\u0e47\u0e1a\u0e22\u0e2d\u0e14\u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48 (\u0e40\u0e09\u0e1e\u0e32\u0e30\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22 week \u0e19\u0e35\u0e49)\n if (catMap[category]) {\n catMap[category] += amount;\n } else {\n catMap[category] = amount;\n }\n countWeekly++;\n }\n }\n }\n}\n\n// 4. \u0e08\u0e31\u0e14\u0e2d\u0e31\u0e19\u0e14\u0e31\u0e1a\u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48 (Top 5 \u0e02\u0e2d\u0e07 Week \u0e19\u0e35\u0e49)\nconst sortedCats = Object.entries(catMap)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 5);\n\n// \u0e2a\u0e23\u0e49\u0e32\u0e07\u0e02\u0e49\u0e2d\u0e04\u0e27\u0e32\u0e21\u0e2a\u0e23\u0e38\u0e1b Top 5 (\u0e43\u0e0a\u0e49 \\\\n \u0e41\u0e17\u0e19 \\n \u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e44\u0e21\u0e48\u0e43\u0e2b\u0e49 JSON \u0e1e\u0e31\u0e07)\nlet topCatText = \"\";\nif (sortedCats.length > 0) {\n topCatText = sortedCats.map((item, index) => {\n const safeName = item[0].replace(/\"/g, '\\\\\"');\n return `${index + 1}. ${safeName}: -${item[1].toLocaleString()} \u0e3f`;\n }).join(\"\\\\n\");\n} else {\n topCatText = \"- \u0e44\u0e21\u0e48\u0e21\u0e35\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e19\u0e35\u0e49 -\";\n}\n\n// [\u0e41\u0e01\u0e49] \u0e04\u0e33\u0e19\u0e27\u0e13\u0e04\u0e07\u0e40\u0e2b\u0e25\u0e37\u0e2d\u0e08\u0e23\u0e34\u0e07 (\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a\u0e17\u0e31\u0e49\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19 - \u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\u0e17\u0e31\u0e49\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19)\nconst netBalance = totalMonthlyIncome - totalMonthlyExpense;\n\n// 5. \u0e2a\u0e48\u0e07\u0e1c\u0e25\u0e25\u0e31\u0e1e\u0e18\u0e4c\nreturn {\n week_range: sevenDaysAgo.toLocaleDateString('th-TH') + ' - ' + today.toLocaleDateString('th-TH'),\n month_name: startOfMonth.toLocaleDateString('th-TH', { month: 'long', year: 'numeric' }),\n \n total_income: totalMonthlyIncome,\n total_expense: totalWeeklyExpense,\n net_balance: netBalance,\n tx_count: countWeekly,\n\n weekly_income_formatted: \"0\", \n weekly_expense_formatted: totalWeeklyExpense.toLocaleString(),\n \n monthly_income_formatted: totalMonthlyIncome.toLocaleString(),\n monthly_expense_formatted: totalMonthlyExpense.toLocaleString(), // [\u0e41\u0e01\u0e49] \u0e43\u0e0a\u0e49\u0e15\u0e31\u0e27\u0e40\u0e25\u0e02\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\u0e17\u0e31\u0e49\u0e07\u0e40\u0e14\u0e37\u0e2d\u0e19\u0e08\u0e23\u0e34\u0e07\u0e46 \u0e41\u0e25\u0e49\u0e27\n \n balance_formatted: netBalance.toLocaleString(),\n top_categories_text: topCatText\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-608,
768
],
"id": "5c6578db-6fb4-403e-9d8f-cd4abbd56986",
"name": "Code in JavaScript1"
},
{
"parameters": {
"method": "POST",
"url": "https://api.line.me/v2/bot/message/push",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"to\": \"U7eb0969b9040dc2ec9d4a19e89e347f4\",\n \"messages\": [\n {\n \"type\": \"flex\",\n \"altText\": \"\ud83d\udcca \u0e2a\u0e23\u0e38\u0e1b\u0e23\u0e32\u0e22\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\",\n \"contents\": {\n \"type\": \"bubble\",\n \"hero\": {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83d\udcca \u0e2a\u0e23\u0e38\u0e1b\u0e23\u0e32\u0e22\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\",\n \"weight\": \"bold\",\n \"size\": \"xl\",\n \"color\": \"#ffffff\"\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $json.week_range }}\",\n \"size\": \"sm\",\n \"color\": \"#ffffff\",\n \"margin\": \"md\"\n }\n ],\n \"backgroundColor\": \"#8B5CF6\",\n \"paddingAll\": \"20px\"\n },\n \"body\": {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e19\u0e35\u0e49 (7 \u0e27\u0e31\u0e19)\",\n \"weight\": \"bold\",\n \"size\": \"sm\",\n \"color\": \"#8B5CF6\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"horizontal\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83d\udcb0 \u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a\",\n \"size\": \"sm\",\n \"color\": \"#555555\",\n \"flex\": 0\n },\n {\n \"type\": \"text\",\n \"text\": \"\u0e3f{{ $json.weekly_income_formatted }}\",\n \"size\": \"sm\",\n \"color\": \"#10b981\",\n \"align\": \"end\",\n \"weight\": \"bold\"\n }\n ],\n \"margin\": \"sm\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"horizontal\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83d\udcb8 \u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\",\n \"size\": \"sm\",\n \"color\": \"#555555\",\n \"flex\": 0\n },\n {\n \"type\": \"text\",\n \"text\": \"\u0e3f{{ $json.weekly_expense_formatted }}\",\n \"size\": \"sm\",\n \"color\": \"#ef4444\",\n \"align\": \"end\",\n \"weight\": \"bold\"\n }\n ],\n \"margin\": \"sm\"\n },\n {\n \"type\": \"separator\",\n \"margin\": \"lg\"\n },\n {\n \"type\": \"text\",\n \"text\": \"\u0e22\u0e2d\u0e14\u0e04\u0e07\u0e40\u0e2b\u0e25\u0e37\u0e2d\u0e08\u0e23\u0e34\u0e07 ({{ $json.month_name }})\",\n \"weight\": \"bold\",\n \"size\": \"sm\",\n \"color\": \"#8B5CF6\",\n \"margin\": \"lg\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"horizontal\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83d\udcb5 \u0e04\u0e07\u0e40\u0e2b\u0e25\u0e37\u0e2d\",\n \"size\": \"md\",\n \"color\": \"#111111\",\n \"flex\": 0,\n \"weight\": \"bold\"\n },\n {\n \"type\": \"text\",\n \"text\": \"\u0e3f{{ $json.balance_formatted }}\",\n \"size\": \"md\",\n \"color\": \"#8B5CF6\",\n \"align\": \"end\",\n \"weight\": \"bold\"\n }\n ],\n \"margin\": \"sm\"\n },\n {\n \"type\": \"text\",\n \"text\": \"\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a \u0e3f{{ $json.monthly_income_formatted }} - \u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22 \u0e3f{{ $json.monthly_expense_formatted }}\",\n \"size\": \"xxs\",\n \"color\": \"#aaaaaa\",\n \"align\": \"end\",\n \"margin\": \"xs\"\n },\n {\n \"type\": \"separator\",\n \"margin\": \"lg\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83c\udfc6 Top 3 \u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48 (\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e19\u0e35\u0e49)\",\n \"weight\": \"bold\",\n \"size\": \"sm\",\n \"color\": \"#8B5CF6\"\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $json.top_categories_text }}\",\n \"size\": \"xs\",\n \"color\": \"#555555\",\n \"margin\": \"sm\",\n \"wrap\": true\n }\n ],\n \"margin\": \"lg\"\n }\n ]\n },\n \"footer\": {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83d\udcdd \u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e19\u0e35\u0e49: {{ $json.tx_count }} \u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\",\n \"size\": \"xs\",\n \"color\": \"#aaaaaa\",\n \"align\": \"center\"\n },\n {\n \"type\": \"text\",\n \"text\": \"\u2728 \u0e21\u0e35\u0e2a\u0e31\u0e1b\u0e14\u0e32\u0e2b\u0e4c\u0e17\u0e35\u0e48\u0e14\u0e35!\",\n \"size\": \"xs\",\n \"color\": \"#aaaaaa\",\n \"align\": \"center\",\n \"margin\": \"sm\"\n }\n ]\n }\n },\n \"quickReply\": {\n \"items\": [\n {\n \"type\": \"action\",\n \"action\": {\n \"type\": \"message\",\n \"label\": \"\ud83d\udcc5 \u0e2a\u0e23\u0e38\u0e1b\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49\",\n \"text\": \"\u0e2a\u0e23\u0e38\u0e1b\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49\"\n }\n },\n {\n \"type\": \"action\",\n \"action\": {\n \"type\": \"message\",\n \"label\": \"\ud83d\udcc5 \u0e2a\u0e23\u0e38\u0e1b\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\",\n \"text\": \"\u0e2a\u0e23\u0e38\u0e1b\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\"\n }\n },\n {\n \"type\": \"action\",\n \"action\": {\n \"type\": \"message\",\n \"label\": \"\ud83d\udcca \u0e2a\u0e23\u0e38\u0e1b 7 \u0e27\u0e31\u0e19\",\n \"text\": \"\u0e2a\u0e23\u0e38\u0e1b 7 \u0e27\u0e31\u0e19\"\n }\n }\n ]\n }\n }\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
-400,
768
],
"id": "516a7d7c-72c1-4ebb-8371-503f8bfd4da9",
"name": "HTTP Request3",
"retryOnFail": true,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "742fca07-264f-4c67-af8d-1cfccdf5a0a4",
"leftValue": "={{ $json.body.events[0].message.text }}",
"rightValue": "\u0e2a\u0e23\u0e38\u0e1b\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-944,
-384
],
"id": "509c80fb-7bea-449a-94bf-a9b8d32b7ba0",
"name": "If1"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds",
"mode": "list",
"cachedResultName": "Pay History",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-656,
-624
],
"id": "02b4c7eb-90d5-4014-ae8a-a000d1d00cf0",
"name": "Get row(s) in sheet2",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// \u0e23\u0e31\u0e1a\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\nconst rows = $input.all();\n\nlet todayIncome = 0;\nlet todayExpense = 0;\nlet todayCount = 0;\nlet catMap = {};\n\n// \u0e01\u0e33\u0e2b\u0e19\u0e14\u0e40\u0e27\u0e25\u0e32\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49\nconst today = new Date();\ntoday.setHours(0, 0, 0, 0);\nconst endOfDay = new Date();\nendOfDay.setHours(23, 59, 59, 999);\n\n// \u0e27\u0e19\u0e25\u0e39\u0e1b\u0e40\u0e0a\u0e47\u0e04\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\nfor (const item of rows) {\n const row = item.json;\n if (row.date) {\n const rowDate = new Date(row.date);\n const amount = parseFloat(row.amount) || 0;\n \n const typeRaw = row['transaction_type (income/expense/transfer)'] || row['type'] || row['transaction_type'] || \"\";\n const type = typeRaw.toString().toLowerCase();\n const category = row['category'] || '\u0e2d\u0e37\u0e48\u0e19\u0e46';\n\n // \u0e40\u0e09\u0e1e\u0e32\u0e30\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49\n if (rowDate >= today && rowDate <= endOfDay) {\n todayCount++;\n \n if (type.includes('income') || type.includes('\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a')) {\n todayIncome += amount;\n }\n if (type.includes('expense') || type.includes('\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22')) {\n todayExpense += amount;\n \n if (catMap[category]) {\n catMap[category] += amount;\n } else {\n catMap[category] = amount;\n }\n }\n }\n }\n}\n\n// \u0e08\u0e31\u0e14\u0e2d\u0e31\u0e19\u0e14\u0e31\u0e1a\u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48\nconst sortedCats = Object.entries(catMap)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3);\n\nlet topCatText = '';\nif (sortedCats.length > 0) {\n sortedCats.forEach((item, index) => {\n topCatText += `${index + 1}. ${item[0]}: \u0e3f${item[1].toLocaleString('th-TH')}\\n`;\n });\n} else {\n topCatText = '- \u0e22\u0e31\u0e07\u0e44\u0e21\u0e48\u0e21\u0e35\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e27\u0e31\u0e19\u0e19\u0e35\u0e49 -';\n}\n\nconst balance = todayIncome - todayExpense;\n\nconst formatNum = (num) => {\n return num.toLocaleString('th-TH', {minimumFractionDigits: 0, maximumFractionDigits: 2});\n};\n\nconst formatDate = (date) => {\n return date.toLocaleDateString('th-TH', { day: '2-digit', month: 'long', year: 'numeric' });\n};\n\n// \u2b50 \u0e2a\u0e33\u0e04\u0e31\u0e0d: \u0e04\u0e37\u0e19\u0e04\u0e48\u0e32\u0e41\u0e1a\u0e1a\u0e2d\u0e32\u0e23\u0e4c\u0e40\u0e23\u0e22\u0e4c\u0e1e\u0e23\u0e49\u0e2d\u0e21 pairedItem\nreturn [\n {\n json: {\n date: formatDate(today),\n income: todayIncome,\n expense: todayExpense,\n balance: balance,\n count: todayCount,\n top_categories: topCatText.trim(),\n income_formatted: formatNum(todayIncome),\n expense_formatted: formatNum(todayExpense),\n balance_formatted: formatNum(balance)\n },\n pairedItem: 0\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-416,
-624
],
"id": "d1a8a5ad-e499-4998-92ad-556f65aacb70",
"name": "Code in JavaScript2"
},
{
"parameters": {
"method": "POST",
"url": "https://api.line.me/v2/bot/message/reply",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "=application/json; charset=UTF-8"
}
]
},
"sendBody": true,
"specifyBody": "=json",
"bodyParameters": {
"parameters": [
{}
]
},
"jsonBody": "={\n \"replyToken\": \"{{ $('Webhook').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"flex\",\n \"altText\": \"\ud83d\udcca \u0e2a\u0e23\u0e38\u0e1b\u0e23\u0e32\u0e22\u0e07\u0e32\u0e19\u0e01\u0e32\u0e23\u0e40\u0e07\u0e34\u0e19\u0e1b\u0e23\u0e30\u0e08\u0e33\u0e27\u0e31\u0e19\",\n \"contents\": {\n \"type\": \"bubble\",\n \"size\": \"mega\",\n \"header\": {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"FINANCIAL REPORT\",\n \"color\": \"#ffffffaa\",\n \"weight\": \"bold\",\n \"size\": \"xxs\"\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('set1').item.json.date }}\",\n \"color\": \"#ffffff\",\n \"weight\": \"bold\",\n \"size\": \"xl\",\n \"flex\": 1\n }\n ],\n \"backgroundColor\": \"#27AE60\",\n \"paddingAll\": \"20px\"\n },\n \"body\": {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e22\u0e2d\u0e14\u0e04\u0e07\u0e40\u0e2b\u0e25\u0e37\u0e2d\u0e2a\u0e38\u0e17\u0e18\u0e34 (Net Balance)\",\n \"color\": \"#8C8C8C\",\n \"size\": \"xs\",\n \"align\": \"center\",\n \"weight\": \"bold\"\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('set1').item.json.balance }}\",\n \"weight\": \"bold\",\n \"size\": \"4xl\",\n \"align\": \"center\",\n \"color\": \"#2c3e50\",\n \"margin\": \"sm\"\n },\n {\n \"type\": \"separator\",\n \"margin\": \"xl\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"horizontal\",\n \"margin\": \"xl\",\n \"contents\": [\n {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a\",\n \"size\": \"xs\",\n \"color\": \"#8C8C8C\"\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('set1').item.json.income }}\",\n \"size\": \"lg\",\n \"weight\": \"bold\",\n \"color\": \"#27AE60\"\n }\n ],\n \"alignItems\": \"center\"\n },\n {\n \"type\": \"separator\",\n \"color\": \"#E0E0E0\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22\",\n \"size\": \"xs\",\n \"color\": \"#8C8C8C\"\n },\n {\n \"type\": \"text\",\n \"text\": \"{{ $('set1').item.json.expense }}\",\n \"size\": \"lg\",\n \"weight\": \"bold\",\n \"color\": \"#EB5757\"\n }\n ],\n \"alignItems\": \"center\"\n }\n ]\n },\n {\n \"type\": \"separator\",\n \"margin\": \"xl\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"margin\": \"lg\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"\ud83d\udccc \u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48\u0e22\u0e2d\u0e14\u0e19\u0e34\u0e22\u0e21\",\n \"size\": \"sm\",\n \"weight\": \"bold\",\n \"color\": \"#333333\",\n \"margin\": \"sm\"\n },\n {\n \"type\": \"box\",\n \"layout\": \"vertical\",\n \"margin\": \"md\",\n \"backgroundColor\": \"#F8F9FA\",\n \"cornerRadius\": \"md\",\n \"paddingAll\": \"md\",\n \"contents\": [\n {\n \"type\": \"text\",\n \"text\": \"{{ JSON.stringify($('set1').item.json.top_categories).slice(1, -1) }}\",\n \"size\": \"xs\",\n \"color\": \"#555555\",\n \"wrap\": true,\n \"lineSpacing\": \"4px\"\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
80,
-624
],
"id": "28c33c96-438d-42ef-b867-1a8da77771ac",
"name": "HTTP Request4",
"retryOnFail": true,
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "0136b485-4002-4737-b823-022ebbe03aaa",
"leftValue": "={{ $json.body.events[0].message.text }}",
"rightValue": "\u0e2a\u0e23\u0e38\u0e1b\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-720,
-368
],
"id": "9903d0f9-016c-4a46-b9b8-fd179b60f5e3",
"name": "If2"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds",
"mode": "list",
"cachedResultName": "Pay History",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-416,
-384
],
"id": "4883e67c-660b-4a4f-9b00-d27abcd07ff1",
"name": "Get row(s) in sheet3",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// \u0e23\u0e31\u0e1a\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\nconst rows = $input.all();\n\nlet yesterdayIncome = 0;\nlet yesterdayExpense = 0;\nlet yesterdayCount = 0;\nlet catMap = {};\n\n// \u0e01\u0e33\u0e2b\u0e19\u0e14\u0e40\u0e27\u0e25\u0e32\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\nconst yesterday = new Date();\nyesterday.setDate(yesterday.getDate() - 1);\nyesterday.setHours(0, 0, 0, 0);\nconst endOfYesterday = new Date(yesterday);\nendOfYesterday.setHours(23, 59, 59, 999);\n\n// \u0e27\u0e19\u0e25\u0e39\u0e1b\u0e40\u0e0a\u0e47\u0e04\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\nfor (const item of rows) {\n const row = item.json;\n if (row.date) {\n const rowDate = new Date(row.date);\n const amount = parseFloat(row.amount) || 0;\n \n const typeRaw = row['transaction_type (income/expense/transfer)'] || row['type'] || row['transaction_type'] || \"\";\n const type = typeRaw.toString().toLowerCase();\n const category = row['category'] || '\u0e2d\u0e37\u0e48\u0e19\u0e46';\n\n // \u0e40\u0e09\u0e1e\u0e32\u0e30\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19\n if (rowDate >= yesterday && rowDate <= endOfYesterday) {\n yesterdayCount++;\n \n if (type.includes('income') || type.includes('\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a')) {\n yesterdayIncome += amount;\n }\n if (type.includes('expense') || type.includes('\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22')) {\n yesterdayExpense += amount;\n \n if (catMap[category]) {\n catMap[category] += amount;\n } else {\n catMap[category] = amount;\n }\n }\n }\n }\n}\n\n// \u0e08\u0e31\u0e14\u0e2d\u0e31\u0e19\u0e14\u0e31\u0e1a\u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48\nconst sortedCats = Object.entries(catMap)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3);\n\nlet topCatText = '';\nif (sortedCats.length > 0) {\n sortedCats.forEach((item, index) => {\n topCatText += `${index + 1}. ${item[0]}: \u0e3f${item[1].toLocaleString('th-TH')}\\n`;\n });\n} else {\n topCatText = '- \u0e44\u0e21\u0e48\u0e21\u0e35\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e40\u0e21\u0e37\u0e48\u0e2d\u0e27\u0e32\u0e19 -';\n}\n\nconst balance = yesterdayIncome - yesterdayExpense;\n\nconst formatNum = (num) => {\n return num.toLocaleString('th-TH', {minimumFractionDigits: 0, maximumFractionDigits: 2});\n};\n\nconst formatDate = (date) => {\n return date.toLocaleDateString('th-TH', { day: '2-digit', month: 'long', year: 'numeric' });\n};\n\nreturn {\n date: formatDate(yesterday),\n income: yesterdayIncome,\n expense: yesterdayExpense,\n balance: balance,\n count: yesterdayCount,\n top_categories: topCatText.trim(),\n income_formatted: formatNum(yesterdayIncome),\n expense_formatted: formatNum(yesterdayExpense),\n balance_formatted: formatNum(balance)\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-192,
-384
],
"id": "5790f64b-f6c7-4eb3-83eb-fa9f0e540bae",
"name": "Code in JavaScript3"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "432a2823-a559-4a44-a3c8-7bf02ada8583",
"leftValue": "={{ $json.body.events[0].message.text }}",
"rightValue": "\u0e2a\u0e23\u0e38\u0e1b 7 \u0e27\u0e31\u0e19",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-544,
-32
],
"id": "7fa979e9-9e58-45e9-8332-6f3d89558016",
"name": "If3"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds",
"mode": "list",
"cachedResultName": "Pay History",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Sheet1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ptHPEg2d_19vbecMjzZga2ETJu3ARfuS6LLomYhyIds/edit#gid=0"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-304,
-144
],
"id": "12f73473-99df-44d2-84aa-d59399661d1d",
"name": "Get row(s) in sheet4",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// \u0e23\u0e31\u0e1a\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e08\u0e32\u0e01 Sheet\nconst rows = $input.all();\n\nlet weeklyIncome = 0;\nlet weeklyExpense = 0;\nlet catMap = {};\nlet count = 0;\n\n// \u0e01\u0e33\u0e2b\u0e19\u0e14\u0e0a\u0e48\u0e27\u0e07\u0e40\u0e27\u0e25\u0e32 (7 \u0e27\u0e31\u0e19\u0e22\u0e49\u0e2d\u0e19\u0e2b\u0e25\u0e31\u0e07)\nconst today = new Date();\nconst sevenDaysAgo = new Date();\nsevenDaysAgo.setDate(today.getDate() - 7);\nsevenDaysAgo.setHours(0, 0, 0, 0);\n\n// \u0e27\u0e19\u0e25\u0e39\u0e1b\u0e04\u0e33\u0e19\u0e27\u0e13\nfor (const item of rows) {\n const row = item.json;\n if (row.date) {\n const rowDate = new Date(row.date);\n // \u0e41\u0e1b\u0e25\u0e07\u0e04\u0e48\u0e32\u0e40\u0e07\u0e34\u0e19\u0e40\u0e1b\u0e47\u0e19\u0e15\u0e31\u0e27\u0e40\u0e25\u0e02\n const amount = parseFloat(row.amount) || 0;\n \n // \u0e15\u0e23\u0e27\u0e08\u0e2a\u0e2d\u0e1a\u0e1b\u0e23\u0e30\u0e40\u0e20\u0e17\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\n const typeRaw = row['transaction_type (income/expense/transfer)'] || row['type'] || \"\";\n const type = typeRaw.toString().toLowerCase();\n const category = row['category'] || '\u0e2d\u0e37\u0e48\u0e19\u0e46';\n\n // \u0e40\u0e07\u0e37\u0e48\u0e2d\u0e19\u0e44\u0e02: \u0e15\u0e49\u0e2d\u0e07\u0e40\u0e1b\u0e47\u0e19\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23\u0e43\u0e19 7 \u0e27\u0e31\u0e19\u0e25\u0e48\u0e32\u0e2a\u0e38\u0e14\n if (rowDate >= sevenDaysAgo && rowDate <= today) {\n count++;\n \n if (type.includes('income') || type.includes('\u0e23\u0e32\u0e22\u0e23\u0e31\u0e1a')) {\n weeklyIncome += amount;\n }\n else if (type.includes('expense') || type.includes('\u0e23\u0e32\u0e22\u0e08\u0e48\u0e32\u0e22')) {\n weeklyExpense += amount;\n // \u0e40\u0e01\u0e47\u0e1a\u0e2a\u0e16\u0e34\u0e15\u0e34\u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48\n if (catMap[category]) {\n catMap[category] += amount;\n } else {\n catMap[category] = amount;\n }\n }\n }\n }\n}\n\n// \u0e08\u0e31\u0e14\u0e2d\u0e31\u0e19\u0e14\u0e31\u0e1a Top 3 \u0e2b\u0e21\u0e27\u0e14\u0e2b\u0e21\u0e39\u0e48\nconst sortedCats = Object.entries(catMap)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3);\n\nlet topCatText = '';\nif (sortedCats.length > 0) {\n sortedCats.forEach((item, index) => {\n topCatText += `${index + 1}. ${item[0]}: \u0e3f${item[1].toLocaleString('th-TH')}\\n`;\n });\n} else {\n topCatText = '- \u0e44\u0e21\u0e48\u0e21\u0e35\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23 -';\n}\n\n// \u0e04\u0e33\u0e19\u0e27\u0e13\u0e04\u0e07\u0e40\u0e2b\u0e25\u0e37\u0e2d\nconst balance = weeklyIncome - weeklyExpense;\n\n// \u0e1f\u0e2d\u0e23\u0e4c\u0e41\u0e21\u0e15\u0e15\u0e31\u0e27\u0e40\u0e25\u0e02\u0e41\u0e25\u0e30\u0e27\u0e31\u0e19\u0e17\u0e35\u0e48\nconst formatNum = (num) => num.toLocaleString('th-TH', {minimumFractionDigits: 0, maximumFractionDigits: 2});\nconst formatDate = (d) => d.toLocaleDateString('th-TH', { day: '2-digit', month: 'short' });\nconst weekRange = `${formatDate(sevenDaysAgo)} - ${formatDate(today)}`;\n\n// \u0e2a\u0e48\u0e07\u0e04\u0e48\u0e32\u0e2d\u0e2d\u0e01\u0e44\u0e1b\nreturn {\n week_range: weekRange,\n weekly_income_formatted: formatNum(weeklyIncome),\n weekly_expense_formatted: formatNum(weeklyExpense),\n balance_formatted: formatNum(balance),\n top_categories: topCatText.trim(),\n count: count,\n month_name: \"7 \u0e27\u0e31\u0e19\u0e25\u0e48\u0e32\u0e2a\u0e38\u0e14\" // \u0e43\u0e0a\u0e49\u0e41\u0e17\u0e19\u0e0a\u0e37\u0e48\u0e2d\u0e40\u0e14\u0e37\u0e2d\u0e19\u0e43\u0e19 Template \u0e40\u0e14\u0e34\u0e21\n};"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-80,
-144
],
"id": "2744d8b9-1fb3-40e1-9305-f50e2fa22543",
"name": "Code in JavaScript4"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "69eafb91-cc9a-41c3-88c0-6de07fd0700f",
"name": "webhookUserId",
"value": "={{ $('Webhook').item.json.body.events[0].source.userId }}",
"type": "string"
},
{
"id": "84a6483b-ee6c-4f54-ae8a-9fe03f8cf02f",
"name": "date",
"value": "={{ $('Code in JavaScript2').first().json.date }}",
"type": "string"
},
{
"id": "a4780242-a652-463d-a381-7275e9525d18",
"name": "income",
"value": "={{ $('Code in JavaScript2').first().json.income_formatted }}",
"type": "string"
},
{
"id": "d14aa5d8-532b-405f-8232-0387e5dc9e69",
"name": "expense",
"value": "={{ $('Code in JavaScript2').first().json.expense_formatted }}",
"type": "string"
},
{
"id": "726ea174-c217-4efe-9946-b6022bde8ced",
"name": "balance",
"value": "={{ $('Code in JavaScript2').first().json.balance_formatted }}",
"type": "string"
},
{
"id": "210653d7-1e7f-4f3c-b4c8-a9800b6e1079",
"name": "count",
"value": "={{ $('Code in JavaScript2').first().json.count }}",
"type": "string"
},
{
"id": "c9471938-cf99-4328-a4f7-ef18e93c7732",
"name": "top_categories",
"value": "={{ $('Code in JavaScript2').first().json.top_categories }}\n",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-208,
-624
],
"id": "ff85a5ed-41db-4064-9118-5a826ce0b001",
"name": "set1"
},
{
"parameters": {
"method": "POST",
"url": "https://api.line.me/v2/bot/message/reply",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "=application/json; charset=UTF-8"
}
]
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.
googlePalmApigoogleSheetsOAuth2ApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
PAY. Uses agent, lmChatGoogleGemini, httpRequest, googleSheets. Webhook trigger; 35 nodes.
Source: https://github.com/Yterayut/N8N-Project/blob/86c7ac7b91736b76c6cf8611b38704cb6c29e0c6/workflow_patches/server-export2/workflows/rZjkLEVp0IM9xKfh.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
⏺ 🚀 How it works
LineOA. Uses httpRequest, agent, lmChatGoogleGemini, outputParserStructured. Webhook trigger; 69 nodes.
Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.
Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.