AutomationFlowsData & Sheets › Rodopi Dent - Get Available Slots

Rodopi Dent - Get Available Slots

Rodopi Dent - Get Available Slots. Uses googleSheets. Webhook trigger; 4 nodes.

Webhook trigger★★★★☆ complexity4 nodesGoogle Sheets
Data & Sheets Trigger: Webhook Nodes: 4 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "name": "Rodopi Dent - Get Available Slots",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "GET",
        "path": "slots-webhook",
        "responseMode": "responseNode",
        "options": {
          "allowedOrigins": "*"
        }
      },
      "id": "webhook-slots",
      "name": "Webhook - Get Slots",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1hv4XAfHhScA40Bm1kQ3I-Ih4SJuCBpOJxTOYDNb167g"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Appointments"
        },
        "options": {
          "returnAllMatches": true
        }
      },
      "id": "sheets-read",
      "name": "Get Appointments",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        220,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Get date from query parameter\nconst queryDate = $('Webhook - Get Slots').first().json.query.date;\n\nif (!queryDate) {\n  return [{ json: { error: 'Date parameter is required', slots: [] } }];\n}\n\n// Parse the requested date\nconst requestedDate = new Date(queryDate);\nconst dayOfWeek = requestedDate.getDay();\n\n// Check if it's a working day (Monday-Friday = 1-5)\nconst workingDays = [1, 2, 3, 4, 5];\nif (!workingDays.includes(dayOfWeek)) {\n  return [{ json: { date: queryDate, slots: [], message: '\u041d\u0435\u0440\u0430\u0431\u043e\u0442\u0435\u043d \u0434\u0435\u043d' } }];\n}\n\n// Working hours configuration\nconst workingHours = {\n  morning: { start: '09:00', end: '12:00' },\n  afternoon: { start: '13:30', end: '17:00' }\n};\n\n// SLOT_INTERVAL = 30 minutes\nconst slotInterval = 30;\n// Default duration for PENDING appointments (before doctor confirms)\nconst pendingDefaultDuration = 30;\n// Pending expiration time in hours\nconst pendingExpirationHours = 2;\n\n// Helper functions\nfunction timeToMinutes(time) {\n  const [hours, minutes] = time.split(':').map(Number);\n  return hours * 60 + minutes;\n}\n\nfunction minutesToTime(mins) {\n  const hours = Math.floor(mins / 60);\n  const minutes = mins % 60;\n  return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;\n}\n\n// Generate all possible 30-minute slots\nconst allSlots = [];\n\n// Morning slots (09:00 - 12:00)\nlet current = timeToMinutes(workingHours.morning.start);\nconst morningEnd = timeToMinutes(workingHours.morning.end);\nwhile (current < morningEnd) {\n  allSlots.push(minutesToTime(current));\n  current += slotInterval;\n}\n\n// Afternoon slots (13:30 - 17:00)\ncurrent = timeToMinutes(workingHours.afternoon.start);\nconst afternoonEnd = timeToMinutes(workingHours.afternoon.end);\nwhile (current < afternoonEnd) {\n  allSlots.push(minutesToTime(current));\n  current += slotInterval;\n}\n\n// Get all appointments\nconst appointments = $('Get Appointments').all();\nconst now = new Date();\n\n// Build list of blocked time ranges\nconst blockedRanges = [];\nconst pendingSlots = [];\nconst confirmedSlots = [];\n\nappointments\n  .filter(apt => {\n    const aptDate = apt.json.date;\n    const status = apt.json.status;\n    \n    // Skip cancelled\n    if (status === 'cancelled') return false;\n    \n    // Only this date\n    if (aptDate !== queryDate) return false;\n    \n    // Check if pending has expired\n    if (status === 'pending') {\n      const createdAt = new Date(apt.json.createdAt);\n      const expiresAt = new Date(createdAt.getTime() + pendingExpirationHours * 60 * 60 * 1000);\n      if (now > expiresAt) {\n        // Pending expired - don't block\n        return false;\n      }\n    }\n    \n    return true;\n  })\n  .forEach(apt => {\n    const startMinutes = timeToMinutes(apt.json.startTime);\n    const status = apt.json.status;\n    \n    // Pending uses default 30 min, confirmed/completed uses actual duration\n    let duration;\n    if (status === 'pending') {\n      duration = pendingDefaultDuration;\n      pendingSlots.push({\n        startTime: apt.json.startTime,\n        patientName: apt.json.patientName,\n        reason: apt.json.reason\n      });\n    } else {\n      duration = parseInt(apt.json.duration) || 30;\n      confirmedSlots.push({\n        startTime: apt.json.startTime,\n        duration: duration,\n        patientName: apt.json.patientName,\n        status: status\n      });\n    }\n    \n    const endMinutes = startMinutes + duration;\n    blockedRanges.push({ \n      start: startMinutes, \n      end: endMinutes,\n      status: status\n    });\n  });\n\n// Check if a slot conflicts with any blocked range\nfunction isSlotBlocked(slotTime) {\n  const slotMinutes = timeToMinutes(slotTime);\n  return blockedRanges.some(range => {\n    return slotMinutes >= range.start && slotMinutes < range.end;\n  });\n}\n\n// Filter out blocked slots\nconst availableSlots = allSlots.filter(slot => !isSlotBlocked(slot));\n\nreturn [{\n  json: {\n    date: queryDate,\n    slots: availableSlots,\n    totalSlots: allSlots.length,\n    pendingCount: pendingSlots.length,\n    confirmedCount: confirmedSlots.length,\n    pendingSlots: pendingSlots,\n    confirmedSlots: confirmedSlots\n  }\n}];"
      },
      "id": "code-filter",
      "name": "Calculate Available Slots",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ $json }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ]
          }
        }
      },
      "id": "response",
      "name": "Respond with Slots",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        660,
        0
      ]
    }
  ],
  "connections": {
    "Webhook - Get Slots": {
      "main": [
        [
          {
            "node": "Get Appointments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Appointments": {
      "main": [
        [
          {
            "node": "Calculate Available Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Available Slots": {
      "main": [
        [
          {
            "node": "Respond with Slots",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "Rodopi Dent"
    }
  ]
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Rodopi Dent - Get Available Slots. Uses googleSheets. Webhook trigger; 4 nodes.

Source: https://github.com/Georgi-Piskov/RODOPI-DENT/blob/07217e5d65fd897a41528db70a4869149ca9a5b1/n8n-workflows/01-slots-webhook.json — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

[SANTOBET] FLUXO TODO - BACKUP. Uses googleSheets, httpRequest, googleSheetsTrigger. Webhook trigger; 57 nodes.

Google Sheets, HTTP Request, Google Sheets Trigger
Data & Sheets

FLUXO DISPARO DATA E HORA. Uses itemLists, googleSheets, httpRequest. Webhook trigger; 48 nodes.

Item Lists, Google Sheets, HTTP Request
Data & Sheets

This workflow allows you to accept online payments via YooKassa and log both orders and transactions in Google Sheets — all without writing a single line of code. It supports full payment flow: produc

Google Sheets, HTTP Request
Data & Sheets

Transform your n8n instance management with this advanced automation system featuring artificial intelligence-driven workflow selection. This template provides comprehensive maintenance operations wit

n8n, HTTP Request, Google Sheets +1
Data & Sheets

Nexus_v6(ล่าสุดจริงๆ)ล่าสุดไกไก. Uses googleSheets, httpRequest. Webhook trigger; 41 nodes.

Google Sheets, HTTP Request