{
  "name": "Employee Leave Approval System",
  "nodes": [
    {
      "id": "90969d48-aed1-4a4b-9a54-557e2c1e66bf",
      "name": "On form submission",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -160,
        -64
      ],
      "parameters": {
        "options": {
          "appendAttribution": false,
          "respondWithOptions": {
            "values": {
              "formSubmittedText": "You will be notified via email regarding the approval or rejection of your leave request."
            }
          }
        },
        "formTitle": "Leave Request Form",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Employee Name",
              "requiredField": true
            },
            {
              "fieldType": "email",
              "fieldLabel": "Email",
              "requiredField": true
            },
            {
              "fieldType": "date",
              "fieldLabel": "From",
              "requiredField": true
            },
            {
              "fieldType": "date",
              "fieldLabel": "To"
            },
            {
              "fieldLabel": "Reason for the Leave",
              "requiredField": true
            },
            {
              "fieldLabel": "What Important tasks were you working on?",
              "placeholder": "Priorities",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Please fill every detail properly, and explain in detail about the task and priorities that you have "
      },
      "typeVersion": 2.3
    },
    {
      "id": "8cd2b052-fd8f-4d79-88f8-4e5a717ad97a",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        64,
        -64
      ],
      "parameters": {
        "text": "=Role:\nYou are an HR Operations Assistant.\n\nInput:\nYou will receive an array of objects containing employee leave request data in the following JSON format:\n\n[\n  {\n    \"Employee Name\": \"{{ $json['Employee Name'] }}\",\n    \"Email\": \"{{ $json.Email }}\",\n    \"From\": \"{{ $json.From }}\",\n    \"To\": \"{{ $json.To }}\",\n    \"Reason for the Leave\": \"{{ $json['Reason for the Leave'] }}\",\n    \"What Important tasks were you working on?\": \"{{ $json['What Important tasks were you working on?'] }}\"\n  }\n]\n\nRules & Logic:\n1. If the \"To\" field is empty or null, treat the leave as a single-day leave on the \"From\" date.\n2. If the \"To\" field is present, treat it as a multi-day leave from the \"From\" date to the \"To\" date (inclusive).\n3. Read all the input data and generate a **professional summary** of the leave request instead of just copying the text.\n4. Include in the summary:\n   - Employee Name\n   - Leave duration (single-day or date range)\n   - Reason for leave (summarized in professional wording)\n   - Status of ongoing tasks and how they are covered or handed over\n5. Keep a **polite and professional tone** suitable for sending to Manager and HR.\n6. Do not add any information not present in the input.\n7. Output must be valid HTML content only.\n8. Allowed HTML tags: <p>, <br>, <strong>, <b>, <em>, <ul>, <ol>, <li>, <span>, <div>\n9. Do NOT include <html>, <head>, or <body> tags.\n10. Generate exactly **two outputs**:\n   - `subject` \u2192 a concise email subject line\n   - `body` \u2192 the full HTML-formatted email content\n\nOutput Example:\n\nsubject:\nLeave Approval Request \u2013 {{ $json['Employee Name'] }}\n\nbody:\n<div>\n  <p>\n    <strong>Employee:</strong> {{ $json['Employee Name'] }}\n  </p>\n\n  <p>\n    <strong>Leave Duration:</strong> {% if $json.To == \"\" %}{{ $json.From }} (single-day leave){% else %}{{ $json.From }} to {{ $json.To }}{% endif %}\n  </p>\n\n  <p>\n    <strong>Reason for Leave:</strong> A summary of the employee's reason for leave in professional wording.\n  </p>\n\n  <p>\n    <strong>Task Status & Coverage:</strong> Summarize the important tasks the employee was working on and explain how they will be managed or covered during their absence.\n  </p>\n\n  <p>\n    Kindly review and approve or reject the leave request.\n  </p>\n</div>",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "ad65648f-540b-45b7-abe7-a9c40391a2f4",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        0,
        144
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "ee1f1646-fb35-4969-91f9-c40c800dbff7",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        240,
        144
      ],
      "parameters": {
        "jsonSchemaExample": "{\n\t\"Subject\": \"\",\n\t\"Body\": \"\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "6e87890d-324b-4619-981d-5c9e1d068215",
      "name": "Send message and wait for response",
      "type": "n8n-nodes-base.gmail",
      "position": [
        416,
        -64
      ],
      "parameters": {
        "sendTo": "={{ $('On form submission').item.json.Email }}",
        "message": "={{ $json.output.Body }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $json.output.Subject }}",
        "operation": "sendAndWait",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "disapproveLabel": "Reject"
          }
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "3efdcfc0-b320-4841-85a4-b6ff480c1f35",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        640,
        -64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c440577b-0795-4886-be4d-4e0e5b541615",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "1fec2797-b9e9-48cf-9aad-c1f75283e4c8",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1008,
        -128
      ],
      "parameters": {
        "sendTo": "={{ $('On form submission').item.json.Email }}",
        "message": "=<div>\n  <p>Dear {{ $('On form submission').item.json['Employee Name'] }},</p>\n\n  <p>\n    We are pleased to inform you that your leave request has been <strong>approved</strong>.\n  </p>\n\n  <p>\n    <strong>Leave Duration:</strong>\n    {{ $('On form submission').item.json.From }} - {{ $('On form submission').item.json.To }}<br>\n    <strong>Reason for Leave:</strong> {{ $('On form submission').item.json['Reason for the Leave'] }}<br>\n    <strong>Task Coverage:</strong> Your submitted plan for handling ongoing tasks has been acknowledged.\n  </p>\n\n  <p>\n    We wish you a productive and safe time off. Please ensure any pending tasks are handed over as discussed.\n  </p>\n\n  <p>\n    Best regards,<br>\n    HR Department<br>\n  </p>\n</div>\n",
        "options": {
          "appendAttribution": false
        },
        "subject": "Leave Approval Status"
      },
      "typeVersion": 2.2
    },
    {
      "id": "30be9378-7c6a-46cb-9956-a0ff539b6c9d",
      "name": "Check Availability",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        992,
        320
      ],
      "parameters": {
        "options": {
          "timezone": {
            "__rl": true,
            "mode": "list",
            "value": "Asia/Kolkata",
            "cachedResultName": "Asia/Kolkata"
          },
          "outputFormat": "availability"
        },
        "timeMax": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(18, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 18:00:00') }}",
        "timeMin": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(9, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 09:00:00') }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "user@example.com"
        },
        "resource": "calendar"
      },
      "typeVersion": 1.3
    },
    {
      "id": "2ccb4823-6d9d-4423-b9be-7d36e6cafa28",
      "name": "Get Events",
      "type": "n8n-nodes-base.googleCalendarTool",
      "position": [
        1120,
        320
      ],
      "parameters": {
        "options": {},
        "timeMax": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(18, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 18:00:00') }}",
        "timeMin": "={{ new Date(new Date().setDate(new Date().getDate() + 1)).setHours(9, 0, 0, 0) && new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().replace('T', ' ').slice(0, 19).replace(/ \\d{2}:\\d{2}:\\d{2}$/, ' 09:00:00') }}",
        "calendar": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "user@example.com"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "typeVersion": 1.3
    },
    {
      "id": "8c598b23-1d6a-47b6-8dc1-fdae56eddd2c",
      "name": "OpenAI",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        864,
        320
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "3e86fb9e-150c-4e7c-b87c-0994676087d9",
      "name": "Booking Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        992,
        96
      ],
      "parameters": {
        "text": "=You are an availability checking agent.\n\nTODAY'S DATE: {{ new Date().toISOString().slice(0, 10) }}\n\nGOAL:\n\nYour job is to:\n1. Check availability for tomorrow (the next business day after today)\n2. Find the FIRST free 10-minute slot between 9:00 AM and 6:00 PM\n3. Return only ONE timeslot (the earliest available)\n\nYou must NEVER ask the user questions.\nYou must NEVER wait for user input.\nEverything must be automatic.\n\nCRITICAL DATE RULE:\n\n- Today is {{ new Date().toISOString().slice(0, 10) }}\n- Tomorrow's date is the day after today\n- The tools are already configured with timeMin and timeMax for tomorrow\n- You MUST use tomorrow's actual date in your output\n- If \"Get Events\" returns empty results, it means ALL slots are free\n- DO NOT make up or hallucinate dates\n- Calculate tomorrow's date from today's date\n\nCHECK AVAILABILITY TOOL RULES:\n\n1. First, call the \"Get Events\" tool to fetch all events booked for tomorrow.\n   - If the result is empty or shows no events: ALL SLOTS ARE FREE - return 09:00:00 as the start time\n   - If there are events: proceed to check availability between them\n   - Extract the date from the timeMin parameter or from any returned events\n\n2. Only if there are existing events, call the \"Check Availability\" tool.\n   - Look for slots with at least 10 minutes of free time between 09:00 and 18:00\n\nSLOT SELECTION LOGIC:\n\n- If NO events exist: The first slot is 09:00-09:10 (return 09:00:00)\n- If events exist: Start checking from 09:00:00 on tomorrow's date\n- Look for the FIRST available 10-minute slot\n- If 09:00-09:10 is booked, check 09:10-09:20\n- Continue in 10-minute increments until you find a free slot\n- Stop as soon as you find the first available slot\n\nOUTPUT RULES:\n\nAfter checking availability:\n- Calculate tomorrow's date from today ({{ new Date().toISOString().slice(0, 10) }})\n- Extract the date from the tool results or timeMin parameter\n- If calendar is completely free (no events): Return 09:00:00 as start_time with tomorrow's date\n- If there are events: Return the FIRST available 10-minute slot\n- Format EXACTLY like this JSON:\n  {\n    \"start_time\": \"YYYY-MM-DD 09:00:00\"\n  }\n- Replace YYYY-MM-DD with tomorrow's actual date\n- The end time is automatically start_time + 10 minutes\n- If no slots available between 09:00-18:00, return: {\"start_time\": \"No available slots found\"}\n- MUST use format: YYYY-MM-DD HH:MM:SS\n- Do NOT list multiple slots - just ONE slot\n- Do NOT add any extra text or explanation\n\nIMPORTANT: Empty events list = All slots free = Return 09:00:00 with tomorrow's date\n\nEverything must be fully automatic.",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "68288708-a89d-4112-a2bc-37878a9926fc",
      "name": "Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1248,
        320
      ],
      "parameters": {
        "jsonSchemaExample": "{\n\t\"start_time\": \"\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "8cf6468f-a58b-46ec-96fc-a673d071b06e",
      "name": "Send a message1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1440,
        96
      ],
      "parameters": {
        "sendTo": "={{ $('On form submission').item.json.Email }}",
        "message": "=<div>\n  <p>Dear {{ $('On form submission').item.json['Employee Name'] }},</p>\n\n  <p>\n    Thank you for submitting your leave request.\n  </p>\n\n  <p>\n    At this time, it is difficult to proceed with the approval of the requested leave without further clarification.\n    To discuss this in more detail, a short discussion has been scheduled.\n  </p>\n\n  <p>\n    <strong>Scheduled Time:</strong> {{ $json.output.start_time }}\n  </p>\n\n  <p>\n    Kindly ensure your availability at the above-mentioned time so we can review the request and reach a conclusion.\n  </p>\n\n  <p>\n    Best regards,<br>\n    HR Department\n  </p>\n</div>",
        "options": {
          "appendAttribution": false
        },
        "subject": "Discussion Required Regarding Leave Request"
      },
      "typeVersion": 2.2
    },
    {
      "id": "89454c1c-11e7-415d-b5e8-62a9e281ef30",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -832,
        -272
      ],
      "parameters": {
        "width": 608,
        "height": 736,
        "content": "## Leave Request Approval Automation with AI & Calendar Scheduling\nThis workflow automates the complete leave request process\u2014from employee submission to approval or discussion scheduling. It uses AI to generate professional summaries, routes decisions automatically, and ensures clear communication between employees and HR.\n\n### How It Works\n### Leave Submission & Review Flow:\n- Employees submit leave requests through a structured form with dates, reason, and task handover details\n- An AI agent generates a professional leave summary suitable for managers and HR\n- The system sends the summary via email and waits for approval or rejection\n- Single-day and multi-day leaves are handled automatically based on form input\n\n### Approval & Follow-Up Handling:\n- If approved, the employee receives an official approval email instantly\n- If rejected or clarification is needed, the workflow checks the manager's calendar\n- Finds the first available 10-minute slot on the next working day\n- Schedules a discussion and emails the time to the employee\n- Ensures all communication remains professional, consistent, and logged\n\n### Setup steps\n- Configure the leave request form fields in On form submission\n- Connect OpenAI and set prompts in AI Agent for summary generation\n- Connect Gmail for approval, rejection, and discussion emails\n- Enable \"Send and Wait\" approval in Send message and wait for response\n- Connect Google Calendar for availability checks\n- Review approval and discussion email templates\n- Turn the workflow ON and test with a sample leave request"
      },
      "typeVersion": 1
    },
    {
      "id": "30641c1f-9701-44e6-95ae-71cbe18fbaac",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 736,
        "content": "## Step 1: Capture, summarize, and request approval\nThe employee submits the leave form, an AI agent generates a professional summary, and the request is emailed to the approver using a send-and-wait step for approval or rejection."
      },
      "typeVersion": 1
    },
    {
      "id": "6dbc5ca3-7224-467f-81d5-239265ea2371",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        832,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 816,
        "height": 736,
        "content": "## Step 2: Notify or schedule discussion\nIf approved, the employee receives an approval email; if rejected or clarification is needed, the workflow checks calendar availability and schedules a discussion automatically."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Booking Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI": {
      "ai_languageModel": [
        [
          {
            "node": "Booking Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Send message and wait for response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Events": {
      "ai_tool": [
        [
          {
            "node": "Booking Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Booking Agent": {
      "main": [
        [
          {
            "node": "Send a message1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Booking Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check Availability": {
      "ai_tool": [
        [
          {
            "node": "Booking Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "AI Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Send message and wait for response": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}