Bilans zamówień i rezerwacji - definicja i wzór

Składniki bilansu

Bilans zamówień i rezerwacji odpowiada na jedno pytanie: ile towaru zostanie wolnego, gdy zrealizuje się wszystko, co już obiecano klientom, i dotrze wszystko, co już zamówiono u dostawców. Według opisu HermesSQL zestawia stan magazynowy z przyjętymi zamówieniami klientów, dzięki czemu widać ilość w drodze oraz ilość zarezerwowaną. Z różnicy wynika, ile trzeba jeszcze zamówić. Sam stan na półce niczego takiego nie mówi, bo nie zawiera przyszłych ruchów.

Wzór ogólny ma cztery składniki: stan magazynowy plus dostawy w drodze, minus rezerwacje, minus niezrealizowane zamówienia klientów. Ostatni składnik dotyczy tylko zamówień bez rezerwacji. Zamówienie z rezerwacją liczy się raz, w rezerwacjach, inaczej ta sama ilość pomniejszyłaby bilans dwukrotnie.

SkładnikZnak we wzorzeŹródło danych
Stan magazynowyPlusIlość dostępna na magazynie, bez towaru zablokowanego
Dostawy w drodzePlusPozostała ilość z otwartych zamówień do dostawców
RezerwacjeMinusIlości przypisane do konkretnych zamówień klientów
Zamówienia bez rezerwacjiMinusNiezrealizowane pozycje zamówień, którym nie przypisano towaru

Wzór i przykład liczbowy

Przykład ma charakter ilustracyjny. Towar ma 120 sztuk na magazynie i 80 sztuk w drodze, rezerwacje obejmują 60 sztuk, a zamówienia bez rezerwacji 90 sztuk. Bilans wynosi 120 plus 80 minus 60 minus 90, czyli 50 sztuk. Wynik dodatni oznacza, że nowe zamówienie klienta na nie więcej niż 50 sztuk da się obiecać bez dokupowania. Wynik ujemny wskazuje ilość do zamówienia u dostawcy.

SELECT t.TowarId,
       ISNULL(s.Ilosc, 0) + ISNULL(d.WDrodze, 0)
         - ISNULL(r.Zarezerwowano, 0) - ISNULL(z.BezRezerwacji, 0) AS Bilans
FROM dbo.Towar AS t
LEFT JOIN dbo.StanMagazynowy AS s ON s.TowarId = t.TowarId
LEFT JOIN (SELECT TowarId, SUM(IloscPozostala) AS WDrodze
           FROM dbo.ZamowienieZakupuPozycja GROUP BY TowarId) AS d
       ON d.TowarId = t.TowarId
LEFT JOIN (SELECT TowarId, SUM(Ilosc) AS Zarezerwowano
           FROM dbo.Rezerwacja WHERE Status = 1 GROUP BY TowarId) AS r
       ON r.TowarId = t.TowarId
LEFT JOIN (SELECT TowarId, SUM(IloscPozostala) AS BezRezerwacji
           FROM dbo.ZamowienieKlientaPozycja
           WHERE MaRezerwacje = 0 GROUP BY TowarId) AS z
       ON z.TowarId = t.TowarId;

Nazwy tabel są przykładowe. Każdy składnik agreguje się osobno, zanim trafi do złączenia, bo złączenie surowych tabel pomnożyłoby wiersze i zawyżyło sumy. Wartości NULL z LEFT JOIN zamienia funkcja ISNULL, inaczej całe wyrażenie dla towaru bez rezerwacji dałoby NULL.

Reguła: bilans liczy się na towar, a przy partiach i terminach ważności także na partię. Suma bilansów partii nie zastąpi bilansu towaru, jeśli rezerwacja wskazuje konkretną partię.

Ilość dostępna do obiecania

W praktyce sprzedaży liczy się nie sam bilans, lecz ilość dostępna do obiecania na konkretną datę. Bierze ona pod uwagę tylko te dostawy, które przyjdą przed terminem wysyłki, i tylko te rezerwacje, które przypadają przed nim lub w tym samym dniu. Dostawa spóźniona o jeden dzień względem daty wysyłki nie zwiększa ilości dostępnej do obiecania, choć zwiększa bilans na koniec horyzontu.

