diff --git a/n8n/exports/current/adressetikette-erstellen.g6FDHAICnQdbW6Ye.json b/n8n/exports/current/adressetikette-erstellen.g6FDHAICnQdbW6Ye.json
new file mode 100644
index 0000000..4ac3877
--- /dev/null
+++ b/n8n/exports/current/adressetikette-erstellen.g6FDHAICnQdbW6Ye.json
@@ -0,0 +1,644 @@
+{
+ "updatedAt": "2026-03-29T19:14:31.580Z",
+ "createdAt": "2025-10-03T10:56:26.208Z",
+ "id": "g6FDHAICnQdbW6Ye",
+ "name": "Adressetikette erstellen",
+ "description": null,
+ "active": true,
+ "isArchived": false,
+ "nodes": [
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "http://192.168.1.199:9901/forms/chromium/convert/html",
+ "sendBody": true,
+ "contentType": "multipart-form-data",
+ "bodyParameters": {
+ "parameters": [
+ {
+ "name": "Response Format",
+ "value": "File"
+ },
+ {
+ "name": "Download File Name",
+ "value": "Versand-Label"
+ },
+ {
+ "parameterType": "formBinaryData",
+ "name": "index",
+ "inputDataFieldName": "index"
+ },
+ {
+ "parameterType": "formBinaryData",
+ "name": "styles",
+ "inputDataFieldName": "styles"
+ },
+ {
+ "name": "preferCssPageSize",
+ "value": "true"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [
+ 1120,
+ 0
+ ],
+ "id": "517816cc-fae4-482b-9b0b-7406c1057a3e",
+ "name": "Gotemberg PDF",
+ "retryOnFail": true,
+ "maxTries": 5
+ },
+ {
+ "parameters": {
+ "protocol": "sftp",
+ "operation": "upload",
+ "path": "={{ $json.filename }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.ftp",
+ "typeVersion": 1,
+ "position": [
+ 2000,
+ 16
+ ],
+ "id": "3ea1d6ad-e706-4677-977a-c733c8e13085",
+ "name": "FTP",
+ "credentials": {
+ "sftp": {
+ "id": "cK8t7TPPZIynTdj7",
+ "name": "Naurua SFTP Account"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "http://192.168.1.199:9902/convert",
+ "sendBody": true,
+ "contentType": "multipart-form-data",
+ "bodyParameters": {
+ "parameters": [
+ {
+ "parameterType": "formBinaryData",
+ "name": "file",
+ "inputDataFieldName": "data"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [
+ 1376,
+ 0
+ ],
+ "id": "b64e3c18-a06c-4c64-9c0b-11f0fe71e45c",
+ "name": "pdf2png"
+ },
+ {
+ "parameters": {
+ "jsCode": "// --- Node-Namen anpassen, falls sie bei dir anders heißen ---\nconst ORDER_NODE = 'Bestellung laden';\nconst ADDRESS_NODE = 'Versandadresse laden';\n\n// kleine Helfer\nconst get = (fn, dflt = undefined) => {\n try { return fn(); } catch { return dflt; }\n};\nconst sanitize = (s) => String(s ?? '')\n .normalize('NFKD').replace(/[\\u0300-\\u036f]/g, '') // Akzente entfernen\n .replace(/[^A-Za-z0-9_-]+/g, '_') // nur sichere Zeichen\n .replace(/^_+|_+$/g, ''); // Trim underscores\n\n// Werte aus anderen Nodes holen (Fallback: aktuelles $json)\nconst createdAtRaw = get(() => $node[ORDER_NODE].json.CreatedAt, $json.CreatedAt);\nconst bestellnummer = get(() => $node[ORDER_NODE].json.Bestellnummer, $json.Bestellnummer);\nconst vorname = get(() => $node[ADDRESS_NODE].json.Vorname, $json.Vorname);\nconst nachname = get(() => $node[ADDRESS_NODE].json.Nachname, $json.Nachname);\n\n// Datum -> YYYYMMDD\nconst dt = createdAtRaw ? new Date(createdAtRaw) : new Date();\nconst yyyymmdd = dt.toISOString().slice(0,10).replace(/-/g, '');\n\n// Dateiname bauen\nconst filename = `${yyyymmdd}_${sanitize(bestellnummer)}_${sanitize(vorname)}_${sanitize(nachname)}.png`;\n\n// Binary-Key ermitteln (meist \"data\")\nconst binKeys = Object.keys($binary || {});\nconst binKey = binKeys[0] || 'data';\n\n// Item zurückgeben, Binary-Dateiname überschreiben\nreturn {\n json: { filename },\n binary: {\n ...$binary,\n [binKey]: { ...$binary[binKey], fileName: filename }\n }\n};"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 1600,
+ 112
+ ],
+ "id": "9f55cde9-8743-4ea8-be0e-c07991e4cf96",
+ "name": "png umbenennen"
+ },
+ {
+ "parameters": {
+ "jsCode": "// INPUT: item.json mit Feldern (Vorname, Nachname, …)\n// OUTPUT: binary.index (HTML), binary.styles (CSS)\n\n// — Layout-Parameter —\nconst WIDTH_MM = 60, HEIGHT_MM = 50;\nconst MARGIN = { top: 10, right: 5, bottom: 5, left: 10 };\nconst FONT_FAMILY = \"AvenirCustom\"; // frei wählbar, unten in @font-face + body benutzen\nconst FONT_SIZE_PT = 11;\nconst LINE_HEIGHT = 1; // realistisch, 0.2 wäre praktisch ohne Zeilenabstand\n\n// — Font-Pfad im Gotenberg-Container (über docker-compose gemountet) —\nconst FONT_PATH = \"file:///usr/local/fonts/avenir-regular.woff2\";\n\nconst d = $json;\n\nconst html = `\n
\n \n \n\n \n
An
\n
${d.Vorname || \"\"} ${d.Nachname || \"\"}
\n
${d.Strasse || \"\"} ${d.Hausnummer || \"\"}
\n
${d.PLZ || \"\"} ${d.Stadt || \"\"}
\n ${d.Land ? `
${d.Land}
` : ``}\n
\n`;\n\nconst css = `\n@page { \n size: ${WIDTH_MM}mm ${HEIGHT_MM}mm; \n margin: 0; \n}\n\n@font-face {\n font-family: \"${FONT_FAMILY}\";\n src: url(\"${FONT_PATH}\") format(\"woff2\");\n font-weight: normal;\n font-style: normal;\n font-display: swap;\n}\n\n* { box-sizing: border-box; }\n\nhtml, body { \n margin: 0; \n height: 100%; \n}\n\n.label {\n width: 100%;\n height: 100%;\n padding: ${MARGIN.top}mm ${MARGIN.right}mm ${MARGIN.bottom}mm ${MARGIN.left}mm;\n font-family: \"${FONT_FAMILY}\", Arial, Helvetica, sans-serif;\n font-size: ${FONT_SIZE_PT}pt;\n line-height: ${LINE_HEIGHT};\n overflow: hidden; /* Verhindert 2. Seite */\n page-break-after: avoid;\n break-after: avoid-page;\n}\n\n.line { margin: 0 0 4mm 0; }\n.line:last-child { margin-bottom: 0; }\n`;\n\nreturn [{\n json: {},\n binary: {\n index: { data: Buffer.from(html, 'utf8').toString('base64'), fileName: 'index.html', mimeType: 'text/html' },\n styles: { data: Buffer.from(css, 'utf8').toString('base64'), fileName: 'styles.css', mimeType: 'text/css' },\n }\n}];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 896,
+ 0
+ ],
+ "id": "153cc657-86e7-4207-a0b2-4bb6f44f4f4d",
+ "name": "Layout HTML und CSS erzeugen"
+ },
+ {
+ "parameters": {
+ "mode": "combine",
+ "combineBy": "combineByPosition",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.2,
+ "position": [
+ 1792,
+ 16
+ ],
+ "id": "66ec492e-0b2e-43d1-acb3-0d808c6e91cf",
+ "name": "Merge"
+ },
+ {
+ "parameters": {
+ "path": "naurua_erp_adressetikette",
+ "authentication": "headerAuth",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.webhook",
+ "typeVersion": 2.1,
+ "position": [
+ 528,
+ 0
+ ],
+ "id": "cf7b0436-e77b-4b10-924c-e4cd911046c2",
+ "name": "Webhook",
+ "webhookId": "1c0f4e40-d5a7-4145-8692-65bdf08d3b35",
+ "credentials": {
+ "httpHeaderAuth": {
+ "id": "CQiLWtrnxEDrrH4n",
+ "name": "naurua erp zugriff webhook"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "// Normalize webhook payload into the exact address contract expected by the label layout node.\nconst raw = $json.body ?? $json;\nconst src = Array.isArray(raw) ? (raw[0] ?? {}) : raw;\n\nreturn [{\n json: {\n Vorname: src.Vorname ?? src.Vorname_LfAdr ?? src.Vorname_LfAdr1 ?? \"\",\n Nachname: src.Nachname ?? src.Nachname_LfAdr ?? src.Nachname_LfAdr1 ?? \"\",\n Strasse: src.Strasse ?? src.Strasse_LfAdr ?? \"\",\n Hausnummer: src.Hausnummer ?? src.Hausnummer_LfAdr ?? \"\",\n PLZ: src.PLZ ?? src.PLZ_LfAdr ?? \"\",\n Stadt: src.Stadt ?? src.Stadt_LfAdr ?? \"\",\n Land: src.Land ?? src.Land_LfAdr ?? \"\",\n }\n}];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 704,
+ 0
+ ],
+ "id": "28d7547e-dfb0-4904-ab43-b410b0c4e2b0",
+ "name": "Adresse an Label-Format anpassen"
+ },
+ {
+ "parameters": {
+ "content": "## Shipping Label Generation\nThis workflow receives a shipping address via webhook, normalizes field names, renders HTML/CSS, creates a PDF/PNG label, and uploads it via FTP.",
+ "height": 180,
+ "width": 640
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "position": [
+ 512,
+ -256
+ ],
+ "typeVersion": 1,
+ "id": "fdb30bcb-e656-45c8-ad99-a27f4887c525",
+ "name": "Sticky Note - Overview"
+ },
+ {
+ "parameters": {
+ "content": "## Expected Webhook Input Fields\nPreferred shipping keys: Vorname_LfAdr, Nachname_LfAdr, Strasse_LfAdr, Hausnummer_LfAdr, PLZ_LfAdr, Stadt_LfAdr, Land_LfAdr.\nFallback keys also accepted: Vorname, Nachname, Strasse, Hausnummer, PLZ, Stadt, Land.",
+ "height": 220,
+ "width": 640,
+ "color": 5
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "position": [
+ 512,
+ -24
+ ],
+ "typeVersion": 1,
+ "id": "947d919c-b976-41fa-bfc7-70fbf383ed53",
+ "name": "Sticky Note - Inputs"
+ }
+ ],
+ "connections": {
+ "Gotemberg PDF": {
+ "main": [
+ [
+ {
+ "node": "pdf2png",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "pdf2png": {
+ "main": [
+ [
+ {
+ "node": "png umbenennen",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Merge",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Layout HTML und CSS erzeugen": {
+ "main": [
+ [
+ {
+ "node": "Gotemberg PDF",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "png umbenennen": {
+ "main": [
+ [
+ {
+ "node": "Merge",
+ "type": "main",
+ "index": 1
+ }
+ ]
+ ]
+ },
+ "Merge": {
+ "main": [
+ [
+ {
+ "node": "FTP",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Webhook": {
+ "main": [
+ [
+ {
+ "node": "Adresse an Label-Format anpassen",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Adresse an Label-Format anpassen": {
+ "main": [
+ [
+ {
+ "node": "Layout HTML und CSS erzeugen",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "settings": {
+ "executionOrder": "v1",
+ "callerPolicy": "workflowsFromSameOwner",
+ "errorWorkflow": "QQ1KFafAxgMOjKWm",
+ "availableInMCP": false
+ },
+ "staticData": null,
+ "meta": {
+ "templateCredsSetupCompleted": true
+ },
+ "pinData": {},
+ "versionId": "410ba0d7-ea83-4374-9de6-de3fcd6c003f",
+ "activeVersionId": "410ba0d7-ea83-4374-9de6-de3fcd6c003f",
+ "versionCounter": 44,
+ "triggerCount": 1,
+ "shared": [
+ {
+ "updatedAt": "2025-10-03T10:56:26.266Z",
+ "createdAt": "2025-10-03T10:56:26.266Z",
+ "role": "workflow:owner",
+ "workflowId": "g6FDHAICnQdbW6Ye",
+ "projectId": "loIw8cF8XKYX00Ow",
+ "project": {
+ "updatedAt": "2025-06-07T09:04:27.150Z",
+ "createdAt": "2025-06-07T06:22:39.698Z",
+ "id": "loIw8cF8XKYX00Ow",
+ "name": "Mathias Gläser ",
+ "type": "personal",
+ "icon": null,
+ "description": null,
+ "creatorId": "f82ed6a8-4704-4f80-8617-622fd5911d56"
+ }
+ }
+ ],
+ "tags": [],
+ "activeVersion": {
+ "updatedAt": "2026-03-29T19:14:31.582Z",
+ "createdAt": "2026-03-29T19:14:31.582Z",
+ "versionId": "410ba0d7-ea83-4374-9de6-de3fcd6c003f",
+ "workflowId": "g6FDHAICnQdbW6Ye",
+ "nodes": [
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "http://192.168.1.199:9901/forms/chromium/convert/html",
+ "sendBody": true,
+ "contentType": "multipart-form-data",
+ "bodyParameters": {
+ "parameters": [
+ {
+ "name": "Response Format",
+ "value": "File"
+ },
+ {
+ "name": "Download File Name",
+ "value": "Versand-Label"
+ },
+ {
+ "parameterType": "formBinaryData",
+ "name": "index",
+ "inputDataFieldName": "index"
+ },
+ {
+ "parameterType": "formBinaryData",
+ "name": "styles",
+ "inputDataFieldName": "styles"
+ },
+ {
+ "name": "preferCssPageSize",
+ "value": "true"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [
+ 1120,
+ 0
+ ],
+ "id": "517816cc-fae4-482b-9b0b-7406c1057a3e",
+ "name": "Gotemberg PDF",
+ "retryOnFail": true,
+ "maxTries": 5
+ },
+ {
+ "parameters": {
+ "protocol": "sftp",
+ "operation": "upload",
+ "path": "={{ $json.filename }}",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.ftp",
+ "typeVersion": 1,
+ "position": [
+ 2000,
+ 16
+ ],
+ "id": "3ea1d6ad-e706-4677-977a-c733c8e13085",
+ "name": "FTP",
+ "credentials": {
+ "sftp": {
+ "id": "cK8t7TPPZIynTdj7",
+ "name": "Naurua SFTP Account"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "method": "POST",
+ "url": "http://192.168.1.199:9902/convert",
+ "sendBody": true,
+ "contentType": "multipart-form-data",
+ "bodyParameters": {
+ "parameters": [
+ {
+ "parameterType": "formBinaryData",
+ "name": "file",
+ "inputDataFieldName": "data"
+ }
+ ]
+ },
+ "options": {}
+ },
+ "type": "n8n-nodes-base.httpRequest",
+ "typeVersion": 4.2,
+ "position": [
+ 1376,
+ 0
+ ],
+ "id": "b64e3c18-a06c-4c64-9c0b-11f0fe71e45c",
+ "name": "pdf2png"
+ },
+ {
+ "parameters": {
+ "jsCode": "// --- Node-Namen anpassen, falls sie bei dir anders heißen ---\nconst ORDER_NODE = 'Bestellung laden';\nconst ADDRESS_NODE = 'Versandadresse laden';\n\n// kleine Helfer\nconst get = (fn, dflt = undefined) => {\n try { return fn(); } catch { return dflt; }\n};\nconst sanitize = (s) => String(s ?? '')\n .normalize('NFKD').replace(/[\\u0300-\\u036f]/g, '') // Akzente entfernen\n .replace(/[^A-Za-z0-9_-]+/g, '_') // nur sichere Zeichen\n .replace(/^_+|_+$/g, ''); // Trim underscores\n\n// Werte aus anderen Nodes holen (Fallback: aktuelles $json)\nconst createdAtRaw = get(() => $node[ORDER_NODE].json.CreatedAt, $json.CreatedAt);\nconst bestellnummer = get(() => $node[ORDER_NODE].json.Bestellnummer, $json.Bestellnummer);\nconst vorname = get(() => $node[ADDRESS_NODE].json.Vorname, $json.Vorname);\nconst nachname = get(() => $node[ADDRESS_NODE].json.Nachname, $json.Nachname);\n\n// Datum -> YYYYMMDD\nconst dt = createdAtRaw ? new Date(createdAtRaw) : new Date();\nconst yyyymmdd = dt.toISOString().slice(0,10).replace(/-/g, '');\n\n// Dateiname bauen\nconst filename = `${yyyymmdd}_${sanitize(bestellnummer)}_${sanitize(vorname)}_${sanitize(nachname)}.png`;\n\n// Binary-Key ermitteln (meist \"data\")\nconst binKeys = Object.keys($binary || {});\nconst binKey = binKeys[0] || 'data';\n\n// Item zurückgeben, Binary-Dateiname überschreiben\nreturn {\n json: { filename },\n binary: {\n ...$binary,\n [binKey]: { ...$binary[binKey], fileName: filename }\n }\n};"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 1600,
+ 112
+ ],
+ "id": "9f55cde9-8743-4ea8-be0e-c07991e4cf96",
+ "name": "png umbenennen"
+ },
+ {
+ "parameters": {
+ "jsCode": "// INPUT: item.json mit Feldern (Vorname, Nachname, …)\n// OUTPUT: binary.index (HTML), binary.styles (CSS)\n\n// — Layout-Parameter —\nconst WIDTH_MM = 60, HEIGHT_MM = 50;\nconst MARGIN = { top: 10, right: 5, bottom: 5, left: 10 };\nconst FONT_FAMILY = \"AvenirCustom\"; // frei wählbar, unten in @font-face + body benutzen\nconst FONT_SIZE_PT = 11;\nconst LINE_HEIGHT = 1; // realistisch, 0.2 wäre praktisch ohne Zeilenabstand\n\n// — Font-Pfad im Gotenberg-Container (über docker-compose gemountet) —\nconst FONT_PATH = \"file:///usr/local/fonts/avenir-regular.woff2\";\n\nconst d = $json;\n\nconst html = `\n\n \n \n\n \n
An
\n
${d.Vorname || \"\"} ${d.Nachname || \"\"}
\n
${d.Strasse || \"\"} ${d.Hausnummer || \"\"}
\n
${d.PLZ || \"\"} ${d.Stadt || \"\"}
\n ${d.Land ? `
${d.Land}
` : ``}\n
\n`;\n\nconst css = `\n@page { \n size: ${WIDTH_MM}mm ${HEIGHT_MM}mm; \n margin: 0; \n}\n\n@font-face {\n font-family: \"${FONT_FAMILY}\";\n src: url(\"${FONT_PATH}\") format(\"woff2\");\n font-weight: normal;\n font-style: normal;\n font-display: swap;\n}\n\n* { box-sizing: border-box; }\n\nhtml, body { \n margin: 0; \n height: 100%; \n}\n\n.label {\n width: 100%;\n height: 100%;\n padding: ${MARGIN.top}mm ${MARGIN.right}mm ${MARGIN.bottom}mm ${MARGIN.left}mm;\n font-family: \"${FONT_FAMILY}\", Arial, Helvetica, sans-serif;\n font-size: ${FONT_SIZE_PT}pt;\n line-height: ${LINE_HEIGHT};\n overflow: hidden; /* Verhindert 2. Seite */\n page-break-after: avoid;\n break-after: avoid-page;\n}\n\n.line { margin: 0 0 4mm 0; }\n.line:last-child { margin-bottom: 0; }\n`;\n\nreturn [{\n json: {},\n binary: {\n index: { data: Buffer.from(html, 'utf8').toString('base64'), fileName: 'index.html', mimeType: 'text/html' },\n styles: { data: Buffer.from(css, 'utf8').toString('base64'), fileName: 'styles.css', mimeType: 'text/css' },\n }\n}];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 896,
+ 0
+ ],
+ "id": "153cc657-86e7-4207-a0b2-4bb6f44f4f4d",
+ "name": "Layout HTML und CSS erzeugen"
+ },
+ {
+ "parameters": {
+ "mode": "combine",
+ "combineBy": "combineByPosition",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.merge",
+ "typeVersion": 3.2,
+ "position": [
+ 1792,
+ 16
+ ],
+ "id": "66ec492e-0b2e-43d1-acb3-0d808c6e91cf",
+ "name": "Merge"
+ },
+ {
+ "parameters": {
+ "path": "naurua_erp_adressetikette",
+ "authentication": "headerAuth",
+ "options": {}
+ },
+ "type": "n8n-nodes-base.webhook",
+ "typeVersion": 2.1,
+ "position": [
+ 528,
+ 0
+ ],
+ "id": "cf7b0436-e77b-4b10-924c-e4cd911046c2",
+ "name": "Webhook",
+ "webhookId": "1c0f4e40-d5a7-4145-8692-65bdf08d3b35",
+ "credentials": {
+ "httpHeaderAuth": {
+ "id": "CQiLWtrnxEDrrH4n",
+ "name": "naurua erp zugriff webhook"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "jsCode": "// Normalize webhook payload into the exact address contract expected by the label layout node.\nconst raw = $json.body ?? $json;\nconst src = Array.isArray(raw) ? (raw[0] ?? {}) : raw;\n\nreturn [{\n json: {\n Vorname: src.Vorname ?? src.Vorname_LfAdr ?? src.Vorname_LfAdr1 ?? \"\",\n Nachname: src.Nachname ?? src.Nachname_LfAdr ?? src.Nachname_LfAdr1 ?? \"\",\n Strasse: src.Strasse ?? src.Strasse_LfAdr ?? \"\",\n Hausnummer: src.Hausnummer ?? src.Hausnummer_LfAdr ?? \"\",\n PLZ: src.PLZ ?? src.PLZ_LfAdr ?? \"\",\n Stadt: src.Stadt ?? src.Stadt_LfAdr ?? \"\",\n Land: src.Land ?? src.Land_LfAdr ?? \"\",\n }\n}];"
+ },
+ "type": "n8n-nodes-base.code",
+ "typeVersion": 2,
+ "position": [
+ 704,
+ 0
+ ],
+ "id": "28d7547e-dfb0-4904-ab43-b410b0c4e2b0",
+ "name": "Adresse an Label-Format anpassen"
+ },
+ {
+ "parameters": {
+ "content": "## Shipping Label Generation\nThis workflow receives a shipping address via webhook, normalizes field names, renders HTML/CSS, creates a PDF/PNG label, and uploads it via FTP.",
+ "height": 180,
+ "width": 640
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "position": [
+ 512,
+ -256
+ ],
+ "typeVersion": 1,
+ "id": "fdb30bcb-e656-45c8-ad99-a27f4887c525",
+ "name": "Sticky Note - Overview"
+ },
+ {
+ "parameters": {
+ "content": "## Expected Webhook Input Fields\nPreferred shipping keys: Vorname_LfAdr, Nachname_LfAdr, Strasse_LfAdr, Hausnummer_LfAdr, PLZ_LfAdr, Stadt_LfAdr, Land_LfAdr.\nFallback keys also accepted: Vorname, Nachname, Strasse, Hausnummer, PLZ, Stadt, Land.",
+ "height": 220,
+ "width": 640,
+ "color": 5
+ },
+ "type": "n8n-nodes-base.stickyNote",
+ "position": [
+ 512,
+ -24
+ ],
+ "typeVersion": 1,
+ "id": "947d919c-b976-41fa-bfc7-70fbf383ed53",
+ "name": "Sticky Note - Inputs"
+ }
+ ],
+ "connections": {
+ "Gotemberg PDF": {
+ "main": [
+ [
+ {
+ "node": "pdf2png",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "pdf2png": {
+ "main": [
+ [
+ {
+ "node": "png umbenennen",
+ "type": "main",
+ "index": 0
+ },
+ {
+ "node": "Merge",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Layout HTML und CSS erzeugen": {
+ "main": [
+ [
+ {
+ "node": "Gotemberg PDF",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "png umbenennen": {
+ "main": [
+ [
+ {
+ "node": "Merge",
+ "type": "main",
+ "index": 1
+ }
+ ]
+ ]
+ },
+ "Merge": {
+ "main": [
+ [
+ {
+ "node": "FTP",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Webhook": {
+ "main": [
+ [
+ {
+ "node": "Adresse an Label-Format anpassen",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Adresse an Label-Format anpassen": {
+ "main": [
+ [
+ {
+ "node": "Layout HTML und CSS erzeugen",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "authors": "Mathias Gläser",
+ "name": null,
+ "description": null,
+ "autosaved": false,
+ "workflowPublishHistory": [
+ {
+ "createdAt": "2026-03-29T19:14:31.659Z",
+ "id": 146,
+ "workflowId": "g6FDHAICnQdbW6Ye",
+ "versionId": "410ba0d7-ea83-4374-9de6-de3fcd6c003f",
+ "event": "activated",
+ "userId": "f82ed6a8-4704-4f80-8617-622fd5911d56"
+ },
+ {
+ "createdAt": "2026-03-29T19:14:31.636Z",
+ "id": 145,
+ "workflowId": "g6FDHAICnQdbW6Ye",
+ "versionId": "410ba0d7-ea83-4374-9de6-de3fcd6c003f",
+ "event": "deactivated",
+ "userId": "f82ed6a8-4704-4f80-8617-622fd5911d56"
+ }
+ ]
+ }
+}
diff --git a/n8n/exports/index.json b/n8n/exports/index.json
index 6ab72a2..63546d7 100644
--- a/n8n/exports/index.json
+++ b/n8n/exports/index.json
@@ -1,11 +1,16 @@
{
- "exported_at": "2026-03-29T18:33:51Z",
+ "exported_at": "2026-03-29T19:14:41Z",
"source": "n8n",
"workflows": [
{
"id": "yNLjtV9yG0T6CqSr",
"name": "Bestell-Eingang Online-Shop",
"file": "n8n/exports/current/bestell-eingang-online-shop.yNLjtV9yG0T6CqSr.json"
+ },
+ {
+ "id": "g6FDHAICnQdbW6Ye",
+ "name": "Adressetikette erstellen",
+ "file": "n8n/exports/current/adressetikette-erstellen.g6FDHAICnQdbW6Ye.json"
}
]
}
diff --git a/order-import.php b/order-import.php
index 61075c3..0365e51 100644
--- a/order-import.php
+++ b/order-import.php
@@ -227,6 +227,124 @@ function ensure_required_tables_exist(PDO $pdo): void
}
}
+function derive_label_webhook_url(array $localEnv): string
+{
+ $explicit = env_value('N8N_LABEL_WEBHOOK_URL', $localEnv);
+ if ($explicit !== '') {
+ return $explicit;
+ }
+
+ $legacy = env_value('N8N_OUTBOUND_URL_ADRESSE', $localEnv);
+ if ($legacy !== '' && str_contains(strtolower($legacy), 'adressetikette')) {
+ return $legacy;
+ }
+
+ $base = env_value('N8N_BASE_URL', $localEnv);
+ if ($base === '') {
+ return '';
+ }
+
+ $root = preg_replace('#/api/v1/?$#', '', rtrim($base, '/'));
+ if (!is_string($root) || $root === '') {
+ return '';
+ }
+
+ return $root . '/webhook/naurua_erp_adressetikette';
+}
+
+function post_json(string $url, array $payload, array $headers = [], int $timeoutSeconds = 15): array
+{
+ $body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ if ($body === false) {
+ return ['ok' => false, 'status' => 0, 'body' => '', 'error' => 'Could not encode payload'];
+ }
+
+ $headerLines = ['Content-Type: application/json'];
+ foreach ($headers as $name => $value) {
+ if ($name === '' || $value === '') {
+ continue;
+ }
+ $headerLines[] = $name . ': ' . $value;
+ }
+
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => implode("\r\n", $headerLines),
+ 'content' => $body,
+ 'timeout' => $timeoutSeconds,
+ 'ignore_errors' => true,
+ ],
+ ]);
+
+ $responseBody = @file_get_contents($url, false, $context);
+ $responseHeaders = $http_response_header ?? [];
+
+ $status = 0;
+ if (isset($responseHeaders[0]) && preg_match('#HTTP/\S+\s+(\d{3})#', $responseHeaders[0], $m) === 1) {
+ $status = (int) $m[1];
+ }
+
+ if ($responseBody === false) {
+ $responseBody = '';
+ }
+
+ return [
+ 'ok' => $status >= 200 && $status < 300,
+ 'status' => $status,
+ 'body' => substr($responseBody, 0, 500),
+ 'error' => ($status === 0) ? 'Request failed or timed out' : '',
+ ];
+}
+
+function trigger_shipping_label_flow(array $order, array $localEnv): array
+{
+ $url = derive_label_webhook_url($localEnv);
+ if ($url === '') {
+ return [
+ 'enabled' => false,
+ 'ok' => false,
+ 'message' => 'Label webhook URL not configured',
+ ];
+ }
+
+ $payload = [
+ 'BestellungNr' => (string) ($order['BestellungNr'] ?? ''),
+ 'Vorname_LfAdr' => (string) ($order['Vorname_LfAdr'] ?? $order['Vorname'] ?? ''),
+ 'Nachname_LfAdr' => (string) ($order['Nachname_LfAdr'] ?? $order['Nachname'] ?? ''),
+ 'Strasse_LfAdr' => (string) ($order['Strasse_LfAdr'] ?? $order['Strasse'] ?? ''),
+ 'Hausnummer_LfAdr' => (string) ($order['Hausnummer_LfAdr'] ?? $order['Hausnummer'] ?? ''),
+ 'PLZ_LfAdr' => (string) ($order['PLZ_LfAdr'] ?? $order['PLZ'] ?? ''),
+ 'Stadt_LfAdr' => (string) ($order['Stadt_LfAdr'] ?? $order['Stadt'] ?? ''),
+ 'Land_LfAdr' => (string) ($order['Land_LfAdr'] ?? $order['Land'] ?? ''),
+ // Also include flat keys to be compatible with both mapping and direct template usage.
+ 'Vorname' => (string) ($order['Vorname_LfAdr'] ?? $order['Vorname'] ?? ''),
+ 'Nachname' => (string) ($order['Nachname_LfAdr'] ?? $order['Nachname'] ?? ''),
+ 'Strasse' => (string) ($order['Strasse_LfAdr'] ?? $order['Strasse'] ?? ''),
+ 'Hausnummer' => (string) ($order['Hausnummer_LfAdr'] ?? $order['Hausnummer'] ?? ''),
+ 'PLZ' => (string) ($order['PLZ_LfAdr'] ?? $order['PLZ'] ?? ''),
+ 'Stadt' => (string) ($order['Stadt_LfAdr'] ?? $order['Stadt'] ?? ''),
+ 'Land' => (string) ($order['Land_LfAdr'] ?? $order['Land'] ?? ''),
+ ];
+
+ $headers = [];
+ $secret = env_value('N8N_WEBHOOK_SECRET', $localEnv);
+ if ($secret !== '') {
+ $headers['X-Webhook-Secret'] = $secret;
+ }
+
+ $result = post_json($url, $payload, $headers, 20);
+
+ return [
+ 'enabled' => true,
+ 'ok' => $result['ok'],
+ 'status' => $result['status'],
+ 'url' => $url,
+ 'message' => $result['ok'] ? 'Label flow triggered' : ($result['error'] !== '' ? $result['error'] : 'Label flow returned non-2xx'),
+ 'responseBody' => $result['body'],
+ ];
+}
+
function find_or_create_party(PDO $pdo, array $data): int
{
$email = trim((string) ($data['EmailKunde'] ?? ''));
@@ -459,11 +577,14 @@ try {
$pdo->commit();
+ $labelTrigger = trigger_shipping_label_flow($data, $env);
+
json_response(200, [
'ok' => true,
'orderId' => $orderId,
'externalRef' => $externalRef,
'lineItemsImported' => $insertedLines,
+ 'labelTrigger' => $labelTrigger,
]);
} catch (Throwable $e) {
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {