{
  "name": "Rodopi Dent - Patients Upsert",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "patients-upsert",
        "responseMode": "responseNode",
        "options": {
          "allowedOrigins": "*"
        }
      },
      "id": "webhook",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Validate input\nconst body = $input.first().json.body;\n\nif (!body.name || !body.phone) {\n  return [{\n    json: {\n      valid: false,\n      error: '\u041b\u0438\u043f\u0441\u0432\u0430 \u0438\u043c\u0435 \u0438\u043b\u0438 \u0442\u0435\u043b\u0435\u0444\u043e\u043d'\n    }\n  }];\n}\n\n// Normalize phone to international +359 format\n// This prevents Google Sheets from stripping the leading 0\nlet phone = String(body.phone).replace(/[\\s()\\-.]/g, '');\nif (phone.startsWith('00359')) phone = '+359' + phone.slice(5);\nelse if (phone.startsWith('0') && phone.length >= 10) phone = '+359' + phone.slice(1);\nelse if (/^[89]\\d{8}$/.test(phone)) phone = '+359' + phone;\nelse if (!phone.startsWith('+') && phone.length >= 9) phone = '+359' + phone;\n\nreturn [{\n  json: {\n    valid: true,\n    name: body.name.trim(),\n    phone: phone\n  }\n}];"
      },
      "id": "validate",
      "name": "Validate Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.valid }}",
              "value2": true
            }
          ]
        }
      },
      "id": "is-valid",
      "name": "Is Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: false, error: $json.error }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ]
          }
        }
      },
      "id": "respond-invalid",
      "name": "Respond Invalid",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        660,
        -100
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1hv4XAfHhScA40Bm1kQ3I-Ih4SJuCBpOJxTOYDNb167g"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Patients"
        },
        "options": {
          "returnAllMatches": true
        }
      },
      "id": "read-patients",
      "name": "Read Patients",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        660,
        100
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Check if patient with this phone already exists\nconst inputData = $('Validate Input').first().json;\nconst existingPatients = $('Read Patients').all().map(item => item.json);\n\n// Extract core 9-digit number for robust comparison across formats\n// +359887493315 / 0887493315 / 887493315 all map to 887493315\nconst phoneKey = (p) => String(p || '').replace(/[\\s()\\-.+]/g, '').replace(/^359/, '').replace(/^0/, '');\n\nconst inputKey = phoneKey(inputData.phone);\nconst phoneExists = existingPatients.some(patient => phoneKey(patient.phone) === inputKey);\n\n// Also check by name (case-insensitive)\nconst nameExists = existingPatients.some(patient =>\n  (patient.name || '').toLowerCase().trim() === (inputData.name || '').toLowerCase().trim()\n);\n\nreturn [{\n  json: {\n    exists: phoneExists || nameExists,\n    name: inputData.name,\n    phone: inputData.phone\n  }\n}];"
      },
      "id": "check-exists",
      "name": "Check If Exists",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        100
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.exists }}",
              "value2": true
            }
          ]
        }
      },
      "id": "already-exists",
      "name": "Already Exists?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1100,
        100
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, message: '\u041f\u0430\u0446\u0438\u0435\u043d\u0442\u044a\u0442 \u0432\u0435\u0447\u0435 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430', action: 'skipped', name: $json.name, phone: $json.phone }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ]
          }
        }
      },
      "id": "respond-exists",
      "name": "Respond Already Exists",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1320,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1hv4XAfHhScA40Bm1kQ3I-Ih4SJuCBpOJxTOYDNb167g"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Patients"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "name": "={{ $json.name }}",
            "phone": "={{ \"'\" + $json.phone }}",
            "createdAt": "={{ new Date().toISOString().split('T')[0] }}"
          }
        },
        "options": {}
      },
      "id": "append-patient",
      "name": "Append New Patient",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1320,
        200
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, message: '\u041f\u0430\u0446\u0438\u0435\u043d\u0442\u044a\u0442 \u0435 \u0434\u043e\u0431\u0430\u0432\u0435\u043d', action: 'created', name: $('Check If Exists').first().json.name, phone: $('Check If Exists').first().json.phone }) }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Access-Control-Allow-Origin",
                "value": "*"
              }
            ]
          }
        }
      },
      "id": "respond-created",
      "name": "Respond Created",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1540,
        200
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Validate Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Input": {
      "main": [
        [
          {
            "node": "Is Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Valid?": {
      "main": [
        [
          {
            "node": "Respond Invalid",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Read Patients",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Patients": {
      "main": [
        [
          {
            "node": "Check If Exists",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Exists": {
      "main": [
        [
          {
            "node": "Already Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Already Exists?": {
      "main": [
        [
          {
            "node": "Respond Already Exists",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Append New Patient",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Append New Patient": {
      "main": [
        [
          {
            "node": "Respond Created",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "tags": [
    {
      "name": "Rodopi Dent"
    }
  ]
}