Add architecture module map

This commit is contained in:
2026-06-15 09:58:33 +02:00
parent 39d936cfba
commit c6b5a0572c
19 changed files with 1134 additions and 2372 deletions
-182
View File
@@ -1,182 +0,0 @@
{
"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"
}
}
-104
View File
@@ -1,104 +0,0 @@
# 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!).
-26
View File
@@ -1,26 +0,0 @@
{
"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": {}
}
}
-161
View File
@@ -1,161 +0,0 @@
# 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)
-58
View File
@@ -1,58 +0,0 @@
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
-108
View File
@@ -1,108 +0,0 @@
# 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;"
```
-208
View File
@@ -1,208 +0,0 @@
# 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
+115
View File
@@ -0,0 +1,115 @@
# Modulkarte ERP Naurua
Stand: 2026-06-15
Status: Architektur-Arbeitskarte
## 1. Zweck
Diese Modulkarte ordnet das System in klar getrennte fachliche Hauptmodule, Submodule und technische Querschnittsbereiche. Sie dient als Zielbild fuer die saubere Trennung von Ownership, Schnittstellen und wiederverwendbaren `shared`-Bausteinen.
## 2. Leitlinien
- Jedes Hauptmodul besitzt genau eine Primaerverantwortung.
- Submodule sind nur interne fachliche Zuschnitte innerhalb eines Hauptmoduls.
- `shared` enthaelt nur fachlich neutrale, wiederverwendbare Bausteine.
- `system` enthaelt nur technische Runtime-, Start-, Trigger- und Laufzeitlogik.
- Fachliche Logik bleibt immer im owning Modul.
- Cross-Modul-Zugriffe erfolgen nur ueber explizite Schnittstellen.
## 3. Moduluebersicht
| Modul | Typ | Submodule | Primaerverantwortung | Owned Daten / Artefakte | Writes | Reads | Public Interface | `shared`-Bedarf |
|---|---|---|---|---|---|---|---|---|
| Kontakte | Hauptmodul | Stammdaten, Adressen, Kommunikationsdaten | Kunden-, Lieferanten- und Kontaktstamm | `party`, `address`, `contact` | Kontaktstamm, Adressen, Kommunikationsdaten | Referenzen aus Bestellungen, Buchhaltung, Beratung | Kontakt-API, Such- und Lookup-Schnittstellen | Validierung, Formatierung, UI-Bausteine |
| Bestellungen | Hauptmodul | Bestellkopf, Positionen, Status | Operative Bestellverarbeitung | `sales_order`, `sales_order_line`, Statusdaten | Bestellanlage, Status, Zuordnung | Kontakte, Artikel-Mapping, Lager | Bestell-API, Status-API | Geld-/Datumsformat, Tabellen- und Status-Patterns |
| Lager | Hauptmodul | Chargen, Bewegungen, Bestandsfuehrung | Bestand, Charge, MHD, Bewegungen | `product`, `stock_lot`, `stock_move`, `v_stock_lot_balance` | Warenzugang, -abgang, Umlagerung, Chargenpflege | Bestellungen, Import, Direktverkauf | Lager-API, Bestands- und Chargen-Schnittstellen | Listen-, Detail- und Statusdarstellung |
| Artikel-Mapping | Hauptmodul | Externe Artikel, interne Artikel, Zuordnung | Aufloesung externer Shopdaten auf interne Artikel | `sellable_item`, `external_item_alias`, `sellable_item_component` | Aliaspflege, Zuordnung, Bundle-/Komponentenpflege | Bestellungen, Import | Mapping-API | Suchlogik, Tabellenmuster, Normalisierung |
| Import / Integration | Hauptmodul | Webhooks, Import-Routing, externe Adapter | Annahme und Verteilung externer Eingangsdaten | Integrationsdaten, Importzustand, technische Events | Importausloesung, technische Events | Externe Quellen, operative Kernmodule | Webhook- und Import-Schnittstellen | HTTP-nahe Bausteine, Parsing, technische Validierung |
| Direktverkauf | Hauptmodul | Tageserfassung, Sammelverkauf | Manuelle Erfassung von Direktverkaeufen | Direktverkaufsbelege, Erfassungsdaten | Direktverkauf, Ausloesung operativer Folgeschritte | Kontakte optional, Lager, Bestellungen | Direktverkaufs-UI und API | Erfassungsmuster, Formularbausteine |
| Buchhaltung | Hauptmodul | Debitoren, Kreditoren, Hauptbuch, Nebenbuecher | Finanzbuchhaltung, Kontierung, Abschluss, Steuerlogik | Buchungssaetze, Konten, OP-Positionen, Steuerdaten | Verbuchung, OP-Pflege, Abschlusslaeufe | Freigegebene ERP-Belege, Zahlungsdaten, Kontenplan | Buchhaltungs-API, Buchungsimport, Zahlungsabgleich | Geld-/Steuerformat, Listen, Prüf- und Statusmuster |
| Kundenberatung | Hauptmodul | Gespraechserfassung, Empfehlungen, Follow-ups | Beratungsfall, Bedarf, Empfehlung, Rueckmeldung | Beratungsgespräche, Empfehlungen, Rueckmeldungen | Gespraech, Notizen, Follow-ups | Kontakte, Produkte, Bestellungen | Beratungs-API, Fall- und Verlaufsschnittstellen | Formular- und Verlaufsmuster |
| `shared` | Technischer Bereich | UI, Helpers, technische Services | Fachlich neutrale Wiederverwendung | Wiederverwendbare technische Bausteine | Keine fachlichen Writes | Mehrere Module | Gemeinsame technische APIs und Komponenten | Kernbereich |
| `system` | Technischer Bereich | Runtime, Trigger, Scheduler, Supervisor | Technische Laufzeit und Ausfuehrungslogik | Jobs, Trigger, technische Laufzeiten | Systemnahe Laufzeitlogik | Technische Zustandsdaten | Systeminterne technische Schnittstellen | Laufzeit-, Job- und Triggerbausteine |
## 4. Modulzuschnitt im Zielbild
### 4.1 ERP-Kern
Der ERP-Kern besteht aus:
- Kontakte
- Bestellungen
- Lager
- Artikel-Mapping
- Import / Integration
- Direktverkauf
Diese Module bilden die operative Kernverarbeitung. Sie duerfen Buchhaltung nur ueber klare Integrationsschnittstellen versorgen.
### 4.2 Buchhaltungssystem
Die Buchhaltung ist ein eigenes Hauptmodul mit eigener fachlicher Ownership.
Es erhaelt aus dem ERP:
- freigegebene Rechnungen oder Erlosereignisse
- Zahlungsinformationen
- relevante Debitoren-/Kreditoren-Referenzen
- Buchungsgrundlagen aus operativen Belegen
Es fuehrt selbst:
- Kontierung
- Hauptbuch
- Nebenbuecher
- OP-Verwaltung
- Zahlungslauf
- Mahnwesen
- Abschluss und Steuerlogik
### 4.3 Kundenberatung
Kundenberatung ist ein eigenes Hauptmodul fuer fall- und gespraechsbezogene Arbeit.
Es erhaelt aus dem ERP:
- Kundenstamm
- Kaufhistorie
- produktbezogene Referenzen
Es fuehrt selbst:
- Gespraechserfassung
- Bedarfserhebung
- Produktempfehlungen
- Rueckmeldungen
- Follow-up-Logik
## 5. `shared`-Regeln fuer das Zielbild
In `shared` gehoeren nur Bausteine, die fachlich neutral sind und in mehreren Modulen wiederverwendet werden koennen:
- UI-Komponenten und Layout-Bausteine
- Tabellen, Formulare, Statusdarstellung
- Validierung ohne Fachentscheidungen
- Datums-, Geld- und Formatierungshelfer
- technische API-Clients
- Logging, Auth- und Session-nahe Hilfen
- generische Fehler- und Ladezustandsmuster
Nicht in `shared` gehoeren:
- Buchhaltungsregeln
- Lagerlogik
- Beratungslogik
- Bestellfachlogik
- modulpezifische Dateninterpretation
## 6. Naechste Strukturierungsarbeit
Aus dieser Modulkarte folgen als naechste Dokumente:
1. Modulvertraege je Hauptmodul
2. Owned-DB-Register je Modul
3. Prozessvertraege fuer die Hauptprozesse je Modul
4. Liste der wiederverwendbaren `shared`-Bausteine
+239
View File
@@ -0,0 +1,239 @@
# Technische Architektur
Stand: 2026-04-08
Status: Verbindlicher Architektur-Master
Dokumentklasse: normative
## 1. Zweck und Geltung
Dieses Dokument ist die verbindliche Architekturvorgabe fuer die Entwicklung hochmodularer Webportale in diesem Repository. Es steuert Analyse, Implementierung, Refactoring und Review durch Mensch und LLM.
Es definiert verbindlich:
- Modul-, Prozess-, Frontend-, API- und Datenbankgrenzen
- Ownership und oeffentliche Schnittstellen
- Standard-Scope fuer Analyse und Implementierung
- Wiederverwendungs- und Redundanzregeln
- Regeln fuer neue Module, Submodule und zentrale Bausteine
Dieses Dokument ist kein Inventar konkreter Tabellen, Prozesse, Dateien oder Runtime-Artefakte. Solche Details liegen in den zustaendigen Modul-, Prozess- oder DB-Vertraegen.
## 2. Architekturprinzipien
### 2.1 Module
Ein Modul ist ein fachlich oder technisch gekapselter Contract, nicht nur eine Verzeichnisstruktur.
Verbindliche Regel:
- jedes Modul besitzt genau eine klar abgegrenzte Primaerverantwortung
- jedes Modul kapselt seine Domain-Logik, Datenzugriffe, externen Schnittstellen, internen Datenstrukturen und Speicherdetails
- jedes Modul definiert eine explizite oeffentliche Schnittstelle fuer andere Module
- jedes Modul besitzt definierte Owned Artefakte und Schreibrechte
- interne Implementierungen anderer Module duerfen nicht direkt genutzt werden
- jede Modulgrenze muss als potenzielle spaetere Service-Grenze behandelbar bleiben
### 2.2 Prozesse
Ein Prozess ist ein sequenzieller Hauptablauf innerhalb genau eines owning Hauptmoduls.
Verbindliche Regel:
- ein Hauptmodul besitzt beliebig viele Hauptprozesse; jeder Hauptprozess gehoert genau einem Hauptmodul
- ein Hauptprozess darf Submodule desselben Hauptmoduls nutzen
- fachliche Hauptprozesse ueber Hauptmodulgrenzen hinweg sind nicht zulaessig
- Batching, Parallelisierung, Retry, Resume, Fehlerklassifikation, Statusermittlung und Ergebnisvertrag gehoeren in den owning Prozessvertrag
- zusaetzliche harte Schreib-, Feld- oder Datenbankschranken sind nur zulaessig, wenn sie explizit im Prozess- oder DB-Vertrag festgelegt sind
### 2.3 Implementierung
Module, Prozesse und Sub-Prozesse sind technisch so scharf wie moeglich zu trennen.
Verbindliche Regel:
- jedes Modul besitzt eine interne API oder Zugriffsschicht
- Cross-Modul-Kommunikation erfolgt nur ueber definierte Modul-Schnittstellen
- direkte Zugriffe auf interne Implementierungen oder Daten anderer Module sind verboten
- Module, Prozesse und Sub-Prozesse muessen getrennt entwickelbar, testbar, ausrollbar, ueberwachbar, debugbar und optimierbar sein
- technische Kopplung zwischen Prozessen und Sub-Prozessen ist auf das notwendige Minimum zu reduzieren
- Module duerfen keine impliziten Abhaengigkeiten besitzen, die eine spaetere Extraktion verhindern
### 2.4 Frontend
Das Frontend ist bewusst minimalistisch. Ziel ist eine kleine Zahl konsistenter, wiederverwendbarer UI-Bausteine statt vieler seiten- oder feature-spezifischer Sonderkomponenten.
Verbindliche Regel:
- Seiten und Views komponieren Daten, Layouts und Komponenten, erzeugen aber keine Fachlogik
- UI-Komponenten duerfen keine fachliche Primaerlogik, fachliche Dateninterpretation oder eigenstaendige Fachwahrheit besitzen
- fachliche UI-Komponenten, fachliche Darstellungsregeln und fachlich interpretierende UI-Logik gehoeren zum owning Fachmodul
- fachlich neutrale UI-Komponenten, Layout-Komponenten, Formatierungshelfer, Interaktionsmuster und Frontend-Utilities gehoeren nach `shared`
- neue oder geaenderte Frontend-Komponenten muessen zuerst gegen bestehende Komponenten, Patterns und `shared`-Bausteine geprueft werden
- Varianten bestehender Komponenten sind ueber Props, Konfiguration oder dokumentierte Erweiterungspunkte umzusetzen, nicht durch Copy-Paste
- Karten, Tabellen, Filter, Tabs, Buttons, Statusanzeigen, Ladezustaende, Empty States, Fehlermeldungen und Detailansichten sind als wiederverwendbare Patterns zu behandeln
- seitenlokale Sonderkomponenten sind nur zulaessig, wenn sie nachweislich nicht sinnvoll wiederverwendbar sind
- API-Responses duerfen frontendnah komponiert sein, aber keine Fachlogik aus dem owning Modul in API oder Frontend verschieben
## 3. Modulmodell
### 3.1 Zulaessige Modulbereiche
Fachliche Hauptmodule:
- `zu definieren
Technische Modulbereiche:
- `system`
- `shared`
Verbindliche Regel:
- fachliche Hauptmodule besitzen Business-Logik und fachliche Ownership
- `system` enthaelt nur technische Runtime-, Start-, Trigger- und Laufzeitlogik
- `shared` enthaelt nur fachlich neutrale, wiederverwendbare technische Bausteine
- konkrete Submodule, Prozesse, Artefakte und Schnittstellen werden in Modulvertraegen dokumentiert, nicht in diesem Architektur-Master
### 3.2 `shared`
`shared` ist der zentrale technische Querschnittsbereich.
Verbindliche Regel:
- `shared` darf keine Business-Logik, Fachwahrheit oder primaere fachliche Ownership besitzen
- `shared` darf Services, Komponenten, Helper, technische Schnittstellen, UI-nahe und API-nahe Bausteine bereitstellen
- ein Baustein gehoert nur nach `shared`, wenn er fachlich neutral ist und keine Verantwortung eines Fachmoduls uebernimmt
- fachliche Regeln, Dateninterpretation, Statuslogik und Entscheidungen bleiben im owning Fachmodul
- technische Querschnittslogik ohne fachlichen Inhalt muss in `shared` oder einem dort dokumentierten zentralen Baustein liegen, sobald Wiederverwendung ueber mehr als eine Stelle absehbar ist
### 3.3 `admin`
`admin` ist kein Hauptmodul und besitzt keine fachliche Ownership.
Verbindliche Regel:
- Admin-Funktionen sind nur Bedienoberflaechen auf bestehende Hauptmodule
- Admin-spezifische Fachlogik liegt im owning Hauptmodul
- ein separates fachliches Modul `admin` ist nicht zulaessig
### 3.4 `public/api/`
`public/api/` ist die HTTP-Service- und Kompositionsschicht des modularen Monolithen.
Verbindliche Regel:
- `public/api/` besitzt keine fachliche Primaerverantwortung
- `public/api/` darf Modul- oder Submodul-Schnittstellen fuer konkrete Frontend-User-Stories zusammensetzen
- `public/api/` bleibt duenn und enthaelt nur HTTP-nahe und technische Kompositionslogik
- fachliche Ownership darf nicht aus Modulen in `public/api/` wandern
## 4. Datenbankgrenze
Die Plattform nutzt aktuell genau eine gemeinsame Datenbank. Diese gemeinsame Datenbank hebt Modul- und Ownership-Grenzen nicht auf.
Verbindliche Regel:
- jede Tabelle, View, materialisierte View und DB-Funktion besitzt genau eine primaere Ownership
- primaere Ownership folgt genau einem Hauptmodul oder einem technischen Shared-Bereich
- nur das owning Modul definiert Struktur, Write-Logik, Constraints und fachliche Semantik eines DB-Artefakts
- direkte Cross-Modul-Writes in fremde Owned Tabellen sind verboten
- andere Module duerfen fremde Artefakte nur ueber definierte Schnittstellen des owning Moduls nutzen
- physische DB-Splits sind kein aktueller Standard und beduerfen einer expliziten Architekturentscheidung
Operative DB-Referenz:
`docs/architektur/database/module_db_ownership.md`
Verbindliche Regel:
- konkrete DB-Artefaktlisten gehoeren in die DB-Ownership-Dokumentation, nicht in diesen Architektur-Master
- bei Analyse, Refactoring und Implementierung wird der DB-Scope zuerst ueber Modul plus Owned DB-Artefakte bestimmt
- fremde DB-Artefakte duerfen nur bei deklarierter Leseabhaengigkeit, FK-Beziehung oder Integrationspruefung in den Scope aufgenommen werden
## 5. Dokumentation und Lesescope
Aktive Dokumentation ist komponentennah.
Verbindliche Regel:
- aktive Modul- und Submodul-Dokumentation liegt unter `docs/modules/<modul>/...`
- aktive technische Shared-Dokumentation liegt unter `docs/modules/shared/...`
- technische Runtime-Dokumentation liegt unter `docs/modules/system/...`
- fachliche Hauptprozesse liegen innerhalb des owning Modulbaums
- historische oder abgeloeste Dokumentation liegt ausserhalb des aktiven Modulbaums
- `docs/architektur/technical_architecture.md` bleibt der einzige aktive Architektur-Master
Standard-Lesescope:
- Arbeit an einem Modul: `docs/architektur/technical_architecture.md` plus `docs/modules/<modul>/...`
- Arbeit an einem Submodul: `docs/architektur/technical_architecture.md` plus `docs/modules/<modul>/<submodul>/...`
- `docs/modules/shared/...`: nur bei betroffener Shared-Schnittstelle oder moeglicher Wiederverwendung
- `docs/modules/system/...`: nur bei Runtime-, Queue-, Scheduler-, Trigger- oder Supervisor-Themen
- andere Module: nur bei explizitem Integrationsbedarf
## 6. Wiederverwendung und Redundanzverbot
Vor jeder Aenderung an App, Frontend, UI-Komponenten, UI-Patterns, API, Prozesslogik, Datenzugriff, Infrastruktur oder Dokumentation ist Wiederverwendung vor Neuerstellung zu pruefen.
Ziel ist eine Architektur ohne redundante Fachlogik, technische Inselloesungen, doppelte UI-Komponenten oder mehrfach gepflegte Varianten desselben Musters.
Verbindliche Regel:
- zuerst bestehende Muster, Komponenten, Services, Modul-Schnittstellen und `shared`-Bausteine pruefen
- vorhandene passende Muster muessen wiederverwendet, erweitert oder als zentrale Schnittstelle bereitgestellt werden
- fachliche Inhalte gehoeren immer in das owning Fachmodul und werden von dort ueber definierte Schnittstellen bereitgestellt
- fachliche Logik darf nicht in `shared`, `public/api/`, `admin`, Skripte oder fachfremde Bereiche ausgelagert oder dupliziert werden
- fachlich neutrale technische Logik gehoert nach `shared`, sofern sie wiederverwendbar ist oder Wiederverwendung absehbar ist
- Frontend-Darstellung, Interaktionslogik, Layout, Formatierung und Zustandsdarstellung sind als bestehende oder neue zentrale UI-Komponenten beziehungsweise UI-Patterns in `shared` zu pruefen
- neue `shared`-Bausteine sind nur zulaessig, wenn sie keine Fachwahrheit enthalten, keine fachliche Primaerverantwortung uebernehmen und wiederverwendbar sind
- technische Querschnittsbausteine duerfen Module entkoppeln, aber keine fachlichen Entscheidungen aus owning Modulen herausziehen
- neue Loesungen sind nur zulaessig, wenn kein passendes bestehendes Muster, keine passende Modul-Schnittstelle und kein geeigneter `shared`-Baustein existiert
- Quickfixes, Workarounds, Copy-Paste-Varianten, seitenlokale UI-Duplikate und isolierte Sonderlogik fuer Einzelstellen sind verboten
- Redundanzvermeidung muss bei Analyse, Umsetzung, Review und Refactoring aktiv nachgewiesen werden koennen
## 7. Verbotene Strukturen
Nicht zulaessig sind:
- Module als reine Ordner ohne Contract
- direkte Cross-Modul-Zugriffe auf DB-Tabellen, interne Implementierungen oder Daten anderer Module
- versteckte oder implizite Modulabhaengigkeiten
- Vermischung mehrerer Primaerverantwortungen innerhalb eines Moduls
- parallele Fachlogik ausserhalb des owning Moduls
- redundante fachliche, technische oder UI-Loesungen, obwohl ein bestehendes Modul, eine Schnittstelle, ein Pattern oder ein `shared`-Baustein erweitert werden koennte
- neue generische Frameworks oder Orchestrator-Umbauten ohne explizite Architekturentscheidung
- implizite Schutz-, Schreib- oder Datenbankschranken ohne Prozess- oder DB-Vertrag
## 8. Erweiterungsregeln
Neue Hauptmodule, Submodule oder zentrale Bausteine duerfen nur eingefuehrt werden, wenn bestehende Strukturen die Verantwortung nicht sauber tragen koennen.
Prueffragen vor jeder Erweiterung:
1. Welches bestehende Hauptmodul ist primaer betroffen?
2. Reicht eine neue Datei, ein neues Submodul oder eine neue Schnittstelle im owning Hauptmodul aus?
3. Handelt es sich um fachlich neutrale Wiederverwendung, die nach `shared` gehoert?
4. Entsteht wirklich eine neue Primaerverantwortung mit eigenen Owned Artefakten und Schreibrechten?
5. Bleibt der Handshake zum Rest des Systems auf minimale Scope-Identifier begrenzt?
6. Reduziert der neue Schnitt Kopplung und Redundanz, statt sie zu erhoehen?
Verbindliche Regel:
- neue Hauptmodule sind nur zulaessig, wenn eine neue fachliche oder technische Primaerverantwortung entsteht, die nicht sauber in `company`, `ratings`, `groups`, `lists`, `system` oder `shared` aufgeht
- neue Submodule sind nur zulaessig, wenn innerhalb eines bestehenden Hauptmoduls eine klar isolierbare Verantwortung mit eigener Schnittstelle entsteht
- neue Prozess- und Sub-Prozess-Dokumente werden im owning Modulpfad angelegt und folgen `component.subcomponent.process_name`
- `admin`, `public/api/`, `scripts/` oder `lib/` begruenden kein neues fachliches Hauptmodul
- Bequemlichkeit, Dateigroesse oder kurzfristiger Umbauaufwand rechtfertigen kein neues Hauptmodul
- vor jeder normativen Architektur- oder Modulerweiterung muss dieses Dokument aktualisiert werden
## 9. Aenderungsregel
Jede strukturelle Aenderung an Hauptmodulen, Modulgrenzen, Prozesszuordnungen, Frontend-Prinzipien, API-Grenzen, DB-Ownership oder primaeren Ownership-Prinzipien muss zuerst in diesem Dokument normativ festgelegt und danach in Detaildokumenten nachgezogen werden.
Verbindlich ist:
- `docs/technical_architecture.md` ist der einzige aktive Architektur-Master des Repositories
- andere Dokumente duerfen diese Architektur erklaeren, anwenden oder historisieren, aber keine konkurrierende Master-Struktur definieren
- konkrete Inventare wie Tabellenlisten, Submodullisten, Prozesslisten oder Runtime-Artefakte werden in Detaildokumenten gepflegt und nicht hier dupliziert
@@ -52,7 +52,7 @@ Schritt 1 basiert auf drei Kern-Domaenen:
Kernaussage: Eine Bestellung kann mehrere Positionen enthalten; jede Position kann einer oder mehreren Chargen zugeordnet werden; jede Charge hat Bewegungen und MHD-Status.
## System Overview
## System Overview / Module
Schritt-1-Systemkomponenten:
+1 -1
View File
@@ -47,7 +47,7 @@ View:
Prozesse:
- `n8n.bestell-eingang-online-shop`
- `n8n.bestell-eingang-online-shop`kzz
- `order-import.php`
- `db.trigger.sales_order`
- `db.trigger.sales_order_line`