{
 "id": "RhR5Leads0000001",
 "name": "R5 N9 ลีดเข้า CRM ชีท + แจ้งแอดมิน",
 "active": false,
 "nodes": [
  {
   "id": "r5n4",
   "name": "Webhook",
   "type": "n8n-nodes-base.webhook",
   "typeVersion": 2,
   "position": [
    0,
    300
   ],
   "parameters": {
    "httpMethod": "POST",
    "path": "r5-lead",
    "responseMode": "responseNode",
    "options": {}
   },
   "webhookId": "a59b7f1a-fc3e-a12f-0226-4ecc98a2e829"
  },
  {
   "id": "r5n5",
   "name": "Upsert lead",
   "type": "n8n-nodes-base.code",
   "typeVersion": 2,
   "position": [
    220,
    300
   ],
   "parameters": {
    "jsCode": "// N9 / L8: normalise a lead, dedupe against the CRM sheet, decide insert/update and the admin notice.\nfunction normPhone(s) {\n  let d = String(s || \"\").replace(/[๐-๙]/g, (c) => String(\"๐๑๒๓๔๕๖๗๘๙\".indexOf(c))).replace(/\\D/g, \"\");\n  if (d.startsWith(\"66\") && d.length === 11) d = \"0\" + d.slice(2);\n  if (/^[1-9]\\d{7,8}$/.test(d)) d = \"0\" + d; // a sheet stored the number as a number and dropped the leading 0\n  return /^0\\d{8,9}$/.test(d) ? d : \"\";\n}\nconst normEmail = (s) => { const e = String(s || \"\").trim().toLowerCase(); return /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(e) ? e : \"\"; };\n// consent must be an explicit yes: a form or JSON that sends the string \"false\" or \"0\" is not consent\nconst consentGiven = (c) => c === true || /^(true|yes|on|1|ยินยอม)$/i.test(String(c == null ? \"\" : c).trim());\n// lead: {source, name, phone, email, interest, consent, at}; rows: existing CRM rows [{id, phone, email, ...}]\n// CRM rows are compared after the same normalisation (a sheet holds \"081-234-5678\", 812345678 or \"A@B.CO\")\nfunction upsertLead(lead, rows, opts = {}) {\n  const phone = normPhone(lead.phone);\n  const email = normEmail(lead.email);\n  if (!consentGiven(lead.consent)) return { action: \"reject\", reason: \"ไม่ได้ยินยอมให้เก็บข้อมูล\", row: null };\n  if (!phone && !email) return { action: \"reject\", reason: \"ไม่มีเบอร์หรืออีเมลที่ติดต่อได้\", row: null };\n  const hit = rows.find((r) => (phone && normPhone(r.phone) === phone) || (email && normEmail(r.email) === email));\n  const row = { phone, email, name: String(lead.name || \"\").trim().slice(0, 80), interest: String(lead.interest || \"\").slice(0, 200), source: lead.source || \"\", last_at: lead.at || \"\" };\n  if (hit) {\n    const merged = { ...hit };\n    for (const k of Object.keys(row)) if (row[k]) merged[k] = row[k];\n    merged.touches = (hit.touches || 1) + 1;\n    return { action: \"update\", row: merged, notify: null };\n  }\n  const id = (opts.prefix || \"L\") + String(rows.length + 1).padStart(5, \"0\");\n  const newRow = { id, ...row, status: \"ใหม่\", touches: 1 };\n  return { action: \"insert\", row: newRow, notify: { text: \"ลีดใหม่ \" + id + \" จาก \" + row.source + \" สนใจ: \" + (row.interest || \"-\") } };\n}\n\n\nconst st = $getWorkflowStaticData(\"global\"); st.rows = st.rows || [];\nconst r = upsertLead($input.first().json.body, st.rows);\nif (r.action === \"insert\") st.rows.push(r.row);\nif (r.action === \"update\") st.rows = st.rows.map((x) => (x.id === r.row.id ? r.row : x));\nreturn [{ json: { ...r, code: r.action === \"reject\" ? 422 : 200, response: { action: r.action, id: r.row ? r.row.id : null, reason: r.reason || null } } }];"
   }
  },
  {
   "id": "r5n6",
   "name": "Respond",
   "type": "n8n-nodes-base.respondToWebhook",
   "typeVersion": 1.1,
   "position": [
    440,
    200
   ],
   "parameters": {
    "respondWith": "json",
    "responseBody": "={{ JSON.stringify($json.response) }}",
    "options": {
     "responseCode": "={{ $json.code }}"
    }
   }
  },
  {
   "id": "r5n7",
   "name": "New lead?",
   "type": "n8n-nodes-base.if",
   "typeVersion": 2,
   "position": [
    440,
    400
   ],
   "parameters": {
    "conditions": {
     "options": {
      "caseSensitive": true,
      "leftValue": "",
      "typeValidation": "loose"
     },
     "conditions": [
      {
       "id": "c1",
       "leftValue": "={{ $json.action }}",
       "rightValue": "insert",
       "operator": {
        "type": "string",
        "operation": "equals"
       }
      }
     ],
     "combinator": "and"
    },
    "options": {}
   }
  },
  {
   "id": "r5n8",
   "name": "CRM sheet append",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    660,
    400
   ],
   "parameters": {
    "method": "POST",
    "url": "={{ $env.BUYER_API_BASE }}/sheets/append",
    "sendBody": true,
    "specifyBody": "json",
    "jsonBody": "={{ JSON.stringify({ sheet: 'ลีด', row: [$json.row.id, $json.row.name, $json.row.phone, $json.row.email, $json.row.interest, $json.row.source] }) }}",
    "options": {
     "timeout": 10000
    }
   },
   "onError": "continueErrorOutput",
   "retryOnFail": true,
   "maxTries": 3
  },
  {
   "id": "r5n9",
   "name": "Notify admin",
   "type": "n8n-nodes-base.httpRequest",
   "typeVersion": 4.2,
   "position": [
    880,
    400
   ],
   "parameters": {
    "method": "POST",
    "url": "={{ $env.BUYER_API_BASE }}/notify",
    "sendBody": true,
    "specifyBody": "json",
    "jsonBody": "={{ JSON.stringify($('Upsert lead').first().json.notify) }}",
    "options": {
     "timeout": 10000
    }
   },
   "onError": "continueErrorOutput",
   "retryOnFail": true,
   "maxTries": 3
  }
 ],
 "connections": {
  "Webhook": {
   "main": [
    [
     {
      "node": "Upsert lead",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "Upsert lead": {
   "main": [
    [
     {
      "node": "Respond",
      "type": "main",
      "index": 0
     },
     {
      "node": "New lead?",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "New lead?": {
   "main": [
    [
     {
      "node": "CRM sheet append",
      "type": "main",
      "index": 0
     }
    ]
   ]
  },
  "CRM sheet append": {
   "main": [
    [
     {
      "node": "Notify admin",
      "type": "main",
      "index": 0
     }
    ]
   ]
  }
 },
 "settings": {
  "executionOrder": "v1",
  "saveDataErrorExecution": "all"
 },
 "pinData": {}
}