Zum Inhalt springen

Das Bestandsmodell

Alles in der Lagerhälfte von Stockly ruht auf einer Zeilenform. Ist sie einmal verstanden, hören die Endpunkte auf, wie eine willkürliche Liste auszusehen.

Eine Menge lebt in p2lab_stockly_warehouse_stock, mit dem Schlüssel Lager + Lagerplatz + Produkt:

FeldBedeutung
warehouseIdwelcher Standort sie hält
binLocationIdwelcher Lagerplatz innerhalb dieses Standorts
productIdwelcher Artikel
stockwie viele körperlich da sind
reservedwie viele davon eine Bestellung bereits beansprucht hat

So etwas wie Bestand ohne Lagerplatz gibt es nicht. Ware, die „nirgendwo” abgelegt wird, landet auf dem unassigned-Lagerplatz des Lagers, einer echten Zeile mit echter Id, beim ersten Gebrauch angelegt. Jeder API-Aufruf, der binLocationId: null entgegennimmt, löst das auf diesen Lagerplatz auf, bevor er irgendetwas tut, und deshalb entnimmt eine Entnahme aus „keinem Lagerplatz” genau der Stelle, an die ein null-Zugang sie gelegt hat.

reserved ist ein Zwischenspeicher. Die Wahrheitsquelle dessen, was eine Bestellung beansprucht hat, ist p2lab_stockly_reservation_line, eine Zeile je Bestellposition und Lagerplatz. reserved zu schreiben, ohne die Zeilen zu schreiben, bringt die beiden aus dem Gleichschritt, und die Integritätsprüfung hat genau dafür einen Befund.

Ein Lagerplatz ohne Rolle ist ein gewöhnlicher Lagerplatz, die einzige Art, auf die die Einlagerung zielt. Die fünf Rollen unten sind Puffer, höchstens einer je Art und Lager:

RolleHältZählt als verfügbar
receivingWare aus einem Wareneingang zu einer Bestellung, vor der geführten Einlagerungja — sie ist im Gebäude
quarantineunter Qualitätskontrolle angenommene Ware, die auf die Prüfung wartetnein
transitfür eine ausgehende Umlagerung zusammengeführte Warenein
returnsWare zurück vom Kunden, die auf eine Entscheidung wartetnein
unassignedBuchbestand, der nie körperlich platziert wurdeja

Einer davon ist für gewöhnliche Aufrufer verschlossen. Der transit-Lagerplatz füllt sich, wenn eine Umlagerung versendet wird, und leert sich, wenn sie ankommt oder storniert wird; ein add, remove oder move, das ihn als eines der beiden Enden benennt, wird abgelehnt, denn eine von Hand dort abgelegte Einheit ließe die Umlagerung stranden, die die Ware für ihre hält.

returns ist mit Absicht nicht derselbe Pool wie quarantine, und der Unterschied zählt für eine Integration: Ware in returns ist nicht verkäuflich. Ein Listener, der eine angenommene Retoure als „Bestand ist zurück” behandelt, wird überverkaufen; die Verfügbarkeit ändert sich erst, wenn jemand die Einheiten zurück in die Lagerung entscheidet.

product.stock = SUM(stock - reserved) über jeden Lagerplatz, dessen Rolle als verfügbar zählt

product.stock ist eine Ausgabe. Stockly berechnet sie nach jedem Vorgang neu und überschreibt, was es dort vorfindet. Sie von außen zu schreiben ist kein Integrationsweg; es ist der Fehlerfall, den der Wächter für externe Änderungen abfangen soll.

Das Journal arbeitet andersherum: p2lab_stockly_warehouse_stock_movement ist append-only. Nichts bearbeitet eine Bewegungszeile; eine Korrektur ist eine neue Zeile mit eigenem Typ. Das ist es, was die Historie beantwortbar macht: Die Frage, warum ein Lagerplatz vier hält, hat eine Antwort aus Zeilen, nicht aus dem aktuellen Wert.

Eine Bewegung trägt beide Enden und beide Stände:

GruppeFelder
Wastype, quantity, productId, batchId, lotId, batchNumber, expiresAt
VonsourceWarehouseId, sourceBinLocationId, sourceBinLpCode
NachtargetWarehouseId, targetBinLocationId, targetBinLpCode
Vorher / nachhersourceStockBefore, sourceStockAfter, sourceReservedBefore, sourceReservedAfter, targetStockBefore, targetStockAfter
Warumcomment, event, reasonCode, meta, userId, userName
Welcher BelegorderId, orderLineItemId, purchaseOrderId, purchaseOrderItemId, transferId, operationId, importId

Die Richtung steckt in den Enden, nie im Vorzeichen: Ein Zugang hat keine Quelle, ein Abgang kein Ziel, eine Umlagerung beides. quantity ist immer positiv.

Vier Namen, die ähnlich klingen und verschiedene Fragen beantworten:

EntitätBeantwortet
p2lab_stockly_warehouse_stock_batchwie viel von der Menge dieses Lagerplatzes welche Lotnummer und welches Ablaufdatum trägt — die operative Wahrheit, gegen die die Entnahme zieht
p2lab_stockly_stock_lotdie Identität eines Lots im ganzen Netz
p2lab_stockly_stock_lot_placementwo dieses Lot derzeit sitzt und auf welchem Ladungsträger
p2lab_stockly_handling_unitein Ladungsträger: eine Palette oder ein Karton, auf dem Ware reist und der in einem anderen verschachtelt sein kann

Chargen entscheiden, was eine Entnahme verbraucht. Lots und Platzierungen sind ein daneben gepflegtes Spiegelbild, und das ist es, was ein Lot mit einer Abfrage über Lagerplätze und Lager hinweg verfolgen lässt. Ein Vorgang schreibt beides; von einem Aufrufer verlangt es niemand.

EntitätHält
p2lab_stockly_warehouseeinen Standort: seinen Code, seine Richtlinien
p2lab_stockly_warehouse_bin_locationeinen Lagerplatz: Code, Koordinaten, Kapazität, Rolle
p2lab_stockly_warehouse_stockdie Mengenzeile von oben
p2lab_stockly_warehouse_stock_batchdie Lot-Aufteilung einer solchen Zeile
p2lab_stockly_warehouse_stock_movementdas Journal
p2lab_stockly_handling_unitLadungsträger
p2lab_stockly_stock_lot / …_stock_lot_placementLot-Identität und wo es sitzt
p2lab_stockly_reservation_linewas eine Bestellung beansprucht hat, je Lagerplatz
p2lab_stockly_order_allocationwas einer Bestellung geschuldet wird und woher es bezogen wird
p2lab_stockly_external_stock_incidentfremde Schreibvorgänge auf product.stock, die der Wächter abgefangen hat

Es sind gewöhnliche Shopware-Entitäten, /api/search/p2lab-stockly-warehouse-stock und Verwandte funktionieren also genau wie überall sonst.