Różnica ma znaczenie przy odpowiedzi na zapytanie klienta. Sprzedawca, który patrzy na bilans końcowy, obieca towar, którego w dniu wysyłki jeszcze nie będzie. Zapytanie liczące bilans narastająco do daty wysyłki daje odpowiedź, na której można polegać.

Rezerwacje twarde i miękkie

Rezerwacja twarda

Rezerwacja twarda odejmuje konkretną ilość od dostępności i uniemożliwia jej sprzedaż komukolwiek innemu. Towar pozostaje fizycznie na magazynie, lecz z punktu widzenia sprzedaży już go nie ma. Opisany podział jest ogólnym modelem stosowanym w systemach ERP i WMS, a nie deklaracją, że HermesSQL udostępnia oba typy pod tymi nazwami. Dla HermesSQL potwierdzone jest blokowanie towaru na określony czas z podaniem daty i godziny oraz automatyczne zwalnianie po jej upływie, co opisuje tekst o zamówieniach i rezerwacjach w systemie ERP.

Rezerwacja miękka

Rezerwacja miękka zapisuje zamiar, ale nie blokuje towaru. Wchodzi do bilansu jako sygnał popytu, więc planista widzi zbliżający się niedobór, a sprzedawca nadal może sprzedać ilość innemu klientowi. Takie podejście pasuje do ofert, które klient jeszcze rozważa. Przekształcenie oferty w zamówienie zamienia rezerwację miękką w twardą i dopiero wtedy ilość znika z dostępności.

CechaRezerwacja twardaRezerwacja miękka
Wpływ na dostępnośćZmniejsza ilość dostępnąNie zmienia ilości dostępnej
ZastosowaniePotwierdzone zamówienie klientaOferta lub zapytanie klienta
RyzykoZamrożony towar, gdy zamówienie zostanie anulowaneSprzedaż tej samej ilości dwa razy
ZwolnieniePo terminie albo ręcznieAutomatyczne przy zmianie statusu oferty

Wygasanie rezerwacji

Rezerwacja bez terminu blokuje towar do czasu ręcznej interwencji, a tej często brakuje. Termin ważności rezerwacji rozwiązuje problem, ponieważ zadanie okresowe zwalnia ilości po jego upływie i zwraca je do puli. Zbyt krótki termin generuje pracę sprzedawców, bo muszą odnawiać rezerwacje. Zbyt długi kumuluje martwe rezerwacje, które zaniżają bilans.

Rezerwacje partii i terminów ważności

Gdy towar podlega kontroli terminów ważności, rezerwacja wskazuje konkretną partię albo przynajmniej najkrótszy akceptowany termin. Klient z umową na towar ze świeżością powyżej trzydziestu dni nie przyjmie partii krótszej, więc bilans dla niego liczy się tylko z partii spełniających warunek. Zasady zarządzania terminami opisuje tekst o terminach ważności w HermesSQL.

Bilans w czasie i data niedoboru

Bilans narastający po datach

Bilans jednorazowy pokazuje sytuację na koniec horyzontu, lecz ukrywa niedobory w środku okresu. Dostawa za dwa tygodnie nie pomoże zamówieniu, które trzeba wysłać jutro. Z tego powodu ruchy planowane układa się na osi czasu i liczy bilans narastająco, po każdym zdarzeniu.

WITH Ruchy AS (
    SELECT TerminDostawy AS Data, IloscPozostala AS Zmiana
    FROM dbo.ZamowienieZakupuPozycja
    WHERE TowarId = @TowarId
    UNION ALL
    SELECT TerminRealizacji, -Ilosc
    FROM dbo.Rezerwacja
    WHERE TowarId = @TowarId AND Status = 1
)
SELECT Data,
       Zmiana,
       @StanPoczatkowy
         + SUM(Zmiana) OVER (ORDER BY Data ROWS UNBOUNDED PRECEDING) AS Bilans
FROM Ruchy
ORDER BY Data;

Klauzula ROWS wymusza sumę po kolejnych wierszach. Bez niej domyślny zakres RANGE sumuje razem wszystkie zdarzenia z tą samą datą i bilans w środku dnia byłby nieczytelny. Składnię funkcji okna opisuje dokumentacja o klauzuli OVER w T-SQL.

Data pierwszego niedoboru

