7.2 KiB
7.2 KiB
Phase 1 Process Spec
Dokumentstatus
- Typ:
operational - Detaillierungsgrad: Event- und Ablaufebene
- Zugehoerige Komponenten-Spezifikation:
/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/SPEC_PHASE1_COMPONENTS.md - Normative Quelle:
/Users/mathias/Documents/Dokumente Chouchou/Codebases/erp_naurua/docs/CONCEPT.md
Zweck
Diese Datei ist die operative Umsetzungsvorlage fuer Schritt 1. Sie uebersetzt die fachlichen Entscheidungen in konkrete Ablaufregeln auf Event-Ebene.
Geltungsbereich
- Bestellimport aus Wix via n8n Webhook
- Kontakt- und Adressanlage
- Chargenzuordnung und Lagerbewegung
- Teil-/Vollstorno inklusive Gegenbuchung
- Outbound-Webhook ERP -> n8n
- Direktverkauf als manuelle Sammelerfassung (ohne Kundenregistrierung)
Begriffe
allocated: Charge ist zugeordnet und Lagerabgang ist bereits gebucht.reserved: Optionaler Zwischenstatus, in Phase 1 standardmaessig nicht verwendet.open-Charge: vorbereitete naechste Charge je Produkt.current-Charge: aktive Abgangscharge je Produkt.closed-Charge: abgeschlossene Charge.order_source: Herkunft der Bestellung (wixoderdirect).
Harte Invarianten
- Pro Produkt genau eine
current-Charge. - Pro Produkt genau eine
open-Charge. opendarf nie fehlen.- Verkaufsabgang nie ohne Charge.
- Negativbestand auf Charge ist nicht erlaubt.
- Datensaetze werden nicht geloescht; Statusaenderung statt Delete.
- Direktverkaeufe haben
external_refmit PraefixDIR-.
Prozess A: order.imported (Inbound Webhook, Quelle wix)
Input
Webhook-JSON aus dem Shop (Bestellung + lineItems).
Schritte
- Idempotenz pruefen ueber
BestellungNr(sales_order.external_ref). sales_orderper Upsert speichern (order_source = wix,payment_status = paid).party,contact,addressspeichern/aktualisieren.- Rechnungsadresse darf
NULLsein; Lieferadresse normal speichern. - Pro
lineIteminternensellable_itemaufloesen:- zuerst ueber
external_article_number - fallback ueber normalisierten Titel
- zuerst ueber
- Falls kein Mapping existiert:
- Position trotzdem speichern (
raw_external_*gesetzt) - in Audit als Mapping-Luecke markieren
- Position trotzdem speichern (
- Fuer gemappte Position jede
sellable_item_componentaufloesen. - Fuer jedes benoetigte Lagerprodukt Abgang auf
current-Charge buchen:stock_movemitmove_type = outsales_order_line_lot_allocationmitallocation_status = allocated
- Chargensaldo gegen
v_stock_lot_balancepruefen (kein negativer Bestand). - Wenn
currentnach Abgangqty_net <= 0hat:- alte
currentaufclosed - vorhandene
openaufcurrent - neue
openautomatisch erzeugen (ohnelot_number)
- alte
- Outbound-Event in Queue schreiben:
order.imported. - Audit-Log schreiben (import + allocation + auto-switch falls erfolgt).
Ergebnis
Bestellung ist vollstaendig erfasst, Chargen sind zugeordnet, Lager ist aktualisiert.
Prozess E: direct.sale.captured (manuelle ERP-Erfassung)
Trigger
Tages-/Sammelverkauf wird im ERP erfasst (z. B. Marktverkauf).
Schritte
- ERP erzeugt interne Bestellnummer mit Praefix
DIR-(z. B.DIR-20260329-00017). sales_orderspeichern mit:order_source = directparty_id = NULL(Laufkundschaft ohne Kontaktanlage)payment_status = paid
- Mengen je Produkt als
sales_order_linespeichern. - Gesamtpreis brutto wird auf Gesamtmenge verteilt und als
unit_priceje Zeile gespeichert. - Zahlungsart wird aus den Direktverkauf-Methoden gespeichert (
twint,cash,paypal,bank_transfer). - Lagerabgang und Chargenzuordnung laufen identisch zu Prozess A.
- Outbound-Event wird wie bei Import in die Queue gestellt (
order.importedmitorderSource = direct). - Audit-Log schreibt
action = direct_sale_captured.
Prozess B: order.cancelled.partial
Trigger
Teil-Storno fuer einzelne Position oder Teilmenge.
Schritte
- Ziel-
sales_order_lineladen,cancel_qtyvalidieren. qty_cancellederhoehen,line_status = partially_cancelledsetzen.- Bereits
allocatedMenge in gleicher Hoehe rueckbuchen:stock_movemitmove_type = adjustmentund positivem Eingang- gleiche
lot_idwie Ursprungsallokation
- Zugehoerige
sales_order_line_lot_allocationaufcancelledsetzen oder anteilig splitten. - Wenn Gesamtstorno der Position erreicht ist,
line_status = cancelled. - Wenn alle Positionen storniert sind,
sales_order.order_status = cancelled. - Outbound-Event in Queue schreiben:
order.cancelled.partial. - Audit-Log schreiben.
Prozess C: order.cancelled.full
Trigger
Vollstorno der Bestellung.
Schritte
- Alle offenen Positionen iterieren.
- Je Position Restmenge stornieren wie in Prozess B.
sales_order.order_status = cancelled,cancelled_at,cancelled_reasonsetzen.- Outbound-Event in Queue schreiben:
order.cancelled.full. - Audit-Log schreiben.
Prozess D: lot.auto_switched
Trigger
Nach Abgang ist current-Charge leer (qty_net <= 0).
Schritte
- Alte
currentaufclosedsetzen. - Existierende
openaufcurrentsetzen. - Neue
openfuer dasselbe Produkt anlegen:lot_number = NULLstatus = open
- Outbound-Event in Queue schreiben:
lot.auto_switched. - Audit-Log schreiben.
Outbound Webhook ERP -> n8n
Ziel
n8n erhaelt statusrelevante Bestell- und Chargenupdates aus dem ERP.
Delivery Model
- Outbox/Queue in DB:
outbound_webhook_event. - Worker liest
pending/failednachnext_attempt_at. - HTTP POST an n8n Incoming Webhook.
- Bei 2xx:
sent. - Bei Fehler: Retry mit Backoff, danach
dead_letter.
Security
- Header
X-ERP-Signature: HMAC-SHA256 ueber Raw Body. - Header
X-ERP-Event: Eventtyp. - Header
X-ERP-Event-Key: Idempotenzschluessel.
Event Payload (Basis)
{
"eventType": "order.imported",
"eventKey": "order.imported:10466:2026-03-29T17:00:00Z",
"occurredAt": "2026-03-29T17:00:00Z",
"order": {
"externalRef": "10466",
"orderSource": "wix",
"orderStatus": "imported",
"paymentStatus": "paid",
"amounts": {
"net": 49.95,
"shipping": 4.95,
"tax": 0,
"discount": 0,
"total": 54.90,
"currency": "CHF"
}
},
"lines": [
{
"lineNo": 1,
"qty": 1,
"qtyCancelled": 0,
"status": "allocated",
"allocations": [
{
"productSku": "REISHI_FLASCHE",
"lotNumber": "2412.003",
"qty": 1
}
]
}
]
}
Retry Policy (Standardannahme)
- Maximal 10 Versuche.
- Exponential Backoff: 1m, 2m, 4m, 8m, ... bis 12h.
- Danach
dead_letter+ operativer Alert.
Abverkauf-Warnung (Phase 1)
- Die Prognose wird im ERP intern berechnet (
fn_refresh_sellout_forecast). - Ausgabe erfolgt als Felder/Status fuer die UI (
sellout_date,warning_state), nicht per E-Mail. - Warnlogik:
due_60dbei Abverkaufdatum in <= 60 Tagen,due_nowbei heute/ueberfaellig.
Offene Details (werden spaeter mit realem n8n-Setup finalisiert)
- Finale n8n Ziel-URL pro Umgebung (dev/staging/prod).
- Secret-Rotation fuer Signatur.
- Dead-letter-Verarbeitung (manuell oder Requeue-Button).
- Exakte Liste weiterer Events fuer spaetere Module.