Technische Eigenschaften (Shopware Property Groups) in ERPNext abbilden und synchronisieren #37

Open
opened 2026-08-23 14:00:47 +00:00 by csaeum · 2 comments
Owner

Ziel

Shopwares natives Eigenschaften-Modul (property_group/property_group_option, Storefront-Tab "Eigenschaften") in ERPNext abbildbar machen. Konkretes Beispiel aus der Praxis: "Gerbung" mit mehreren gleichzeitig ausgewählten Werten (z. B. "Gerbung zertifiziert nach ISO 9001:2015", "Naturleder", "chromfrei", "vegetabile Nachgerbung").

Wichtig: das ist nicht dasselbe wie die varianten-treibenden Optionen aus #14/#19 (configuratorSettings) — Shopware trennt "Eigenschaften" (filterbar, mehrere Werte gleichzeitig möglich, erzeugen keine Variante) klar von "Konfigurator-Optionen" (erzeugen jeweils eine eigene Variante). Dieses Issue betrifft nur Erstere.

Design: zwingend zwei Phasen, in dieser Reihenfolge

Explizite Nutzer-Entscheidung: da in bestehenden Shopware-Shops schon Eigenschaftswerte gepflegt sind, darf ERPNext erst dann zum führenden System werden, wenn es Shopwares bestehende Daten vollständig gespiegelt hat — nicht andersherum. Ein reiner Push (ERPNext → Shopware) von Anfang an würde bestehende Shopware-Werte, die noch nicht in ERPNext erfasst sind, per Full-Replace stillschweigend löschen (siehe Kommentar unten).

Phase 1 — Pull: Shopwares bestehende Eigenschaften nach ERPNext lesen, wiederholbar (kein einmaliges Einweg-Skript)

  • Pro Artikel product.properties live aus Shopware lesen (m2m-Relation zu property_group_option, inkl. Namensauflösung über associations: {"properties": {"associations": {"group": {}}}}).
  • Als Item Ecommerce Property-Zeilen in ERPNext anlegen bzw. aktualisieren — Dedup über (Artikel, property_group, property_value), damit ein wiederholter Lauf keine Duplikate erzeugt.
  • Muss beliebig oft erneut ausführbar sein (Re-Sync), solange ERPNext für einen Artikel/Bereich noch nicht als vollständig/geprüft gilt — kein Migrations-Einmalskript.

Phase 2 — Push: ERPNext → Shopware, erst NACHDEM Phase 1 für den jeweiligen Artikel abgeschlossen und geprüft ist

  • Referenzmuster (Marcel-Fork, siehe unten): Full-Replace-PATCH properties: [{id: ...}], IDs über get_or_create_property_group()/get_or_create_property_option() (Live-Suche nach Name/GroupId+Name vor Neuanlage, keine Duplikate; Neuanlage nur bei echtem Fehlen, mit deterministischer MD5-Hash-ID).
  • Zu klären: braucht es ein Flag/Status pro Artikel (oder pro Eigenschaftsgruppe), ob Phase 1 (nur lesen) oder Phase 2 (ERPNext führend, pushen aktiv) gilt — damit nie versehentlich gepusht wird, bevor die Migration für diesen Artikel abgeschlossen ist?

Referenzcode (Marcel-Fork)

Drei zusammenspielende DocTypes:

  • Ecommerce Property Group (ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_property_group/) — Stammdaten: Eigenschaftsname, Anzeigename DE/EN, filterable-Flag, sync_to_shopware-Flag, Kind-Tabelle mit den möglichen Werten.
  • Ecommerce Property Option — Kind-Tabelle der möglichen Werte je Gruppe (Wert + Anzeige DE/EN).
  • Item Ecommerce Property (Kind-Tabelle auf Item) — eine Zeile pro ausgewähltem Wert. Mehrere Zeilen mit derselben property_group = mehrere gleichzeitig ausgewählte Werte (genau das "Gerbung"-Beispiel), pro Zeile eigenes sync_to_shopware-Flag.

