diff --git a/docs/konzepte/phase1-technisches-konzept.md b/docs/konzepte/phase1-technisches-konzept.md new file mode 100644 index 0000000..bb0db0a --- /dev/null +++ b/docs/konzepte/phase1-technisches-konzept.md @@ -0,0 +1,501 @@ +# Phase 1: DEV-Ist-Stand Tabellen- und Prozesskonzept +Stand: 2026-06-02 +Status: Verbindlicher DEV-Ist-Stand fuer Phase 1 + +## 1. Uebersicht + +Dieses Dokument beschreibt die technische Wahrheit des aktuellen DEV-Standes auf Basis von: + +- `docs/CONCEPT.md` +- `docs/SCHEMA_PHASE1.sql` +- `docs/PROCESS_PHASE1.md` +- `db/migrations/0001_phase1_core.sql` bis `db/migrations/0006_phase1_otc_products.sql` +- `order-import.php` +- `n8n/exports/current/*.json` + +Wichtige Einordnung: + +- `SCHEMA_PHASE1.sql` bleibt ein Draft und ist nicht identisch mit der live DB. +- Die live DB hat 18 Base Tables und 1 View. +- Die Forecast-Erweiterung aus `0003_phase1_inventory_forecast.sql` ist im live Schema nicht aktiv. +- Die Doku unten trennt deshalb zwischen implementiertem Ist-Stand und nicht gefundenen Zielbild-Teilen. + +Tabellen: + +- `party` +- `address` +- `contact` +- `product` +- `sellable_item` +- `external_item_alias` +- `sellable_item_component` +- `warehouse` +- `location` +- `stock_lot` +- `payment_method` +- `shipping_method` +- `sales_order` +- `sales_order_line` +- `stock_move` +- `sales_order_line_lot_allocation` +- `audit_log` +- `outbound_webhook_event` + +View: + +- `v_stock_lot_balance` + +Prozesse: + +- `n8n.bestell-eingang-online-shop` +- `order-import.php` +- `db.trigger.sales_order` +- `db.trigger.sales_order_line` +- `db.trigger.stock_move` +- `db.trigger.product` +- `n8n.adressetikette-erstellen` +- Outbox-Ablage ueber `outbound_webhook_event` + +## 2. Zweck + +Dieses Dokument beschreibt die aktuelle DB- und Prozesswahrheit fuer Phase 1 auf dem DEV-Stand. + +Es ist eine technische Uebersicht fuer Analyse, Umsetzung und Review. + +Nicht Bestandteil dieses Dokuments sind: + +- API-Details +- UI-Details +- Migrations-Rollout-Planung +- offene Zielbilddiskussionen ausserhalb des aktuellen DEV-Standes + +## 3. Normatives Datenmodell + +### 3.1 `party` + +Zweck: + +- gemeinsamer Kontaktstamm fuer Kunden und Lieferanten + +Ist-Stand: + +- `type` ist `customer`, `supplier` oder `both` +- `status` ist `active` oder `inactive` +- `email` wird fuer Match/Upsert genutzt + +### 3.2 `address` + +Zweck: + +- Rechnungs- und Lieferadressen pro `party` + +Ist-Stand: + +- `type` ist `billing` oder `shipping` +- `raw_payload` speichert die Rohdaten des Eingangsdokuments + +### 3.3 `contact` + +Zweck: + +- zusaetzliche Ansprechpartnerdaten pro `party` + +Ist-Stand: + +- keine weitere Prozesslogik im Code gefunden + +### 3.4 `product` + +Zweck: + +- lagergefuehrtes Produkt mit Bestand und Charge + +Ist-Stand: + +- `sku` ist eindeutig +- beim Insert erzeugt der Trigger `trg_product_bootstrap_lots` sofort eine `current`- und eine `open`-Charge + +### 3.5 `sellable_item` + +Zweck: + +- verkaufbarer Shop-Artikel + +Ist-Stand: + +- Bestellungen referenzieren zuerst `sellable_item` +- der Import kann neue `sellable_item`-Datensaetze automatisch erzeugen + +### 3.6 `external_item_alias` + +Zweck: + +- Mapping externer Shop-Daten auf `sellable_item` + +Ist-Stand: + +- `source_system = 'wix'` +- Aufloesung ueber Artikelnummer, normalisierten Titel oder Originaltitel + +### 3.7 `sellable_item_component` + +Zweck: + +- Stueckliste eines `sellable_item` auf lagergefuehrte Produkte + +Ist-Stand: + +- `qty_per_item > 0` +- wird vom Import und von Seed-Skripten gepflegt + +### 3.8 `warehouse` + +Zweck: + +- oberer Lagerstandort + +Ist-Stand: + +- im Seed ist ein Hauptlager vorgesehen + +### 3.9 `location` + +Zweck: + +- konkreter Lagerort innerhalb eines `warehouse` + +Ist-Stand: + +- `type` ist `storage`, `receiving`, `dispatch` oder `adjustment` + +### 3.10 `stock_lot` + +Zweck: + +- Charge eines Produkts + +Ist-Stand: + +- `status` ist `open`, `current` oder `closed` +- pro Produkt sind `current` und `open` per Unique Index abgesichert +- die live DB hat keine `sellout_date`- oder `warning_state`-Spalten +- die Sicht `v_stock_lot_balance` ist die Bestandswahrheit + +### 3.11 `payment_method` + +Zweck: + +- normalisierte Zahlungsart + +Ist-Stand: + +- Seed-Werte im DEV: `card`, `twint`, `bank_transfer`, `cash`, `paypal` + +### 3.12 `shipping_method` + +Zweck: + +- normalisierte Versandart + +Ist-Stand: + +- Seed-Werte im DEV: `post_standard`, `pickup` + +### 3.13 `sales_order` + +Zweck: + +- Bestellkopf fuer Online-Import und Direktverkauf + +Ist-Stand: + +- `external_ref` ist eindeutig +- `order_source` ist `wix` oder `direct` +- `party_id` ist nullable +- `payment_status` ist faktisch auf `paid` beschraenkt +- `shipping_date` existiert im live Schema und wird per Trigger berechnet, falls leer + +### 3.14 `sales_order_line` + +Zweck: + +- Bestellposition + +Ist-Stand: + +- `qty_cancelled` und `line_status` werden per Trigger synchronisiert +- `sellable_item_id` kann `NULL` sein + +### 3.15 `stock_move` + +Zweck: + +- Bewegungsjournal fuer Zu- und Abgaenge + +Ist-Stand: + +- `move_type` ist `in`, `out`, `transfer` oder `adjustment` +- Bewegungen werden vor Insert/Update gegen Negativbestand validiert + +### 3.16 `sales_order_line_lot_allocation` + +Zweck: + +- explizite Rueckverfolgung zwischen Bestellposition und Charge + +Ist-Stand: + +- `allocation_status` ist `reserved`, `allocated`, `released` oder `cancelled` +- im Import wird `allocated` verwendet + +### 3.17 `audit_log` + +Zweck: + +- technische Historisierung + +Ist-Stand: + +- im Schema vorhanden +- im geprüften PHP- und n8n-Flow nicht als zentraler Pflichtpfad sichtbar + +### 3.18 `outbound_webhook_event` + +Zweck: + +- Outbox fuer ERP-Events + +Ist-Stand: + +- Spalten: `event_type`, `event_key`, `aggregate_type`, `aggregate_id`, `payload`, `status`, `attempt_count`, `next_attempt_at`, `last_attempt_at`, `last_error`, `created_at`, `sent_at` +- `fn_enqueue_event(...)` schreibt idempotent in diese Tabelle +- ein Dispatcher/Worker fuer die Auslieferung wurde im geprüften DEV-Baum nicht gefunden + +### 3.19 `v_stock_lot_balance` + +Zweck: + +- berechneter Chargensaldo + +Ist-Stand: + +- die Sicht ermittelt `qty_in`, `qty_out` und `qty_net` +- sie ist die Grundlage fuer Bestandspruefung und Auto-Switch + +## 4. Normatives Prozessmodell + +Alle Prozesse gehoeren fachlich zur Phase-1-Bestell-, Lager- und Integrationslogik. + +### 4.1 `n8n.bestell-eingang-online-shop` + +Fachliche Aufgabe: + +- E-Mail auf `Neue Bestellung` erkennen +- Payload in ERP-JSON umformen +- JSON per HTTP POST an `order-import.php` senden + +Liest: + +- IMAP-Eingang +- n8n-Extraktionslogik + +Schreibt: + +- keine DB direkt + +Fachliche Wirkung: + +- n8n ist hier nur die Integrationshuelle fuer den ERP-Import + +### 4.2 `order-import.php` + +Fachliche Aufgabe: + +- eingehende Bestelldaten idempotent in die ERP-DB schreiben +- Kontakte, Adressen, Bestellkopf, Positionen und Chargenrueckverfolgung aufbauen +- bei Reimport vorhandene Allokationen zurueckbuchen +- nach Commit die Label- und Excel-Flows direkt anstossen + +Liest: + +- `.env` +- eingehendes Webhook-JSON +- `party`, `address`, `sales_order`, `sales_order_line` +- `external_item_alias` +- `sellable_item_component` +- `stock_lot` +- `v_stock_lot_balance` + +Schreibt: + +- `party` +- `address` +- `sales_order` +- `sales_order_line` +- `sellable_item` +- `external_item_alias` +- `sellable_item_component` +- `warehouse` +- `location` +- `stock_lot` +- `stock_move` +- `sales_order_line_lot_allocation` + +Fachliche Wirkung: + +- die Bestellung wird im ERP gespeichert und mit Lagerbewegung verknuepft +- vorhandene Allokationen derselben `external_ref` werden vor dem Neuimport rueckwaerts gebucht +- nach erfolgreichem Commit werden Label- und Excel-N8N-Flows direkt per HTTP ausgelöst + +### 4.3 `db.trigger.sales_order` + +Fachliche Aufgabe: + +- `shipping_date` aus `order_date` ableiten +- `order.imported` und `order.cancelled.full` in die Outbox schreiben +- `direct`-Bestellungen automatisch mit `DIR-...` Nummer versehen + +Liest: + +- `sales_order` +- `fn_next_business_day(timestamp)` + +Schreibt: + +- `sales_order.shipping_date` +- `outbound_webhook_event` + +Fachliche Wirkung: + +- fehlendes `shipping_date` wird auf den naechsten Werktag gesetzt +- neue Auftraege und Vollstornos werden als Event in die Outbox gelegt +- direkte Auftraege erhalten bei leerer Nummer automatisch einen `DIR-...`-Ref + +### 4.4 `db.trigger.sales_order_line` + +Fachliche Aufgabe: + +- `line_status` aus `qty_cancelled` ableiten +- Partial-Cancel-Event enqueuen + +Liest: + +- `sales_order_line` + +Schreibt: + +- `sales_order_line.line_status` +- `outbound_webhook_event` + +Fachliche Wirkung: + +- `allocated`, `partially_cancelled` und `cancelled` werden deterministisch gesetzt +- ein Wechsel auf `partially_cancelled` erzeugt ein `order.cancelled.partial`-Event + +### 4.5 `db.trigger.stock_move` + +Fachliche Aufgabe: + +- Negativbestand verhindern +- Chargenwechsel bei leerer `current`-Charge ausloesen + +Liest: + +- `stock_move` +- `stock_lot` +- `v_stock_lot_balance` + +Schreibt: + +- `stock_lot` +- `outbound_webhook_event` + +Fachliche Wirkung: + +- `out`-Bewegungen sind nur auf `current`-Chargen erlaubt +- bei `qty_net <= 0` wird die naechste `open`-Charge `current` +- danach wird wieder eine neue `open`-Charge angelegt + +### 4.6 `db.trigger.product` + +Fachliche Aufgabe: + +- neue Produkte sofort chargenfaehig machen + +Liest: + +- `product` + +Schreibt: + +- `stock_lot` + +Fachliche Wirkung: + +- jedes neue Produkt bekommt direkt eine `current`- und eine `open`-Charge + +### 4.7 `n8n.adressetikette-erstellen` + +Fachliche Aufgabe: + +- Lieferadresse entgegennehmen +- Felder normalisieren +- HTML/CSS in PDF/PNG umsetzen +- Ergebnis per SFTP hochladen + +Liest: + +- Webhook-Input +- Gotenberg / pdf2png / SFTP-Ziele + +Schreibt: + +- keine ERP-DB + +Fachliche Wirkung: + +- eigenstaendiger Ausgabekanal fuer Versandetiketten + +### 4.8 Outbox-Ablage ueber `outbound_webhook_event` + +Fachliche Aufgabe: + +- Eventdaten fuer spaetere Auslieferung sammeln + +Liest: + +- `fn_enqueue_event` + +Schreibt: + +- `outbound_webhook_event` + +Fachliche Wirkung: + +- Queue wird im geprüften DEV-Stand befuellt +- ein dazugehoeriger Dispatcher/Worker wurde im Code nicht gefunden + +## 5. Technische Einbettung + +Die reale Phase-1-Logik bildet einen kleinen operativen Kern mit vier Schwerpunkten: + +1. Kontakt- und Adressstamm +2. Bestellimport via n8n -> PHP -> DB +3. Chargen- und Lagerbewegung mit Trigger-Logik +4. Label-Generierung als separater n8n-Ausgang + +Wichtige Abweichungen zum alten Zielbild: + +- Sellout-Forecast ist im live Schema nicht aktiv. +- Ein Dispatcher fuer `outbound_webhook_event` wurde im geprüften DEV-Baum nicht gefunden. +- Eine separate Direct-Sale-Eingabestrecke wurde im geprüften DEV-Baum nicht gefunden; vorhanden ist nur die DB-/Trigger-Unterstuetzung fuer `order_source = direct`. + +## 6. Kurzfazit + +Die aktuelle technische Wahrheit von Phase 1 ist auf Nachvollziehbarkeit und direkte Integrationspfade ausgerichtet: + +- Bestellungen werden idempotent gespeichert +- Lagerbestand wird ueber Chargen und Bewegungen abgebildet +- Rueckverfolgung erfolgt ueber explizite Allokationen +- n8n importiert und erzeugt Labels direkt +- Outbox-Events werden geschrieben, aber ein Dispatcher ist im geprüften DEV-Stand nicht vorhanden