This workflow corresponds to n8n.io template #14496 — we link there as the canonical source.
This workflow follows the Agent → Google Calendar 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 →
{
"id": "k2YnknQUWqF88QOLuH41o",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Automate Odoo Accounting with Telegram and ChatGPT",
"tags": [],
"nodes": [
{
"id": "26bf9399-e7c0-4104-aaf0-b1fe1ad975a6",
"name": "Bot Data Receiver",
"type": "n8n-nodes-base.webhook",
"position": [
-2000,
1328
],
"parameters": {
"path": "YOUR_WEBHOOK_UUID",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "0e948798-59d4-4061-8640-9bbf3e26eb3c",
"name": "Find Expense Account ID",
"type": "n8n-nodes-base.odoo",
"position": [
32,
560
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.entities.debit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "c55ccf3f-64ca-4952-8eef-209eb4676301",
"name": "Find Cash Account ID",
"type": "n8n-nodes-base.odoo",
"position": [
256,
560
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.credit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "1d8fa225-a34c-467c-aa54-59d397b046e7",
"name": "Confirm & Post Entry",
"type": "n8n-nodes-base.odoo",
"position": [
928,
560
],
"parameters": {
"resource": "custom",
"operation": "update",
"customResource": "account.move",
"customResourceId": "={{ $json.id }}",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "state",
"fieldValue": "posted"
}
]
}
},
"typeVersion": 1
},
{
"id": "afe2f67f-92dd-41b6-9cff-47edf75e584e",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-1776,
1552
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {},
"builtInTools": {}
},
"typeVersion": 1.3
},
{
"id": "cc3c17dd-e763-4816-a6f6-48e608cae9ff",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-1648,
1552
],
"parameters": {
"sessionKey": "chat_history",
"sessionIdType": "customKey",
"contextWindowLength": 50
},
"typeVersion": 1.3
},
{
"id": "68be6e21-c635-4d98-8395-bfbeb6d62586",
"name": "AI Financial Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"position": [
-1776,
1328
],
"parameters": {
"text": "={{ JSON.stringify($json.body) }}",
"options": {
"systemMessage": "=You are an expert Financial Intent Classifier for Odoo ERP. Output ONLY a valid JSON object.\n\n# CORE DECISION LOGIC (CRITICAL):\n1. IF the user mentions an AMOUNT and an EXPENSE CATEGORY:\n -> route_path MUST BE \"save_expense\"\n -> action_type MUST BE \"save\"\n2. IF the user asks for a REPORT or BALANCE (No amount):\n -> route_path MUST BE \"fetch_expense_report\" or \"fetch_safe_balance\"\n -> action_type MUST BE \"fetch\"\n\n# ROUTE PATHS:\n- \"save_expense\": Recording an expense.\n- \"save_supplier_payment\": Paying a supplier.\n- \"fetch_safe_balance\": Inquiries about balances/checks.\n- \"fetch_expense_report\": Inquiries about expense totals.\n- \"fetch_supplier_statement\": Supplier statement.\n\n# ACCOUNT MAPPING (TEMPLATE - UPDATE THESE CODES):\n- Safes/Checks: [SAFE_1_NAME]=[SAFE_1_CODE] | [SAFE_2_NAME]=[SAFE_2_CODE] | [CHECKS_NAME]=[CHECKS_CODE] | [WALLET_NAME]=[WALLET_CODE]\n- Expenses: 1. [EXPENSE_1_NAME]=[EXPENSE_1_CODE] | 2. [EXPENSE_2_NAME]=[EXPENSE_2_CODE] \n\n# SPECIAL LOGIC FOR CHECKS (\u0634\u064a\u0643\u0627\u062a):\n- IF recording an expense paid by \"\u0634\u064a\u0643\": You MUST set source=\"\u0634\u064a\u0643\u0627\u062a\", credit_code=\"[CHECKS_CODE]\", and debit_code to the expense account. You MUST extract \"check_number\" and \"due_date\".\n\n# JSON SCHEMA:\n{\n \"route_path\": \"string\",\n \"company\": \"[COMPANY_A]\" | \"[COMPANY_B]\" | null,\n \"action_type\": \"save|fetch\", \n \"entities\": {\n \"amount\": number,\n \"source\": \"string|null\",\n \"target_name\": \"string|null\",\n \"credit_code\": \"string|null\",\n \"debit_code\": \"string|null\",\n \"category_or_type\": \"string|null\",\n \"date_filter\": \"today|yesterday|null\",\n \"check_details\": {\"check_number\": \"string\", \"due_date\": \"string\"}\n },\n \"is_complete\": true\n}"
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "7e93fd70-aaef-40a3-a4da-a06fe4e3f81e",
"name": "Clean AI Output",
"type": "n8n-nodes-base.code",
"position": [
-1424,
1328
],
"parameters": {
"jsCode": "// 1. Extract response\nlet inputData = $input.first().json;\nlet rawString = inputData.text || inputData.output || inputData.message || \"\";\n\n// 2. Clean markdown code fences and parse JSON\nlet cleanString = rawString.replace(/```json\\n?/gi, '').replace(/```\\n?/gi, '').trim();\n\n// 3. Try to extract JSON object\nlet jsonMatch = cleanString.match(/\\{[\\s\\S]*\\}/);\nif (jsonMatch) {\n cleanString = jsonMatch[0];\n}\n\n// 4. Parse and return\nlet parsed = JSON.parse(cleanString);\nreturn parsed;"
},
"typeVersion": 2
},
{
"id": "1bcda57f-cf6a-49d6-a016-05153da77f2e",
"name": "Route to Expense Path",
"type": "n8n-nodes-base.switch",
"position": [
-1120,
1248
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Expense Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "40764b6c-4e80-4fcb-8e3d-24132ddeb919",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "save_expense"
}
]
},
"renameOutput": true
},
{
"outputKey": "Discount Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "d3cabdbe-6667-4d7f-aba6-2c3a4e9739df",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "save_supplier_discount"
}
]
},
"renameOutput": true
},
{
"outputKey": "Supplier Payment Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "1da844d2-6c17-4156-acb2-0b2d340c81af",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "save_supplier_payment"
}
]
},
"renameOutput": true
},
{
"outputKey": "Supplier Invoice Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "a44e6a18-e047-41bc-abc6-b9f112bf835e",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "save_supplier_invoice"
}
]
},
"renameOutput": true
},
{
"outputKey": "Advance Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "0c0db7ec-12c0-4dac-a18f-ff70080137e2",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "save_advance"
}
]
},
"renameOutput": true
},
{
"outputKey": "Supplier Statement Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "835c54cc-ad8a-4bb9-967f-c6fad1c8383c",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "=fetch_supplier_statement"
}
]
},
"renameOutput": true
},
{
"outputKey": "Fetch Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "13f0fbf0-8ecd-4399-9a9c-c1717ba90eab",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.route_path }}",
"rightValue": "fetch"
}
]
},
"renameOutput": true
}
]
},
"options": {},
"looseTypeValidation": true
},
"typeVersion": 3.4
},
{
"id": "7d57fcf3-71e8-4762-8af2-b42e2b18f258",
"name": "Build Odoo Journal Entry",
"type": "n8n-nodes-base.code",
"position": [
480,
560
],
"parameters": {
"jsCode": "const botData = $('Clean AI Output').item.json;\nconst amount = parseFloat(botData.entities.amount);\nconst category = botData.entities.category_or_type;\nconst company = botData.company || \"[DEFAULT_COMPANY_NAME]\"; \n\nconst debitAccountId = $('Find Expense Account ID').item.json.id;\nconst creditAccountId = $('Find Cash Account ID').item.json.id;\n\nconst fullRef = `${category} ${company}`;\nconst debitLabel = category;\nconst creditLabel = `\u0633\u062f\u0627\u062f \u0645\u0635\u0631\u0648\u0641: ${category}`;\n\nreturn {\n \"ref\": fullRef,\n \"move_type\": \"entry\",\n \"journal_id\": 2, // [TEMPLATE: REPLACE WITH YOUR ODOO JOURNAL ID]\n \"line_ids\": [\n [0, 0, { \"account_id\": debitAccountId, \"name\": debitLabel, \"debit\": amount, \"credit\": 0.0 }],\n [0, 0, { \"account_id\": creditAccountId, \"name\": creditLabel, \"debit\": 0.0, \"credit\": amount }]\n ]\n};"
},
"typeVersion": 2
},
{
"id": "f953e928-ba2f-40d4-9f20-0d6f085c4ba0",
"name": "Create Odoo Entry",
"type": "n8n-nodes-base.odoo",
"position": [
704,
560
],
"parameters": {
"resource": "custom",
"customResource": "account.move",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "journal_id",
"fieldValue": "={{ $json.journal_id }}"
},
{
"fieldName": "move_type",
"fieldValue": "={{ $json.move_type }}"
},
{
"fieldName": "line_ids",
"fieldValue": "={{ $json.line_ids }}"
},
{
"fieldName": "ref",
"fieldValue": "={{ $json.ref }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "7035e0e0-32f4-4617-a02a-2ceab249f883",
"name": "Find Partner ID",
"type": "n8n-nodes-base.odoo",
"position": [
-192,
752
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.entities.target_name }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1
},
{
"id": "572ba219-c428-46fc-bdf1-43d5e94b6144",
"name": "Build Discount Entry",
"type": "n8n-nodes-base.code",
"position": [
480,
752
],
"parameters": {
"jsCode": "const partnerId = $('Find Partner ID').item.json.id;\nconst debitAccountId = $('Find Debit Account ID').item.json.id;\nconst creditAccountId = $('Find Credit Account ID').item.json.id;\n\nconst currencyId = 1; // [TEMPLATE: REPLACE WITH YOUR ODOO CURRENCY ID]\n\nconst aiData = $('Clean AI Output').item.json.entities;\nconst amount = parseFloat(aiData.amount);\nconst label = aiData.label;\n\nconst lines = [\n [0, 0, { \"account_id\": debitAccountId, \"partner_id\": partnerId, \"name\": label, \"debit\": amount, \"credit\": 0.0, \"currency_id\": currencyId }],\n [0, 0, { \"account_id\": creditAccountId, \"name\": label, \"debit\": 0.0, \"credit\": amount, \"currency_id\": currencyId }]\n];\nreturn { lines_to_odoo: lines };"
},
"typeVersion": 2
},
{
"id": "cc798d9e-dbd0-45eb-ab82-ccb9631e3f10",
"name": "Confirm & Post Entry1",
"type": "n8n-nodes-base.odoo",
"position": [
928,
752
],
"parameters": {
"resource": "custom",
"operation": "update",
"customResource": "account.move",
"customResourceId": "={{ $json.id }}",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "state",
"fieldValue": "posted"
}
]
}
},
"typeVersion": 1
},
{
"id": "7954d235-8f6b-4bcc-b540-de2f55de95f8",
"name": "Create Discount Entry",
"type": "n8n-nodes-base.odoo",
"position": [
704,
752
],
"parameters": {
"resource": "custom",
"customResource": "account.move",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "line_ids",
"fieldValue": "={{ $json.lines_to_odoo }}"
},
{
"fieldName": "ref",
"fieldValue": "={{ $('Clean AI Output').item.json.entities.label }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "e2a96501-4101-45ab-9a59-5731c767b4e5",
"name": "Find Debit Account ID",
"type": "n8n-nodes-base.odoo",
"position": [
32,
752
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.debit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "0c98bcf0-c0c4-49ad-9d4d-4038e6fb08d9",
"name": "Find Credit Account ID",
"type": "n8n-nodes-base.odoo",
"position": [
256,
752
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.credit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "da4ae748-de3f-482f-b6a3-7e9e95da5623",
"name": "Filter Payment Source",
"type": "n8n-nodes-base.switch",
"position": [
-864,
1040
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Checks Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "49b2f04f-1c50-4783-b932-ffefeaf4cba7",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.entities.source }}",
"rightValue": "\u0634\u064a\u0643\u0627\u062a"
}
]
},
"renameOutput": true
},
{
"outputKey": "Instant Payment Path",
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1028f762-a9bc-4766-b9b8-f2e161fed176",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.entities.source }}",
"rightValue": "\u0634\u064a\u0643\u0627\u062a"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.4
},
{
"id": "7ac9d563-4b8d-4c5d-bc7f-9cfae4960ac1",
"name": "Format Check Date",
"type": "n8n-nodes-base.code",
"position": [
-640,
944
],
"parameters": {
"jsCode": "for (const item of $input.all()) {\n if (item.json.entities && item.json.entities.check_details && item.json.entities.check_details.due_date) {\n const rawDate = item.json.entities.check_details.due_date; \n const [day, month, year] = rawDate.split('/');\n item.json.entities.check_details.formatted_due_date = `${year}-${month}-${day}`; \n }\n}\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "a7b55785-8077-49f2-a854-149c9854081b",
"name": "Create an event",
"type": "n8n-nodes-base.googleCalendar",
"position": [
-416,
944
],
"parameters": {
"end": "={{ $json.entities.check_details.formatted_due_date }}T10:00:00",
"start": "={{ $json.entities.check_details.formatted_due_date }}T09:00:00",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com"
},
"additionalFields": {
"summary": "=\u0627\u0633\u062a\u062d\u0642\u0627\u0642 \u0634\u064a\u0643 \u0644\u0645\u0648\u0631\u062f: {{ $json.entities.target_name }}",
"description": "=\u0645\u0628\u0644\u063a \u0627\u0644\u0634\u064a\u0643: {{ $json.entities.amount }} | \u0631\u0642\u0645 \u0627\u0644\u0634\u064a\u0643: {{ $json.entities.check_details.check_number }}"
},
"useDefaultReminders": "={{ true }}"
},
"typeVersion": 1.3
},
{
"id": "d746453e-191f-4ae7-b83b-4b058460631f",
"name": "Find Partner ID2",
"type": "n8n-nodes-base.odoo",
"position": [
-192,
944
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Format Check Date').item.json.entities.target_name }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1
},
{
"id": "3d8fbdb6-e61f-40bc-9888-f04560651138",
"name": "Find Debit Account ID1",
"type": "n8n-nodes-base.odoo",
"position": [
32,
944
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Format Check Date').item.json.entities.debit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "72ea3603-d788-4105-b6fd-edc51a3d2a3d",
"name": "Find Credit Account ID1",
"type": "n8n-nodes-base.odoo",
"position": [
256,
944
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Format Check Date').item.json.entities.credit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "f1af3626-24f7-4d28-b5e5-ddacdad84b4a",
"name": "Build Journal Lines",
"type": "n8n-nodes-base.code",
"position": [
480,
944
],
"parameters": {
"jsCode": "const payload = $('Format Check Date').item.json.entities;\nconst checkDetails = payload.check_details;\nconst partnerId = $('Find Partner ID2').item.json.id;\nconst debitAccountId = $('Find Debit Account ID1').item.json.id;\nconst creditAccountId = $('Find Credit Account ID1').item.json.id;\n\nconst amount = payload.amount;\nconst checkNumber = checkDetails.check_number;\nconst dueDate = checkDetails.formatted_due_date;\nconst supplierName = payload.target_name;\n\nconst lineLabel = `\u062f\u0641\u0639\u0629 \u0628\u0634\u064a\u0643 \u0631\u0642\u0645 ${checkNumber} \u062a\u0633\u062a\u062d\u0642 \u0641\u064a ${dueDate}`;\nconst currencyId = 74; // [TEMPLATE: REPLACE WITH YOUR ODOO CURRENCY ID]\n\nconst lines = [\n [0, 0, { \"account_id\": debitAccountId, \"partner_id\": partnerId, \"name\": lineLabel, \"debit\": amount, \"credit\": 0, \"currency_id\": currencyId }],\n [0, 0, { \"account_id\": creditAccountId, \"name\": lineLabel, \"debit\": 0, \"credit\": amount, \"currency_id\": currencyId }]\n];\n\nreturn { json: { journal_ref: `\u0633\u062f\u0627\u062f \u0645\u0648\u0631\u062f: ${supplierName} - \u0634\u064a\u0643 ${checkNumber}`, lines_to_odoo: lines } };"
},
"typeVersion": 2
},
{
"id": "06ee9e6b-9f04-4f4e-a244-6a7ac8937028",
"name": "Create Discount Entry1",
"type": "n8n-nodes-base.odoo",
"position": [
704,
944
],
"parameters": {
"resource": "custom",
"customResource": "account.move",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "line_ids",
"fieldValue": "={{ $json.lines_to_odoo }}"
},
{
"fieldName": "ref",
"fieldValue": "={{ $json.journal_ref }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "280fbca2-9609-4c81-ab3a-0ebb214c7ae1",
"name": "Confirm & Post Entry2",
"type": "n8n-nodes-base.odoo",
"position": [
928,
944
],
"parameters": {
"resource": "custom",
"operation": "update",
"customResource": "account.move",
"customResourceId": "={{ $json.id }}",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "state",
"fieldValue": "posted"
}
]
}
},
"typeVersion": 1
},
{
"id": "8581cc65-762f-4b68-a9e5-a36f286b2810",
"name": "Find Partner ID3",
"type": "n8n-nodes-base.odoo",
"position": [
-192,
1136
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.entities.target_name }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1
},
{
"id": "310fb4dc-b00a-4889-97a3-0a493ed4dd7d",
"name": "Find Debit Account ID2",
"type": "n8n-nodes-base.odoo",
"position": [
32,
1136
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Filter Payment Source').item.json.entities.debit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "019c7dd5-20ce-46ce-ac99-5abe20103bb5",
"name": "Find Credit Account ID2",
"type": "n8n-nodes-base.odoo",
"position": [
256,
1136
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Filter Payment Source').item.json.entities.credit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "4cd9c072-8710-48be-872b-fc578eabcd9b",
"name": "Build Instant Entry Lines",
"type": "n8n-nodes-base.code",
"position": [
480,
1136
],
"parameters": {
"jsCode": "const payload = $('Clean AI Output').first().json;\nconst partnerId = $('Find Partner ID3').first().json.id;\nconst debitAccountId = $('Find Debit Account ID2').first().json.id;\nconst creditAccountId = $('Find Credit Account ID2').first().json.id;\n\nconst today = new Date().toISOString().split('T')[0];\nconst lineLabel = `\u0633\u062f\u0627\u062f \u0641\u0648\u0631\u064a \u0644\u0644\u0645\u0648\u0631\u062f: ${payload.entities.target_name} \u0628\u062a\u0627\u0631\u064a\u062e ${today}`;\nconst currencyId = 74; // [TEMPLATE: REPLACE WITH YOUR ODOO CURRENCY ID]\n\nconst lines = [\n [0, 0, { \"account_id\": debitAccountId, \"partner_id\": partnerId, \"name\": lineLabel, \"debit\": payload.entities.amount, \"credit\": 0, \"currency_id\": currencyId }],\n [0, 0, { \"account_id\": creditAccountId, \"name\": lineLabel, \"debit\": 0, \"credit\": payload.entities.amount, \"currency_id\": currencyId }]\n];\n\nreturn { json: { journal_ref: `\u0633\u062f\u0627\u062f \u0641\u0648\u0631\u064a: ${payload.entities.target_name}`, entry_date: today, lines_to_odoo: lines } };"
},
"typeVersion": 2
},
{
"id": "b57f1f78-7da6-4976-b2ba-93fc40a0b561",
"name": "Create Discount Entry2",
"type": "n8n-nodes-base.odoo",
"position": [
704,
1136
],
"parameters": {
"resource": "custom",
"customResource": "account.move",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "line_ids",
"fieldValue": "={{ $json.lines_to_odoo }}"
},
{
"fieldName": "ref",
"fieldValue": "={{ $json.journal_ref }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "58839b88-ab53-4a88-bc22-ea4ab96fe5fa",
"name": "Confirm & Post Entry3",
"type": "n8n-nodes-base.odoo",
"position": [
928,
1136
],
"parameters": {
"resource": "custom",
"operation": "update",
"customResource": "account.move",
"customResourceId": "={{ $json.id }}",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "state",
"fieldValue": "posted"
}
]
}
},
"typeVersion": 1
},
{
"id": "b3446b1f-8a7b-49a6-90f9-4f2f9f89861c",
"name": "Find Partner ID4",
"type": "n8n-nodes-base.odoo",
"position": [
-192,
1328
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.entities.target_name.replace(/[\u0623\u0625\u0622]/g, '\u0627').replace(/[\u0649]/g, '\u064a').replace(/[\u0629]/g, '\u0647').trim() }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1
},
{
"id": "48504344-8f4d-43f1-b2db-5d1f20d18154",
"name": "Find Debit Account ID3",
"type": "n8n-nodes-base.odoo",
"position": [
32,
1328
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.debit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "77181831-cdfa-40ac-8a55-bf4391d07581",
"name": "Find Credit Account ID3",
"type": "n8n-nodes-base.odoo",
"position": [
256,
1328
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.credit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "7f98d204-8c65-4a4e-a643-e86352a276db",
"name": "Build Invoice Entry Lines",
"type": "n8n-nodes-base.code",
"position": [
480,
1328
],
"parameters": {
"jsCode": "const payload = $('Clean AI Output').first().json;\nconst partnerId = $('Find Partner ID4').first().json.id; \nconst debitAccountId = $('Find Debit Account ID3').first().json.id; \nconst creditAccountId = $('Find Credit Account ID3').first().json.id; \n\nconst today = new Date().toISOString().split('T')[0];\nconst lineLabel = `\u0625\u062b\u0628\u0627\u062a \u0641\u0627\u062a\u0648\u0631\u0629 \u0645\u0648\u0631\u062f: ${payload.entities.target_name} \u0628\u062a\u0627\u0631\u064a\u062e ${today}`;\nconst currencyId = 74; // [TEMPLATE: REPLACE WITH YOUR ODOO CURRENCY ID]\n\nconst lines = [\n [0, 0, { \"account_id\": debitAccountId, \"name\": lineLabel, \"debit\": payload.entities.amount, \"credit\": 0, \"currency_id\": currencyId }],\n [0, 0, { \"account_id\": creditAccountId, \"partner_id\": partnerId, \"name\": lineLabel, \"debit\": 0, \"credit\": payload.entities.amount, \"currency_id\": currencyId }]\n];\n\nreturn { json: { journal_ref: `\u0641\u0627\u062a\u0648\u0631\u0629: ${payload.entities.target_name}`, entry_date: today, lines_to_odoo: lines } };"
},
"typeVersion": 2
},
{
"id": "a168aa30-2e4e-4c84-a9ab-2f9d9a6efe62",
"name": "Create Discount Entry3",
"type": "n8n-nodes-base.odoo",
"position": [
704,
1328
],
"parameters": {
"resource": "custom",
"customResource": "account.move",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "line_ids",
"fieldValue": "={{ $json.lines_to_odoo }}"
},
{
"fieldName": "ref",
"fieldValue": "={{ $json.journal_ref }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "45fb5210-c6b2-420c-9f5c-71535305762d",
"name": "Confirm & Post Entry4",
"type": "n8n-nodes-base.odoo",
"position": [
928,
1328
],
"parameters": {
"resource": "custom",
"operation": "update",
"customResource": "account.move",
"customResourceId": "={{ $json.id }}",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "state",
"fieldValue": "posted"
}
]
}
},
"typeVersion": 1
},
{
"id": "19aa1c72-87f3-4759-a164-999f3a04ac77",
"name": "Find Partner ID5",
"type": "n8n-nodes-base.odoo",
"position": [
-192,
1520
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.entities.target_name.replace(/[\u0623\u0625\u0622]/g, '\u0627').replace(/[\u0649]/g, '\u064a').replace(/[\u0629]/g, '\u0647').trim() }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1
},
{
"id": "3a4379cd-752d-4ea7-bffb-5806237c36ca",
"name": "Find Debit Account ID4",
"type": "n8n-nodes-base.odoo",
"position": [
32,
1520
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.debit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "2148de06-b201-496f-b1b7-062b422c486b",
"name": "Find Credit Account ID4",
"type": "n8n-nodes-base.odoo",
"position": [
256,
1520
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.credit_code }}",
"fieldName": "code"
}
]
},
"customResource": "account.account"
},
"typeVersion": 1
},
{
"id": "8925d278-7e4c-4602-9c1b-2092a91dbd83",
"name": "Build Advance Entry Lines",
"type": "n8n-nodes-base.code",
"position": [
480,
1520
],
"parameters": {
"jsCode": "const payload = $('Clean AI Output').first().json;\nconst partnerId = $('Find Partner ID5').first().json.id; \nconst debitAccountId = $('Find Debit Account ID4').first().json.id; \nconst creditAccountId = $('Find Credit Account ID4').first().json.id; \n\nconst today = new Date().toISOString().split('T')[0];\nconst lineLabel = `\u0633\u0644\u0641\u0629 \u0644\u0644\u0645\u0648\u0638\u0641: ${payload.entities.target_name} \u0628\u062a\u0627\u0631\u064a\u062e ${today}`;\nconst currencyId = 74; // [TEMPLATE: REPLACE WITH YOUR ODOO CURRENCY ID]\n\nconst lines = [\n [0, 0, { \"account_id\": debitAccountId, \"partner_id\": partnerId, \"name\": lineLabel, \"debit\": payload.entities.amount, \"credit\": 0, \"currency_id\": currencyId }],\n [0, 0, { \"account_id\": creditAccountId, \"name\": lineLabel, \"debit\": 0, \"credit\": payload.entities.amount, \"currency_id\": currencyId }]\n];\n\nreturn { json: { journal_ref: `\u0633\u0644\u0641\u0629: ${payload.entities.target_name}`, entry_date: today, lines_to_odoo: lines } };"
},
"typeVersion": 2
},
{
"id": "3417ebbb-7ada-437f-b934-e0740f393daa",
"name": "Create Discount Entry4",
"type": "n8n-nodes-base.odoo",
"position": [
704,
1520
],
"parameters": {
"resource": "custom",
"customResource": "account.move",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "line_ids",
"fieldValue": "={{ $json.lines_to_odoo }}"
},
{
"fieldName": "ref",
"fieldValue": "={{ $json.journal_ref }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "15e17889-5553-47e6-a40c-44a4a42614a1",
"name": "Confirm & Post Entry5",
"type": "n8n-nodes-base.odoo",
"position": [
928,
1520
],
"parameters": {
"resource": "custom",
"operation": "update",
"customResource": "account.move",
"customResourceId": "={{ $json.id }}",
"fieldsToCreateOrUpdate": {
"fields": [
{
"fieldName": "state",
"fieldValue": "posted"
}
]
}
},
"typeVersion": 1
},
{
"id": "6199887a-1680-429a-8744-69a927c06d90",
"name": "Fetch Safe/Bank Move Lines",
"type": "n8n-nodes-base.odoo",
"position": [
960,
1840
],
"parameters": {
"limit": "={{ 0 }}",
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $('Route to Expense Path').item.json.entities.credit_code }}",
"fieldName": "account_code"
},
{
"value": "={{ $json.startDate }}",
"fieldName": "date"
}
]
},
"customResource": "account.move.line"
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "caed4b1b-6606-407e-834f-cdab1a9cdc63",
"name": "Date Resolver",
"type": "n8n-nodes-base.code",
"position": [
736,
1840
],
"parameters": {
"jsCode": "const input = $input.first().json;\nconst userMessage = input.user_message || \"\";\nconst now = new Date();\nlet startDate, endDate, displayName;\n\nif (userMessage.includes(\"\u0634\u0647\u0631\") || userMessage.includes(\"\u0627\u0644\u0634\u0647\u0631\")) {\n startDate = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().split('T')[0];\n endDate = now.toISOString().split('T')[0];\n displayName = \"\u062e\u0644\u0627\u0644 \u0634\u0647\u0631 \" + (now.getMonth() + 1);\n} else if (userMessage.includes(\"\u0627\u0645\u0628\u0627\u0631\u062d\")) {\n const yesterday = new Date(now);\n yesterday.setDate(now.getDate() - 1);\n startDate = yesterday.toISOString().split('T')[0];\n endDate = startDate;\n displayName = \"\u0627\u0645\u0628\u0627\u0631\u062d\";\n} else {\n startDate = now.toISOString().split('T')[0];\n endDate = startDate;\n displayName = \"\u0627\u0644\u0646\u0647\u0627\u0631\u062f\u0629\";\n}\n\nreturn { startDate, endDate, display_name: displayName };"
},
"typeVersion": 2
},
{
"id": "e35ee0d5-cf8c-4d9b-954a-e46c8fe222fc",
"name": "Build Final Report",
"type": "n8n-nodes-base.code",
"position": [
1168,
1840
],
"parameters": {
"jsCode": "const items = $input.all();\nconst aiData = $('Clean AI Output').first().json.entities || {};\nconst dateData = $('Date Resolver').first().json || {};\n\nlet totalDebit = 0; \nlet totalCredit = 0; \nlet details = \"\";\n\nlet accountName = aiData.source || aiData.category_or_type || \"\u0627\u0644\u062d\u0633\u0627\u0628\";\nlet displayDate = dateData.display_name || \"\u0627\u0644\u0641\u062a\u0631\u0629 \u0627\u0644\u0645\u062d\u062f\u062f\u0629\";\nlet actualDate = dateData.startDate || dateData.resolved_date || \"\";\n\nif (items.length > 0 && items[0].json && items[0].json.account_id) {\n accountName = items[0].json.account_id[1]; \n for (const item of items) {\n if (!item.json.id) continue;\n const debit = item.json.debit || 0;\n const credit = item.json.credit || 0;\n const ref = item.json.ref || item.json.name || \"\u0628\u062f\u0648\u0646 \u0648\u0635\u0641\";\n \n totalDebit += debit;\n totalCredit += credit;\n\n if (debit > 0) details += `\u2022 \u0645\u062f\u064a\u0646/\u0648\u0627\u0631\u062f \ud83d\udfe2: ${debit.toLocaleString()} \u062c \u2b05\ufe0f (${ref})\\n`;\n if (credit > 0) details += `\u2022 \u062f\u0627\u0626\u0646/\u0645\u0646\u0635\u0631\u0641 \ud83d\udd34: ${credit.toLocaleString()} \u062c \u2b05\ufe0f (${ref})\\n`;\n }\n}\n\nlet report = `\ud83d\udcca \u062a\u0642\u0631\u064a\u0631 \u062d\u0633\u0627\u0628: ${accountName}\\n\ud83d\udcc5 \u0644\u0640 (${displayDate} - ${actualDate}):\\n`;\nreport += `--------------------------\\n`;\n\nif (details) {\n report += details;\n report += `--------------------------\\n`;\n if (accountName.includes(\"\u0634\u064a\u0643\u0627\u062a\") || accountName.includes(\"\u062e\u0632\u064a\u0646\") || accountName.includes(\"\u0643\u0627\u0634\")) {\n report += `\ud83d\udcb0 \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0648\u0627\u0631\u062f (\u062f\u062e\u0644): ${totalDebit.toLocaleString()} \u062c\u0646\u064a\u0647\\n`;\n report += `\ud83d\udcb8 \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0646\u0635\u0631\u0641 (\u062e\u0631\u062c): ${totalCredit.toLocaleString()} \u062c\u0646\u064a\u0647\\n`;\n report += `\u2696\ufe0f \u0635\u0627\u0641\u064a \u0627\u0644\u062d\u0631\u0643\u0629: ${(totalDebit - totalCredit).toLocaleString()} \u062c\u0646\u064a\u0647\\n`;\n } else {\n report += `\ud83d\udcb8 \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0635\u0631\u0648\u0641: ${totalDebit.toLocaleString()} \u062c\u0646\u064a\u0647\\n`;\n if (totalCredit > 0) report += `\ud83d\udd04 \u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0645\u0631\u062a\u062c\u0639\u0627\u062a: ${totalCredit.toLocaleString()} \u062c\u0646\u064a\u0647\\n`;\n }\n} else {\n report += `\u26a0\ufe0f \u0645\u0641\u064a\u0634 \u0623\u064a \u062d\u0631\u0643\u0627\u062a \u0627\u062a\u0633\u062c\u0644\u062a \u0641\u064a \u0627\u0644\u0641\u062a\u0631\u0629 \u062f\u064a.\\n`;\n}\n\nreport += `--------------------------\\n`;\nreport += `\ud83d\udc81\u200d\u2640\ufe0f \u0627\u0644\u0646\u0638\u0627\u0645: \u0627\u0644\u062d\u0633\u0628\u0629 \u0638\u0628\u0637\u062a\u060c \u0627\u0644\u062a\u0642\u0631\u064a\u0631 \u0645\u0639\u0627\u0643!`;\n\nreturn { json: { report_text: report } };"
},
"typeVersion": 2
},
{
"id": "37d2638c-d712-46cf-af4d-a4c7a765fee0",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1392,
1840
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ { \"reply\": $json.report_text } }}"
},
"typeVersion": 1.5
},
{
"id": "b8abee07-052b-4cd6-b2d1-1b5cd7968f62",
"name": "Find Supplier ID",
"type": "n8n-nodes-base.odoo",
"position": [
736,
2032
],
"parameters": {
"limit": 1,
"options": {},
"resource": "custom",
"operation": "getAll",
"filterRequest": {
"filter": [
{
"value": "={{ $json.entities.target_name.replace(/[\u0623\u0625\u0622]/g, '\u0627').replace(/[\u0649]/g, '\u064a').replace(/[\u0629]/g, '\u0647').trim() }}",
"operator": "like",
"fieldName": "name"
}
]
},
"customResource": "res.partner"
},
"typeVersion": 1
},
{
"id": "a4e6111d-55f9-43ac-81ca-268b19025e0a",
"name": "Get many items",
"type": "n8n-nodes-base.odoo",
"position": [
960,
2032
],
"parameters": {
"options": {},
"resource": "custom",
"operation": "getAll",
"returnAll": true,
"filterRequest": {
"filter": [
{
"value": "={{ $json.id }}",
"fieldName": "partner_id"
},
{
"value": "posted",
"fieldName": "parent_state"
}
]
},
"customResource": "account.move.line"
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "851c295c-3e2e-4549-96b0-61299590bf20",
"name": "Calculate Supplier Balance",
"type": "n8n-nodes-base.code",
"position": [
1168,
2032
],
"parameters": {
"jsCode": "const items = $input.all();\nconst supplierName = $('Clean AI Output').first().json.entities.target_name;\n\nlet totalDebit = 0; \nlet totalCredit = 0; \n\nitems.sort((a, b) => new Date(b.json.date) - new Date(a.json.date));\n\nlet details = \"\";\nlet counter = 0;\n\nfor (const item of items) {\n const debit = item.json.debit || 0;\n const credit = item.json.credit || 0;\n const ref = item.json.name || item.json.ref || \"\u0628\u062f\u0648\u0646 \u0648\u0635\u0641\";\n \n totalDebit += debit;\n totalCredit += credit;\n\n if (counter < 10) {\n if (credit > 0) details += `\ud83d\udd34 \u0641\u0627\u062a\u0648\u0631\u0629/\u0631\u0635\u064a\u062f (+${credit.toLocaleString()} \u062c) \u2b05\ufe0f ${ref}\\n`;\n else if (debit > 0) details += `\ud83d\udfe2 \u0633\u062f\u0627\u062f/\u062e\u0635\u0645 (-${debit.toLocaleString()} \u062c) \u2b05\ufe0f ${ref}\\n`;\n counter++;\n }\n}\n\nconst balance = totalCredit - totalDebit;\nlet balanceText = \"\";\n\nif (balance > 0) balanceText = `\u26a0\ufe0f \u0627\u0644\u0645\u0648\u0631\u062f \u0644\u064a\u0647 \u0641\u064a \u0630\u0645\u062a\u0646\u0627: ${balance.toLocaleString()} \u062c\u0646\u064a\u0647`;\nelse if (balance < 0) balanceText = `\u2705 \u0644\u064a\u0646\u0627 \u0639\u0646\u062f \u0627\u0644\u0645\u0648\u0631\u062f: ${Math.abs(balance).toLocaleString()} \u062c\u0646\u064a\u0647`;\nelse balanceText = `\ud83d\udc4c \u0627\u0644\u062d\u0633\u0627\u0628 \u0645\u0635\u0641\u0631 (\u062e\u0627\u0644\u0635\u064a\u0646)`;\n\nlet report = `\ud83e\uddfe \u0643\u0634\u0641 \u062d\u0633\u0627\u0628 \u0645\u062c\u0645\u0639: ${supplierName}\\n`;\nreport += `--------------------------\\n`;\nreport += `\u0623\u062d\u062f\u062b \u0627\u0644\u062d\u0631\u0643\u0627\u062a:\\n${details}`;\nif (items.length > 10) report += `... (\u062a\u0645 \u0625\u062e\u0641\u0627\u0621 \u0628\u0627\u0642\u064a \u0627\u0644\u062d\u0631\u0643\u0627\u062a \u0627\u0644\u0642\u062f\u064a\u0645\u0629)\\n`;\nreport += `--------------------------\\n`;\nreport += `\u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0644\u064a \u0646\u0632\u0644 \u062d\u0633\u0627\u0628\u0647: ${totalCredit.toLocaleString()} \u062c\\n`;\nreport += `\u0625\u062c\u0645\u0627\u0644\u064a \u0627\u0644\u0644\u064a \u0625\u062a\u062f\u0641\u0639\u0640\u0644\u0640\u0647: ${totalDebit.toLocaleString()} \u062c\\n`;\nreport += `--------------------------\\n`;\nreport += `\ud83d\udcb0 \u0627\u0644\u0635\u0627\u0641\u064a \u0627\u0644\u0646\u0647\u0627\u0626\u064a:\\n${balanceText}\\n`;\nreport += `--------------------------\\n`;\nreport += `\ud83d\udc81\u200d\u2640\ufe0f \u0627\u0644\u0646\u0638\u0627\u0645: \u0627\u0644\u062a\u0642\u0631\u064a\u0631 \u0645\u062a\u0642\u0641\u0644\u060c \u0623\u0623\u0645\u0631\u0646\u064a \u0628\u0627\u0644\u0644\u064a \u0628\u0639\u062f\u0647!`;\n\nreturn { json: { report_text: report } };"
},
"typeVersion": 2
},
{
"id": "f49324c9-3ab3-487c-9618-09ea2a980020",
"name": "Success Message",
"type": "n8n-nodes-base.code",
"position": [
1152,
1040
],
"parameters": {
"jsCode": "const aiData = $('Clean AI Output').first().json.entities;\nconst amount = aiData.amount || 0;\nconst target = aiData.target_name || aiData.category_or_type || \"\u0627\u0644\u0639\u0645\u0644\u064a\u0629\";\n\nlet report = `\ud83d\udc81\u200d\u2640\ufe0f \u0627\u0644\u0646\u0638\u0627\u0645: \u0643\u0644\u0647 \u062a\u0645\u0627\u0645 \u064a\u0627 \u0631\u064a\u0633! \u2705\\n`;\nreport += `\u062a\u0645 \u0625\u062b\u0628\u0627\u062a \u0648\u062a\u0633\u062c\u064a\u0644 (${target}) \u0628\u0645\u0628\u0644\u063a ${amount.toLocaleString()} \u062c\u0646\u064a\u0647 \u0641\u064a \u0627\u0644\u062f\u0641\u0627\u062a\u0631 \u0628\u0646\u062c\u0627\u062d!`;\n\nreturn { json: { report_text: report } };"
},
"typeVersion": 2
},
{
"id": "e30a0644-0b16-43b9-ad5b-533bf399c807",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3280,
1120
],
"parameters": {
"width": 1184,
"height": 432,
"content": "### How it works\nThis workflow acts as an AI-powered financial assistant, connecting chat platforms (like Telegram or Botpress) to your Odoo ERP. \n\nWhen a user sends a natural language message in Arabic (e.g., \"I spent 2000 on office supplies\"), an OpenAI LangChain agent extracts the intent, amounts, dates, and account categories. The workflow then routes the request to either post a new double-entry journal record in Odoo (for expenses, supplier payments, or employee advances) or fetch real-time account balances to send a formatted Arabic report back to the chat.\n\n### Setup steps\n1. **Credentials:** Connect your Odoo, OpenAI, and Google Calendar credentials.\n2. **Webhook:** Add the Webhook URL to your Telegram/Botpress bot setup.\n3. **Prompt Mapping:** Open the \"AI Financial Agent\" node and update the `# ACCOUNT MAPPING` section with your specific Odoo Chart of Accounts codes (e.g., replace `[EXPENSE_1_CODE]` with your actual Odoo account code).\n4. **Code Nodes:** Open the \"Build [X] Entry\" Code nodes and replace the placeholder `journal_id` and `currency_id` with your Odoo internal IDs.\n\n### Customization tips\nYou can easily expand the `Route to Expense Path` Switch node to handle other operations, such as creating customer invoices or logging petty cash transfers."
},
"typeVersion": 1
},
{
"id": "851894c1-1675-4c91-829f-705ab0d597dd",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2000,
1168
],
"parameters": {
"color": 7,
"width": 720,
"height": 512,
"content": "## 1. Receive & Classify Intent\nReceives the chat message and uses an AI agent to extract the financial operation, amounts, dates, and target accounts into a clean JSON format."
},
"typeVersion": 1
},
{
"id": "843171a8-7bff-4999-8784-7aacb06ea19b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1216,
1072
],
"parameters": {
"color": 7,
"width": 288,
"height": 304,
"content": "## 2. Route Operation\nDirects the workflow down the correct path based on whether the user is logging an expense, paying a supplier, or requesting a balance report."
},
"typeVersion": 1
},
{
"id": "e28f9027-4299-4081-84ca-b1800e8c75f3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-224,
448
],
"parameters": {
"color": 7,
"width": 608,
"height": 1280,
"content": "## 3. Odoo Data Lookups\nSearches Odoo to find the internal internal `account_id` and `partner_id` required to build accurate journal entries."
},
"typeVersion": 1
},
{
"id": "ae32833a-f179-4156-84ea-d6fc42a4e3a0",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
416,
352
],
"parameters": {
"color": 7,
"height": 1376,
"content": "## 4. Build & Post Entries\nConstructs the double-entry accounting arrays (debits and credits) and automatically posts the confirmed journal entries into Odoo."
},
"typeVersion": 1
},
{
"id": "b146e4fe-a168-4549-8d5f-91006809df52",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-720,
848
],
"parameters": {
"color": 7,
"width": 480,
"height": 240,
"content": "## 5. Schedule Checks\nParses future due dates for physical check payments and creates a Google Calendar event reminder."
},
"typeVersion": 1
},
{
"id": "918285b8-5950-44e2-a439-5569962c8db0",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
912,
1744
],
"parameters": {
"color": 7,
"width": 640,
"height": 416,
"content": "## 6. Reports & Chat Response\nCalculates net balances from Odoo ledger lines, generates a formatted Arabic summary, and sends the response back to the user via Webhook."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "ef929821-22a6-4dd1-87f6-d09f47581fb4",
"connections": {
"Date Resolver": {
"main": [
[
{
"node": "Fetch Safe/Bank Move Lines",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory": {
"ai_memory": [
[
{
"node": "AI Financial Agent",
"type": "ai_memory",
"index": 0
}
]
]
},
"Get many items": {
"main": [
[
{
"node": "Calculate Supplier Balance",
"type": "main",
"index": 0
}
]
]
},
"Clean AI Output": {
"main": [
[
{
"node": "Route to Expense Path",
"type": "main",
"index": 0
}
]
]
},
"Create an event": {
"main": [
[
{
"node": "Find Partner ID2",
"type": "main",
"index": 0
}
]
]
},
"Find Partner ID": {
"main": [
[
{
"node": "Find Debit Account ID",
"type": "main",
"index": 0
}
]
]
},
"Success Message": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Find Partner ID2": {
"main": [
[
{
"node": "Find Debit Account ID1",
"type": "main",
"index": 0
}
]
]
},
"Find Partner ID3": {
"main": [
[
{
"node": "Find Debit Account ID2",
"type": "main",
"index": 0
}
]
]
},
"Find Partner ID4": {
"main": [
[
{
"node": "Find Debit Account ID3",
"type": "main",
"index": 0
}
]
]
},
"Find Partner ID5": {
"main": [
[
{
"node": "Find Debit Account ID4",
"type": "main",
"index": 0
}
]
]
},
"Find Supplier ID": {
"main": [
[
{
"node": "Get many items",
"type": "main",
"index": 0
}
]
]
},
"Bot Data Receiver": {
"main": [
[
{
"node": "AI Financial Agent",
"type": "main",
"index": 0
}
]
]
},
"Create Odoo Entry": {
"main": [
[
{
"node": "Confirm & Post Entry",
"type": "main",
"index": 0
}
]
]
},
"Format Check Date": {
"main": [
[
{
"node": "Create an event",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Financial Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI Financial Agent": {
"main": [
[
{
"node": "Clean AI Output",
"type": "main",
"index": 0
}
]
]
},
"Build Final Report": {
"main": [
[
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Business owners, managers, and accountants waste valuable time manually entering daily expenses, supplier payments, and employee advances into Odoo. Getting quick balance reports usually requires logging into the ERP, navigating multiple menus, and generating complex reports.…
Source: https://n8n.io/workflows/14496/ — 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.
This workflow is an AI-powered Dental Appointment Assistant that automates appointment booking, rescheduling, and cancellations through Telegram or a Webhook. It uses intelligent agents to understand
This workflow is an AI-powered Salon Booking Assistant that automates hair, beauty, and spa appointment scheduling through a Webhook trigger. It interacts with the user in natural conversation, collec
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
My workflow 15. Uses httpRequest, memoryBufferWindow, agent, lmChatOpenAi. Webhook trigger; 74 nodes.
Enhance your support, onboarding, and internal knowledge workflows with an intelligent RAG-powered chatbot that responds using live data stored in Google Sheets. 🤖📚 Built for teams that rely on struct