{
  "name": "Nodbot - MoySklad stock and order sync with idempotency",
  "nodes": [
    {
      "parameters": {
        "path": "moysklad-stock-order-sync",
        "responseMode": "responseNode",
        "httpMethod": "POST"
      },
      "id": "1",
      "name": "Webhook input",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $json.body ?? $json;\nconst orderId = String(src.external_order_id ?? src.id ?? '').trim();\nif (!orderId) throw new Error('No external_order_id for MoySklad sync');\nconst items = Array.isArray(src.items) ? src.items : [];\nif (!items.length) throw new Error(`Order ${orderId} has no items`);\nconst normalizedItems = items.map((item) => ({\n  sku: String(item.sku ?? item.article ?? '').trim().toUpperCase(),\n  quantity: Number(item.qty ?? item.quantity ?? 0),\n  price: Number(item.price ?? 0)\n}));\nfor (const item of normalizedItems) {\n  if (!item.sku || item.quantity <= 0) throw new Error(`Invalid item in ${orderId}`);\n}\nreturn [{ json: {\n  idempotency_key: `moysklad:customerorder:${orderId}`,\n  external_order_id: orderId,\n  customer_phone: String(src.customer?.phone ?? '').replace(/[^0-9+]/g, ''),\n  customer_inn: String(src.customer?.inn ?? '').replace(/\\D/g, ''),\n  items: normalizedItems,\n  warehouse: src.warehouse ?? 'main',\n  operation: 'upsert_customer_order',\n  audit: { event_id: src.event_id, received_at: new Date().toISOString() }\n}}];"
      },
      "id": "2",
      "name": "Normalize and validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO integration_events (idempotency_key, payload, created_at) VALUES ($1, $2, now()) ON CONFLICT (idempotency_key) DO NOTHING RETURNING id;"
      },
      "id": "3",
      "name": "Check idempotency",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$env.TARGET_API_URL}}",
        "sendBody": true,
        "contentType": "json",
        "jsonBody": "={{$json}}"
      },
      "id": "4",
      "name": "Call target API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        720,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { ok: true, idempotency_key: $json.idempotency_key } }}"
      },
      "id": "5",
      "name": "Respond 200",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        960,
        0
      ]
    }
  ],
  "connections": {
    "Webhook input": {
      "main": [
        [
          {
            "node": "Normalize and validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize and validate": {
      "main": [
        [
          {
            "node": "Check idempotency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check idempotency": {
      "main": [
        [
          {
            "node": "Call target API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Call target API": {
      "main": [
        [
          {
            "node": "Respond 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}