Jak położyć zapas w binie
Cztery endpointy zmieniają zawartość binu. Wszystko inne w magazynie (przyjęcia, odkładanie, kompletacja, remanenty, przewozy, zwroty) sprowadza się do wywołania jednego z nich z innym powodem.
Wszystkie cztery leżą pod /api/_action/p2lab-stockly/warehouse/stock/, uwierzytelniają się jak każde
wywołanie Admin API i są chronione tym samym uprawnieniem:
p2lab_stockly.execute_stock_operations. Wszystkie cztery odpowiadają {"success": true}.
POST warehouse/stock/add
Towar przychodzi. Nie ma źródła; to jest wejście do budynku.
{ "warehouseId": "0191f1…", "binLocationId": "0191f2…", "productId": "0191f3…", "quantity": 12, "comment": "Przyjęte bez zamówienia zakupu", "movementType": "add", "batchNumber": "L-2411", "expiresAt": "2027-03-31T00:00:00+00:00", "handlingUnitCode": "LP-000241"}| Pole | Wymagane | Uwagi |
|---|---|---|
warehouseId | tak | |
productId | tak | |
quantity | tak | liczba dodatnia; kierunek bierze się z endpointu, nigdy ze znaku |
binLocationId | nie | pominięte — towar trafia na bin unassigned magazynu |
comment | nie | dowolny tekst, widoczny w historii |
movementType | nie | add, return albo correction |
batchNumber, expiresAt | nie | razem identyfikują partię, do której dołączają sztuki |
handlingUnitCode | nie | nośnik, na którym towar jest odkładany |
POST warehouse/stock/remove
Towar wychodzi. Nie ma celu.
{ "warehouseId": "0191f1…", "binLocationId": "0191f2…", "productId": "0191f3…", "quantity": 3, "movementType": "damage", "comment": "Zgniecione przy przeładunku", "batchId": "0191f4…", "cleanupEmptySource": false, "handlingUnitId": "0191f5…", "placementScoped": true}| Pole | Wymagane | Uwagi |
|---|---|---|
warehouseId, productId, quantity | tak | jak wyżej |
binLocationId | nie | pominięte znaczy bin unassigned, a nie „którykolwiek bin” |
movementType | nie | remove, damage, loss albo correction |
batchId | nie | pobierz z tej partii, zamiast pozwolić wybrać strategii wydania |
cleanupEmptySource | nie | usuń wiersz, jeśli wydanie go opróżni |
handlingUnitId + placementScoped | nie | pobierz dokładnie z tego nośnika |
Bez batchId sztuki są konsumowane partia po partii w kolejności narzuconej przez strategię wydania
magazynu, a rejestr dostaje jeden wiersz na każdą ruszoną partię: wydanie obejmujące trzy partie
to trzy ruchy, nie jeden.
placementScoped odróżnia „operator wskazał sztuki luzem” od „operator nic nie powiedział”. Z
ustawionym handlingUnitId i placementScoped: true pobranie dotyka tego nośnika i żadnego innego;
bez tego użyte zostanie pierwsze znalezione umiejscowienie, a to bywa niewłaściwe, gdy ta sama partia
leży w binie i luzem, i na nośniku.
POST warehouse/stock/move
Towar jedzie. Oba końce istnieją, suma magazynu się nie zmienia.
{ "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}| Pole | Wymagane | Uwagi |
|---|---|---|
sourceWarehouseId, targetWarehouseId, productId, quantity | tak | |
sourceBinLocationId, targetBinLocationId | nie | każdy z nich można pominąć — to bin unassigned |
movementType | nie | move, mapping, putaway albo putaway_undo |
batchId | nie | przenieś konkretnie tę partię |
operationId | nie | wiąże ruch z operacją magazynową |
purchaseOrderId, purchaseOrderItemId | nie | odkładanie towaru z przyjęcia |
sourceHandlingUnitId + sourcePlacementScoped | nie | z którego nośnika schodzą sztuki |
targetHandlingUnitCode | nie | na jaki nośnik trafiają |
cleanupEmptySource | nie | usuń wiersz źródłowy, gdy się opróżni |
Przeniesienie całej zawartości nośnika w obrębie jednego magazynu przemieszcza nośnik;
przeniesienie części rozbija go, a reszta zostaje na miejscu. Podanie targetHandlingUnitCode to
sposób, w jaki towar luzem trafia na paletę przy okazji przewozu.
Przewóz z podanym purchaseOrderId albo purchaseOrderItemId przelicza dodatkowo postęp odkładania
tego przyjęcia. Zwykły ruch bin-do-binu nie niesie takiego odniesienia i nie zmienia niczego poza
magazynem.
Źródło i cel nie mogą być tym samym miejscem, także w przypadku unassigned do unassigned, bo oba końce są rozwiązywane do binu przed sprawdzeniem.
POST warehouse/stock/map
Kładzie istniejący zapas nieprzypisany na prawdziwy bin w tym samym magazynie, zapisując to jako
ruch typu mapping.
{ "warehouseId": "0191f1…", "productId": "0191f3…", "targetBinLocationId": "0191f6…", "comment": "Mapowanie lokalizacji", "batchId": "0191f4…"}To nie jest przewóz pod ładniejszą nazwą. Przewóz przemieszcza towar, który gdzieś był; mapowanie po raz pierwszy zapisuje, gdzie towar leżał cały czas, i dlatego korzysta z niego kreator wdrożenia. Gdy bin docelowy jest wolny, wiersz zachowuje tożsamość i po prostu dostaje bin; gdy produkt już ten bin zajmuje, oba wiersze się scalają.
Przeniesienie między magazynami to przewóz. Ten endpoint działa wyłącznie wewnątrz jednego magazynu.
Sześć najczęstszych błędów
Dział zatytułowany „Sześć najczęstszych błędów”Nieznany movementType degraduje się, nie kończy błędem. Każdy endpoint przyjmuje własną krótką
listę i po cichu podstawia swoją wartość domyślną za wszystko inne: add dla wejścia, remove dla
wyjścia, move dla trzeciego. Wywołanie się udaje, rejestr zapisuje niewłaściwy powód, a każdy raport
grupujący po typie jest cicho nieprawdziwy. Nie ma tu błędu do złapania; wysyłaną wartość trzeba
sprawdzić samodzielnie.
Ilość jest zawsze dodatnia. Wszystkie trzy endpointy odrzucają zero i wartości ujemne, zgłaszając
P2LAB_STOCKLY__STOCK__INVALID_QUANTITY (HTTP 400). Kierunek siedzi w tym, który endpoint wywołałeś.
Ujemny przewóz opróżniłby cel i powiększył źródło, czyli dokładnie odwrócił intencję.
Pominięcie binu to decyzja, nie luka. binLocationId: null za każdym razem znaczy bin unassigned:
przy wejściu, przy wyjściu i na obu końcach przewozu. Nigdy nie znaczy „tam, gdzie ten produkt akurat
leży”.
Bin transit jest zamknięty. Wskazanie go jako źródła albo celu kończy się odmową
P2LAB_STOCKLY__STOCK__TRANSIT_SOURCE_NOT_ALLOWED / …_TARGET_NOT_ALLOWED (HTTP 409). Towar wchodzi
tam, gdy przewóz wyrusza, i wychodzi, gdy przewóz dojedzie albo zostanie anulowany. Użyj endpointów
przewozu.
Wydanie większe niż stan półki jest odrzucane, z błędem P2LAB_STOCKLY__STOCK__INSUFFICIENT_STOCK
(HTTP 422), z podaniem, ile zażądano i ile było dostępne. Ilość nie jest po cichu przycinana do zera.
Zepsute żądanie to 400 z nazwą pola. Brakujące albo niepoprawne identyfikatory i nieliczbowe
ilości kończą się P2LAB_STOCKLY__REQUEST__INVALID i komunikatem mówiącym, o które pole chodzi —
zanim cokolwiek zostanie zapisane.
Nośniki
Dział zatytułowany „Nośniki”Magazyn może wymagać, by towar jeździł na nośniku. Tam, gdzie ta polityka jest włączona, dodanie bez
handlingUnitCode jest odrzucane, podobnie jak przewóz kończący się towarem luzem po stronie celu —
chyba że przewóz przenosi cały nośnik w obrębie tego samego magazynu, co samo w sobie spełnia
wymóg. Reguła jest egzekwowana po stronie serwera, więc integracja pomijająca kod nie obchodzi jej po
cichu.
Nośniki mają własne operacje strukturalne, wszystkie pod tym samym uprawnieniem i wszystkie neutralne dla stanu: towar zostaje w binie, zmienia się tylko to, na czym leży:
| Metoda | Ścieżka | Co robi |
|---|---|---|
POST | handling-unit/palletize | sztuki leżące w binie luzem trafiają na nośnik |
POST | handling-unit/depalletize | odwrotnie |
POST | handling-unit/{huId}/nest | nośnik zostaje włożony w inny |
POST | handling-unit/{huId}/unnest | i z niego wyjęty |
POST | handling-unit/{huId}/move | cały nośnik jedzie do innego binu |
GET | handling-unit/{huId}/contents | co na nim leży |
GET | handling-unit/generate-code | wolny kod do oznaczenia nowego nośnika |
W procesie, bez HTTP
Dział zatytułowany „W procesie, bez HTTP”Wtyczka w tej samej instalacji może wołać StockService wprost. Metody odpowiadają endpointom
(addStock(), removeStock(), moveStock(), mapStock()) i przyjmują własny Context wołającego.
Dwie rzeczy, które endpointy załatwiają same, a o których wołający wprost musi pomyśleć.
atomic() uruchamia kilka operacji jako jedną całość, na wypadek gdy połowicznie wykonana para
byłaby gorsza niż błąd: postawienie werdyktu i przewiezienie towaru, zaksięgowanie linii remanentu
i położenie jej na binie. Wyłącznie praca na bazie: zakleszczenie powtarza wywołanie od początku, więc
wszystko, co ma skutek poza bazą, musi zostać na zewnątrz.
lockProductStockRows() jest pierwszą instrukcją każdej jednostki zapisu i endpointy wywołują ją
same. To ona zamienia sprawdzenie dostępności w odczyt pod blokadą; bez niej dwa równoległe wydania
widzą tę samą liczbę i rejestr zapisuje więcej wydanych sztuk, niż półka kiedykolwiek miała.
Co dzieje się po zapisie
Dział zatytułowany „Co dzieje się po zapisie”Po kolei, w jednej transakcji:
- wiersz ilości się zmienia, a razem z nim podział na partie;
- jeden wiersz rejestru na każdą ruszoną partię;
- za nimi idą umiejscowienia lotów i lustro nośników;
product.stockzostaje przeliczony z półek;- przy wydaniu, jeśli półka spadła poniżej tego, co przypisały zamówienia, przypisanie ustępuje, a różnica staje się zamówieniem oczekującym.
Fakty są publikowane po zatwierdzeniu transakcji: p2lab_stockly.stock.moved dla samego ruchu i
p2lab_stockly.stock.availability_changed, gdy przeliczenie faktycznie zmieniło to, co da się
sprzedać. Oba opisuje katalog zdarzeń.
Przed tym wszystkim wysyłane jest BeforeStockOperationEvent, poza transakcją, i nasłuchujący może
odmówić wykonania całej operacji. Zobacz punkty decyzyjne.
- Korekty i zadania masowe — liczenie, zasilanie, opróżnianie magazynu
- Odczyt zapasu — pytanie przed zapisem
- Model zapasu — czym są te wiersze