Pierwszy wiersz z bilansem ujemnym wyznacza datę, do której trzeba mieć dostawę. Od tej daty odejmuje się czas dostawy dostawcy i otrzymuje ostatni dzień na złożenie zamówienia. Sposób wyliczania tego terminu opisuje tekst o planowaniu zakupów materiałowych, który zaczyna od potrzeb produkcyjnych, a nie od zamówień klientów.

Ilustracja transakcji zapisujących ruchy towaru, z których liczony jest bilans narastający
Bilans narastający powstaje z uporządkowanych po dacie ruchów planowanych: dostaw oraz rezerwacji

Konflikty rezerwacji i ich rozwiązywanie

Skąd bierze się konflikt

Konflikt występuje wtedy, gdy suma rezerwacji przekracza dostępną ilość. Ma dwa źródła. Pierwsze jest biznesowe: dostawa się opóźnia i rezerwacje, które miała pokryć, tracą pokrycie. Drugie jest techniczne: dwóch sprzedawców rezerwuje ostatnie sztuki w tej samej sekundzie, a oba zapisy przechodzą kontrolę, bo każdy widział stan sprzed zapisu drugiego.

Blokada przy zapisie rezerwacji

Konflikt techniczny eliminuje się na poziomie bazy. Zapis rezerwacji obejmuje odczyt stanu, odczyt sumy rezerwacji i wstawienie wiersza, więc musi przebiegać w jednej transakcji z blokadą, która uniemożliwia równoległy zapis dla tego samego towaru.

SET XACT_ABORT ON;
DECLARE @Stan DECIMAL(18,3), @Zarez DECIMAL(18,3);

BEGIN TRANSACTION;

SELECT @Stan = Ilosc
FROM dbo.StanMagazynowy WITH (UPDLOCK, HOLDLOCK)
WHERE TowarId = @TowarId;

SELECT @Zarez = ISNULL(SUM(Ilosc), 0)
FROM dbo.Rezerwacja
WHERE TowarId = @TowarId AND Status = 1;

IF @Stan - @Zarez >= @Ilosc
    INSERT INTO dbo.Rezerwacja (TowarId, KlientId, Ilosc, Status)
    VALUES (@TowarId, @KlientId, @Ilosc, 1);
ELSE
    THROW 50001, N'Brak wolnej ilości do rezerwacji.', 1;

COMMIT TRANSACTION;

Podpowiedź UPDLOCK z HOLDLOCK zakłada blokadę aktualizacji na wierszu stanu do końca transakcji. Druga sesja czeka, aż pierwsza zatwierdzi rezerwację, i widzi już zmniejszoną dostępność. Znaczenie podpowiedzi opisuje strona o podpowiedziach tabelarycznych, a mechanizm blokad i wersjonowania wierszy omawia przewodnik po blokowaniu transakcji. Kosztem jest serializacja rezerwacji tego samego towaru, którą łagodzi krótki czas trwania transakcji.

Reguły rozstrzygania konfliktów

Konflikt biznesowy wymaga reguły, która wskaże zamówienia tracące pokrycie. Bez reguły decyzja zapada ręcznie i zależy od tego, kto pierwszy zauważył problem. Cztery reguły spotyka się najczęściej.

  • Termin realizacji - pokrycie zachowują zamówienia z najbliższą datą wysyłki.
  • Priorytet klienta - zamówienia klientów o wyższym priorytecie umowy tracą pokrycie na końcu.
  • Kolejność zapisu - obowiązuje zasada pierwszeństwa, stosowana przy braku innych kryteriów.
  • Decyzja ręczna - sprzedaż wskazuje zamówienie do odroczenia, gdy reguły automatyczne dają remis.

Ślad decyzji przy konflikcie

Każde odroczenie zamówienia z powodu konfliktu powinno zostawić ślad z nazwiskiem decydenta i regułą, według której zapadła decyzja. Do zapisu należy dołączyć dostępność z tamtej chwili. Zapis w historii dokumentu pozwala rozstrzygać reklamacje klientów, którzy nie dostali towaru w obiecanym terminie. Bez niego argumentem pozostaje pamięć sprzedawcy.

Od niedoboru do zamówienia do dostawcy

Zamówienie generowane z bilansu

Według opisu HermesSQL pozwala generować zamówienia do dostawców bezpośrednio z poziomu bilansu. Pozycje z ujemnym wynikiem trafiają do propozycji, planista uzupełnia dostawcę i termin, a system tworzy dokument. Dalszy przebieg, od zamówienia przez przyjęcie do faktury, opisuje tekst o zakupach w HermesSQL.

