Zum Inhalt springen

Bestand in einen Lagerplatz legen

Vier Endpunkte ändern, was ein Lagerplatz hält. Alles andere im Lager (Wareneingang, Einlagerung, Kommissionieren, Inventuren, Umlagerungen, Retouren) ruft am Ende einen davon mit einem anderen angehängten Grund auf.

Alle vier liegen unter /api/_action/p2lab-stockly/warehouse/stock/, authentifizieren sich wie jeder Admin-API-Aufruf und sind durch dieselbe Berechtigung begrenzt: p2lab_stockly.execute_stock_operations. Alle vier antworten {"success": true}.

POST warehouse/stock/add

Ware trifft ein. Es gibt keine Quelle; hier betritt Bestand das Gebäude.

{
"warehouseId": "0191f1…",
"binLocationId": "0191f2…",
"productId": "0191f3…",
"quantity": 12,
"comment": "Received without a purchase order",
"movementType": "add",
"batchNumber": "L-2411",
"expiresAt": "2027-03-31T00:00:00+00:00",
"handlingUnitCode": "LP-000241"
}
FeldPflichtAnmerkungen
warehouseIdja
productIdja
quantityjaeine positive Zahl; die Richtung kommt vom Endpunkt, nie vom Vorzeichen
binLocationIdneinweglassen, und die Ware landet auf dem unassigned-Lagerplatz des Lagers
commentneinfreier Text, in der Historie gezeigt
movementTypeneinadd, return oder correction
batchNumber, expiresAtneinzusammen kennzeichnen sie das Lot, dem die Einheiten beitreten
handlingUnitCodeneinder Ladungsträger, auf den die Ware gelegt wird

POST warehouse/stock/remove

Ware geht hinaus. Es gibt kein Ziel.

{
"warehouseId": "0191f1…",
"binLocationId": "0191f2…",
"productId": "0191f3…",
"quantity": 3,
"movementType": "damage",
"comment": "Crushed in handling",
"batchId": "0191f4…",
"cleanupEmptySource": false,
"handlingUnitId": "0191f5…",
"placementScoped": true
}
FeldPflichtAnmerkungen
warehouseId, productId, quantityjawie oben
binLocationIdneinweggelassen heißt der unassigned-Lagerplatz, nicht „irgendein Lagerplatz”
movementTypeneinremove, damage, loss oder correction
batchIdneinaus diesem Lot ziehen, statt die Entnahmestrategie wählen zu lassen
cleanupEmptySourceneindie Zeile fallen lassen, wenn die Entnahme sie leert
handlingUnitId + placementScopedneingenau von diesem Ladungsträger ziehen

Ohne eine batchId werden die Einheiten Lot für Lot in der Reihenfolge verbraucht, die die Entnahmestrategie des Lagers vorgibt, und das Journal bekommt eine Zeile je berührtem Lot: Eine Entnahme über drei Lots sind drei Bewegungen, nicht eine.

placementScoped ist das, was „der Mitarbeiter hat die losen Einheiten genommen” von „der Mitarbeiter hat nichts gesagt” trennt. Mit gesetzter handlingUnitId und placementScoped: true trifft die Entnahme genau diesen Ladungsträger und keinen anderen; ohne das wird die erste gefundene Platzierung verwendet, und das kann die falsche sein, wenn dasselbe Lot im selben Lagerplatz sowohl lose als auch auf einem Träger liegt.

POST warehouse/stock/move

Ware reist. Beide Enden bestehen, die Lagersumme ändert sich nicht.

{
"sourceWarehouseId": "0191f1…",
"sourceBinLocationId": "0191f2…",
"targetWarehouseId": "0191f1…",
"targetBinLocationId": "0191f6…",
"productId": "0191f3…",
"quantity": 6,
"movementType": "putaway",
"purchaseOrderItemId": "0191f7…",
"sourceHandlingUnitId": "0191f5…",
"sourcePlacementScoped": true,
"targetHandlingUnitCode": "LP-000242",
"cleanupEmptySource": true
}
FeldPflichtAnmerkungen
sourceWarehouseId, targetWarehouseId, productId, quantityja
sourceBinLocationId, targetBinLocationIdneinjedes darf für den unassigned-Lagerplatz weggelassen werden
movementTypeneinmove, mapping, putaway oder putaway_undo
batchIdneingezielt dieses Lot bewegen
operationIdneinbindet die Bewegung an einen Lagervorgang
purchaseOrderId, purchaseOrderItemIdneineine Einlagerung zu einem Wareneingang
sourceHandlingUnitId + sourcePlacementScopedneinvon welchem Träger die Einheiten kommen
targetHandlingUnitCodeneinder Träger, auf dem sie landen
cleanupEmptySourceneindie Quellzeile fallen lassen, wenn sie sich leert

