This workflow corresponds to n8n.io template #12298 — we link there as the canonical source.
This workflow follows the Execute Workflow Trigger → HTTP Request 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": "cSn5kCBQMdFpf39B",
"name": "Notify project members when a teammate has an approved leave tomorrow.",
"tags": [
{
"id": "eumrxkLAF6iNOufx",
"name": "Noti",
"createdAt": "2025-12-26T10:56:32.851Z",
"updatedAt": "2025-12-26T10:56:32.851Z"
},
{
"id": "P0InItjxOPutjm62",
"name": "Slack",
"createdAt": "2025-09-22T07:52:01.502Z",
"updatedAt": "2025-09-22T07:52:01.502Z"
},
{
"id": "ROlM2EPm0TCikpBv",
"name": "Redmine 6",
"createdAt": "2025-03-26T07:11:53.633Z",
"updatedAt": "2025-03-26T07:11:53.633Z"
},
{
"id": "HyLSX6V7QZs5MrZy",
"name": "Odoo",
"createdAt": "2025-03-24T01:54:27.030Z",
"updatedAt": "2025-03-24T01:54:27.030Z"
}
],
"nodes": [
{
"id": "425cc66f-75b9-4b24-b952-c960881186a4",
"name": "Step2: Set Variables",
"type": "n8n-nodes-base.set",
"position": [
512,
32
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={\n\n}"
},
"typeVersion": 3.4
},
{
"id": "8291b5bb-94fa-46ac-8036-33d0cae9bec9",
"name": "Step 1: Schedule the trigger to run every weekday at 5:15 PM.",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
320,
32
],
"parameters": {
"rule": {
"interval": [
{
"daysInterval": "={{ 1 }}",
"triggerAtHour": "={{ 17 }}",
"triggerAtMinute": "={{ 15 }}"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "a0cf3311-95b5-4a17-a2fc-ba8e91102467",
"name": "Step3: Get datetime",
"type": "n8n-nodes-base.code",
"position": [
320,
240
],
"parameters": {
"jsCode": "const toVNTime = (d = new Date()) => {\n const date = new Date(d.getTime() + 7 * 60 * 60 * 1000); // UTC+7\n return date;\n};\n\nconst formatDate = (date) => {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n};\n\nconst getDayOfWeek = (date) => {\n const days = [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"];\n return days[date.getDay()];\n};\n\nconst titleSheet = (date) => {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n return `${y}-${m}`;\n};\n\nconst now = toVNTime();\nconst yesterday = new Date(now);\nyesterday.setDate(yesterday.getDate() - 1);\n\nconst twoDaysAgo = new Date(now);\ntwoDaysAgo.setDate(twoDaysAgo.getDate() - 2);\n\nconst tomorrow = new Date(now);\ntomorrow.setDate(tomorrow.getDate() + 1);\n\nconst todayString = formatDate(now);\nconst tomorrowString = formatDate(tomorrow);\n\nreturn [{\n today: todayString,\n yesterday: formatDate(yesterday),\n twoDaysAgo: formatDate(twoDaysAgo),\n tomorrow: tomorrowString,\n dayOfWeekYesterday: getDayOfWeek(yesterday),\n monthYesterday: titleSheet(yesterday),\n}];\n"
},
"typeVersion": 2
},
{
"id": "5bc1ad0c-b4a7-4c23-962f-939638ff59e6",
"name": "Step4: Get all user in Redmine6",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
512,
240
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.redmine6_Url}}users.json?limit={{ $node['Step2: Set Variables'].json.limit}}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "0040816b-cf09-4c83-b2c4-36168ccdee06",
"name": "Step5: Get a list of closed projects.",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
320,
448
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.get_prj_closed}}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "23da7fd5-2a18-4669-8a1e-650e2a8d4d13",
"name": "Step7: Get the list of members' leave records for tomorrow.",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
320,
800
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.hr_leave_web_search_read}}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"id\": 81,\n \"jsonrpc\": \"2.0\",\n \"method\": \"call\",\n \"params\": {\n \"model\": \"hr.leave\",\n \"method\": \"web_search_read\",\n \"args\": [],\n \"kwargs\": {\n \"specification\": {\n \"employee_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"department_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"holiday_status_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"name\": {},\n \"date_from\": {},\n \"date_to\": {},\n \"duration_display\": {},\n \"state\": {},\n \"active_employee\": {},\n \"user_id\": {\n \"fields\": {}\n },\n \"message_needaction\": {},\n \"company_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"activity_exception_decoration\": {},\n \"activity_exception_icon\": {}\n },\n \"offset\": 0,\n \"order\": \"\",\n \"limit\": {{ $node['Step2: Set Variables'].json.limit}},\n \"context\": {\n \"lang\": \"en_US\",\n \"tz\": \"Asia/Bangkok\",\n \"allowed_company_ids\": [\n 1\n ],\n \"bin_size\": true,\n \"params\": {\n \"action\": \"time-off-approval\",\n \"actionStack\": [\n {\n \"action\": \"time-off-approval\"\n }\n ]\n },\n \"hide_employee_name\": 1,\n \"holiday_status_display_name\": false,\n \"current_company_id\": 1\n },\n \"count_limit\": 10001,\n \"domain\": [\n \"&\",\n [\n \"employee_id.company_id\",\n \"in\",\n [\n 1\n ]\n ],\n \"&\",\n \"&\",\n [\n \"date_from\",\n \">=\",\n \"{{ $node['Step3: Get datetime'].json.today}} 18:54:03\"\n ],\n [\n \"date_from\",\n \"<=\",\n \"{{ $node['Step3: Get datetime'].json.tomorrow}} 16:54:03\"\n ],\n \"|\",\n [\n \"number_of_hours\",\n \">=\",\n 4\n ],\n [\n \"number_of_days\",\n \">=\",\n 0.5\n ]\n ]\n }\n }\n}\n\n\n\n\n\n\n\n\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "79ed5d66-df67-45c2-9498-e4d101fd8837",
"name": "End",
"type": "n8n-nodes-base.noOp",
"position": [
1008,
-208
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5522d06e-4dd3-426d-a783-bb6166e9388c",
"name": "End1",
"type": "n8n-nodes-base.noOp",
"position": [
512,
960
],
"parameters": {},
"typeVersion": 1
},
{
"id": "c885a8f2-93cd-4836-8163-52d68716b4b6",
"name": "Step1: When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
1696,
320
],
"parameters": {
"inputSource": "passthrough"
},
"executeOnce": true,
"typeVersion": 1.1,
"alwaysOutputData": true
},
{
"id": "cb4c6a1d-3db0-4dcc-91fc-b9786de3a2c9",
"name": "Step3: Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2080,
320
],
"parameters": {
"options": {},
"batchSize": "={{ 1 }}"
},
"typeVersion": 3,
"alwaysOutputData": false
},
{
"id": "e4759e7e-64ac-405d-89b9-046cba790e95",
"name": "Step4: Get membership list of user in Redmine 6",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
2320,
432
],
"parameters": {
"url": "={{ $node['Step3: Loop Over Items'].json.URL }}projects/{{ $node['Step3: Loop Over Items'].json.id }}/memberships.json",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "51723393-16c7-444c-ae38-a2a9f9e84a8f",
"name": "Step2: Return redmine list ProjectID of user",
"type": "n8n-nodes-base.code",
"position": [
1888,
320
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst redmine_listProjectID = inputData[0].json.redmine_listProjectID;\nreturn redmine_listProjectID;"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "ea0432ff-1efc-4d65-944a-4d366bf58357",
"name": "Step5: Get redmine project Team MemberIds",
"type": "n8n-nodes-base.code",
"position": [
2544,
432
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst membershipsProject = inputData[0].json.memberships || [];\n\nconst redmine_projectTeamMemberIds = membershipsProject\n .filter(record => record?.user?.id)\n .map(record => ({ id: record.user.id }));\n\nreturn [\n {\n json: {\n redmine_projectTeamMemberIds\n }\n }\n];\n"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "aef87abe-6838-4d3a-b1e8-a09d5e8a69fc",
"name": "Step6: Remove duplicate id",
"type": "n8n-nodes-base.code",
"position": [
2320,
224
],
"parameters": {
"jsCode": "const input = $items(\"Step3: Loop Over Items\");\nlet existingIds = $node[\"Step1: When Executed by Another Workflow\"].json.redmine_userID;\nexistingIds = Array.isArray(existingIds) ? existingIds : [existingIds];\n\nconst allIds = input.flatMap(item =>\n item.json.redmine_projectTeamMemberIds.map(i => i.id)\n);\nconst uniqueIds = [...new Set(allIds)];\nconst filteredIds = uniqueIds.filter(id => !existingIds.includes(id));\nconst result = filteredIds.map(id => ({ id }));\n\nreturn [\n {\n json: {\n redmine_projectTeamMemberIds: result\n }\n }\n];"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "5c4f7669-d8b9-47d8-a17e-3d501aff18a6",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"position": [
1904,
-304
],
"parameters": {},
"typeVersion": 1
},
{
"id": "045acc35-2104-40dc-9538-7faee912426f",
"name": "Step2: Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1696,
-288
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "ab3ee8da-a620-4113-89b2-f70a3a2c887a",
"name": "Step3: Wait 1s",
"type": "n8n-nodes-base.wait",
"position": [
1696,
-144
],
"parameters": {
"amount": 1
},
"executeOnce": true,
"typeVersion": 1.1
},
{
"id": "cf4467ec-2c81-4c13-928f-13c70dd3cdb7",
"name": "Step6: Get many users",
"type": "n8n-nodes-base.slack",
"position": [
512,
448
],
"parameters": {
"limit": 100,
"resource": "user",
"operation": "getAll"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"executeOnce": true,
"typeVersion": 2.4,
"alwaysOutputData": true
},
{
"id": "27b24219-1edc-4b9d-9ecc-9e165f322041",
"name": "Step8: Check if there is a record or not?",
"type": "n8n-nodes-base.if",
"position": [
320,
944
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f1ab87cc-5385-4e2b-86c6-95a5a9d615ab",
"operator": {
"type": "number",
"operation": "notEquals"
},
"leftValue": "={{ $json.result.length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.3
},
{
"id": "4ddbe9f6-fdb5-4f82-aa46-e993c6033dae",
"name": "Step9: Handling and get user information in Odoo 18",
"type": "n8n-nodes-base.code",
"position": [
512,
800
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst records = inputData[0].json.result.records;\n\nconst result = records\n .map(item => ({\n odoo_leave_record_id: item.id,\n odoo_employee_id: item.employee_id.id,\n odoo_display_name: item.employee_id.display_name,\n odoo_user_id: item.user_id.id\n }));\n\nreturn result;\n"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "aa110700-708b-4920-8365-f3a8a08e5e39",
"name": "Step10: Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
800,
768
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "a029aeb0-3a12-474e-bfad-3f2f3d23e5fc",
"name": "Step11: Get leave record information",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
1008,
784
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.hr_leave_web_read}}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"id\": 6,\n \"jsonrpc\": \"2.0\",\n \"method\": \"call\",\n \"params\": {\n \"model\": \"hr.leave\",\n \"method\": \"web_read\",\n \"args\": [\n [\n {{ $node['Step10: Loop Over Items'].json.odoo_leave_record_id}}\n ]\n ],\n \"kwargs\": {\n \"context\": {\n \"lang\": \"en_US\",\n \"tz\": \"Asia/Bangkok\",\n \"allowed_company_ids\": [\n 1\n ],\n \"bin_size\": true,\n \"params\": {\n \"resId\": {{ $node['Step10: Loop Over Items'].json.odoo_leave_record_id}},\n \"action\": \"time-off-approval\",\n \"actionStack\": [\n {\n \"action\": \"time-off-approval\"\n },\n {\n \"resId\": {{ $node['Step10: Loop Over Items'].json.odoo_leave_record_id}},\n \"action\": \"time-off-approval\"\n }\n ]\n },\n \"hide_employee_name\": 1,\n \"holiday_status_display_name\": false\n },\n \"specification\": {\n \"can_reset\": {},\n \"can_approve\": {},\n \"can_cancel\": {},\n \"has_mandatory_day\": {},\n \"state\": {},\n \"tz\": {},\n \"tz_mismatch\": {},\n \"leave_type_request_unit\": {},\n \"display_name\": {},\n \"leave_type_increases_duration\": {},\n \"employee_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"employee_company_id\": {\n \"fields\": {}\n },\n \"company_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"department_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"holiday_status_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"date_from\": {},\n \"date_to\": {},\n \"request_date_from\": {},\n \"request_date_to\": {},\n \"request_date_from_period\": {},\n \"request_unit_half\": {},\n \"request_unit_hours\": {},\n \"request_hour_from\": {},\n \"request_hour_to\": {},\n \"number_of_days\": {},\n \"number_of_hours\": {},\n \"duration_display\": {},\n \"employee_overtime\": {},\n \"name\": {},\n \"user_id\": {\n \"fields\": {}\n },\n \"leave_type_support_document\": {},\n \"supported_attachment_ids\": {\n \"fields\": {\n \"name\": {},\n \"mimetype\": {}\n }\n },\n \"overtime_deductible\": {}\n }\n }\n }\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "65bb8b85-d89f-4fed-a8c8-29184c9ae14c",
"name": "Step12: Get name record",
"type": "n8n-nodes-base.code",
"position": [
1216,
784
],
"parameters": {
"jsCode": "const inputData = $input.all();\nif (!inputData?.length || !inputData[0]?.json?.result?.length) {\n throw new Error(\"No leave record data found in input\");\n}\n\nconst record = inputData[0].json.result[0];\nconst display_name_leave_record = record.display_name;\nconst rawFrom = record.date_from;\nconst rawTo = record.date_to;\nif (!rawFrom || !rawTo) {\n throw new Error(\"Missing date_from or date_to in leave record\");\n}\n\nfunction parseAndAdjustDate(dateStr, offsetHours = 7) {\n const normalized = dateStr.includes(\"T\") ? dateStr : dateStr.replace(\" \", \"T\");\n const dt = new Date(normalized);\n \n if (isNaN(dt.getTime())) {\n throw new Error(`Invalid date format: ${dateStr}`);\n }\n \n dt.setHours(dt.getHours() + offsetHours);\n return dt;\n}\n\nfunction formatDate(dt) {\n const pad = (n) => String(n).padStart(2, \"0\");\n return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ` +\n `${pad(dt.getHours())}:${pad(dt.getMinutes())}:${pad(dt.getSeconds())}`;\n}\n\nconst dateFromObj = parseAndAdjustDate(rawFrom);\nconst dateToObj = parseAndAdjustDate(rawTo);\n\nreturn {\n display_name_leave_record,\n date_from: formatDate(dateFromObj),\n date_to: formatDate(dateToObj)\n};"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "afc80ffe-e5bd-401b-baad-10904ff96ae6",
"name": "Step13: Get employee information in Odoo 18",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
1424,
784
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.hr_employee_web_read}}",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={\n \"id\": 29,\n \"jsonrpc\": \"2.0\",\n \"method\": \"call\",\n \"params\": {\n \"model\": \"hr.employee\",\n \"method\": \"web_read\",\n \"args\": [\n [\n {{ $node['Step10: Loop Over Items'].json.odoo_employee_id}}\n ]\n ],\n \"kwargs\": {\n \"context\": {\n \"lang\": \"en_US\",\n \"tz\": \"Asia/Bangkok\",\n \"allowed_company_ids\": [\n 1\n ],\n \"bin_size\": true,\n \"chat_icon\": true\n },\n \"specification\": {\n \"active\": {},\n \"user_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"user_partner_id\": {\n \"fields\": {}\n },\n \"company_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"last_activity_time\": {},\n \"last_activity\": {},\n \"work_contact_id\": {\n \"fields\": {}\n },\n \"work_email\": {},\n \"work_phone\": {},\n \"company_country_code\": {},\n \"parent_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"country_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"ssnid\": {},\n \"gender\": {},\n \"birthday\": {},\n \"place_of_birth\": {},\n \"country_of_birth\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"contract_warning\": {},\n \"has_subscribed_courses\": {},\n \"display_name\": {}\n }\n }\n }\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "9add14fe-50c3-4122-a9fa-4a722569dbac",
"name": "Step14: Get work_email",
"type": "n8n-nodes-base.code",
"position": [
1616,
784
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst odoo_emailTrim = inputData[0].json.result[0].work_email.split('@')[0];\nconst odoo_work_email = inputData[0].json.result[0].work_email;\nconst odoo_parent_id = inputData[0].json.result[0].parent_id.id;\nreturn {odoo_work_email, odoo_emailTrim, odoo_parent_id};"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "6c9f7bc6-2f98-49cb-910c-e61bf5f7e0e0",
"name": "Step15: Get information for this employee's manager on Odoo 18",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
1808,
784
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.hr_employee_web_read}}",
"method": "POST",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
},
"jsonBody": "={\n \"id\": 29,\n \"jsonrpc\": \"2.0\",\n \"method\": \"call\",\n \"params\": {\n \"model\": \"hr.employee\",\n \"method\": \"web_read\",\n \"args\": [\n [\n {{ $node['Step14: Get work_email'].json.odoo_parent_id}}\n ]\n ],\n \"kwargs\": {\n \"context\": {\n \"lang\": \"en_US\",\n \"tz\": \"Asia/Bangkok\",\n \"allowed_company_ids\": [\n 1\n ],\n \"bin_size\": true,\n \"chat_icon\": true\n },\n \"specification\": {\n \"active\": {},\n \"user_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"user_partner_id\": {\n \"fields\": {}\n },\n \"company_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"last_activity_time\": {},\n \"last_activity\": {},\n \"work_contact_id\": {\n \"fields\": {}\n },\n \"work_email\": {},\n \"work_phone\": {},\n \"company_country_code\": {},\n \"parent_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"country_id\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"ssnid\": {},\n \"gender\": {},\n \"birthday\": {},\n \"place_of_birth\": {},\n \"country_of_birth\": {\n \"fields\": {\n \"display_name\": {}\n }\n },\n \"contract_warning\": {},\n \"has_subscribed_courses\": {},\n \"display_name\": {}\n }\n }\n }\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "a2f22d05-b3b1-4a67-9ee3-f414d7d4c995",
"name": "Step16: Get work_email of manager",
"type": "n8n-nodes-base.code",
"position": [
2000,
784
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst odoo_manager_email_trimmed = inputData[0].json.result[0].work_email.split('@')[0];\nconst odoo_manager_work_email = inputData[0].json.result[0].work_email\nreturn {odoo_manager_work_email, odoo_manager_email_trimmed};"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "75bc9fa2-d85d-481a-b132-09779fd4b324",
"name": "Step17: Get user info in Redmine 6",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
816,
1200
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.redmine6_Url}}users.json?name={{ $node['Step14: Get work_email'].json.odoo_emailTrim}}",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "ace5d7ea-8041-4d77-a0f3-8f63b756516e",
"name": "Step18: Check if there is a record or not?",
"type": "n8n-nodes-base.if",
"position": [
1008,
1200
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "557ae5fd-116a-4bc5-aac2-7343bc7e3eb8",
"operator": {
"type": "number",
"operation": "equals"
},
"leftValue": "={{ $json.total_count }}",
"rightValue": "={{ 1 }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f57ff24e-fc83-4bfa-97a5-b6bfc815331c",
"name": "Step19.1: Get membership list of user in Redmine 6",
"type": "n8n-nodes-base.httpRequest",
"maxTries": 2,
"position": [
1200,
1104
],
"parameters": {
"url": "={{ $node['Step2: Set Variables'].json.redmine6_Url}}users/{{ $node['Step18: Check if there is a record or not?'].json.users[0].id }}.json?include=memberships",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2
},
{
"id": "761f0ff6-e3f3-43d9-bfb0-be2ecba75485",
"name": "Step19.2: Return isAccountRedmine = false",
"type": "n8n-nodes-base.code",
"position": [
1200,
1296
],
"parameters": {
"jsCode": "return [\n {\n json: {\n isAccountRedmine: false,\n redmine_listProjectID: []\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "aaeb9361-2b7d-483a-a221-39737f5f99ea",
"name": "Step20: Get project IDs that the member is participating in.",
"type": "n8n-nodes-base.code",
"position": [
1376,
1200
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst projects = $node[\"Step5: Get a list of closed projects.\"].json.projects;\n\nconst excludedprojectIds = new Set([\n 8, 9, 10, 12,\n ...projects.map(p => p.id)\n]);\n\nconst membershipsProject = inputData[0].json.user.memberships;\nconst redmine_userID = inputData[0].json.user.id;\nconst redmine_mail = inputData[0].json.user.mail;\n\nconst result = membershipsProject\n .filter(record => !excludedprojectIds.has(record.project.id))\n .map(record => ({ id: record.project.id }));\n\nreturn [\n {\n json: {\n isAccountRedmine: true,\n redmine_listProjectID: result,\n redmine_userID: redmine_userID,\n redmine_mail: redmine_mail\n }\n }\n];\n"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "4b2531fe-9f23-493a-b235-5b1a33a72413",
"name": "Step21: Merge data",
"type": "n8n-nodes-base.merge",
"position": [
1616,
1120
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition",
"numberInputs": 5
},
"executeOnce": true,
"typeVersion": 3.2,
"alwaysOutputData": true
},
{
"id": "f02df777-b0db-4fc1-bacd-eb46d2dd8663",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
1632,
80
],
"parameters": {
"color": 5,
"width": 1072,
"height": 560,
"content": "## Subflow: Get membership list of user in Redmine\n- It starts when the subflow is called from the main flow.\n- Loop over projects to extract project members and remove duplicate records.\n\nNote: Subflows don't need to be \"Active,\" only the main flow. The main flow \"Active\" will be able to call the subflow.\n"
},
"typeVersion": 1
},
{
"id": "9f4f9620-c23b-4b7e-9107-01b27b5d5966",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1632,
-432
],
"parameters": {
"color": 5,
"width": 640,
"height": 496,
"content": "## Subflow: Push message to member\n- There are 3 different notifications: Time Off, Remote, Leave company\n\nNote: Need to add trigger node \"When Executed by Another Workflow\""
},
"typeVersion": 1
},
{
"id": "aa1ab49e-5a19-4adb-93ca-c2abda7fa33e",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
656
],
"parameters": {
"color": 5,
"width": 448,
"height": 448,
"content": "## Querying data in Odoo\n- Check if there are any records of the member's leave schedule for tomorrow?\n- If there are no records, end the flow.\n- If so, proceed to the next step for filtering."
},
"typeVersion": 1
},
{
"id": "5e346261-0467-4a65-a0f0-145d00787fc2",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
240,
-96
],
"parameters": {
"color": 5,
"width": 448,
"height": 736,
"content": "## Schedule daily runs at 5:15 PM.\n- Get some information about datetime, the list of users on Redmine, and the list of closed projects.\n- Add necessary variables."
},
"typeVersion": 1
},
{
"id": "795758f1-1ebb-472d-b1cc-a1f32de81602",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
656
],
"parameters": {
"color": 5,
"width": 1472,
"height": 320,
"content": "## Loop over item\n- Read the detailed information of the records and extract the member's leave information.\n- Then get the information for that employee and their manager."
},
"typeVersion": 1
},
{
"id": "9f0e0f3e-0dd0-4c48-9885-eabb92f1223a",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
992
],
"parameters": {
"color": 5,
"width": 1072,
"height": 464,
"content": "## Get user info in Redmine\n- Get the information for that member on Redmine \n- If there's any information, then find out which projects they are currently involved in.\n- If not, return isAccountRedmine = false."
},
"typeVersion": 1
},
{
"id": "7b7fb23c-7b68-4393-817d-5150548084c8",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
240
],
"parameters": {
"color": 5,
"width": 912,
"height": 400,
"content": "## Loop over members to check if a member has a Redmine account.\n\n- If there's any information, call 'Subflow Get membership list of user in RM' and handle \"Get email Redmine project Team Member Info\n- If there is none, skip to the next iteration."
},
"typeVersion": 1
},
{
"id": "ba51edeb-7203-4b4e-a248-f6308049e20d",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
-320
],
"parameters": {
"color": 5,
"width": 912,
"height": 544,
"content": "## Loop over members to check if a member has a Redmine account.\n- If you have an account, send a notification about the holiday schedule to the project members.Call 'Subflow push message to member' when there are multiple members in the project. Loop over member and send a notification.\n- If there is no account, send a notification to that member's manager."
},
"typeVersion": 1
},
{
"id": "0992fa70-f1d7-4b94-8e4b-29e29576a03b",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
-528,
-352
],
"parameters": {
"width": 720,
"height": 1392,
"content": "# Notify project members when a teammate has an approved leave tomorrow.\n\n## \ud83d\udcccWho is this for?\nThis workflow is designed for engineering teams, project managers, and IT operations who need consistent visibility into team availability across multiple projects.\nIt\u2019s perfect for organizations that use Odoo for leave management and Redmine for project collaboration, and want to ensure that everyone involved gets timely, automated Slack notifications whenever a team member will be absent the next day.\n\n## \ud83d\udcccThe problem\nWhen team members go dark, everything grinds to a halt. You're stuck with:\n- Last-minute meeting reschedules (and frustrated stakeholders)\n- Tasks assigned to people who aren't there\n- No time to redistribute workload\n- Bottlenecks affecting multiple projects\n\n## \ud83d\udcccHow it works\n1. **Runs** daily at 17:15 - Set it and forget it. Executes every afternoon, giving teams time to prepare.\n2. **Fetches** Tomorrow's Approved Leaves from Odoo - Pulls all leave records with tomorrow's start date and \"approved\" status.\n3. **Maps** Employee & Project Data - Grabs the employee's details and identifies every Redmine project they're assigned to.\n4. **Finds** All Teammates on the Same Projects - Deduplicates across overlapping projects to avoid notification spam.\n5. **Sends** Targeted Slack Notifications - Only notifies people who actually work with the absent member, plus optional manager alerts.\n\n## \ud83d\udcccQuick setup\nBefore you start, you\u2019ll need:\n- Odoo API credentials\n- Redmine API key\n- Slack Bot Token (or Incoming Webhook URL)\n- Subflows need to be created within a new flow; the main flow will call these subflows.\n\n## \ud83d\udcccResults\nWhat changes immediately:\n- Zero surprises - teams know absences 24 hours ahead\n- Workload rebalancing happens before the person goes off\n- Managers make proactive decisions, not reactive ones\n- No more wasted Slack messages to irrelevant people\n- This creates a more predictable and transparent workflow across your engineering and project teams.\n\n## \ud83d\udcccTake it further\nReady to supercharge it? Add:\n- Auto-assign backup owners for critical tasks\n- Sync absences to Google Calendar/Outlook\n- Log notifications to a database for auditing\n- Conditional alerts (key roles, high-priority projects only)\n- Daily summary digest of all upcoming absences\n\n## \ud83d\udcccNeed help customizing?\nContact me for consulting and support:\n[Linkedin](https://www.linkedin.com/company/bac-ha-software/posts/?feedView=all) / [Website](https://bachasoftware.com/bhsoft-contacts)"
},
"typeVersion": 1
},
{
"id": "bb42b7a1-97b0-4ee8-93ee-6ca20518a928",
"name": "Step22: Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
800,
400
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "175fa503-91db-457a-8534-0175e8ff8bc0",
"name": "Step 23: If redmine_listProjectID != [] ==> true",
"type": "n8n-nodes-base.if",
"position": [
1008,
416
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4c054922-0a1b-4ddd-a088-8b7944a40189",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.isAccountRedmine }}",
"rightValue": true
},
{
"id": "e4aa9bf6-2441-44fd-be3e-ca03c1913df5",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.redmine_listProjectID }}",
"rightValue": ""
}
]
}
},
"executeOnce": true,
"typeVersion": 2.3
},
{
"id": "7df52887-ecf5-4fe0-9c1f-6e13b455037f",
"name": "Step 24: Call subflow: \"Get membership list of user in Redmine'",
"type": "n8n-nodes-base.executeWorkflow",
"maxTries": 2,
"position": [
1216,
416
],
"parameters": {
"options": {},
"workflowId": {
"__rl": true,
"mode": "list",
"value": "AbvvdohzMjLfRZut",
"cachedResultUrl": "/workflow/AbvvdohzMjLfRZut",
"cachedResultName": "Subflow Get membership list of user in RM"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"retryOnFail": true,
"typeVersion": 1.3
},
{
"id": "a8e87f6e-6347-4758-8e91-40752b780b90",
"name": "Step25: Get email Redmine project Team Member Info",
"type": "n8n-nodes-base.code",
"position": [
1408,
416
],
"parameters": {
"jsCode": "const redmine_projectTeamMemberIds = $input.first().json.redmine_projectTeamMemberIds;\nconst usersData = $node[\"Step4: Get all user in Redmine6\"].json.users;\n\nconst redmine_projectTeamMemberInfo = redmine_projectTeamMemberIds.map(member => {\n const found = usersData.find(user => user.id === member.id);\n\n return {\n redmine_userID: member.id,\n redmine_mail: found ? found.mail : null\n };\n});\n\nreturn {redmine_projectTeamMemberInfo};"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "27fc1840-7725-42a3-8f1d-5eb05872ffb2",
"name": "Step26: Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
800,
-112
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "3ee1a0f9-c28c-4baf-9077-c10ca310e348",
"name": "Step 27: If redmine_listProjectID != [] ==> true",
"type": "n8n-nodes-base.if",
"position": [
1008,
-32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4c054922-0a1b-4ddd-a088-8b7944a40189",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.isAccountRedmine }}",
"rightValue": true
}
]
}
},
"executeOnce": true,
"typeVersion": 2.3
},
{
"id": "6e6f12ce-d5c5-40da-99a1-38a3213ba621",
"name": "Step28.1: Prepare information about leave schedules for announcement.",
"type": "n8n-nodes-base.code",
"position": [
1216,
-128
],
"parameters": {
"jsCode": "const inputData = $input.all();\nconst redmineMembers = inputData[0].json.redmine_projectTeamMemberInfo || [];\nconst mergedResults = $items(\"Step6: Get many users\");\nconst display_name_leave_record = $input.first().json.display_name_leave_record;\nconst date_from_leave_record = $input.first().json.date_from;\nconst date_to_leave_record = $input.first().json.date_to;\nconst odoo_leave_record_id = $input.first().json.odoo_leave_record_id;\nconst time_off_approval_link = $node[\"Step2: Set Variables\"].json.time_off_approval_link;\nconst odoo_manager_work_email = $input.first().json.odoo_manager_work_email;\n\nfunction findMemberIDByEmail(email) {\n if (!email) return null;\n\n const cleanEmail = String(email).toLowerCase().trim();\n const local = cleanEmail.split(\"@\")[0];\n\n let bestMatch = null;\n let bestScore = -1;\n\n for (const item of mergedResults) {\n const data = item.json || {};\n if (!data.Email) continue;\n\n const itemEmail = String(data.Email).toLowerCase().trim();\n const itemLocal = itemEmail.split(\"@\")[0];\n\n let score = 0;\n if (itemEmail === cleanEmail) score = 100;\n else if (itemLocal === local) score = 80;\n else if (itemEmail.includes(local)) score = 50;\n\n if (score > bestScore) {\n bestScore = score;\n bestMatch = data;\n }\n }\n\n return bestMatch ? bestMatch.memberID : null;\n}\n\n// -------------------------------------------------\n// 1) If redmineMembers is empty \u2192 only process management emails\n// -------------------------------------------------\nif (redmineMembers.length === 0) {\n const managerMemberID = findMemberIDByEmail(odoo_manager_work_email);\n\n return [\n {\n type: \"timeoff\",\n memberID: managerMemberID || undefined,\n title: display_name_leave_record,\n date_from: date_from_leave_record,\n date_to: date_to_leave_record,\n link: time_off_approval_link + odoo_leave_record_id,\n isManager: true,\n redmine_mail: odoo_manager_work_email\n }\n ];\n}\n\n// -------------------------------------------------\n// 2) If redmineMembers is not empty\n// -------------------------------------------------\nfor (const member of redmineMembers) {\n const inputEmail = String(member.redmine_mail || \"\").toLowerCase().trim();\n const inputLocal = inputEmail.split(\"@\")[0];\n\n let bestMatch = null;\n let bestScore = -1;\n\n for (const item of mergedResults) {\n const data = item.json || {};\n if (!data.Email) continue;\n\n const itemEmail = String(data.Email).toLowerCase().trim();\n const itemLocal = itemEmail.split(\"@\")[0];\n\n let score = 0;\n\n if (itemEmail === inputEmail) score = 100;\n else if (itemLocal === inputLocal) score = 80;\n else if (itemEmail.includes(inputLocal)) score = 50;\n\n if (score > bestScore) {\n bestScore = score;\n bestMatch = data;\n }\n }\n\n member.type = \"timeoff\";\n member.memberID = bestMatch ? bestMatch.memberID : undefined;\n member.title = display_name_leave_record;\n member.date_from = date_from_leave_record;\n member.date_to = date_to_leave_record;\n member.link = time_off_approval_link + odoo_leave_record_id;\n}\n\n// -------------------------------------------------\n// 3) Find the managerMemberID and check if it's already in redmineMembers.\n// -------------------------------------------------\nconst managerMemberID = findMemberIDByEmail(odoo_manager_work_email);\n\nif (managerMemberID) {\n const exists = redmineMembers.some(m => m.memberID === managerMemberID);\n\n if (!exists) {\n redmineMembers.push({\n type: \"timeoff\",\n memberID: managerMemberID,\n title: display_name_leave_record,\n date_from: date_from_leave_record,\n date_to: date_to_leave_record,\n link: time_off_approval_link + odoo_leave_record_id,\n isManager: true,\n redmine_mail: odoo_manager_work_email\n });\n }\n}\n\nreturn redmineMembers;\n"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "0fc89b6f-69b3-4456-85ae-5a819f86cf94",
"name": "Step28.2: Prepare information about leave schedules for announcement.",
"type": "n8n-nodes-base.code",
"position": [
1216,
64
],
"parameters": {
"jsCode": "const mergedResults = $items(\"Step6: Get many users\");\nconst display_name_leave_record = $input.first().json.display_name_leave_record;\nconst date_from_leave_record = $input.first().json.date_from;\nconst date_to_leave_record = $input.first().json.date_to;\nconst odoo_leave_record_id = $input.first().json.odoo_leave_record_id;\nconst time_off_approval_link = $node[\"Step2: Set Variables\"].json.time_off_approval_link;\nconst odoo_manager_work_email = $input.first().json.odoo_manager_work_email;\n\nfunction findMemberIDByEmail(email) {\n if (!email) return null;\n\n const cleanEmail = String(email).toLowerCase().trim();\n const local = cleanEmail.split(\"@\")[0];\n\n let bestMatch = null;\n let bestScore = -1;\n\n for (const item of mergedResults) {\n const data = item.json || {};\n if (!data.Email) continue;\n\n const itemEmail = String(data.Email).toLowerCase().trim();\n const itemLocal = itemEmail.split(\"@\")[0];\n\n let score = 0;\n if (itemEmail === cleanEmail) score = 100;\n else if (itemLocal === local) score = 80;\n else if (itemEmail.includes(local)) score = 50;\n\n if (score > bestScore) {\n bestScore = score;\n bestMatch = data;\n }\n }\n\n return bestMatch ? bestMatch.memberID : null;\n}\n\nconst managerMemberID = findMemberIDByEmail(odoo_manager_work_email);\nreturn [\n {\n type: \"timeoff\",\n memberID: managerMemberID || undefined,\n title: display_name_leave_record,\n date_from: date_from_leave_record,\n date_to: date_to_leave_record,\n link: time_off_approval_link + odoo_leave_record_id,\n redmine_mail: odoo_manager_work_email\n }\n];\n"
},
"executeOnce": true,
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "285b91d1-c0ce-4f20-9fff-bc918e9415ff",
"name": "Step29: Call subflow: 'Push message to member'",
"type": "n8n-nodes-base.executeWorkflow",
"position": [
1408,
-48
],
"parameters": {
"mode": "each",
"options": {
"waitForSubWorkflow": true
},
"workflowId": {
"__rl": true,
"mode": "list",
"value": "8VJZxH3K4mdPeU1c",
"cachedResultUrl": "/workflow/8VJZxH3K4mdPeU1c",
"cachedResultName": "Subflow push message to member"
},
"workflowInputs": {
"value": {},
"schema": [],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": true
}
},
"executeOnce": false,
"typeVersion": 1.3
},
{
"id": "d7d8d48c-e5a8-4183-a6fa-f78afe041604",
"name": "Step4: Switch",
"type": "n8n-nodes-base.switch",
"position": [
1904,
-144
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1c407394-fd0f-4ce8-800a-e1d551003dfe",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $node['Step1: When Executed by Another Workflow'].json.type}}",
"rightValue": "=timeoff"
}
]
}
},
{
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "847a6ec3-9afc-41ff-8c02-538dc347b365",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $node['Step1: When Executed by Another Workflow'].json.type}}",
"rightValue": "=remote"
}
]
}
}
]
},
"options": {}
},
"typeVersion": 3.4
},
{
"id": "85ced538-00cb-42e7-97ea-98614c550d72",
"name": "Step5.1: Send a message to the project team members.",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 2,
"position": [
2112,
-304
],
"parameters": {
"url": "=https://slack.com/api/chat.postMessage",
"method": "POST",
"options": {},
"sendBody": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "=channel",
"value": "={{ $node['Step1: When Executed by Another Workflow'].json.memberID}}"
},
{
"name": "=text",
"value": "=*Time Off Notification!*\n\n*Title*: {{ $node['Step1: When Executed by Another Workflow'].json.title }}\n*From*: {{ $node['Step1: When Executed by Another Workflow'].json.date_from }}\n*To*: {{ $node['Step1: When Executed by Another Workflow'].json.date_to }}\n*Link Odoo*: <{{ $node['Step1: When Executed by Another Workflow'].json.link }}|Link here>"
}
]
},
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpQueryAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": false
},
{
"id": "65ec9cba-3f59-4410-b376-e8f0e901d011",
"name": "Step5.2: Send a message to the project team members.",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"maxTries": 2,
"position": [
2112,
-128
],
"parameters": {
"url": "=https://slack.com/api/chat.postMessage",
"method": "POST",
"options": {},
"sendBody": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "=channel",
"value": "={{ $node['Step1: When Executed by Another Workflow'].json.memberID}}"
},
{
"name": "=text",
"value": "=*Remote Notification!*\n\n*Title*: {{ $node['Step1: When Executed by Another Workflow'].json.title }}\n*From*: {{ $node['Step1: When Executed by Another Workflow'].json.date_from }}\n*To*: {{ $node['Step1: When Executed by Another Workflow'].json.date_to }}\n*Link Odoo*: <{{ $node['Step1: When Executed by Another Workflow'].json.link }}|Link here>"
}
]
},
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpQueryAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
},
"executeOnce": true,
"retryOnFail": true,
"typeVersion": 4.2,
"alwaysOutputData": false
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "77d11d95-d216-45c4-92f9-8393fd2e728b",
"connections": {
"End": {
"main": [
[]
]
},
"Step4: Switch": {
"main": [
[
{
"node": "Step5.1: Send a message to the project team members.",
"type": "main",
"index": 0
}
],
[
{
"node": "Step5.2: Send a message to the project team members.",
"type": "main",
"index": 0
}
]
]
},
"Step3: Wait 1s": {
"main": [
[
{
"node": "Step4: Switch",
"type": "main",
"index": 0
}
]
]
},
"Step21: Merge data": {
"main": [
[
{
"node": "Step10: Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Step3: Get datetime": {
"main": [
[
{
"node": "Step4: Get all user in Redmine6",
"type": "main",
"index": 0
}
]
]
},
"Step2: Set Variables": {
"main": [
[
{
"node": "Step3: Get datetime",
"type": "main",
"index": 0
}
]
]
},
"Step6: Get many users": {
"main": [
[
{
"node": "Step7: Get the list of members' leave records for tomorrow.",
"type": "main",
"index": 0
}
]
]
},
"Step14: Get work_email": {
"main": [
[
{
"node": "Step21: Merge data",
"type": "main",
"index": 0
},
{
"node": "Step15: Get information for this employee's manager on Odoo 18",
"type": "main",
"index": 0
}
]
]
},
"Step2: Loop Over Items": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
],
[
{
"node": "Step3: Wait 1s",
"type": "main",
"index": 0
}
]
]
},
"Step3: Loop Over Items": {
"main": [
[
{
"node": "Step6: Remove duplicate id",
"type": "main",
"index": 0
}
],
[
{
"node": "Step4: Get membership list of user in Redmine 6",
"type": "main",
"index": 0
}
]
]
},
"Step10: Loop Over Items": {
"main": [
[
{
"node": "Step22: Loop Over Items",
"type": "main",
"index": 0
}
],
[
{
"node": "Step11: Get leave record information",
"type": "main",
"index": 0
},
{
"node": "Step21: Merge data",
"type": "main",
"index": 2
}
]
]
},
"Step12: Get name record": {
"main": [
[
{
"node": "Step13: Get employee information in Odoo 18",
"type": "main",
"index": 0
},
{
"node": "Step21: Merge data",
"type": "main",
"index": 1
}
]
]
},
"Step22: Loop Over Items": {
"main": [
[
{
"node": "Step26: Loop Over Items",
"type": "main",
"index": 0
}
],
[
{
"node": "Step 23: If redmine_listProjectID != [] ==> true",
"type": "main",
"index": 0
}
]
]
},
"Step26: Loop Over Items": {
"main": [
[
{
"node": "End",
"type": "main",
"index": 0
}
],
[
{
"node": "Step 27: If redmine_listProjectID != [] ==> true",
"type": "main",
"index": 0
}
]
]
},
"Step4: Get all user in Redmine6": {
"main": [
[
{
"node": "Step5: Get a list of closed projects.",
"type": "main",
"index": 0
}
]
]
},
"Step16: Get work_email of manager": {
"main": [
[
{
"node": "Step17: Get user info in Redmine 6",
"type": "main",
"index": 0
},
{
"node": "Step21: Merge data",
"type": "main",
"index": 4
}
]
]
},
"Step17: Get user info in Redmine 6": {
"main": [
[
{
"node": "Step18: Check if there is a record or not?",
"type": "main",
"index": 0
}
]
]
},
"Step11: Get leave record information": {
"main": [
[
{
"node": "Step12: Get name record",
"type": "main",
"index": 0
}
]
]
},
"Step5: Get a list of closed projects.": {
"main": [
[
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.
httpBearerAuthhttpHeaderAuthhttpQueryAuthslackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is designed for engineering teams, project managers, and IT operations who need consistent visibility into team availability across multiple projects. It’s perfect for organizations that use Odoo for leave management and Redmine for project collaboration, and want…
Source: https://n8n.io/workflows/12298/ — 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 automated system that tracks End-of-Life (EOL) dates for software and technologies used across your projects. It eliminates the need to manually monitor EOL dates in spreadsheets o
This workflow continuously monitors the Meta Ads Library for new creatives from a specific competitor pages, logs them into Google Sheets, and sends a concise Telegram notification with the number of
Enhance financial oversight with this automated n8n workflow. Triggered every 5 minutes, it fetches real-time bank transactions via an API, enriches and transforms the data, and applies smart logic to
This workflow automates competitive price intelligence using Bright Data's enterprise web scraping API. On a scheduled basis (default: daily at 9 AM), the system loops through configured competitor pr
> n8n, Binance API, Google Sheets, Slack, Telegram, Jira & Email