Push-Logik (Phase 2): ecommerce_integrations/product_sync/engine/adapters/shopware.py (push_product_properties()/_push_properties_impl()) plus get_or_create_property_group()/get_or_create_property_option() in ecommerce_integrations/shopware6/export/property_handler.py.

Pull-Logik (Phase 1) muss neu gebaut werden — geprüft, ob Marcels vorhandener PropertyImporter (shopware6/import_handlers/property_importer.py) das schon löst: nein, der importiert Shopwares property_group/property_group_option in ERPNexts Item Attribute (das varianten-treibende System aus #14), nicht in Item Ecommerce Property, aus der die Push-Logik liest. Import- und Push-Richtung zielen bei Marcel auf unterschiedliche DocTypes — vermutlich Altlast aus mehreren Überarbeitungsstufen seines eigenen Codes (es existiert zusätzlich noch eine ältere, per SQL migrierte Item Shopware Property-Tabelle). Kein direkt wiederverwendbarer Rückweg gefunden — Phase 1 braucht eigene Import-Logik, analog zu #13s "one-time backfill vor Produktimport"-Muster, aber wiederholbar statt einmalig.

Deterministische IDs (kein Sync-/Speicherbedarf): generate_uuid(name) (shopware6/export/utils.py, MD5-Hash über einen stabilen Namens-String, z. B. f"property_group_{name}") berechnet die Shopware-UUID bei Neuanlage deterministisch — ERPNext muss keine Shopware-IDs speichern oder synchron halten.

Zu klären im Umsetzungs-Plan

  • Phase-1/Phase-2-Statusmodell (siehe oben) — pro Artikel oder pro Eigenschaftsgruppe?
  • Marcels 3-DocType-Struktur direkt übernehmen (mit Attribution) oder vereinfachen — z. B. ist bei uns evtl. nur sync_to_shopware relevant, sync_to_medusa nicht (kein Medusa-Anschluss in diesem Projekt).
  • Verhältnis zu #36 (bereits gemeldete EAN/Zollnummer/Brand/Gewicht-Push) und #19s bestehendem Property-Group-Handling für Varianten-Optionen klären, damit beide Push-Pfade sich nicht überschneiden oder gegenseitig überschreiben.
  • Live gegen die echte Shopware-Testinstanz verifizieren (Referenzcode ist plausibel, aber wie immer vor Umsetzung live nachprüfen).

Explizit außerhalb des Scopes

  • Varianten-treibende Konfigurator-Optionen — bereits #14/#19.
  • Die übrigen drei in #32 offen gelassenen Kategorien (Marketingtexte je Zielgruppe, Dokumente/Datenblätter, Cross-/Upselling) — nicht Teil dieses Issues.
## Ziel Shopwares natives Eigenschaften-Modul (`property_group`/`property_group_option`, Storefront-Tab "Eigenschaften") in ERPNext abbildbar machen. Konkretes Beispiel aus der Praxis: "Gerbung" mit mehreren gleichzeitig ausgewählten Werten (z. B. "Gerbung zertifiziert nach ISO 9001:2015", "Naturleder", "chromfrei", "vegetabile Nachgerbung"). Wichtig: das ist **nicht** dasselbe wie die varianten-treibenden Optionen aus #14/#19 (`configuratorSettings`) — Shopware trennt "Eigenschaften" (filterbar, mehrere Werte gleichzeitig möglich, erzeugen keine Variante) klar von "Konfigurator-Optionen" (erzeugen jeweils eine eigene Variante). Dieses Issue betrifft nur Erstere. ## Design: zwingend zwei Phasen, in dieser Reihenfolge Explizite Nutzer-Entscheidung: da in bestehenden Shopware-Shops schon Eigenschaftswerte gepflegt sind, **darf ERPNext erst dann zum führenden System werden, wenn es Shopwares bestehende Daten vollständig gespiegelt hat** — nicht andersherum. Ein reiner Push (ERPNext → Shopware) von Anfang an würde bestehende Shopware-Werte, die noch nicht in ERPNext erfasst sind, per Full-Replace stillschweigend löschen (siehe Kommentar unten). **Phase 1 — Pull: Shopwares bestehende Eigenschaften nach ERPNext lesen, wiederholbar (kein einmaliges Einweg-Skript)** - Pro Artikel `product.properties` live aus Shopware lesen (m2m-Relation zu `property_group_option`, inkl. Namensauflösung über `associations: {"properties": {"associations": {"group": {}}}}`). - Als `Item Ecommerce Property`-Zeilen in ERPNext anlegen bzw. aktualisieren — Dedup über (Artikel, `property_group`, `property_value`), damit ein wiederholter Lauf keine Duplikate erzeugt. - Muss beliebig oft erneut ausführbar sein (Re-Sync), solange ERPNext für einen Artikel/Bereich noch nicht als vollständig/geprüft gilt — kein Migrations-Einmalskript. **Phase 2 — Push: ERPNext → Shopware, erst NACHDEM Phase 1 für den jeweiligen Artikel abgeschlossen und geprüft ist** - Referenzmuster (Marcel-Fork, siehe unten): Full-Replace-PATCH `properties: [{id: ...}]`, IDs über `get_or_create_property_group()`/`get_or_create_property_option()` (Live-Suche nach Name/GroupId+Name vor Neuanlage, keine Duplikate; Neuanlage nur bei echtem Fehlen, mit deterministischer MD5-Hash-ID). - Zu klären: braucht es ein Flag/Status pro Artikel (oder pro Eigenschaftsgruppe), ob Phase 1 (nur lesen) oder Phase 2 (ERPNext führend, pushen aktiv) gilt — damit nie versehentlich gepusht wird, bevor die Migration für diesen Artikel abgeschlossen ist? ## Referenzcode (Marcel-Fork) Drei zusammenspielende DocTypes: - `Ecommerce Property Group` (`ecommerce_integrations/ecommerce_integrations/doctype/ecommerce_property_group/`) — Stammdaten: Eigenschaftsname, Anzeigename DE/EN, `filterable`-Flag, `sync_to_shopware`-Flag, Kind-Tabelle mit den möglichen Werten. - `Ecommerce Property Option` — Kind-Tabelle der möglichen Werte je Gruppe (Wert + Anzeige DE/EN). - `Item Ecommerce Property` (Kind-Tabelle auf `Item`) — eine Zeile **pro ausgewähltem Wert**. Mehrere Zeilen mit derselben `property_group` = mehrere gleichzeitig ausgewählte Werte (genau das "Gerbung"-Beispiel), pro Zeile eigenes `sync_to_shopware`-Flag. Push-Logik (Phase 2): `ecommerce_integrations/product_sync/engine/adapters/shopware.py` (`push_product_properties()`/`_push_properties_impl()`) plus `get_or_create_property_group()`/`get_or_create_property_option()` in `ecommerce_integrations/shopware6/export/property_handler.py`. Pull-Logik (Phase 1) **muss neu gebaut werden** — geprüft, ob Marcels vorhandener `PropertyImporter` (`shopware6/import_handlers/property_importer.py`) das schon löst: **nein**, der importiert Shopwares `property_group`/`property_group_option` in ERPNexts `Item Attribute` (das varianten-treibende System aus #14), nicht in `Item Ecommerce Property`, aus der die Push-Logik liest. Import- und Push-Richtung zielen bei Marcel auf unterschiedliche DocTypes — vermutlich Altlast aus mehreren Überarbeitungsstufen seines eigenen Codes (es existiert zusätzlich noch eine ältere, per SQL migrierte `Item Shopware Property`-Tabelle). Kein direkt wiederverwendbarer Rückweg gefunden — Phase 1 braucht eigene Import-Logik, analog zu #13s "one-time backfill vor Produktimport"-Muster, aber wiederholbar statt einmalig. **Deterministische IDs (kein Sync-/Speicherbedarf)**: `generate_uuid(name)` (`shopware6/export/utils.py`, MD5-Hash über einen stabilen Namens-String, z. B. `f"property_group_{name}"`) berechnet die Shopware-UUID bei Neuanlage deterministisch — ERPNext muss keine Shopware-IDs speichern oder synchron halten. ## Zu klären im Umsetzungs-Plan - Phase-1/Phase-2-Statusmodell (siehe oben) — pro Artikel oder pro Eigenschaftsgruppe? - Marcels 3-DocType-Struktur direkt übernehmen (mit Attribution) oder vereinfachen — z. B. ist bei uns evtl. nur `sync_to_shopware` relevant, `sync_to_medusa` nicht (kein Medusa-Anschluss in diesem Projekt). - Verhältnis zu #36 (bereits gemeldete EAN/Zollnummer/Brand/Gewicht-Push) und #19s bestehendem Property-Group-Handling für Varianten-Optionen klären, damit beide Push-Pfade sich nicht überschneiden oder gegenseitig überschreiben. - Live gegen die echte Shopware-Testinstanz verifizieren (Referenzcode ist plausibel, aber wie immer vor Umsetzung live nachprüfen). ## Explizit außerhalb des Scopes - Varianten-treibende Konfigurator-Optionen — bereits #14/#19. - Die übrigen drei in #32 offen gelassenen Kategorien (Marketingtexte je Zielgruppe, Dokumente/Datenblätter, Cross-/Upselling) — nicht Teil dieses Issues.
Author
Owner

Wichtige Ergänzung nach genauerer Prüfung — Pflicht-Vorbedingung für die Umsetzung:

Duplikate sind kein Problem: get_or_create_property_group()/get_or_create_property_option() (shopware6/export/property_handler.py) suchen bei jedem Push zuerst live in Shopware nach Name (search/property-group nach name, search/property-group-option nach groupId+name) und legen nur an, wenn nichts gefunden wird — ein bereits in Shopware vorhandener Wert wird wiederverwendet, egal ob manuell angelegt oder aus einem früheren Push.

Überschreiben ist dagegen ein echtes, ungelöstes Risiko: Der Push (_push_properties_impl) ist ein Full-Replace (PATCH product/{id} mit properties: [{id: ...}, ...]) und baut die Liste ausschließlich aus dem, was aktuell in ERPNexts Item Ecommerce Property steht. Wenn ein Artikel in Shopware bereits Eigenschaftswerte hat, die noch nicht als Zeilen in ERPNext existieren (z. B. weil sie nie migriert wurden), würde ein Push diese in Shopware stillschweigend löschen.

Geprüft, ob Marcels Fork das über einen Rückweg (Shopware → ERPNext) löst: es gibt einen PropertyImporter (shopware6/import_handlers/property_importer.py), der importiert Shopwares property_group/property_group_option aber in ERPNexts Item Attribute (das varianten-treibende System aus #14) — nicht in Item Ecommerce Property, aus der der Push tatsächlich liest. Import- und Push-Richtung zielen bei Marcel also auf unterschiedliche DocTypes — vermutlich Altlast aus mehreren Überarbeitungsstufen (es existiert zusätzlich noch eine ältere, per SQL-Migration abgelöste Item Shopware Property-Tabelle). Kein sauberer, fertiger Rückweg gefunden, der das für uns löst.

Konsequenz für den Umsetzungs-Plan: Ein einmaliger Rückimport (Shopwares bestehende product.properties je Artikel live auslesen, inkl. associations: {"properties": {"associations": {"group": {}}}}, und als Item Ecommerce Property-Zeilen in ERPNext anlegen) ist eine Pflicht-Vorbedingung, bevor der Push (ERPNext → Shopware) für einen Artikel aktiviert wird — sonst gehen bestehende Shopware-Daten beim ersten Push verloren. Analog zu #13s "one-time backfill vor Produktimport"-Muster in diesem Projekt.

**Wichtige Ergänzung nach genauerer Prüfung — Pflicht-Vorbedingung für die Umsetzung:** **Duplikate sind kein Problem**: `get_or_create_property_group()`/`get_or_create_property_option()` (`shopware6/export/property_handler.py`) suchen bei jedem Push zuerst live in Shopware nach Name (`search/property-group` nach `name`, `search/property-group-option` nach `groupId`+`name`) und legen nur an, wenn nichts gefunden wird — ein bereits in Shopware vorhandener Wert wird wiederverwendet, egal ob manuell angelegt oder aus einem früheren Push. **Überschreiben ist dagegen ein echtes, ungelöstes Risiko**: Der Push (`_push_properties_impl`) ist ein **Full-Replace** (`PATCH product/{id}` mit `properties: [{id: ...}, ...]`) und baut die Liste ausschließlich aus dem, was aktuell in ERPNexts `Item Ecommerce Property` steht. Wenn ein Artikel in Shopware bereits Eigenschaftswerte hat, die noch nicht als Zeilen in ERPNext existieren (z. B. weil sie nie migriert wurden), würde ein Push diese in Shopware stillschweigend löschen. Geprüft, ob Marcels Fork das über einen Rückweg (Shopware → ERPNext) löst: es gibt einen `PropertyImporter` (`shopware6/import_handlers/property_importer.py`), der importiert Shopwares `property_group`/`property_group_option` aber in ERPNexts **`Item Attribute`** (das varianten-treibende System aus #14) — nicht in `Item Ecommerce Property`, aus der der Push tatsächlich liest. Import- und Push-Richtung zielen bei Marcel also auf unterschiedliche DocTypes — vermutlich Altlast aus mehreren Überarbeitungsstufen (es existiert zusätzlich noch eine ältere, per SQL-Migration abgelöste `Item Shopware Property`-Tabelle). **Kein sauberer, fertiger Rückweg gefunden, der das für uns löst.** **Konsequenz für den Umsetzungs-Plan**: Ein einmaliger Rückimport (Shopwares bestehende `product.properties` je Artikel live auslesen, inkl. `associations: {"properties": {"associations": {"group": {}}}}`, und als `Item Ecommerce Property`-Zeilen in ERPNext anlegen) ist eine **Pflicht-Vorbedingung**, bevor der Push (ERPNext → Shopware) für einen Artikel aktiviert wird — sonst gehen bestehende Shopware-Daten beim ersten Push verloren. Analog zu #13s "one-time backfill vor Produktimport"-Muster in diesem Projekt.
Author
Owner

Weitere wichtige Korrektur am Design — Marcels 3-DocType-Ansatz wird NICHT 1:1 übernommen:

Zu Recht angemerkt: Shopware nutzt property_group/property_group_option als eine einzige Entität sowohl für Varianten-Optionen (configuratorSettings, #14/#19) als auch für reine, nicht-varianten-treibende Eigenschaften (properties). Ob eine Gruppe bei einem Produkt varianten-treibend ist, hängt nicht an der Gruppe selbst, sondern an der Verwendung durch das jeweilige Produkt — dieselbe Gruppe (z. B. "Farbe") kann bei Artikel A Variante sein und bei Artikel B nur Filter-Eigenschaft.

Marcels Ansatz baut dafür zwei komplett getrennte Stammdaten-Systeme (Item Attribute für Varianten vs. Ecommerce Property Group/Ecommerce Property Option für Eigenschaften) — das würde bei uns dazu führen, dass z. B. "Farbe" an zwei Stellen parallel gepflegt werden müsste und auseinanderlaufen könnte, je nachdem ob ein Artikel sie als Variante oder als reine Eigenschaft nutzt.

Korrigiertes Design für diesen Fork: Item Attribute/Item Attribute Value (bereits aus #14 vorhanden) bleibt die einzige Stammdatenquelle für Attributname + mögliche Werte — entspricht 1:1 Shopwares property_group/property_group_option. Keine zweite parallele Stammdaten-Tabelle. Pro Artikel entscheidet nicht die Gruppe, sondern wo sie referenziert wird:

  • In Item.attributes (bestehende #14-Kind-Tabelle) → varianten-treibend, läuft über #19s bereits gebauten configuratorSettings-Push.
  • In einer neuen Kind-Tabelle (analog Marcels Item Ecommerce Property, aber mit Link-Feldern auf Item Attribute/Item Attribute Value statt Freitext) → nur Eigenschaft (Shopwares properties-Relation, Phase 1/2 wie oben beschrieben).

Damit landet ein Attribut nie in zwei getrennten, potenziell widersprüchlichen Stammdatensätzen — nur die Zuordnung (welcher Artikel nutzt es wie) unterscheidet sich, die Werte-Liste bleibt an einer Stelle gepflegt. Bei der Umsetzungsplanung zu klären: exakte Feldtypen der neuen Kind-Tabelle (Link vs. weitere Details), und ob/wie ein Artikel dasselbe Attribut gleichzeitig varianten-treibend UND als sichtbare Eigenschaft führen können soll.

**Weitere wichtige Korrektur am Design — Marcels 3-DocType-Ansatz wird NICHT 1:1 übernommen:** Zu Recht angemerkt: Shopware nutzt `property_group`/`property_group_option` als **eine einzige Entität** sowohl für Varianten-Optionen (`configuratorSettings`, #14/#19) als auch für reine, nicht-varianten-treibende Eigenschaften (`properties`). Ob eine Gruppe bei einem Produkt varianten-treibend ist, hängt nicht an der Gruppe selbst, sondern an der Verwendung durch das jeweilige Produkt — dieselbe Gruppe (z. B. "Farbe") kann bei Artikel A Variante sein und bei Artikel B nur Filter-Eigenschaft. Marcels Ansatz baut dafür zwei **komplett getrennte** Stammdaten-Systeme (`Item Attribute` für Varianten vs. `Ecommerce Property Group`/`Ecommerce Property Option` für Eigenschaften) — das würde bei uns dazu führen, dass z. B. "Farbe" an zwei Stellen parallel gepflegt werden müsste und auseinanderlaufen könnte, je nachdem ob ein Artikel sie als Variante oder als reine Eigenschaft nutzt. **Korrigiertes Design für diesen Fork**: `Item Attribute`/`Item Attribute Value` (bereits aus #14 vorhanden) bleibt die **einzige** Stammdatenquelle für Attributname + mögliche Werte — entspricht 1:1 Shopwares `property_group`/`property_group_option`. Keine zweite parallele Stammdaten-Tabelle. Pro Artikel entscheidet nicht die Gruppe, sondern wo sie referenziert wird: - In `Item.attributes` (bestehende #14-Kind-Tabelle) → varianten-treibend, läuft über #19s bereits gebauten `configuratorSettings`-Push. - In einer neuen Kind-Tabelle (analog Marcels `Item Ecommerce Property`, aber **mit Link-Feldern auf `Item Attribute`/`Item Attribute Value` statt Freitext**) → nur Eigenschaft (Shopwares `properties`-Relation, Phase 1/2 wie oben beschrieben). Damit landet ein Attribut nie in zwei getrennten, potenziell widersprüchlichen Stammdatensätzen — nur die Zuordnung (welcher Artikel nutzt es wie) unterscheidet sich, die Werte-Liste bleibt an einer Stelle gepflegt. Bei der Umsetzungsplanung zu klären: exakte Feldtypen der neuen Kind-Tabelle (Link vs. weitere Details), und ob/wie ein Artikel dasselbe Attribut gleichzeitig varianten-treibend UND als sichtbare Eigenschaft führen können soll.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#37
No description provided.