Den ganzen Inhalt eines Ladungsträgers innerhalb eines Lagers zu bewegen verlagert den Träger; einen Teil davon zu bewegen bricht den Träger auf, und der Rest bleibt zurück. Einen targetHandlingUnitCode zu benennen ist der Weg, auf dem loser Bestand durch eine Bewegung palettiert wird.

Eine Bewegung, die eine purchaseOrderId oder purchaseOrderItemId benennt, leitet auch den Einlagerungsfortschritt dieses Wareneingangs neu ab. Reine Lagerplatz-zu-Lagerplatz-Bewegungen tragen keinen solchen Verweis und ändern außerhalb des Lagers nichts.

Quelle und Ziel dürfen nicht derselbe Lagerplatz sein, eine Bewegung von unassigned nach unassigned eingeschlossen, denn beide Enden werden vor der Prüfung auf den Lagerplatz aufgelöst.

POST warehouse/stock/map

Legt bestehenden unassigned-Bestand auf einen echten Lagerplatz im selben Lager, als mapping-Bewegung festgehalten.

{
"warehouseId": "0191f1…",
"productId": "0191f3…",
"targetBinLocationId": "0191f6…",
"comment": "Location mapping",
"batchId": "0191f4…"
}

Das ist keine Bewegung mit einem schöneren Namen. Eine Bewegung verlagert Ware, die irgendwo war; eine Zuordnung hält zum ersten Mal fest, wo Ware die ganze Zeit gewesen ist, und deshalb ist es der Vorgang, den der Einrichtungsassistent verwendet. Ist der Ziel-Lagerplatz frei, behält die Zeile ihre Identität und bekommt schlicht einen Lagerplatz; belegt das Produkt diesen Lagerplatz bereits, verschmelzen die zwei Zeilen.

Eine Verlagerung über Lager hinweg ist eine Bewegung. Dieser Endpunkt arbeitet nur innerhalb eines Lagers.

Ein unbekannter movementType wird herabgestuft, er scheitert nicht. Jeder Endpunkt nimmt seine eigene kurze Liste entgegen und setzt für alles andere stillschweigend seine Voreinstellung ein: add für den eingehenden Endpunkt, remove für den ausgehenden, move für den dritten. Der Aufruf gelingt, das Journal hält den falschen Grund fest, und jeder Bericht, der nach Typ gruppiert, ist still falsch. Es gibt keinen Fehler zum Abfangen; prüfen Sie den Wert, den Sie senden.

Die Menge ist immer positiv. Alle drei Endpunkte weisen null und negative Werte mit P2LAB_STOCKLY__STOCK__INVALID_QUANTITY (HTTP 400) ab. Die Richtung liegt darin, welcher Endpunkt aufgerufen wurde. Eine negative Bewegung entleerte das Ziel und blähte die Quelle auf, das genaue Gegenteil der Absicht.

Den Lagerplatz wegzulassen ist eine Entscheidung, keine Lücke. binLocationId: null heißt jedes Mal den unassigned-Lagerplatz: auf dem Weg hinein, auf dem Weg hinaus, an beiden Enden einer Bewegung. Es heißt nie „wo auch immer dieses Produkt gerade ist”.

Der transit-Lagerplatz ist verschlossen. Ihn als Quelle oder Ziel zu benennen wird mit P2LAB_STOCKLY__STOCK__TRANSIT_SOURCE_NOT_ALLOWED / …_TARGET_NOT_ALLOWED (HTTP 409) abgelehnt. Ware betritt ihn, wenn eine Umlagerung versendet wird, und verlässt ihn, wenn diese Umlagerung ankommt oder storniert wird. Verwenden Sie stattdessen die Umlagerungs-Endpunkte.

