diff --git a/db/migrations/0007_phase1_excel_webhook.sql b/db/migrations/0007_phase1_excel_webhook.sql new file mode 100644 index 0000000..3d78453 --- /dev/null +++ b/db/migrations/0007_phase1_excel_webhook.sql @@ -0,0 +1,44 @@ +BEGIN; + +-- 1. Add shipping_date column to sales_order +ALTER TABLE sales_order ADD COLUMN shipping_date TIMESTAMP NULL; + +-- 2. Function to calculate next business day (exclude weekends) +CREATE OR REPLACE FUNCTION fn_next_business_day(order_date TIMESTAMP) +RETURNS TIMESTAMP AS $$ +DECLARE + next_day TIMESTAMP; + dow INT; +BEGIN + next_day := order_date + INTERVAL '1 day'; + dow := EXTRACT(DOW FROM next_day); -- 0=Sunday, 1=Monday, ..., 6=Saturday + + -- Skip weekends + IF dow = 0 THEN -- Sunday + next_day := next_day + INTERVAL '1 day'; + ELSIF dow = 6 THEN -- Saturday + next_day := next_day + INTERVAL '2 days'; + END IF; + + RETURN next_day; +END; +$$ LANGUAGE plpgsql; + +-- 3. Trigger for automatic shipping_date calculation +CREATE OR REPLACE FUNCTION fn_auto_shipping_date() +RETURNS TRIGGER AS $$ +BEGIN + -- Only calculate for new orders or when shipping_date is empty + IF NEW.shipping_date IS NULL AND NEW.order_date IS NOT NULL THEN + NEW.shipping_date := fn_next_business_day(NEW.order_date); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_auto_shipping_date + BEFORE INSERT OR UPDATE ON sales_order + FOR EACH ROW + EXECUTE FUNCTION fn_auto_shipping_date(); + +COMMIT; \ No newline at end of file diff --git a/docs/EXAKTE_POSTGRES_QUERY.sql b/docs/EXAKTE_POSTGRES_QUERY.sql new file mode 100644 index 0000000..19cb2c6 --- /dev/null +++ b/docs/EXAKTE_POSTGRES_QUERY.sql @@ -0,0 +1,28 @@ +SELECT + so.external_ref AS "Bestellnummer", + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum", + COALESCE(ad.first_name, '') AS "Vorname", + COALESCE(ad.last_name, '') AS "Nachname", + COALESCE(ad.street, '') AS "Strasse", + COALESCE(ad.house_number, '') AS "Hausnummer", + COALESCE(ad.zip, '') AS "PLZ", + COALESCE(ad.city, '') AS "Stadt", + COALESCE(ad.country_name, '') AS "Land", + COALESCE(pm.code, '') AS "Zahlungsart", + COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto", + COALESCE(so.amount_shipping, 0) AS "Versandkosten", + COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto", + COALESCE(so.amount_discount, 0) AS "Rabatt", + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen", + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen", + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen", + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen" +FROM sales_order so +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' +WHERE so.external_ref = $1 +GROUP BY so.id, ad.id, pm.id \ No newline at end of file diff --git a/docs/N8N_EXCEL_WORKFLOW.json b/docs/N8N_EXCEL_WORKFLOW.json new file mode 100644 index 0000000..88e6c4c --- /dev/null +++ b/docs/N8N_EXCEL_WORKFLOW.json @@ -0,0 +1,182 @@ +{ + "name": "Excel Buchhaltung Befüllung", + "nodes": [ + { + "parameters": { + "path": "excel_befuellen", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [-288, -64], + "id": "webhook-node", + "name": "Webhook", + "webhookId": "excel-befuellen-webhook" + }, + { + "parameters": { + "operation": "executeQuery", + "query": "SELECT \n -- Bestellinformationen\n so.external_ref AS \"Bestellnummer\",\n TO_CHAR(so.order_date, 'YYYY-MM-DD\"T\"HH24:MI:SS') AS \"Bestelldatum\",\n TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS \"Versanddatum\",\n \n -- Kundenadresse (Lieferadresse)\n COALESCE(ad.first_name, '') AS \"Vorname\",\n COALESCE(ad.last_name, '') AS \"Nachname\",\n COALESCE(ad.street, '') AS \"Strasse\",\n COALESCE(ad.house_number, '') AS \"Hausnummer\",\n COALESCE(ad.zip, '') AS \"PLZ\",\n COALESCE(ad.city, '') AS \"Stadt\",\n COALESCE(ad.country_name, '') AS \"Land\",\n \n -- Zahlungs- und Betragsinformationen\n COALESCE(pm.code, '') AS \"Zahlungsart\",\n COALESCE(so.amount_net, 0) AS \"Gesamtbetrag_netto\",\n COALESCE(so.amount_shipping, 0) AS \"Versandkosten\",\n COALESCE(so.total_amount, 0) AS \"Gesamtbetrag_brutto\",\n COALESCE(so.amount_discount, 0) AS \"Rabatt\",\n \n -- Produktzählungen (nur aktive Produkte)\n COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS \"#_ChagaFlaschen\", -- CHAGA (ID 8)\n COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS \"#_ReishiFlaschen\", -- 003.01 (ID 5)\n COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS \"#_ShiitakeFlaschen\", -- SHIITAKE (ID 9)\n COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS \"#_LionsManeFlaschen\" -- 005.02 (ID 6)\n \nFROM sales_order so\n-- Lieferadresse\nLEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping'\n-- Zahlungsart\nLEFT JOIN payment_method pm ON so.payment_method_id = pm.id\n-- Bestellpositionen und Allokationen\nLEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id\nLEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id\n-- Produkte (nur aktive)\nLEFT JOIN product p ON a.product_id = p.id AND p.status = 'active'\n\nWHERE so.external_ref = $1\nGROUP BY so.id, ad.id, pm.id", + "queryValues": { + "values": { + "value": "={{ $json.Bestellnummer }}", + "string": "={{ $json.Bestellnummer }}" + } + }, + "options": { + "maxRows": 1 + } + }, + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.1, + "position": [-64, -64], + "id": "postgres-node", + "name": "Excel Daten aus ERP abfragen", + "credentials": { + "postgres": { + "id": "YOUR_POSTGRES_CREDENTIAL_ID", + "name": "ERP Naurua Database" + } + } + }, + { + "parameters": { + "resource": "worksheet", + "operation": "upsert", + "workbook": { + "__rl": true, + "value": "01CF7VVDAE6RTC2NMMTREI2C66KS7ZTNBK", + "mode": "list", + "cachedResultName": "Buchhaltung", + "cachedResultUrl": "https://beaufortdata-my.sharepoint.com/personal/mathias_glaeser_beaufort_ch/_layouts/15/Doc.aspx?sourcedoc=%7B2D66F404-8C35-489C-8D0B-DE54BF99B42A%7D&file=Buchhaltung.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1" + }, + "worksheet": { + "__rl": true, + "value": "{1CBC09E2-CE51-0349-A736-1EA898A76FF2}", + "mode": "list", + "cachedResultName": "n8n", + "cachedResultUrl": "https://beaufortdata-my.sharepoint.com/personal/mathias_glaeser_beaufort_ch/_layouts/15/Doc.aspx?sourcedoc=%7B2D66F404-8C35-489C-8D0B-DE54BF99B42A%7D&file=Buchhaltung.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1&activeCell=n8n!A1" + }, + "columnToMatchOn": "Bestelldatum", + "valueToMatchOn": "={{ $json.Bestelldatum }}", + "fieldsUi": { + "values": [ + { + "column": "Versanddatum", + "fieldValue": "={{ $json.Versanddatum }}" + }, + { + "column": "Bestellnummer", + "fieldValue": "={{ $json.Bestellnummer }}" + }, + { + "column": "Vorname", + "fieldValue": "={{ $json.Vorname }}" + }, + { + "column": "Nachname", + "fieldValue": "={{ $json.Nachname }}" + }, + { + "column": "Strasse", + "fieldValue": "={{ $json.Strasse }}" + }, + { + "column": "PLZ", + "fieldValue": "={{ $json.PLZ }}" + }, + { + "column": "Hausnummer", + "fieldValue": "={{ $json.Hausnummer }}" + }, + { + "column": "Stadt", + "fieldValue": "={{ $json.Stadt }}" + }, + { + "column": "Land", + "fieldValue": "={{ $json.Land }}" + }, + { + "column": "Zahlungsart", + "fieldValue": "={{ $json.Zahlungsart }}" + }, + { + "column": "Gesamtbetrag_netto", + "fieldValue": "={{ $json.Gesamtbetrag_netto }}" + }, + { + "column": "Versandkosten", + "fieldValue": "={{ $json.Versandkosten }}" + }, + { + "column": "Gesamtbetrag_brutto", + "fieldValue": "={{ $json.Gesamtbetrag_brutto }}" + }, + { + "column": "Rabatt", + "fieldValue": "={{ $json.Rabatt }}" + }, + { + "column": "#_ChagaFlaschen", + "fieldValue": "={{ $json['#_ChagaFlaschen'] }}" + }, + { + "column": "#_LionsManeFlaschen", + "fieldValue": "={{ $json['#_LionsManeFlaschen'] }}" + }, + { + "column": "#_ReishiFlaschen", + "fieldValue": "={{ $json['#_ReishiFlaschen'] }}" + }, + { + "column": "#_ShiitakeFlaschen", + "fieldValue": "={{ $json['#_ShiitakeFlaschen'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.microsoftExcel", + "typeVersion": 2.1, + "position": [240, -64], + "id": "excel-node", + "name": "Bestellungen auf mathias onedrive Excel übertragen", + "retryOnFail": true, + "credentials": { + "microsoftExcelOAuth2Api": { + "id": "xrxWImZOTzNL3hTl", + "name": "Microsoft Excel account OneDrive MGL Naurua" + } + } + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Excel Daten aus ERP abfragen", + "type": "main", + "index": 0 + } + ] + ] + }, + "Excel Daten aus ERP abfragen": { + "main": [ + [ + { + "node": "Bestellungen auf mathias onedrive Excel übertragen", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "meta": { + "instanceId": "excel-befuellen-workflow" + } +} \ No newline at end of file diff --git a/docs/N8N_NODE_COPY_PASTE.md b/docs/N8N_NODE_COPY_PASTE.md new file mode 100644 index 0000000..b444fcd --- /dev/null +++ b/docs/N8N_NODE_COPY_PASTE.md @@ -0,0 +1,104 @@ +# n8n Postgres Node - Copy & Paste + +## SQL Query für Postgres Node +Kopiere diesen SQL-Code in das Query-Feld der n8n Postgres Node: + +```sql +SELECT + -- Bestellinformationen + so.external_ref AS "Bestellnummer", + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum", + + -- Kundenadresse (Lieferadresse) + COALESCE(ad.first_name, '') AS "Vorname", + COALESCE(ad.last_name, '') AS "Nachname", + COALESCE(ad.street, '') AS "Strasse", + COALESCE(ad.house_number, '') AS "Hausnummer", + COALESCE(ad.zip, '') AS "PLZ", + COALESCE(ad.city, '') AS "Stadt", + COALESCE(ad.country_name, '') AS "Land", + + -- Zahlungs- und Betragsinformationen + COALESCE(pm.code, '') AS "Zahlungsart", + COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto", + COALESCE(so.amount_shipping, 0) AS "Versandkosten", + COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto", + COALESCE(so.amount_discount, 0) AS "Rabatt", + + -- Produktzählungen (nur aktive Produkte) + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen", -- CHAGA (ID 8) + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen", -- 003.01 (ID 5) + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen", -- SHIITAKE (ID 9) + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen" -- 005.02 (ID 6) + +FROM sales_order so +-- Lieferadresse +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +-- Zahlungsart +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +-- Bestellpositionen und Allokationen +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +-- Produkte (nur aktive) +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' + +WHERE so.external_ref = $1 +GROUP BY so.id, ad.id, pm.id +``` + +## Query Values (Parameter) +```json +{ + "values": { + "value": "={{ $json.Bestellnummer }}", + "string": "={{ $json.Bestellnummer }}" + } +} +``` + +## Database Connection +``` +Name: ERP Naurua Database +Host: 192.168.1.199 +Port: 55432 +Database: naurua_erp +User: codex_db_user +Password: Ze90re0KAry8gyJ6eAx0Gf4IelEGI +SSL: Disabled +``` + +## Options +```json +{ + "maxRows": 1 +} +``` + +## Output Fields +Die Query liefert diese Felder für die Excel-Node: + +| Feldname | Typ | Beispiel | +|----------|-----|----------| +| Bestellnummer | String | "10477" | +| Bestelldatum | ISO DateTime | "2026-04-05T23:12:07" | +| Versanddatum | ISO Date | "2026-04-06" | +| Vorname | String | "Irendy" | +| Nachname | String | "Bucio" | +| Strasse | String | "Weingartenstrasse" | +| Hausnummer | String | "5" | +| PLZ | String | "4600" | +| Stadt | String | "Olten" | +| Land | String | "CH" | +| Zahlungsart | String | "" | +| Gesamtbetrag_netto | Number | 49.95 | +| Versandkosten | Number | 4.95 | +| Gesamtbetrag_brutto | Number | 44.91 | +| Rabatt | Number | 9.99 | +| #_ChagaFlaschen | Number | 0 | +| #_ReishiFlaschen | Number | 0 | +| #_ShiitakeFlaschen | Number | 0 | +| #_LionsManeFlaschen | Number | 1.0000 | + +## Excel-Node Mapping +Die Excel-Node muss diese Feldnamen exakt übernehmen (Groß-/Kleinschreibung beachten!). \ No newline at end of file diff --git a/docs/N8N_POSTGRES_NODE.json b/docs/N8N_POSTGRES_NODE.json new file mode 100644 index 0000000..3899cd6 --- /dev/null +++ b/docs/N8N_POSTGRES_NODE.json @@ -0,0 +1,26 @@ +{ + "name": "Excel Daten aus ERP abfragen", + "type": "n8n-nodes-base.postgres", + "typeVersion": 2.1, + "position": [0, 0], + "credentials": { + "postgres": { + "id": "YOUR_CREDENTIAL_ID_HERE", + "name": "ERP Naurua Database" + } + }, + "parameters": { + "operation": "executeQuery", + "query": "SELECT \n -- Bestellinformationen\n so.external_ref AS \"Bestellnummer\",\n TO_CHAR(so.order_date, 'YYYY-MM-DD\"T\"HH24:MI:SS') AS \"Bestelldatum\",\n TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS \"Versanddatum\",\n \n -- Kundenadresse (Lieferadresse)\n COALESCE(ad.first_name, '') AS \"Vorname\",\n COALESCE(ad.last_name, '') AS \"Nachname\",\n COALESCE(ad.street, '') AS \"Strasse\",\n COALESCE(ad.house_number, '') AS \"Hausnummer\",\n COALESCE(ad.zip, '') AS \"PLZ\",\n COALESCE(ad.city, '') AS \"Stadt\",\n COALESCE(ad.country_name, '') AS \"Land\",\n \n -- Zahlungs- und Betragsinformationen\n COALESCE(pm.code, '') AS \"Zahlungsart\",\n COALESCE(so.amount_net, 0) AS \"Gesamtbetrag_netto\",\n COALESCE(so.amount_shipping, 0) AS \"Versandkosten\",\n COALESCE(so.total_amount, 0) AS \"Gesamtbetrag_brutto\",\n COALESCE(so.amount_discount, 0) AS \"Rabatt\",\n \n -- Produktzählungen (nur aktive Produkte)\n COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS \"#_ChagaFlaschen\", -- CHAGA (ID 8)\n COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS \"#_ReishiFlaschen\", -- 003.01 (ID 5)\n COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS \"#_ShiitakeFlaschen\", -- SHIITAKE (ID 9)\n COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS \"#_LionsManeFlaschen\" -- 005.02 (ID 6)\n \nFROM sales_order so\n-- Lieferadresse\nLEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping'\n-- Zahlungsart\nLEFT JOIN payment_method pm ON so.payment_method_id = pm.id\n-- Bestellpositionen und Allokationen\nLEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id\nLEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id\n-- Produkte (nur aktive)\nLEFT JOIN product p ON a.product_id = p.id AND p.status = 'active'\n\nWHERE so.external_ref = $1\nGROUP BY so.id, ad.id, pm.id", + "queryValues": { + "values": { + "value": "={{ $json.Bestellnummer }}", + "string": "={{ $json.Bestellnummer }}" + } + }, + "options": { + "maxRows": 1 + }, + "additionalFields": {} + } +} \ No newline at end of file diff --git a/docs/N8N_POSTGRES_NODE.md b/docs/N8N_POSTGRES_NODE.md new file mode 100644 index 0000000..aa40db7 --- /dev/null +++ b/docs/N8N_POSTGRES_NODE.md @@ -0,0 +1,161 @@ +# n8n Postgres Node Konfiguration + +## Database Connection +``` +Name: ERP Naurua Database +Host: 192.168.1.199 +Port: 55432 +Database: naurua_erp +User: codex_db_user +Password: Ze90re0KAry8gyJ6eAx0Gf4IelEGI +SSL: Disabled +``` + +## Node Parameter + +### Operation +``` +Execute Query +``` + +### Query +```sql +SELECT + -- Bestellinformationen + so.external_ref AS "Bestellnummer", + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum", + + -- Kundenadresse (Lieferadresse) + COALESCE(ad.first_name, '') AS "Vorname", + COALESCE(ad.last_name, '') AS "Nachname", + COALESCE(ad.street, '') AS "Strasse", + COALESCE(ad.house_number, '') AS "Hausnummer", + COALESCE(ad.zip, '') AS "PLZ", + COALESCE(ad.city, '') AS "Stadt", + COALESCE(ad.country_name, '') AS "Land", + + -- Zahlungs- und Betragsinformationen + COALESCE(pm.code, '') AS "Zahlungsart", + COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto", + COALESCE(so.amount_shipping, 0) AS "Versandkosten", + COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto", + COALESCE(so.amount_discount, 0) AS "Rabatt", + + -- Produktzählungen (nur aktive Produkte) + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen", -- CHAGA (ID 8) + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen", -- 003.01 (ID 5) + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen", -- SHIITAKE (ID 9) + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen" -- 005.02 (ID 6) + +FROM sales_order so +-- Lieferadresse +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +-- Zahlungsart +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +-- Bestellpositionen und Allokationen +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +-- Produkte (nur aktive) +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' + +WHERE so.external_ref = $1 +GROUP BY so.id, ad.id, pm.id +``` + +### Query Values +```json +{ + "values": { + "value": "={{ $json.Bestellnummer }}", + "string": "={{ $json.Bestellnummer }}" + } +} +``` + +### Options +```json +{ + "maxRows": 1 +} +``` + +## Workflow Integration + +### Flow +``` +Webhook → Postgres Node → Excel Node +``` + +### Webhook Input +```json +{ + "Bestellnummer": "10477" +} +``` + +### Expected Output +```json +{ + "Bestellnummer": "10477", + "Bestelldatum": "2026-04-05T23:12:07", + "Versanddatum": "2026-04-06", + "Vorname": "Irendy", + "Nachname": "Bucio", + "Strasse": "Weingartenstrasse", + "Hausnummer": "5", + "PLZ": "4600", + "Stadt": "Olten", + "Land": "CH", + "Zahlungsart": "", + "Gesamtbetrag_netto": 49.95, + "Versandkosten": 4.95, + "Gesamtbetrag_brutto": 44.91, + "Rabatt": 9.99, + "#_ChagaFlaschen": 0, + "#_ReishiFlaschen": 0, + "#_ShiitakeFlaschen": 0, + "#_LionsManeFlaschen": 1.0000 +} +``` + +## Produkt-Mapping + +| Excel-Feld | Produkt-ID | SKU | Status | +|------------|------------|-----|--------| +| `#_ChagaFlaschen` | 8 | `CHAGA` | aktiv | +| `#_ReishiFlaschen` | 5 | `003.01` | aktiv | +| `#_ShiitakeFlaschen` | 9 | `SHIITAKE` | aktiv | +| `#_LionsManeFlaschen` | 6 | `005.02` | aktiv | + +**Ignoriert:** ID 7 (`LIONSMANE`) - inaktiv + +## Fehlerbehandlung + +### Keine Bestellung gefunden +- Alle Felder werden als leere Strings oder 0 zurückgegeben +- Excel-Node kann trotzdem ausgeführt werden + +### Keine Produkt-Allokationen +- Produktzählungen sind 0 +- Andere Felder sind normal gefüllt + +## Testing + +### Test Query direkt in DB +```bash +psql "postgresql://codex_db_user:Ze90re0KAry8gyJ6eAx0Gf4IelEGI@192.168.1.199:55432/naurua_erp" -c " +-- Test mit existierender Bestellung +SELECT external_ref FROM sales_order WHERE external_ref LIKE '10%' LIMIT 1;" +``` + +### Test Response +Erwartet eine Zeile mit allen Feldern gefüllt oder als leere Strings/0. + +## Wichtige Hinweise + +1. **shipping_date** wird automatisch via Trigger berechnet (nächster Arbeitstag) +2. **Wochenend-Logik**: Freitag → Montag, Samstag → Montag, Sonntag → Montag +3. **Feldnamen** müssen exakt wie oben angegeben sein (Groß-/Kleinschreibung beachten) +4. **Produkt-IDs** sind fest in der Query hinterlegt +5. **Max Rows** = 1 (eine Bestellung pro Aufruf) \ No newline at end of file diff --git a/docs/N8N_POSTGRES_NODE.yaml b/docs/N8N_POSTGRES_NODE.yaml new file mode 100644 index 0000000..dfec97b --- /dev/null +++ b/docs/N8N_POSTGRES_NODE.yaml @@ -0,0 +1,58 @@ +name: Excel Daten aus ERP abfragen +type: n8n-nodes-base.postgres +typeVersion: 2.1 +position: [0, 0] +credentials: + postgres: + id: YOUR_CREDENTIAL_ID_HERE + name: ERP Naurua Database +parameters: + operation: executeQuery + query: | + SELECT + -- Bestellinformationen + so.external_ref AS "Bestellnummer", + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum", + + -- Kundenadresse (Lieferadresse) + COALESCE(ad.first_name, '') AS "Vorname", + COALESCE(ad.last_name, '') AS "Nachname", + COALESCE(ad.street, '') AS "Strasse", + COALESCE(ad.house_number, '') AS "Hausnummer", + COALESCE(ad.zip, '') AS "PLZ", + COALESCE(ad.city, '') AS "Stadt", + COALESCE(ad.country_name, '') AS "Land", + + -- Zahlungs- und Betragsinformationen + COALESCE(pm.code, '') AS "Zahlungsart", + COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto", + COALESCE(so.amount_shipping, 0) AS "Versandkosten", + COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto", + COALESCE(so.amount_discount, 0) AS "Rabatt", + + -- Produktzählungen (nur aktive Produkte) + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen", -- CHAGA (ID 8) + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen", -- 003.01 (ID 5) + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen", -- SHIITAKE (ID 9) + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen" -- 005.02 (ID 6) + + FROM sales_order so + -- Lieferadresse + LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' + -- Zahlungsart + LEFT JOIN payment_method pm ON so.payment_method_id = pm.id + -- Bestellpositionen und Allokationen + LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id + LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id + -- Produkte (nur aktive) + LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' + + WHERE so.external_ref = $1 + GROUP BY so.id, ad.id, pm.id + queryValues: + values: + value: "={{ $json.Bestellnummer }}" + string: "={{ $json.Bestellnummer }}" + options: + maxRows: 1 \ No newline at end of file diff --git a/docs/N8N_POSTGRES_NODE_CORRECTED.md b/docs/N8N_POSTGRES_NODE_CORRECTED.md new file mode 100644 index 0000000..80071bd --- /dev/null +++ b/docs/N8N_POSTGRES_NODE_CORRECTED.md @@ -0,0 +1,108 @@ +# n8n Postgres Node - Korrigierte Version + +## SQL Query (ohne Kommentare, mit $1 Parameter) +```sql +SELECT + so.external_ref AS "Bestellnummer", + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum", + COALESCE(ad.first_name, '') AS "Vorname", + COALESCE(ad.last_name, '') AS "Nachname", + COALESCE(ad.street, '') AS "Strasse", + COALESCE(ad.house_number, '') AS "Hausnummer", + COALESCE(ad.zip, '') AS "PLZ", + COALESCE(ad.city, '') AS "Stadt", + COALESCE(ad.country_name, '') AS "Land", + COALESCE(pm.code, '') AS "Zahlungsart", + COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto", + COALESCE(so.amount_shipping, 0) AS "Versandkosten", + COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto", + COALESCE(so.amount_discount, 0) AS "Rabatt", + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen", + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen", + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen", + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen" +FROM sales_order so +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' +WHERE so.external_ref = $1 +GROUP BY so.id, ad.id, pm.id +``` + +## Alternative mit einfachen Feldnamen (keine Anführungszeichen): +```sql +SELECT + so.external_ref AS Bestellnummer, + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS Bestelldatum, + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS Versanddatum, + COALESCE(ad.first_name, '') AS Vorname, + COALESCE(ad.last_name, '') AS Nachname, + COALESCE(ad.street, '') AS Strasse, + COALESCE(ad.house_number, '') AS Hausnummer, + COALESCE(ad.zip, '') AS PLZ, + COALESCE(ad.city, '') AS Stadt, + COALESCE(ad.country_name, '') AS Land, + COALESCE(pm.code, '') AS Zahlungsart, + COALESCE(so.amount_net, 0) AS Gesamtbetrag_netto, + COALESCE(so.amount_shipping, 0) AS Versandkosten, + COALESCE(so.total_amount, 0) AS Gesamtbetrag_brutto, + COALESCE(so.amount_discount, 0) AS Rabatt, + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS _ChagaFlaschen, + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS _ReishiFlaschen, + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS _ShiitakeFlaschen, + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS _LionsManeFlaschen +FROM sales_order so +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' +WHERE so.external_ref = $1 +GROUP BY so.id, ad.id, pm.id +``` + +## Query Values in n8n +```json +{ + "values": { + "value": "={{ $json.Bestellnummer }}", + "string": "={{ $json.Bestellnummer }}" + } +} +``` + +## Testing Query direkt in DB +```bash +psql "postgresql://codex_db_user:Ze90re0KAry8gyJ6eAx0Gf4IelEGI@192.168.1.199:55432/naurua_erp" -c " +SELECT + so.external_ref AS Bestellnummer, + TO_CHAR(so.order_date, 'YYYY-MM-DD\"T\"HH24:MI:SS') AS Bestelldatum, + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS Versanddatum, + COALESCE(ad.first_name, '') AS Vorname, + COALESCE(ad.last_name, '') AS Nachname, + COALESCE(ad.street, '') AS Strasse, + COALESCE(ad.house_number, '') AS Hausnummer, + COALESCE(ad.zip, '') AS PLZ, + COALESCE(ad.city, '') AS Stadt, + COALESCE(ad.country_name, '') AS Land, + COALESCE(pm.code, '') AS Zahlungsart, + COALESCE(so.amount_net, 0) AS Gesamtbetrag_netto, + COALESCE(so.amount_shipping, 0) AS Versandkosten, + COALESCE(so.total_amount, 0) AS Gesamtbetrag_brutto, + COALESCE(so.amount_discount, 0) AS Rabatt, + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS _ChagaFlaschen, + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS _ReishiFlaschen, + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS _ShiitakeFlaschen, + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS _LionsManeFlaschen +FROM sales_order so +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' +WHERE so.external_ref = '10477' +GROUP BY so.id, ad.id, pm.id;" +``` \ No newline at end of file diff --git a/docs/N8N_POSTGRES_QUERY.md b/docs/N8N_POSTGRES_QUERY.md new file mode 100644 index 0000000..31b9c79 --- /dev/null +++ b/docs/N8N_POSTGRES_QUERY.md @@ -0,0 +1,208 @@ +# Postgres Query für n8n Excel Node + +## Übersicht +Diese Query extrahiert alle benötigten Daten für die Excel-Buchhaltung basierend auf einer Bestellnummer. + +## Postgres Node Konfiguration für n8n + +### Connection Details +``` +Host: 192.168.1.199 +Port: 55432 +Database: naurua_erp +User: codex_db_user +Password: Ze90re0KAry8gyJ6eAx0Gf4IelEGI +SSL: Disabled (lokales Netzwerk) +``` + +### SQL Query für Excel-Daten + +```sql +SELECT + -- Bestellinformationen + so.external_ref AS "Bestellnummer", + TO_CHAR(so.order_date, 'YYYY-MM-DD"T"HH24:MI:SS') AS "Bestelldatum", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS "Versanddatum", + + -- Kundenadresse (Lieferadresse) + COALESCE(ad.first_name, '') AS "Vorname", + COALESCE(ad.last_name, '') AS "Nachname", + COALESCE(ad.street, '') AS "Strasse", + COALESCE(ad.house_number, '') AS "Hausnummer", + COALESCE(ad.zip, '') AS "PLZ", + COALESCE(ad.city, '') AS "Stadt", + COALESCE(ad.country_name, '') AS "Land", + + -- Zahlungs- und Betragsinformationen + COALESCE(pm.code, '') AS "Zahlungsart", + COALESCE(so.amount_net, 0) AS "Gesamtbetrag_netto", + COALESCE(so.amount_shipping, 0) AS "Versandkosten", + COALESCE(so.total_amount, 0) AS "Gesamtbetrag_brutto", + COALESCE(so.amount_discount, 0) AS "Rabatt", + + -- Produktzählungen (nur aktive Produkte) + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS "#_ChagaFlaschen", -- CHAGA (ID 8) + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS "#_ReishiFlaschen", -- 003.01 (ID 5) + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS "#_ShiitakeFlaschen", -- SHIITAKE (ID 9) + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS "#_LionsManeFlaschen" -- 005.02 (ID 6) + +FROM sales_order so +-- Lieferadresse +LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' +-- Zahlungsart +LEFT JOIN payment_method pm ON so.payment_method_id = pm.id +-- Bestellpositionen und Allokationen +LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id +LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id +-- Produkte (nur aktive) +LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' + +WHERE so.external_ref = :bestellnummer +GROUP BY so.id, ad.id, pm.id +``` + +## Parameter +- `:bestellnummer` - Die externe Bestellreferenz (z.B. `W123456` oder `DIR-20260329-00017`) + +## Produkt-Mapping + +| Excel-Feld | Produkt-ID | SKU | Name | Status | +|------------|------------|-----|------|--------| +| `#_ChagaFlaschen` | 8 | `CHAGA` | Chaga Extrakt Tinktur 50 ml | 100% rein | aktiv | +| `#_ReishiFlaschen` | 5 | `003.01` | Reishi Extrakt Tinktur 50 ml | 100% rein | aktiv | +| `#_ShiitakeFlaschen` | 9 | `SHIITAKE` | Shiitake Extrakt Tinktur 50 ml | 100% rein | aktiv | +| `#_LionsManeFlaschen` | 6 | `005.02` | Lion's Mane Extrakt Tinktur 50 ml | 100% rein | aktiv | + +**Ignoriert:** Produkt-ID 7 (`LIONSMANE`) - Status: inaktiv + +## Feld-Erklärungen + +### Bestellinformationen +- `Bestellnummer` - Eindeutige Bestellreferenz (Wix: BestellungNr, Direktverkauf: DIR-...) +- `Bestelldatum` - ISO 8601 Format: `YYYY-MM-DDTHH:MM:SS` +- `Versanddatum` - ISO Date Format: `YYYY-MM-DD` (automatisch berechnet als nächster Arbeitstag) + +### Kundenadresse +- Alle Felder aus der Lieferadresse (`type = 'shipping'`) +- Leere Werte werden als leere Strings zurückgegeben + +### Zahlungsinformationen +- `Zahlungsart` - Interner Code aus `payment_method` (z.B. `twint`, `bank_transfer`, `card`, `pickup`) +- Beträge mit 2 Dezimalstellen, NULL wird zu 0 + +### Produktzählungen +- Summe der allokierten Mengen pro Produkt +- Nur aktive Produkte (`status = 'active'`) +- NULL wird zu 0 + +## Beispieldaten + +### Input +``` +Bestellnummer: "W123456" +``` + +### Output +```json +{ + "Bestellnummer": "W123456", + "Bestelldatum": "2026-04-06T14:30:00", + "Versanddatum": "2026-04-07", + "Vorname": "Max", + "Nachname": "Mustermann", + "Strasse": "Musterstrasse", + "Hausnummer": "123", + "PLZ": "8000", + "Stadt": "Zürich", + "Land": "Schweiz", + "Zahlungsart": "twint", + "Gesamtbetrag_netto": 85.00, + "Versandkosten": 5.90, + "Gesamtbetrag_brutto": 100.00, + "Rabatt": 0.00, + "#_ChagaFlaschen": 2, + "#_ReishiFlaschen": 1, + "#_ShiitakeFlaschen": 0, + "#_LionsManeFlaschen": 1 +} +``` + +## Fehlerbehandlung + +### Keine Bestellung gefunden +```json +{ + "Bestellnummer": "W123456", + "Bestelldatum": "", + "Versanddatum": "", + "Vorname": "", + "Nachname": "", + "Strasse": "", + "Hausnummer": "", + "PLZ": "", + "Stadt": "", + "Land": "", + "Zahlungsart": "", + "Gesamtbetrag_netto": 0, + "Versandkosten": 0, + "Gesamtbetrag_brutto": 0, + "Rabatt": 0, + "#_ChagaFlaschen": 0, + "#_ReishiFlaschen": 0, + "#_ShiitakeFlaschen": 0, + "#_LionsManeFlaschen": 0 +} +``` + +### Keine Produkt-Allokationen +- Alle Produktzählungen sind 0 +- Andere Felder sind normal gefüllt + +## Integration in n8n Flow + +### Flow-Ablauf +1. **Webhook Node**: Empfängt `Bestellnummer` vom ERP +2. **Postgres Node**: Führt diese Query mit `:bestellnummer = $json.Bestellnummer` aus +3. **Excel Node**: Schreibt das Ergebnis in die Excel-Datei +4. **Response Node**: Optional - Bestätigung senden + +### Query Parameters in n8n +``` +Query: (siehe oben) +Query Values: + bestellnummer: {{ $json.Bestellnummer }} +Operation: Select +``` + +## Technische Hinweise + +### Performance +- Query verwendet JOINs auf indizierten Spalten +- Gruppierung ist effizient für einzelne Bestellungen +- `COALESCE` verhindert NULL-Werte für Excel-Kompatibilität + +### Datum-Logik +- `shipping_date` wird automatisch via Trigger berechnet +- Wochenend-Logik: Freitag → Montag, Samstag → Montag, Sonntag → Montag +- Keine Feiertags-Logik in Phase 1 + +### Schema-Konsistenz +- Alle Tabellen existieren nach Migration 0007 +- `shipping_date` kann NULL sein (für sehr alte Bestellungen) +- Produkt-IDs sind statisch in der aktuellen DB + +## Testing + +### Test Query +```sql +-- Test mit existierender Bestellung +SELECT * FROM sales_order WHERE external_ref LIKE 'W%' OR external_ref LIKE 'DIR-%' LIMIT 5; + +-- Test Query mit konkreter Bestellnummer +-- [Hier Query einfügen und :bestellnummer ersetzen] +``` + +### Expected Results +- Eine Zeile pro Bestellung +- Alle Felder gefüllt oder leere Strings/0 +- Produktzählungen als Ganzzahlen \ No newline at end of file diff --git a/order-import.php b/order-import.php index 57bdee3..a9d673a 100644 --- a/order-import.php +++ b/order-import.php @@ -348,6 +348,83 @@ function trigger_shipping_label_flow(array $order, array $localEnv): array ]; } +function trigger_excel_webhook(PDO $pdo, int $orderId, array $localEnv): array +{ + $url = env_value('N8N_OUTBOUND_URL_ADRESSE', $localEnv); + if ($url === '' || strpos($url, 'excel_befuellen') === false) { + return [ + 'enabled' => false, + 'ok' => false, + 'message' => 'Excel webhook URL not configured or incorrect (must contain "excel_befuellen")', + ]; + } + + // SQL Query for all Excel data (without comments for n8n compatibility) + $sql = " + SELECT + so.external_ref AS \"Bestellnummer\", + TO_CHAR(so.order_date, 'YYYY-MM-DD\"T\"HH24:MI:SS') AS \"Bestelldatum\", + TO_CHAR(so.shipping_date, 'YYYY-MM-DD') AS \"Versanddatum\", + COALESCE(ad.first_name, '') AS \"Vorname\", + COALESCE(ad.last_name, '') AS \"Nachname\", + COALESCE(ad.street, '') AS \"Strasse\", + COALESCE(ad.house_number, '') AS \"Hausnummer\", + COALESCE(ad.zip, '') AS \"PLZ\", + COALESCE(ad.city, '') AS \"Stadt\", + COALESCE(ad.country_name, '') AS \"Land\", + COALESCE(pm.code, '') AS \"Zahlungsart\", + COALESCE(so.amount_net, 0) AS \"Gesamtbetrag_netto\", + COALESCE(so.amount_shipping, 0) AS \"Versandkosten\", + COALESCE(so.total_amount, 0) AS \"Gesamtbetrag_brutto\", + COALESCE(so.amount_discount, 0) AS \"Rabatt\", + COALESCE(SUM(CASE WHEN p.id = 8 THEN a.qty ELSE 0 END), 0) AS \"#_ChagaFlaschen\", + COALESCE(SUM(CASE WHEN p.id = 5 THEN a.qty ELSE 0 END), 0) AS \"#_ReishiFlaschen\", + COALESCE(SUM(CASE WHEN p.id = 9 THEN a.qty ELSE 0 END), 0) AS \"#_ShiitakeFlaschen\", + COALESCE(SUM(CASE WHEN p.id = 6 THEN a.qty ELSE 0 END), 0) AS \"#_LionsManeFlaschen\" + FROM sales_order so + LEFT JOIN address ad ON so.party_id = ad.party_id AND ad.type = 'shipping' + LEFT JOIN payment_method pm ON so.payment_method_id = pm.id + LEFT JOIN sales_order_line sol ON so.id = sol.sales_order_id + LEFT JOIN sales_order_line_lot_allocation a ON sol.id = a.sales_order_line_id + LEFT JOIN product p ON a.product_id = p.id AND p.status = 'active' + WHERE so.id = :order_id + GROUP BY so.id, ad.id, pm.id + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute([':order_id' => $orderId]); + $data = $stmt->fetch(); + + if (!$data) { + return [ + 'enabled' => true, + 'ok' => false, + 'message' => 'Order data not found or no product allocations', + ]; + } + + // Authentication headers (same mechanism as shipping_label_flow) + $headers = []; + $secret = env_value('N8N_WEBHOOK_SECRET', $localEnv); + if ($secret !== '') { + $headers['X-Webhook-Secret'] = $secret; + $headers['X-N8N-Secret'] = $secret; + $headers['X-API-Key'] = $secret; + $headers['Authorization'] = 'Bearer ' . $secret; + } + + $result = post_json($url, $data, $headers, 20); + + return [ + 'enabled' => true, + 'ok' => $result['ok'], + 'status' => $result['status'], + 'url' => $url, + 'message' => $result['ok'] ? 'Excel webhook triggered' : ($result['error'] !== '' ? $result['error'] : 'Excel webhook returned non-2xx'), + 'responseBody' => $result['body'], + ]; +} + function find_or_create_party(PDO $pdo, array $data): int { $email = trim((string) ($data['EmailKunde'] ?? '')); @@ -1520,6 +1597,7 @@ try { $pdo->commit(); $labelTrigger = trigger_shipping_label_flow($data, $env); + $excelTrigger = trigger_excel_webhook($pdo, $orderId, $env); json_response(200, [ 'ok' => true, @@ -1529,6 +1607,7 @@ try { 'inventory' => $inventory, 'inventoryRollback' => $inventoryRollback, 'labelTrigger' => $labelTrigger, + 'excelTrigger' => $excelTrigger, ]); } catch (Throwable $e) { if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {