Magazynowy system informacyjny a informatyczny
W literaturze przedmiotu magazynowy system informacyjny obejmuje całość: dane o towarze i zasady ich zapisu, a także pracowników oraz ich narzędzia. Magazynowy system informatyczny to jego część programowa i sprzętowa, czyli bazę danych z aplikacjami oraz urządzeniami. W ofertach i zapytaniach obie nazwy opisują zwykle ten sam produkt, więc dalej używamy ich zamiennie. Przedmiotem opisu jest to, co system musi przechowywać, aby stan magazynu dało się odtworzyć.
Jedna zasada porządkuje cały model: fizyczne przemieszczenie towaru zostaje w systemie jako dokument, a dokument zostaje jako ruch. Stan magazynowy jest skutkiem tych zapisów, a nie samodzielną liczbą, którą ktoś wpisuje. Dzięki temu każdą wartość da się prześledzić wstecz do konkretnej operacji i osoby.
| Pojęcie | Zakres | Przykładowe elementy |
|---|---|---|
| System informacyjny | dane i zasady zapisu oraz ludzie z narzędziami | instrukcja przyjęcia, uprawnienia magazyniera, kartoteka towarów |
| System informatyczny | oprogramowanie oraz baza danych z siecią | serwer SQL Server, aplikacja webowa, terminale ze skanerem |
| Model danych | struktura tabel i relacji w bazie | tabele dokumentów oraz ruchów |
Kolejne sekcje opisują ten model od strony dokumentów i transakcji. Warstwę techniczną (serwer i terminale) opisuje osobny tekst o informatycznym systemie magazynowym i jego warstwach, więc tutaj jej nie rozwijamy. Zarządzanie samym stanem (rezerwacje, stany minimalne, partie) opisuje strona o zarządzaniu stanami magazynowymi w systemie informatycznym.
Dokumenty magazynowe i ich skutek dla stanu
Dokument magazynowy jest jednostką księgową ruchu. Pięć typów pokrywa większość codziennych operacji, a ich skutek dla stanu pokazuje tabela poniżej. O rozróżnieniu decyduje to, czy towar przekracza granicę magazynu i czy zmienia jego wartość księgową. Pełny obieg dokumentów opisuje tekst o dokumentach magazynowych, a zestawienie tych skrótów omawia też artykuł na stronie SoftwareStudio.
| Dokument | Znaczenie | Skutek dla stanu | Ruch w tabeli |
|---|---|---|---|
| PZ | przyjęcie zewnętrzne od dostawcy | zwiększa stan w lokalizacji przyjęcia | ruch dodatni |
| WZ | wydanie zewnętrzne do odbiorcy | zmniejsza stan w lokalizacji wydania | ruch ujemny |
| MM | przesunięcie między magazynami lub lokalizacjami | zmniejsza jedną lokalizację i zwiększa drugą | para ruchów o sumie zero |
| RW | rozchód wewnętrzny, na przykład na produkcję albo próbki | zmniejsza stan bez odbiorcy zewnętrznego | ruch ujemny |
| PW | przychód wewnętrzny, na przykład z produkcji | zwiększa stan bez dostawcy | ruch dodatni |
Dwie cechy odróżniają MM od pozostałych. Dokument nie zmienia sumy zapasu w firmie, tylko jego rozmieszczenie, więc suma ilości po obu stronach musi wynosić zero. Zwykle wymaga też dwóch potwierdzeń: wydania z lokalizacji źródłowej i przyjęcia w docelowej. W czasie między nimi towar jest „w drodze”.
Reguła: zatwierdzony dokument nie podlega edycji, a błąd koryguje się dokumentem odwrotnym.
Cykl życia dokumentu
Dokument przechodzi przez statusy, z których tylko ostatni zmienia stan magazynowy. Poniższy podział jest typowy dla systemów magazynowych i służy jako punkt odniesienia; konkretne nazwy statusów zależą od produktu.
- Bufor - dokument roboczy, który nie zmienia stanu i można go dowolnie edytować.
- Zatwierdzony - treść jest zamrożona, a ilości mogą rezerwować towar do czasu realizacji.
- Zaksięgowany - ruchy zostały zapisane w tabeli, a stan w lokalizacjach uległ zmianie.
- Anulowany - dokument wycofany przed księgowaniem, bez ruchów w tabeli.

