Kunden aus Shopware automatisch in ERPNext anlegen #28

Closed
opened 2026-05-10 16:46:22 +00:00 by csaeum · 1 comment
csaeum commented 2026-05-10 16:46:22 +00:00 (Migrated from gitlab.localdomain)

Beim Bestellungsimport den Shopware-Kunden automatisch als ERPNext Customer anlegen:

  • Shopware Customer ID in Custom Field am ERPNext Customer speichern
  • Bereits vorhandene Kunden (gleiche E-Mail) nicht doppelt anlegen
  • Felder: Name, E-Mail, Adresse, Umsatzsteuer-ID (falls vorhanden)
  • Kundentyp aus B2B/B2C-Erkennung setzen (Individual / Company)
  • Implementierung in shopware6/api/customer.py
Beim Bestellungsimport den Shopware-Kunden automatisch als ERPNext Customer anlegen: - Shopware Customer ID in Custom Field am ERPNext Customer speichern - Bereits vorhandene Kunden (gleiche E-Mail) nicht doppelt anlegen - Felder: Name, E-Mail, Adresse, Umsatzsteuer-ID (falls vorhanden) - Kundentyp aus B2B/B2C-Erkennung setzen (Individual / Company) - Implementierung in `shopware6/api/customer.py`
Owner

Umsetzung

Ersetzt #27s bisherigen reinen E-Mail-Lookup durch echte Resolve-oder-Anlegen-Logik. Keine zusätzlichen Shopware-API-Aufrufe nötig — #27 lädt orderCustomer/billingAddress/deliveries[].shippingOrderAddress bereits vollständig.

Wichtigster Fund vor dem Bauen: Dieses Repo hat bereits eine geteilte Basisklasse EcommerceCustomer (ecommerce_integrations/controllers/customer.py), die auch der bestehende Shopify-Connector nutzt. shopware6/customer.py:ShopwareCustomer ist nur eine dünne Unterklasse davon — kein neuer Mechanismus.

Auflösungsreihenfolge in get_or_create_customer(order_data, storefront):

  1. Bestehender Customer über shopware_customer_id (Custom Field, primärer Schlüssel).
  2. Sonst E-Mail-Abgleich gegen bestehenden Customer (Contact Email → Dynamic Link) — bei Treffer wird shopware_customer_id nachgetragen, keine Dublette.
  3. Sonst Neuanlage: Customer + Billing-Address + (nur falls abweichend) Shipping-Address + Contact. customer_type/tax_id wiederverwenden #25s determine_customer_tax_treatment(). Fehlende Straße/Stadt im Adress-Payload wirft frappe.throw() — keine erfundenen Platzhalter.

Zwei echte Bugs live gefunden und behoben

  1. Selling Settings.customer_group-Fallback war unmöglich: get_root_of("Customer Group") liefert immer den Wurzelknoten, der per Nested-Set-Definition ein Gruppen-Typ ist — ERPNext-Core lehnt das für Customer.customer_group grundsätzlich ab. Kein sicherer generischer Fallback existiert. Fix: fail-loud statt raten — Selling Settings > Standard-Kundengruppe muss auf eine echte Blattgruppe konfiguriert sein, sonst klare Fehlermeldung.

  2. Upstream-Bug in der geteilten Basisklasse EcommerceCustomer.sync_customer(): "customer_type": _("Individual") übersetzt den Wert vor dem Speichern — auf einer deutschsprachigen Site wird literal "Einzelperson" gespeichert, was ERPNext-Core ablehnt (die Select-Optionen bleiben unübersetzt englisch). Betrifft nicht nur Shopware, sondern jeden Connector auf EcommerceCustomer (auch Shopify) auf jeder nicht-englischen Site. Da wir noch keinen PR an Frappe senden und uns weiter mit Upstream syncen: nicht in der geteilten Basisklasse gefixt. Stattdessen: Issue #34 bei uns + frappe/ecommerce_integrations#465 upstream, lokaler Override in shopware6/customer.py bis der Fix übernommen ist.

  3. Eigener Bug (nicht upstream): ERPNext legt beim Speichern eines Customers mit gesetzter email_id automatisch selbst einen Contact an (Customer.on_update() → create_primary_contact()) — führte zu doppelten Contacts pro neuem Kunden. Fix: Customer.email_id bewusst nicht setzen, da die eigene E-Mail-Dedup-Logik ohnehin die Contact Email-Tabelle liest.

Live-Verifikation

Alle Fälle gegen die echte Testbench durchgespielt, jeweils in einer separaten Konsolen-Session unabhängig nachgeprüft: neuer B2C-Kunde, neuer B2B-Kunde (Firma + USt-IdNr., abweichende Lieferadresse → zwei Adressen), Dedup per shopware_customer_id, E-Mail-Backfill auf bestehenden Kunden, Fail-loud bei fehlender Straße/Stadt, identische Rechnungs-/Lieferadresse → nur eine Address, sowie ein voller import_orders()-Lauf, der bei unbekanntem Kunden jetzt korrekt eine Sales Order statt eines Skips erzeugt. Alle Testdaten anschließend gelöscht und unabhängig als entfernt bestätigt.