Obieg ma jedną pułapkę. Zamówienie do dostawcy wchodzi do bilansu jako dostawa w drodze, więc przy kolejnym przeliczeniu niedobór znika. Jeśli dostawa się opóźni, bilans nadal wygląda dobrze, dopóki ktoś nie porówna dat. Z tego powodu bilans w czasie, opisany wyżej, jest bezpieczniejszy niż jedna liczba.

Zamówienia zależne od dostawy

Część zamówień klientów obsługuje się bez zapasu, wprost z dostawy: towar przychodzi od dostawcy i od razu jedzie do odbiorcy. Taka pozycja ma powiązanie z zamówieniem do dostawcy i nie wchodzi do ogólnego bilansu jako wolna ilość. Bez takiego powiązania dostawa zwiększałaby ilość dostępną dla innych klientów i konflikt powstałby przy pierwszej próbie wydania.

Zapas minimalny w bilansie

Opis wspomina o uwzględnianiu minimalnych stanów magazynowych przy wyliczaniu zapotrzebowania. Zapas minimalny odejmuje się w bilansie jak rezerwację, ponieważ ta część towaru nie jest przeznaczona do sprzedaży. Bilans dodatni po uwzględnieniu progu oznacza więc towar rzeczywiście wolny. Podstawy pojęcia stanów omawia tekst o stanach magazynowych.

Bilans jako źródło raportów i powiadomień

Współpraca działów na jednym bilansie

Ten sam bilans obsługuje kilka działów. Sprzedaż i zakupy czytają ten sam bilans w różnym celu: pierwsza sprawdza, czy da się przyjąć zamówienie, drugie odczytują niedobory do zamówienia. Finanse wykorzystują wartości do analizy rentowności, jak wynika z opisu integracji bilansu z modułami ERP. Podstawą jest jedno źródło danych, ponieważ dwa różne zestawienia dostępności prowadzą do sprzecznych obietnic wobec klienta.

Ilustracja historii obrotów magazynowych używanej do porównania bilansu z rzeczywistymi ruchami towaru
Historia obrotów pozwala sprawdzić, czy bilans z przeszłości zgadzał się z faktycznymi ruchami

Powiadomienia o brakach

System generuje powiadomienia o konieczności złożenia nowych zamówień do dostawców, gdy bilans spada poniżej progu. Mechanizm kolejek powiadomień opisuje tekst o powiadomieniach ERP. Progi ustawia się per towar, bo materiał o długim czasie dostawy wymaga alarmu wcześniej niż towar dostępny od ręki.

Rezerwacje osierocone

Rezerwacja, której zamówienie klienta zostało anulowane albo zrealizowane, nadal zmniejsza bilans, jeśli nikt jej nie zwolnił. Takie zapisy wykrywa zapytanie kontrolne uruchamiane cyklicznie, na przykład jako zadanie SQL Agent.

SELECT r.Id, r.TowarId, r.Ilosc
FROM dbo.Rezerwacja AS r
LEFT JOIN dbo.ZamowienieKlientaPozycja AS z
       ON z.Id = r.ZamowieniePozId
WHERE r.Status = 1
  AND (z.Id IS NULL OR z.Status IN (3, 4));

Wartości 3 i 4 w przykładzie oznaczają umownie zamówienie zrealizowane i anulowane. Wynik zapytania trafia do osoby odpowiedzialnej za sprzedaż, która zwalnia rezerwacje albo wyjaśnia, dlaczego pozostają aktywne. Regularne czyszczenie utrzymuje bilans blisko rzeczywistej dostępności.

Raporty bilansowe i rotacja

Zestawienia bilansu na widokach SQL udostępnia platforma opisana w artykule o raportowaniu HermesSQL. Uzupełnia je analiza rotacji i zalegania towarów, bo towar o ujemnym bilansie i towar zalegający wymagają przeciwnych decyzji. Wpływ bilansu na wyniki sprzedaży opisuje z kolei strona o analizie sprzedaży w HermesSQL. Moduły magazynowe klasy WMS, które przydzielają rezerwacje do lokalizacji i partii, prezentuje strona producenta Studio WMS.net.