Mehr zu entnehmen, als das Regal hält, wird abgelehnt, mit P2LAB_STOCKLY__STOCK__INSUFFICIENT_STOCK (HTTP 422), das benennt, was angefordert und was verfügbar war; die Menge wird nicht unbemerkt auf null begrenzt.

Eine fehlerhafte Anfrage ist eine 400, die das Feld benennt. Fehlende oder ungültige Ids und nicht numerische Mengen antworten mit P2LAB_STOCKLY__REQUEST__INVALID und einer Meldung, die sagt, welches Feld, bevor irgendetwas geschrieben wird.

Ein Lager kann verlangen, dass Ware auf einem Ladungsträger reist. Wo diese Richtlinie gilt, wird ein add ohne handlingUnitCode abgelehnt, und ebenso eine Bewegung, die am Ziel lose landet, es sei denn, die Bewegung trägt einen ganzen Träger aus demselben Lager herüber, was die Richtlinie von selbst erfüllt. Die Regel wird auf dem Server durchgesetzt, eine Integration, die den Code weglässt, umgeht sie also nicht stillschweigend.

Träger haben eigene strukturelle Vorgänge, alle unter derselben Berechtigung und alle bestandsneutral: Die Ware bleibt im Lagerplatz, nur ihr Träger ändert sich:

MethodePfadWas er tut
POSThandling-unit/palletizelose Einheiten in einem Lagerplatz kommen auf einen Träger
POSThandling-unit/depalletizedas Umgekehrte
POSThandling-unit/{huId}/nestein Träger wird auf einen anderen geladen
POSThandling-unit/{huId}/unnestwieder heruntergenommen
POSThandling-unit/{huId}/moveder ganze Träger geht in einen anderen Lagerplatz
GEThandling-unit/{huId}/contentswas darauf ist
GEThandling-unit/generate-codeein freier Code, um einen neuen Träger zu beschriften

Ein Plugin derselben Installation kann StockService unmittelbar aufrufen. Die Methoden spiegeln die Endpunkte (addStock(), removeStock(), moveStock(), mapStock()) und nehmen den eigenen Context des Aufrufers entgegen.

Zwei Dinge, die die Endpunkte für Sie erledigen und über die ein unmittelbarer Aufrufer nachdenken muss:

atomic() führt mehrere Primitive als eine Arbeitseinheit aus, für Fälle, in denen ein halb angewandtes Paar schlimmer wäre als ein Fehlschlag: ein Urteil stempeln und die Ware bewegen, eine Inventurposition buchen und sie platzieren. Nur Datenbankarbeit: Ein Deadlock spielt das Callable von vorn ab, alles mit einer Wirkung außerhalb der Datenbank muss also draußen bleiben.

lockProductStockRows() ist die erste Anweisung jeder verändernden Einheit, und die Endpunkte setzen sie bereits ab. Sie ist es, die aus der Verfügbarkeitsprüfung ein gesperrtes Lesen macht; ohne sie sehen zwei gleichzeitige Entnahmen dieselbe Zahl, und das Journal hält mehr hinausgehende Einheiten fest, als das Regal je hielt.

Der Reihe nach, innerhalb einer Transaktion:

  1. die Mengenzeile bewegt sich, und die Chargenaufteilung mit ihr;
  2. eine Journalzeile je berührtem Lot;
  3. die Lot-Platzierungen und das Ladungsträger-Spiegelbild folgen;
  4. product.stock wird aus den Regalen neu berechnet;
  5. bei einer Entnahme gibt die Vormerkung nach, wenn das Regal unter das fiel, was Bestellungen beansprucht hatten, und die Differenz wird zu einem Rückstand.

Tatsachen werden nach dem Commit veröffentlicht: p2lab_stockly.stock.moved für die Bewegung selbst und p2lab_stockly.stock.availability_changed, wenn die Neuberechnung tatsächlich geändert hat, was verkauft werden kann. Beide sind im Ereigniskatalog beschrieben.

Vor all dem wird BeforeStockOperationEvent außerhalb der Transaktion ausgelöst, und ein Listener darf den ganzen Vorgang ablehnen. Siehe Policy Hooks.