Details: docs/architecture.md#issue-28

## Umsetzung Ersetzt #27s bisherigen reinen E-Mail-Lookup durch echte Resolve-oder-Anlegen-Logik. Keine zusätzlichen Shopware-API-Aufrufe nötig — #27 lädt `orderCustomer`/`billingAddress`/`deliveries[].shippingOrderAddress` bereits vollständig. **Wichtigster Fund vor dem Bauen**: Dieses Repo hat bereits eine geteilte Basisklasse `EcommerceCustomer` (`ecommerce_integrations/controllers/customer.py`), die auch der bestehende Shopify-Connector nutzt. `shopware6/customer.py:ShopwareCustomer` ist nur eine dünne Unterklasse davon — kein neuer Mechanismus. **Auflösungsreihenfolge** in `get_or_create_customer(order_data, storefront)`: 1. Bestehender Customer über `shopware_customer_id` (Custom Field, primärer Schlüssel). 2. Sonst E-Mail-Abgleich gegen bestehenden Customer (`Contact Email` → `Dynamic Link`) — bei Treffer wird `shopware_customer_id` nachgetragen, keine Dublette. 3. Sonst Neuanlage: Customer + Billing-Address + (nur falls abweichend) Shipping-Address + Contact. `customer_type`/`tax_id` wiederverwenden #25s `determine_customer_tax_treatment()`. Fehlende Straße/Stadt im Adress-Payload wirft `frappe.throw()` — keine erfundenen Platzhalter. ## Zwei echte Bugs live gefunden und behoben 1. **`Selling Settings.customer_group`-Fallback war unmöglich**: `get_root_of("Customer Group")` liefert immer den Wurzelknoten, der per Nested-Set-Definition ein Gruppen-Typ ist — ERPNext-Core lehnt das für `Customer.customer_group` grundsätzlich ab. Kein sicherer generischer Fallback existiert. Fix: fail-loud statt raten — `Selling Settings > Standard-Kundengruppe` muss auf eine echte Blattgruppe konfiguriert sein, sonst klare Fehlermeldung. 2. **Upstream-Bug in der geteilten Basisklasse** `EcommerceCustomer.sync_customer()`: `"customer_type": _("Individual")` übersetzt den Wert vor dem Speichern — auf einer deutschsprachigen Site wird literal `"Einzelperson"` gespeichert, was ERPNext-Core ablehnt (die Select-Optionen bleiben unübersetzt englisch). Betrifft nicht nur Shopware, sondern jeden Connector auf `EcommerceCustomer` (auch Shopify) auf jeder nicht-englischen Site. Da wir noch keinen PR an Frappe senden und uns weiter mit Upstream syncen: **nicht** in der geteilten Basisklasse gefixt. Stattdessen: [Issue #34](https://git.web-seo-consulting.eu/Frappe-Projekte/ecommerce_integrations/issues/34) bei uns + [frappe/ecommerce_integrations#465](https://github.com/frappe/ecommerce_integrations/issues/465) upstream, lokaler Override in `shopware6/customer.py` bis der Fix übernommen ist. 3. **Eigener Bug** (nicht upstream): ERPNext legt beim Speichern eines Customers mit gesetzter `email_id` automatisch selbst einen Contact an (`Customer.on_update() → create_primary_contact()`) — führte zu doppelten Contacts pro neuem Kunden. Fix: `Customer.email_id` bewusst nicht setzen, da die eigene E-Mail-Dedup-Logik ohnehin die `Contact Email`-Tabelle liest. ## Live-Verifikation Alle Fälle gegen die echte Testbench durchgespielt, jeweils in einer separaten Konsolen-Session unabhängig nachgeprüft: neuer B2C-Kunde, neuer B2B-Kunde (Firma + USt-IdNr., abweichende Lieferadresse → zwei Adressen), Dedup per `shopware_customer_id`, E-Mail-Backfill auf bestehenden Kunden, Fail-loud bei fehlender Straße/Stadt, identische Rechnungs-/Lieferadresse → nur eine Address, sowie ein voller `import_orders()`-Lauf, der bei unbekanntem Kunden jetzt korrekt eine Sales Order statt eines Skips erzeugt. Alle Testdaten anschließend gelöscht und unabhängig als entfernt bestätigt. Details: [docs/architecture.md#issue-28](https://git.web-seo-consulting.eu/Frappe-Projekte/ecommerce_integrations/src/branch/shopware6-dach/docs/architecture.md#issue-28-automatically-create-erpnext-customers-from-shopware-orders)
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Frappe-Projekte/ecommerce_integrations#28
No description provided.