# Ta sama paleta wraca pod tym samym numerem. Skąd stan ujemny

> Dlaczego ta sama paleta przyjęta dwa razy pod jednym numerem daje stan ujemny w WMS. Analiza zapytania bez sortowania i odczyt daty produkcji ze stanu palety.

Adres strony: https://magazyn.softwarestudio.com.pl/stan-ujemny-powtorzony-numer-palety/

---

Stan ujemny rzadko bierze się z błędnej ilości. W opisywanym wdrożeniu przyczyną było zapytanie, które przy powtórzonym numerze palety czytało datę produkcji z najstarszego przyjęcia. Poprawka zmieniła odczyt na stan palety i objęła sześć zapytań wózka.
Opisujemy przypadek z magazynu operatora logistycznego z południa Polski, który pracuje na platformie [Studio WMS.net](/wms/), a szerszy kontekst takiego magazynu przedstawia strona [WMS dla operatora 3PL](https://www.programmagazyn.pl/wms-dla-operatora-3pl/). Wartości w przykładach są zmyślone, a dat i numerów palet z rzeczywistego zdarzenia nie podajemy. Tekst jest napisany dla działu IT: pokazuje, jak ustalić przyczynę na danych i jak bezpiecznie podmienić zapytania w repozytorium zapytań.

## Objaw - minus 9,9 na lokalizacji

Przykład liczbowy wygląda tak. Paleta z towarem w ilości 9,9 zostaje przyjęta dokumentem przyjęcia zewnętrznego (PZ) i wydana dokumentem wydania zewnętrznego (WZ), a oba dokumenty opisuje strona o [dokumentach magazynowych](/dokumenty-magazynowe/). Po kilkudziesięciu dniach ta sama paleta wraca do magazynu pod tym samym numerem, z inną datą produkcji towaru. Dokument zmiany lokalizacji (ZL) przesuwa ją na inną lokalizację.

| Dokument | Ilość | Data produkcji | Uwaga |
| --- | --- | --- | --- |
| PZ | +9,9 | A | Pierwsze przyjęcie palety |
| WZ | -9,9 | A | Wydanie palety z magazynu |
| PZ ponowne | +9,9 | B | Ta sama paleta, ten sam numer, inna data produkcji |
| ZL | przesunięcie 9,9 | A zamiast B | Zapis daty ze starego przyjęcia |

Skutek widać na obu lokalizacjach. Na lokalizacji źródłowej grupa ze starą datą spada do -9,9, a grupa z nową datą zostaje na +9,9. Suma obu grup wynosi zero, więc minus widać dopiero po rozbiciu stanu według daty produkcji. Na lokalizacji docelowej paleta dostaje złą datę, a bieżący stan na lokalizacjach pokazuje strona o [bieżących stanach magazynowych](/stany-magazynowe/).

Numer palety pochodzi z etykiety logistycznej, której budowę opisuje artykuł o [trzech kodach etykiety GS1-128](https://system.wms.net.pl/trzy-kody-etykiety-gs1-128/). Sposób, w jaki WMS prowadzi stan według partii i dat, wyjaśnia strona o [partiach produkcyjnych i datach ważności](/partie-produkcyjne-i-daty-waznosci/).

## Oś zdarzeń z dokładnością do sekundy

O przyczynie przesądziły czasy zapisane w dokumentach i zadaniach wózka. Dopełnia je historia operacji, w której zdarzenia mają znaczniki czasu z dokładnością do sekundy. ZL założony na ekranie zapisał datę poprawną, zgodną ze stanem. Po około 47 sekundach wózek rozpoczął zadanie, a po kolejnych 4 sekundach zapis realizacji nadpisał datę i partię.

| Czas | Zdarzenie | Data produkcji w ZL |
| --- | --- | --- |
| 0 s | ZL założony na ekranie | Poprawna, odczytana ze stanu palety |
| około 47 s | Wózek rozpoczyna zadanie | Bez zmiany |
| około 51 s | Zapis realizacji | Nadpisana datą z najstarszego przyjęcia |

Takie czasy da się odtworzyć dla każdej palety, bo dokumenty i zadania mają znaczniki czasu, tak samo jak historia operacji. Zestawienie wskazuje moment zmiany. Data zmieniła się w zapisie realizacji, a nie przy zakładaniu dokumentu, więc szukamy zapytania, które ten zapis wykonuje.

## Przyczyna - TOP 1 bez ORDER BY

Zapytanie wózka czytało partię i datę produkcji poleceniem, które zwraca pierwszy wiersz z danym numerem palety, bez klauzuli ORDER BY. W praktyce wynikiem był najstarszy zapis, czyli dane z pierwszego przyjęcia, którego towar dawno wyszedł z magazynu.

Taki wzorzec działa poprawnie, dopóki numer palety występuje w stanie raz. Problem pojawia się, gdy ta sama paleta ma dwa przyjęcia z różnymi datami, bo zapytanie nie rozstrzyga, które z nich czytać.

> TOP 1 bez ORDER BY nie ma gwarantowanej kolejności. Wynik zwykle najstarszy to przypadek, nie reguła.

Dokumentacja Microsoft opisuje, że [klauzula TOP](https://learn.microsoft.com/pl-pl/sql/t-sql/queries/top-transact-sql?view=sql-server-ver17) użyta bez [klauzuli ORDER BY](https://learn.microsoft.com/pl-pl/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver17) zwraca wiersze w nieokreślonej kolejności. Wynik, który zwykle jest najstarszym zapisem, zależy więc od planu zapytania, a ten może się zmienić. Zależy też od tego, którym indeksem silnik przechodzi po tabeli, więc po przebudowie indeksu albo zmianie statystyk to samo zapytanie mogłoby zwrócić inny wiersz.

Ten sam wzorzec miało sześć zapytań wózka. Zestawienie podaje pięć czynności, w których występował.

| Czynność wózka | Rola zapytania ze wzorcem |
| --- | --- |
| Zapis ZL | Odczyt partii i daty przy zapisie realizacji zmiany lokalizacji |
| Zamiana palet | Odczyt partii i daty przy zamianie palety proponowanej na zeskanowaną |
| Paleta innego indeksu | Odczyt partii i daty przy skanie palety o innym indeksie |
| Nowa pozycja ZL do strefy pośredniej | Odczyt partii i daty przy dopisaniu pozycji ZL |
| Kontrola zgodności palety | Odczyt partii i daty przy sprawdzeniu, czy paleta pasuje do pozycji |

## Poprawka - odczyt ze stanu palety

Poprawka podmienia jeden wzorzec. Zamiast pierwszego wiersza z danym numerem palety zapytania wywołują funkcję tabelaryczną, która czyta ze stanu palety partię oraz dwie daty: produkcji i terminu. Funkcja wybiera grupę w ustalonej kolejności.

1. Grupa z dodatnią ilością.
2. Preferencja lokalizacji źródłowej.
3. Preferencja partii.
4. Najnowszy ruch.

Pierwsza reguła odrzuca grupy puste i ujemne, bo tylko grupa z towarem opisuje paletę, która faktycznie stoi w magazynie. Z wyboru wyłączona jest własna para ZL i własny dokument. Gdy palety nie ma na stanie, funkcja bierze dane z ostatniego przyjęcia. Funkcja zajmuje miejsce wzorca w sześciu zapytaniach, a pozostałe elementy zapytań zostają takie, jakie były.

W zapytaniach typu UPDATE przy braku wyniku zostaje wartość już zapisana w ZL. Poza podmienionym wzorcem nic się nie zmieniło: skrypty przeglądarki i parametry zapytań zostały takie, jakie były.

## Jak znaleźć inne palety z tym samym problemem

Zapytanie uruchamiane cyklicznie w zadaniu agenta SQL pilnuje stanów ujemnych. Grupuje ono stany według siedmiu pól (indeks, lokalizacja, magazyn, paleta, seria, kod palety i data produkcji) i zostawia grupy, w których suma ilości jest mniejsza od zera. Ponieważ grupowanie obejmuje datę produkcji, minus ujawnia się nawet wtedy, gdy suma na lokalizacji wynosi zero.

Drugie zapytanie wskazuje palety z więcej niż jedną datą produkcji, czyli kandydatów na ten sam problem, zanim powstanie minus. Zapytanie monitorujące działa według harmonogramu, więc minus trafia na listę przy najbliższym przebiegu, a nie dopiero przy inwentaryzacji. Wynik pierwszego zapytania da się czytać jak listę kontrolną, bo każda grupa z sumą poniżej zera wskazuje paletę i lokalizację do sprawdzenia w dokumentach. Identyfikowalność partii i palet w dokumentach opisuje strona o [Track and Trace w magazynie](/track-and-trace-w-magazynie/).

## Wdrożenie zmiany w repozytorium zapytań

Zapytania wózka nie siedzą w kodzie przeglądarki. Przeglądarka przekazuje identyfikator, a treść zapytania leży w repozytorium zapytań w bazie. Poprawka jest więc zmianą danych w bazie klienta i dostaje własny skrypt wdrożeniowy.

- Zgodność treści. Treść w bazie musi zgadzać się z kopią w pliku skryptu.
- Liczba wystąpień. Skrypt liczy wystąpienia zmienianego fragmentu przed zapisem.
- Kompilacja. Nowa treść jest kompilowana przed zapisem do bazy.
- Tryb próbny. Skrypt można uruchomić bez zapisu zmian.

Do skryptu dołączony jest osobny skrypt wycofania z oryginalnymi treściami zapytań. Po zapisie skrypt resetuje pamięć podręczną repozytorium zapytań, żeby wózki zaczęły używać nowej treści. Kolejność wydań z magazynu, w której data przyjęcia palety odgrywa własną rolę, opisuje artykuł o [FIFO według daty przyjęcia palety](https://magazyn.wysokiego.skladowania.pl/fifo-wedlug-daty-przyjecia-palety/).

## Pytania i odpowiedzi o stanie ujemnym w WMS

## Słownik pojęć o stanie ujemnym w WMS

Terminy używane przez dział IT, gdy szuka przyczyny stanu ujemnego i poprawia zapytania w bazie WMS.