Przy pracy na terminalu odczyt kodu tworzy pozycję dokumentu od razu, a nie po zakończeniu zmiany. Skanowanie w aplikacji, na przykład opisanej w tekście o aplikacji magazynowej na Androida, skraca więc czas między ruchem fizycznym a zapisem w bazie. Krótsze okno oznacza mniej sytuacji, w których stan systemowy odbiega od półki.
Numeracja dokumentów
Numer dokumentu, na przykład PZ/2026/000123, pochodzi z serii prowadzonej osobno dla każdego typu dokumentu i roku. Obiekt SEQUENCE w SQL Server nie gwarantuje ciągłości, bo wartość pobrana przez wycofaną transakcję przepada. Gdy przepisy lub audyt wymagają numeracji bez luk, używa się tabeli liczników zwiększanej w tej samej transakcji, w której zapisuje się dokument.
Model danych - dokument i ruch magazynowy
Minimalny model magazynowy składa się z pięciu grup tabel. Kartoteka towarowa opisuje, co jest przechowywane, a słownik lokalizacji - gdzie. Nagłówki i pozycje dokumentów zapisują, dlaczego coś się zmieniło, a tabela ruchów - o ile. Tabela stanów łączy wynik w bieżącą ilość per towar i lokalizacja.
| Tabela | Klucz | Zawartość | Zmienność |
|---|---|---|---|
| Kartoteka towaru | TowarId | indeks, nazwa, jednostka miary, kod EAN | rzadko zmieniana |
| Lokalizacja | LokalizacjaId | adres regału, strefa, typ miejsca | rzadko zmieniana |
| Dokument (nagłówek i pozycje) | DokumentId, PozycjaNr | typ, kontrahent, status, ilości | edytowalny do zatwierdzenia |
| Ruch | RuchId | ilość ze znakiem, data, dokument, lokalizacja | tylko dopisywanie |
| Stan | TowarId i LokalizacjaId | bieżąca ilość | zmieniany przy księgowaniu |
Poniższy szkic w T-SQL pokazuje tabele ruchów i stanów. Nazwy tabel i kolumn są umowne i nie odzwierciedlają schematu żadnego konkretnego produktu.
CREATE TABLE dbo.Ruch (
RuchId BIGINT IDENTITY(1,1) PRIMARY KEY,
DokumentId INT NOT NULL,
PozycjaNr SMALLINT NOT NULL,
TowarId INT NOT NULL,
LokalizacjaId INT NOT NULL,
Ilosc DECIMAL(18,3) NOT NULL, -- dodatnia: przyjęcie, ujemna: wydanie
DataRuchu DATETIME2(0) NOT NULL DEFAULT SYSDATETIME()
);
CREATE TABLE dbo.Stan (
TowarId INT NOT NULL,
LokalizacjaId INT NOT NULL,
Ilosc DECIMAL(18,3) NOT NULL,
CONSTRAINT PK_Stan PRIMARY KEY (TowarId, LokalizacjaId),
CONSTRAINT CK_Stan_Ilosc CHECK (Ilosc >= 0)
);
- Ilość ze znakiem - ruch dodatni zwiększa stan, a ujemny go zmniejsza, więc SUM(Ilosc) daje saldo bez rozróżniania typów dokumentów.
- Ograniczenie CHECK - warunek nieujemnej ilości w tabeli stanów odrzuca wydanie ponad stan na poziomie bazy, niezależnie od aplikacji.
- Tylko dopisywanie - tabela ruchów nie przyjmuje poleceń UPDATE ani DELETE, co można wymusić uprawnieniami DENY.
- Klucz stanu - para TowarId i LokalizacjaId daje jeden wiersz na miejsce składowania i przyspiesza wyszukiwanie.
Partia i termin ważności w ruchu
Towar prowadzony partiami wymaga dodatkowej kolumny PartiaId w tabelach ruchów i stanów, a klucz stanu rośnie o tę kolumnę. Wtedy jedna kartoteka ma tyle wierszy stanu, ile partii leży w danej lokalizacji. Identyfikatory partii i dat trafiają do systemu z etykiety: w standardzie GS1 partię niesie identyfikator zastosowania (10), a termin ważności identyfikator (17), co opisuje strona o etykiecie logistycznej GS1.
Zasady rotacji FIFO i FEFO wynikają z tych samych kolumn. System wybiera do wydania partię o najwcześniejszej dacie, a szczegóły ewidencji opisuje tekst o partiach produkcyjnych i datach ważności.
Księgowanie ruchu w tabelach SQL Server
Księgowanie dokumentu jest jedną transakcją, która obejmuje zmianę tabeli stanów i dopisanie ruchu. Obie operacje muszą się udać razem albo wcale. W przeciwnym razie stan i suma ruchów rozjadą się bez śladu w aplikacji, a różnicę zauważy dopiero inwentaryzacja. Studio WMS.net działa na bazie MS SQL Server, więc opisane niżej mechanizmy dotyczą tego środowiska, a szerszy kontekst daje strona o systemie magazynowym opartym na SQL.
CREATE PROCEDURE dbo.KsiegujWydanie
@DokumentId INT, @PozycjaNr SMALLINT, @TowarId INT,
@LokalizacjaId INT, @Ilosc DECIMAL(18,3)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
UPDATE dbo.Stan WITH (UPDLOCK, ROWLOCK)
SET Ilosc = Ilosc - @Ilosc
WHERE TowarId = @TowarId
AND LokalizacjaId = @LokalizacjaId
AND Ilosc >= @Ilosc;
IF @@ROWCOUNT = 0
THROW 50001, N'Brak wystarczającego stanu w lokalizacji.', 1;
INSERT dbo.Ruch (DokumentId, PozycjaNr, TowarId, LokalizacjaId, Ilosc)
VALUES (@DokumentId, @PozycjaNr, @TowarId, @LokalizacjaId, -@Ilosc);
COMMIT TRANSACTION;
END;
Warunek w klauzuli WHERE odrzuca aktualizację, gdy ilość w lokalizacji jest mniejsza od żądanej, a wskazówka UPDLOCK zakłada blokadę aktualizacyjną na czas transakcji. Drugi terminal, który próbuje wydać ten sam towar, czeka na zwolnienie blokady i po jej zwolnieniu widzi już nowy stan. Przy XACT_ABORT ON błąd rzucony przez THROW cofa całą transakcję.
Reguła: sprawdzenie ilości i jej zmniejszenie wykonuje jedna instrukcja UPDATE, a nie odczyt w aplikacji z późniejszym zapisem.
Poziom izolacji i blokady
Przy domyślnym poziomie READ COMMITTED odczyt nie widzi niezatwierdzonych zmian. Odczyt i późniejszy zapis tego samego wiersza nie tworzą jednak niepodzielnej operacji. Z tego powodu wzorzec „sprawdź, potem zapisz” w dwóch instrukcjach zawodzi przy dwóch równoległych wydaniach. Wybór poziomu izolacji zmienia także to, czy raport stanu czeka na księgowanie.
| Poziom izolacji | Zachowanie odczytu | Skutek dla magazynu |
|---|---|---|
| READ COMMITTED | odczyt czeka na zwolnienie blokad zapisu | raport stanu może czekać na trwające księgowanie |
| READ COMMITTED SNAPSHOT | odczyt korzysta z wersji wiersza | raport nie blokuje księgowania, rośnie użycie bazy tempdb |
| SNAPSHOT | spójny obraz danych z początku transakcji | konflikt aktualizacji kończy się błędem 3960 i wymaga ponowienia |
| SERIALIZABLE | blokady zakresów kluczy | najsilniejsza ochrona i największe ryzyko blokad |
Pełny opis poziomów i zachowania blokad zawiera dokumentacja Microsoft: SET TRANSACTION ISOLATION LEVEL oraz przewodnik po blokowaniu i wersjonowaniu wierszy.
Dwie transakcje, które blokują te same wiersze w odwrotnej kolejności, kończą się zakleszczeniem: silnik wybiera ofiarę i zwraca błąd 1205. Prosta praktyka ogranicza to ryzyko. Pozycje dokumentu księguje się zawsze w tej samej kolejności, na przykład rosnąco po TowarId, a transakcję ofiary ponawia się automatycznie.
Stan magazynowy jako suma ruchów
W praktyce spotyka się dwa sposoby obliczania stanu. Pierwszy liczy go za każdym razem z ruchów zapytaniem SUM, drugi trzyma wynik w osobnej tabeli i zmienia go przy księgowaniu. Różnica dotyczy kosztu odczytu i odporności na błędy.
- Stan wyliczany - prosty i odporny na błędy, bo saldo zawsze wynika z historii, ale wolnieje wraz z liczbą ruchów.
- Stan zapisany - odczytywany natychmiast po kluczu, lecz wymaga transakcji z poprzedniej sekcji i okresowej kontroli.
Rozwiązaniem pośrednim jest tabela stanów jako zapis zbiorczy i zapytanie kontrolne, które porównuje ją z sumą ruchów. Można je uruchamiać jako zadanie SQL Agent po zamknięciu dnia. Pełny outer join wychwytuje także towar, który ma ruchy, a nie ma wiersza stanu.
SELECT COALESCE(s.TowarId, r.TowarId) AS TowarId,
COALESCE(s.LokalizacjaId, r.LokalizacjaId) AS LokalizacjaId,
s.Ilosc AS StanZapisany, r.SumaRuchow
FROM dbo.Stan AS s
FULL OUTER JOIN (
SELECT TowarId, LokalizacjaId, SUM(Ilosc) AS SumaRuchow
FROM dbo.Ruch
GROUP BY TowarId, LokalizacjaId
) AS r
ON r.TowarId = s.TowarId AND r.LokalizacjaId = s.LokalizacjaId
WHERE COALESCE(s.Ilosc, 0) <> COALESCE(r.SumaRuchow, 0);
Zapytania o ruchy jednego towaru przyspiesza indeks nieklastrowy po kolumnie TowarId z kolumnami dołączonymi. Taki indeks pokrywa zapytanie, więc silnik nie sięga po dane do tabeli. Składnię opisuje dokumentacja CREATE INDEX.
CREATE NONCLUSTERED INDEX IX_Ruch_Towar
ON dbo.Ruch (TowarId)
INCLUDE (LokalizacjaId, Ilosc, DataRuchu);
Sposób prezentowania i kontroli wartości w raportach opisuje strona o stanach magazynowych, a rezerwacje i stany dostępne omawia tekst o zarządzaniu stanami magazynowymi.
Dokumenty z ERP i zapis idempotentny
Większość dokumentów magazynowych nie powstaje w magazynie. Zlecenie przyjęcia lub wydania przychodzi z systemu ERP jako komunikat, a magazyn potwierdza jego wykonanie. Ten sam komunikat może dotrzeć dwa razy, na przykład po przerwie w łączności i ponowieniu wysyłki, a system nie może wtedy założyć drugiego dokumentu.
Przed duplikatem chroni numer zewnętrzny zapisany razem z nazwą systemu źródłowego. Unikalny indeks filtrowany odrzuca powtórzenie na poziomie bazy, a integracja traktuje taki błąd jako sygnał, że komunikat został już przetworzony. Nazwy w przykładzie są umowne.
CREATE UNIQUE NONCLUSTERED INDEX UX_Dokument_NrZewnetrzny
ON dbo.Dokument (SystemZrodlowy, NrZewnetrzny)
WHERE NrZewnetrzny IS NOT NULL;
Tryby wymiany danych z ERP, od plików po interfejsy usługowe, opisuje tekst o integracji systemów magazynowych. Po stronie magazynu liczy się jedno: ponowienie komunikatu nie może zmienić stanu drugi raz.
Storno i historia obrotów
Błąd w zatwierdzonym dokumencie koryguje się dokumentem odwrotnym, a nie edycją. Storno PZ tworzy ruchy o przeciwnym znaku i wskazuje dokument źródłowy w osobnej kolumnie. Historia obrotów zachowuje oba zapisy, więc audytor widzi zarówno pomyłkę, jak i jej naprawę.

Inwentaryzacja działa tym samym mechanizmem. Różnica między stanem spisanym a systemowym nie zmienia tabeli stanów wprost, tylko generuje dokument nadwyżek lub niedoborów, którego ruchy korygują saldo. Zasady liczenia i rozliczania opisuje tekst o inwentaryzacji w magazynie.
Archiwizacja i wielkość tabeli ruchów
Tabela ruchów rośnie liniowo z liczbą operacji, więc po kilku latach zapytania po dacie zaczynają obejmować miliony wierszy. Przy dużej historii tabelę można partycjonować po dacie ruchu, a starsze partycje przenosić do archiwum bez zmian w aplikacji. Saldo otwarcia okresu warto wtedy zapisywać jako osobny ruch, żeby suma po archiwizacji nadal zgadzała się ze stanem.
Ten sam podział obowiązków widać w operacjach dnia codziennego: kompletacja rezerwuje i zdejmuje towar, a przyjęcie go dopisuje, co szczegółowo opisuje tekst o operacjach magazynowych. Model danych pozostaje przy tym ten sam, niezależnie od tego, czy operację zainicjował człowiek, czy integracja z ERP.




