Multi-Warehouse

    Mehrlagerverwaltung mit Click & Collect und Abholsystem

    Überblick

    Das Plugin BronnMultiWarehouse erweitert Shopware 6 um vollständige Mehrlagerverwaltung mit Click & Collect und integriertem Abholsystem.

    Standard-Shopware kennt nur einen globalen Produktbestand. Dieses Plugin führt eigene Lager-Entities ein und verknüpft Lagerbestände pro Artikel. Kunden sehen auf der Produktseite, in welcher Filiale ihr Produkt verfügbar ist. Im Warenkorb wählen sie ihre Abholfiliale. Der Shopbetreiber markiert Bestellungen als abholbereit, der Kunde wird per E-Mail benachrichtigt.

    Aktuelle Version: 2.2.0

  1. Shopware: ~6.7.0
  2. PHP: 8.1+
  3. Entwickler: Daniel Bronner / Bronner Consulting
  4. Systemvoraussetzungen

  5. Shopware 6.7 oder höher
  6. PHP 8.1 oder höher
  7. MySQL 8.0 oder höher
  8. Empfohlen: mindestens 256 MB PHP Memory Limit
  9. Installation

  10. 1.Plugin über den Shopware Admin installieren (Erweiterungen > Meine Erweiterungen)
  11. 2.Plugin aktivieren
  12. 3.Cache leeren:
  13. php bin/console cache:clear
  14. 4.Datenbank-Migrationen werden automatisch bei der Installation ausgeführt
  15. Konfiguration

    Die Plugin-Konfiguration befindet sich unter Erweiterungen > Meine Erweiterungen > BronnMultiWarehouse > Konfiguration.

    Allgemeine Einstellungen:

  16. **Plugin aktiv:** Schaltet alle Funktionen ein/aus
  17. **Bestandsanzeige:** Wählen Sie "Summe aller Lager" oder "Einzeln pro Lager"
  18. **Lagerzuordnungsstrategie:** "Nach Priorität" (Hauptlager zuerst) oder "Gleichmäßig verteilen"
  19. Warenkorb & Bestellung:

  20. **Warenkorb nach Lager unterteilen:** Gruppiert Positionen nach Lager
  21. **Eigene Versandkosten pro Lager:** Berechnet separate Versandkosten
  22. **Bestellungen pro Lager aufteilen:** Erstellt Teilbestellungen
  23. Click & Collect:

  24. **Click & Collect aktivieren:** Erlaubt Abholung in Filialen
  25. **Bestand pro Filiale auf Produktseite:** Zeigt Verfügbarkeit pro Standort
  26. **Filialauswahl im Warenkorb:** Kunden wählen ihre Abholfiliale
  27. **Filialadresse anzeigen:** Zeigt Adresse und Öffnungszeiten
  28. Architektur

    Datenbank (5 Tabellen):

    | Tabelle | Zweck |

    |---|---|

    | bronn_warehouse | Lager mit Name, Code, Priorität, Adresse, Pickup-Flag, Öffnungszeiten |

    | bronn_warehouse_translation | Übersetzbare Felder (Name, Beschreibung) |

    | bronn_warehouse_stock | Bestand pro Produkt und Lager (min: 0) |

    | bronn_warehouse_sales_channel | M:N Zuordnung Lager ↔ Verkaufskanal |

    | bronn_warehouse_order | Zuordnung Lager ↔ Bestellposition |

    Entity-Extensions:

  29. `ProductExtension` → `warehouseStocks` (OneToMany)
  30. `SalesChannelExtension` → `warehouses` (ManyToMany)
  31. `OrderExtension` → `warehouseOrders` (OneToMany)
  32. Custom Fields (Order):

    | Feld | Typ | Zweck |

    |---|---|---|

    | bronn_pickup_order | bool | Bestellung ist Abholbestellung |

    | bronn_pickup_status | string | "pending" oder "ready" |

    | bronn_pickup_warehouse_id | string | UUID der Abholfiliale |

    | bronn_pickup_warehouse_name | string | Name der Abholfiliale |

    Services & Controller

    WarehouseStockService:

  33. `getAvailableStock()` – Gesamtbestand aus aktiven Lagern
  34. `getStockPerWarehouse()` – Bestand pro Lager mit Filialdaten
  35. `allocateStockForOrder()` – Verteilt Bestellmengen auf Lager
  36. `deductStock()` – Zieht Bestand ab (SQL mit `GREATEST(0, stock - qty)`)
  37. `getWarehousesForSalesChannel()` – Aktive Lager für Verkaufskanal
  38. `getPickupStoresForSalesChannel()` – Aktive Pickup-Filialen
  39. WarehouseCartProcessor (Priority 4400):

  40. Enriches Cart Line Items mit `bronnWarehouseId` / `bronnWarehouseName`
  41. Optionale separate Versandkosten pro Lager
  42. Speichert Allokation als Cart Extension
  43. PickupController (Admin API):

  44. `POST /api/bronn/pickup/mark-ready/{orderId}` – Markiert als abholbereit, dispatched PickupReadyEvent
  45. `GET /api/bronn/pickup/status/{orderId}` – Liest Abholstatus
  46. ClickAndCollectController (Storefront):

  47. `POST /bronn/click-and-collect/select` – Filialauswahl im Warenkorb
  48. Event-Subscriber

    | Subscriber | Event | Funktion |

    |---|---|---|

    | OrderPlacedSubscriber | CheckoutOrderPlacedEvent | Allokation + Bestandsabzug + Pickup-Custom-Fields setzen |

    | WarehouseStockSyncSubscriber | bronn_warehouse_stock.written/deleted, bronn_warehouse.written | product.stock = Summe aktiver Lager |

    | ProductPageSubscriber | ProductPageLoadedEvent | PDP mit Lagerbestand anreichern |

    | CartPageSubscriber | Cart/Confirm Page Events | Click & Collect Daten bereitstellen |

    Bestandssynchronisation

    product.stock wird automatisch aktualisiert bei:

  49. Erstellen/Ändern/Löschen von Warehouse-Stock-Einträgen
  50. Aktivieren/Deaktivieren eines Lagers
  51. SQL-Abfrage:

    SELECT COALESCE(SUM(ws.stock), 0)
    FROM bronn_warehouse_stock ws
    INNER JOIN bronn_warehouse w ON ws.warehouse_id = w.id
    WHERE ws.product_id = UNHEX(:productId)
      AND w.active = 1

    Besonderheit: Beim Update eines Stock-Eintrags (nur stock-Feld geändert) ist die productId nicht im DAL-Event-Payload. Der Subscriber löst die productId per DB-Lookup über die Stock-Entry-ID auf.

    Allokationsstrategien

    Priority (Standard):

    Füllt Bestellmenge vom höchstpriorisierten Lager (niedrigster Prioritätswert) auf. Cascade zum nächsten bei Unterbestand.

    Equal Distribution:

    Verteilt gleichmäßig über alle Lager mit Bestand. baseShare = floor(qty / warehouseCount), Remainder auf erste Lager. Fallback auf Priority bei Unterbestand.

    Abholsystem (Pickup)

    Workflow:

  52. 1.Kunde wählt Click & Collect → ClickAndCollectController speichert bronnClickAndCollect in Cart Extension
  53. 2.Bestellung wird erstellt → OrderPlacedSubscriber.setPickupCustomFields() setzt Custom Fields
  54. 3.Admin sieht Pickup-Card → sw-order-detail-general Override
  55. 4.Admin klickt "Abholbereit" → PickupController.markReady() → setzt Status auf "ready" → dispatched PickupReadyEvent
  56. 5.Flow Builder reagiert auf Event → E-Mail an Kunde
  57. PickupReadyEvent:

  58. Implements: `FlowEventAware`, `OrderAware`, `CustomerAware`, `MailAware`
  59. Name: `bronn_multi_warehouse.pickup_ready`
  60. Stellt Order-Entity für E-Mail-Template bereit
  61. E-Mail-Templates (via Migration):

  62. `bronn_pickup_ready` – Kundenbenachrichtigung mit Bestelldetails und Abholort (DE/EN)
  63. `bronn_new_pickup_order` – Shopbetreiber-Info mit Handlungsanweisung (DE/EN)
  64. Admin-UI

    Modul: bronn-warehouse unter Kataloge > Lagerverwaltung

  65. Lagerliste mit Filter (Alle/Aktive/Inaktive)
  66. Detailseite mit Stammdaten, Adresse, Click & Collect, Verkaufskanäle, Bestände
  67. Extensions:

  68. `sw-product-deliverability-form` Override – Lagerbestände im Produkt pflegen
  69. `sw-order-detail-general` Override – Pickup-Card mit Status und Abholbereit-Button
  70. Storefront-Templates

    | Template | Block | Funktion |

    |---|---|---|

    | buy-widget.html.twig | buy_widget_buy_form | Bestandsanzeige pro Lager auf PDP |

    | cart/index.html.twig | page_checkout_cart_add_product_and_shipping | Click & Collect Filialauswahl |

    | confirm/index.html.twig | page_checkout_confirm_shipping | Ausgewählte Filiale + Abholhinweis |

    Bekannte Grenzen

  71. Die `equalDistribution`-Strategie wird aktuell nur im CartProcessor berechnet. Der OrderPlacedSubscriber nutzt die Cart-Allokation aus den Line-Item-Payloads. Wenn keine Payload vorhanden, wird Fallback auf Priority verwendet.
  72. Öffnungszeiten werden als JSON-Objekt gespeichert. Die Eingabe im Admin erfolgt als Freitext-Textarea, keine strukturierte UI.
  73. Das Plugin setzt keine eigene State Machine. Der Abholstatus wird über Custom Fields verwaltet.
  74. Kompatibilität

  75. Kompatibel mit der B2B Plattform (Firmenkonto-basierte Lagersteuerung)
  76. Kompatibel mit dem Plugin Bestelllisten
  77. Kompatibel mit Standard-Shopware-Themes
  78. Getestet mit Shopware 6.7.x
  79. Unterstützt Multi-Sales-Channel
  80. Unterstützt Multi-Language (DE/EN)
  81. Reine DAL-Nutzung, keine Core-Hacks
  82. Negative Bestände durch Validierung verhindert
  83. FAQ