{
  "name": "killBottleneck — new complaint",
  "nodes": [
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "SEM_ID_TABULKY",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reklamace"
        },
        "event": "rowAdded",
        "options": {}
      },
      "id": "11111111-1111-4111-8111-111111111111",
      "name": "New row in the sheet",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "id": "SEM_ID_PRIHLASENI",
          "name": "Google Sheets"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// ⚙️ FILL IN FOUR LINES ------------------------------------------\nconst KB_ADDRESS = 'https://yourcompany.killbottleneck.com'; // no trailing slash\nconst KB_KEY     = 'kb_user_paste_your_key_here';            // API key with write permission\nconst KB_MAP     = 'paste_your_map_id_here';                 // from the address /map/xxxxx\nconst KB_BRANCH  = 'New complaints';                         // what complaints hang under\n// ---------------------------------------------------------------\n\n// Tip: the key does not have to live in the code. Put it in an n8n environment\n// variable and replace the line above with:  const KB_KEY = $env.KB_KEY;\n\nconst headers = { Authorization: 'Bearer ' + KB_KEY };\n\n/** Walks the map tree and finds a node by its title. */\nfunction findByTitle(nodes, title) {\n  for (const u of nodes || []) {\n    if (u.title === title) return u;\n    const deeper = findByTitle(u.children, title);\n    if (deeper) return deeper;\n  }\n  return null;\n}\n\n/** Digs the HTTP code out of the error, wherever n8n hides it. */\nfunction errorCode(e) {\n  return Number(e.httpCode || e.statusCode || (e.response && (e.response.status || e.response.statusCode)) || 0);\n}\n\nconst results = [];\n\nfor (const item of $input.all()) {\n  const row = item.json;\n  const title = 'Complaint ' + (row['Complaint number'] || '?') + ' — ' + (row['Customer'] || '');\n  const description = String(row['Problem description'] || '');\n\n  let goalId = null;\n  let lastError = '';\n\n  // Up to four attempts. On every write killBottleneck wants to know which version\n  // of the map we saw (base_updated). If somebody changed it meanwhile it answers 409\n  // and asks us to read the map again — a safeguard against overwriting other work.\n  for (let attempt = 1; attempt <= 4 && !goalId; attempt++) {\n    const map = await this.helpers.httpRequest({\n      method: 'GET',\n      url: KB_ADDRESS + '/api/kb/v1/maps/' + KB_MAP,\n      headers: headers,\n      json: true,\n    });\n\n    const branch = findByTitle(map.tree, KB_BRANCH);\n    if (!branch) throw new Error('The map has no \"' + KB_BRANCH + '\" branch.');\n\n    try {\n      const response = await this.helpers.httpRequest({\n        method: 'POST',\n        url: KB_ADDRESS + '/api/kb/v1/maps/' + KB_MAP + '/nodes',\n        headers: headers,\n        body: {\n          base_updated: map.updated,\n          parent_id: branch.id,\n          items: [{ title: title, description: description }],\n        },\n        json: true,\n      });\n      goalId = response.added_ids[0];\n    } catch (e) {\n      const code = errorCode(e);\n      lastError = code + ' ' + e.message;\n      if (code === 401) throw new Error('Invalid API key — check KB_KEY.');\n      if (code === 404) throw new Error('Map not found — check KB_MAP.');\n      if (code !== 409 && code !== 429) throw e;\n      await new Promise((r) => setTimeout(r, 700 * attempt)); // somebody changed the map, retry\n    }\n  }\n\n  if (!goalId) throw new Error('The goal could not be created: ' + lastError);\n\n  results.push({ json: Object.assign({}, row, { killBottleneck: goalId, kb_title: title }) });\n}\n\nreturn results;"
      },
      "id": "22222222-2222-4222-8222-222222222222",
      "name": "Create the complaint in the map",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "documentId": {
          "__rl": true,
          "value": "SEM_ID_TABULKY",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Reklamace"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "Complaint number"
          ],
          "value": {}
        },
        "options": {}
      },
      "id": "33333333-3333-4333-8333-333333333333",
      "name": "Write the id back to the sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        440,
        0
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "SEM_ID_PRIHLASENI",
          "name": "Google Sheets"
        }
      }
    }
  ],
  "connections": {
    "New row in the sheet": {
      "main": [
        [
          {
            "node": "Create the complaint in the map",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create the complaint in the map": {
      "main": [
        [
          {
            "node": "Write the id back to the sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
