{ "meta": { "instanceId": "32d80f55a35a7b57f8e47a2ac19558d9f5bcec983a5519d9c29ba713ff4f12c7" }, "nodes": [ { "id": "fdd55253-5cb6-4b1f-9c93-6915f254f700", "name": "Schedule Trigger", "type": "n8n-nodes-base.scheduleTrigger", "position": [ -60, -240 ], "parameters": { "rule": { "interval": [ { "field": "months", "triggerAtDayOfMonth": 5 } ] } }, "typeVersion": 1.2 }, { "id": "c8d6064a-3fd7-478d-891c-6ade336daa1f", "name": "YTD vs Prevoius Month1", "type": "n8n-nodes-base.mySql", "onError": "continueRegularOutput", "position": [ 640, 0 ], "parameters": { "query": "SELECT\n -- budget_data.fiscal_year AS `Year`,\n -- budget_data.cost_center AS `Cost Center`,\n budget_data.budget_group AS `Budget Group`,\n-- budget_data.sort_order AS `Sort Order`,\n\n -- YTD Totals up to previous month (up to dynamic month)\n SUM(budget_data.budget_amount) AS `Budget YTD`,\n SUM(COALESCE(actual_data.actual_amount, 0)) AS `Actual YTD`,\n SUM(COALESCE(actual_data.actual_amount, 0)) - SUM(budget_data.budget_amount) AS `Variance YTD`,\n\n -- Previous Month Totals Only\n SUM(CASE WHEN budget_data.budget_month = {{ $('PreviousMonth').item.json.previousMonth }} THEN budget_data.budget_amount ELSE 0 END) AS `Budget PM`,\n SUM(CASE WHEN budget_data.budget_month = {{ $('PreviousMonth').item.json.previousMonth }} THEN COALESCE(actual_data.actual_amount, 0) ELSE 0 END) AS `Actual PM`,\n SUM(CASE WHEN budget_data.budget_month = {{ $('PreviousMonth').item.json.previousMonth }} THEN COALESCE(actual_data.actual_amount, 0) ELSE 0 END) -\n SUM(CASE WHEN budget_data.budget_month = {{ $('PreviousMonth').item.json.previousMonth }} THEN budget_data.budget_amount ELSE 0 END) AS `Variance PM`\n\nFROM\n (\n SELECT\n bg.budget_group_name AS budget_group,\n bg.sort_order,\n bgd.fiscal_year,\n bgd.budget_month,\n bgd.cost_center,\n CAST(bgd.budget_amount AS DECIMAL(18,6)) AS budget_amount\n FROM\n `tabBudget Group Detail` bgd\n JOIN\n `tabBudget Group` bg ON bg.name = bgd.parent\n WHERE\n bgd.fiscal_year = {{ $('PreviousMonth').item.json.year }}\n AND bgd.budget_month <= {{ $('PreviousMonth').item.json.previousMonth }}\n AND bgd.cost_center = '{{ $json.CostCenter }}'\n ) AS budget_data\n\nLEFT JOIN (\n SELECT\n acc.budget_group AS budget_group,\n YEAR(gl.posting_date) AS fiscal_year,\n MONTH(gl.posting_date) AS budget_month,\n gl.cost_center,\n SUM(\n CASE \n WHEN acc.root_type = 'Income' THEN gl.credit - gl.debit\n WHEN acc.root_type = 'Expense' THEN gl.debit - gl.credit\n ELSE 0\n END\n ) AS actual_amount\n FROM\n `tabGL Entry` gl\n JOIN\n `tabAccount` acc ON gl.account = acc.name\n WHERE\n acc.budget_group IS NOT NULL\n AND acc.root_type IN ('Income', 'Expense')\n AND gl.docstatus = 1\n AND YEAR(gl.posting_date) = {{ $('PreviousMonth').item.json.year }}\n AND MONTH(gl.posting_date) <= {{ $('PreviousMonth').item.json.previousMonth }}\n AND gl.cost_center = '{{ $('Filter').item.json['Cost Center'] }}'\n GROUP BY\n acc.budget_group,\n YEAR(gl.posting_date),\n MONTH(gl.posting_date),\n gl.cost_center\n) AS actual_data\nON\n budget_data.budget_group = actual_data.budget_group AND\n budget_data.fiscal_year = actual_data.fiscal_year AND\n budget_data.budget_month = actual_data.budget_month AND\n budget_data.cost_center = actual_data.cost_center\n\nGROUP BY\n budget_data.fiscal_year,\n budget_data.cost_center,\n budget_data.budget_group,\n budget_data.sort_order\n\nORDER BY\n budget_data.cost_center,\n budget_data.sort_order,\n budget_data.budget_group;\n", "options": {}, "operation": "executeQuery" }, "retryOnFail": false, "typeVersion": 2.4 }, { "id": "13102b1c-8a06-4a23-8174-75254bf783ac", "name": "Loop Over Items", "type": "n8n-nodes-base.splitInBatches", "position": [ -40, 200 ], "parameters": { "options": {} }, "typeVersion": 3 }, { "id": "da2a0b30-3df4-430c-8cac-cd9d735ce759", "name": "CostCentrs", "type": "n8n-nodes-base.set", "position": [ 1100, -240 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "ac6bcf14-13e3-464d-b9cd-4adee56018d7", "name": "Cost Center", "type": "string", "value": "={{ $json['Cost Center'] }}" } ] } }, "typeVersion": 3.4 }, { "id": "7891d71c-18f8-4e07-aa30-f50bec10cef6", "name": "Date & Time", "type": "n8n-nodes-base.dateTime", "position": [ 260, -240 ], "parameters": { "options": {} }, "typeVersion": 2 }, { "id": "3e69dc27-0850-4978-bf10-e81ff575ec60", "name": "PreviousMonth", "type": "n8n-nodes-base.code", "position": [ 520, -240 ], "parameters": { "jsCode": "// Get the input date from the previous node\nconst inputDateStr = $input.first().json.currentDate;\nconst inputDate = new Date(inputDateStr);\n\n// Move to the first day of the current month\ninputDate.setDate(1);\n\n// Step back one day to land in the previous month\ninputDate.setDate(0);\n\n// Extract previous month and year\nconst previousMonth = inputDate.getMonth() + 1; // Months are 0-based\nconst year = inputDate.getFullYear(); // This will reflect the correct year, even in January\n\nreturn [\n {\n json: {\n previousMonth: previousMonth.toString().padStart(2, '0'), // e.g., \"01\", \"12\"\n year: year.toString() // e.g., \"2024\"\n }\n }\n];\n" }, "typeVersion": 2 }, { "id": "f6776225-39d2-4746-a90f-b4d1b12a66ee", "name": "Selected Cost Center", "type": "n8n-nodes-base.set", "position": [ 260, 220 ], "parameters": { "options": {}, "assignments": { "assignments": [ { "id": "c4a6c71a-0df4-49df-9068-f039ddf7d507", "name": "CostCenter", "type": "string", "value": "={{ $json['Cost Center'] }}" }, { "id": "ade95f85-baa2-4f5d-a125-7360b17cf99b", "name": "previousMonth", "type": "string", "value": "={{ $('PreviousMonth').item.json.previousMonth }}" }, { "id": "36c1d772-5bb7-47a6-81f9-1b70208e558b", "name": "year", "type": "string", "value": "={{ $('PreviousMonth').item.json.year }}" } ] } }, "typeVersion": 3.4 }, { "id": "1e23d876-21be-4d90-b5e4-38f3543a0c3b", "name": "Get Cost Centers with Budgets", "type": "n8n-nodes-base.mySql", "position": [ 800, -240 ], "parameters": { "query": "SELECT DISTINCT\n budget_data.cost_center AS `Cost Center`\nFROM\n(\n SELECT\n bgd.cost_center,\n bgd.fiscal_year,\n bgd.budget_month\n FROM\n `tabBudget Group Detail` bgd\n JOIN\n `tabBudget Group` bg ON bg.name = bgd.parent\n WHERE\n bgd.fiscal_year = {{ $json.year }}\n AND bgd.budget_month <= {{ $json.previousMonth }}\n) AS budget_data\n\nINNER JOIN\n(\n SELECT DISTINCT\n gl.cost_center,\n YEAR(gl.posting_date) AS fiscal_year,\n MONTH(gl.posting_date) AS budget_month\n FROM\n `tabGL Entry` gl\n JOIN\n `tabAccount` acc ON gl.account = acc.name\n WHERE\n acc.budget_group IS NOT NULL\n AND acc.root_type IN ('Income', 'Expense')\n AND gl.docstatus = 1\n AND YEAR(gl.posting_date) = {{ $json.year }}\n AND MONTH(gl.posting_date) <= {{ $json.previousMonth }}\n AND gl.cost_center IS NOT NULL\n) AS gl_data\nON\n budget_data.cost_center = gl_data.cost_center\n AND budget_data.fiscal_year = gl_data.fiscal_year\n AND budget_data.budget_month = gl_data.budget_month\n\nORDER BY\n budget_data.cost_center;\n", "options": {}, "operation": "executeQuery" }, "typeVersion": 2.4 }, { "id": "d4429595-b1b9-4121-a612-24be11e6a36a", "name": "Filter", "type": "n8n-nodes-base.filter", "position": [ 1380, -240 ], "parameters": { "options": {}, "conditions": { "options": { "version": 2, "leftValue": "", "caseSensitive": true, "typeValidation": "strict" }, "combinator": "and", "conditions": [ { "id": "d7a13ce7-24d3-406a-934b-97f9a47b206c", "operator": { "name": "filter.operator.equals", "type": "string", "operation": "equals" }, "leftValue": "={{ $json['Cost Center'] }}", "rightValue": "AI DEPARTMENT" } ] } }, "typeVersion": 2.2 }, { "id": "67bbe834-ae40-4aad-b468-6fa73c9dc6c6", "name": "HTML", "type": "n8n-nodes-base.html", "position": [ 40, 920 ], "parameters": { "html": "{{ $json.html }}" }, "typeVersion": 1.2 }, { "id": "58d1dc63-9ba7-41b8-af39-b7c134ab3cea", "name": "verticalPL", "type": "n8n-nodes-base.code", "position": [ 900, 220 ], "parameters": { "jsCode": "const rows = items;\n\n// Get column names from the first row\nconst headers = Object.keys(rows[0].json);\n\n// Build header HTML\nlet headerHtml = headers.map(col => `