Awizacje dostaw w jednym widoku - lista i statusy

Przy kilkudziesięciu dostawach dziennie pojedyncza awizacja przestaje być obiektem, na którym pracuje planista. Pracuje on na zbiorze rekordów, który filtruje, a potem zmienia zbiorczo. Ten tekst dotyczy właśnie takiej pracy na wielu awizacjach dostaw naraz.

Punktem wyjścia jest lista z jasnym statusem każdego rekordu. W bazie VSS.net kolumna statusu oznacza bufor wartością 0 i zatwierdzenie wartością 1, a anulowanie lub usunięcie literą X. To jednak status rekordu, a nie wizyty. Przebieg samej wizyty na terenie zakładu opisuje osobny cykl, w którym pojazd przechodzi przez kolejne etapy.

Status wizytyZnaczenieKto zmienia
zatwierdzonaokno zarezerwowane, pojazd oczekiwanyplanista albo system po kontroli
na tereniepojazd wjechał przez bramęochrona lub skan kodu
w rozładunkupojazd stoi przy rampiebrygadzista przyjęć
zakończonarozładunek zamknięty, pojazd wyjechałochrona lub system przy wyjeździe
anulowanarezerwacja zwolniona, okno wraca do puliplanista lub przewoźnik

Reguła: status wizyty można zmienić tylko na następny dozwolony etap, a każda zmiana zostawia w historii godzinę i użytkownika.

Tę regułę najprościej wyrazić jako tabelę dozwolonych przejść. Poniższy fragment C# opisuje ją w słowniku i sprawdza zmianę jednym wywołaniem. Nazwy typów są przykładowe.

enum StatusWizyty { Zatwierdzona, NaTerenie, Rozladunek, Zakonczona, Anulowana }

static readonly Dictionary<StatusWizyty, StatusWizyty[]> Dozwolone = new()
{
    [StatusWizyty.Zatwierdzona] = new[] { StatusWizyty.NaTerenie, StatusWizyty.Anulowana },
    [StatusWizyty.NaTerenie]    = new[] { StatusWizyty.Rozladunek },
    [StatusWizyty.Rozladunek]   = new[] { StatusWizyty.Zakonczona },
};

static bool MoznaZmienic(StatusWizyty z, StatusWizyty na) =>
    Dozwolone.TryGetValue(z, out var cele) && Array.IndexOf(cele, na) >= 0;

Dzięki takiemu rozwiązaniu nie da się oznaczyć rozładunku jako zakończonego dla pojazdu, który nigdy nie wjechał. Typy wyliczeniowe w C# opisuje dokumentacja Microsoft Learn.

Kalendarz i filtry przy dużej liczbie awizacji

Lista dobrze pokazuje szczegóły, ale nie pokazuje rozkładu w czasie. Do tego służy kalendarz: oś godzin i rampy jako kolumny, a każda awizacja jako blok o długości okna. Kolizje i luki widać od razu, bez porównywania godzin w tabeli. Funkcje widoków opisuje artykuł o kalendarzach w VSS.net, a sam mechanizm rezerwacji z poziomu kalendarza - tekst o aplikacji kalendarz do awizacji dostaw.

Ilustracja definiowania interaktywnego kalendarza do awizacji transportu z blokami okien czasowych
Interaktywny kalendarz awizacji - widok, w którym planista ocenia obłożenie ramp w ciągu doby

Przy setkach rekordów sam kalendarz też przestaje wystarczać, bo blok zasłania blok. Potrzebne są filtry, które zawężają widok do jednego pytania. Zestawienie poniżej pokazuje cztery najczęściej używane.

FiltrPytanie, na które odpowiadaTypowy użytkownik
rampaco dzieje się przy konkretnej bramiebrygadzista przyjęć
przewoźnikjak często dana firma się spóźniadział logistyki
statusktóre awizacje czekają w buforzeplanista
zakres datczy przyszły tydzień jest przeciążonykierownik magazynu

Po stronie bazy danych filtry opcjonalne zapisuje się jednym zapytaniem z parametrami dopuszczającymi wartość pustą. Aby optymalizator nie użył planu zbudowanego dla innego zestawu parametrów, dodaje się OPTION (RECOMPILE). Wskazówki zapytań opisuje dokumentacja Query Hints.

SELECT a.Id, a.OknoOd, a.OknoDo, a.RampaId, a.PrzewoznikId, a.Ach
FROM dbo.Awizacja AS a
WHERE a.OknoOd >= @od AND a.OknoOd < @do
  AND (@rampa      IS NULL OR a.RampaId      = @rampa)
  AND (@przewoznik IS NULL OR a.PrzewoznikId = @przewoznik)
  AND (@ach        IS NULL OR a.Ach          = @ach)
ORDER BY a.OknoOd
OPTION (RECOMPILE);

Indeks na zakres dat i status skraca odczyt, ale zapytanie musi zostać czytelne dla ludzi utrzymujących kod. Jedno zapytanie z filtrami opcjonalnymi zwykle wystarcza, dopóki tabela nie urośnie do milionów wierszy.

Masowe zmiany terminów i anulowanie serii

Awaria bramy o siódmej rano dotyka kilkunastu awizacji naraz. Dyspozytor nie powinien dzwonić do każdego przewoźnika z osobna ani poprawiać rekordów jeden po drugim. Zarządzanie awizacjami z jednego panelu pozwala zmienić termin lub przenieść wizytę na inną bramę. Anulowanie rezerwacji także odbywa się zbiorczo, a system powiadamia zainteresowanych.

Zmiana zbiorcza ma sens tylko wtedy, gdy jest bezpieczna. Zanim rekordy zostaną zapisane, system musi sprawdzić, czy przesunięcie nie stworzy kolizji z innymi wizytami. Poniższy przykład przesuwa wszystkie zatwierdzone awizacje jednej rampy o zadaną liczbę minut i wycofuje zmianę, gdy któreś okna zaczną się nakładać. Przykład zakłada jedną wizytę na rampę w danym czasie.

BEGIN TRAN;

UPDATE a
SET OknoOd = DATEADD(MINUTE, @minuty, a.OknoOd),
    OknoDo = DATEADD(MINUTE, @minuty, a.OknoDo)
FROM dbo.Awizacja AS a
WHERE a.RampaId = @rampa AND a.Ach = '1'
  AND a.OknoOd >= @od AND a.OknoOd < @do;

IF EXISTS (SELECT 1
           FROM dbo.Awizacja AS x
           JOIN dbo.Awizacja AS y
             ON y.RampaId = x.RampaId AND y.Id > x.Id AND y.Ach = '1'
            AND x.OknoOd < y.OknoDo AND y.OknoOd < x.OknoDo
           WHERE x.RampaId = @rampa AND x.Ach = '1')
    ROLLBACK;
ELSE
    COMMIT;

Funkcję dodającą minuty do daty opisuje dokumentacja DATEADD. Wszystkie zmiany odbywają się w jednej transakcji, więc częściowo przesunięty harmonogram nigdy nie zostaje zapisany.

  • Podgląd przed zapisem - lista rekordów, których zmiana dotknie, z podświetlonymi kolizjami.
  • Jedna transakcja - albo przesuwają się wszystkie wybrane awizacje, albo żadna.
  • Powiadomienia po zatwierdzeniu - wiadomość do przewoźników wysyłana dopiero po udanym zapisie.
  • Dziennik zmian - zapis, kto i kiedy przesunął okna oraz z jakim uzasadnieniem.

Anulowanie serii działa podobnie, ale zamiast przesuwać okna oznacza rekordy statusem X i zwalnia pojemność. Zwolnione sloty wracają do puli i mogą zostać zaproponowane transportom z listy rezerwowej. Proces zmian z perspektywy przewoźnika, w tym statusy i integrację przez API, opisuje artykuł o awizacji transportu.

Awizacje cykliczne dla stałych przewoźników

Część dostaw powtarza się bez zmian: kurierzy odbierają towar kilka razy w tygodniu o tej samej godzinie, a dostawcy komponentów przyjeżdżają według stałego grafiku. Ręczne zgłaszanie każdego wystąpienia oznaczałoby setki niepotrzebnych operacji miesięcznie. Kalendarz awizacji VSS.net pozwala tworzyć cykliczne rezerwacje okien czasowych w ustawionym interwale.

Seria a pojedyncze wystąpienie

Podstawowa decyzja przy każdej zmianie dotyczy zakresu: jedno wystąpienie albo cała seria od wskazanej daty. Przesunięcie jednej dostawy z powodu święta nie powinno zmieniać grafiku na kolejne miesiące. Odwrotnie, zmiana trasy przewoźnika dotyczy całej serii od wskazanej daty.

Technicznie seria jest regułą powtarzania, z której system generuje konkretne rekordy na okres z wyprzedzeniem. Wyjątek od reguły zapisuje się jako osobny rekord, który ma pierwszeństwo przed wygenerowanym. Dzięki temu edycja jednego wystąpienia nie zmienia pozostałych.

Powiadomienia SMS i e-mail należą do standardowych funkcji kalendarza i ograniczają liczbę telefonów do dyspozytora w dniu dostawy. Porządek okien w takim harmonogramie opisuje tekst o oknach czasowych, a ogólną ideę harmonogramu online - artykuł o harmonogramie online w zarządzaniu transportem.

Widoczność awizacji dla przewoźników i najemców

Przy wielu awizacjach różne osoby powinny widzieć różne podzbiory. Przewoźnik ogląda wyłącznie własne zgłoszenia. W centrum logistycznym obsługującym kilku najemców każdy z nich ma osobny harmonogram i nie widzi rezerwacji pozostałych. Planista przegląda całość, a ochrona tylko pojazdy oczekiwane danego dnia.

RolaZakres widocznościTypowe operacje
przewoźnikawizacje własnej firmyzgłoszenie, zmiana terminu, anulowanie
najemca w centrum logistycznymwłasny harmonogram i własne rampyrezerwacje w ramach umowy
planista magazynuwszystkie awizacje obiektuzmiany zbiorcze, rezerwa pojemności
ochronapojazdy oczekiwane danego dniarejestracja wjazdu i wyjazdu

Ograniczenie widoczności musi działać w bazie danych, a nie tylko w interfejsie. Jeśli filtr po firmie istnieje wyłącznie w przeglądarce, wystarczy zmienić parametr żądania, żeby zobaczyć cudze dane. Najprościej wymusić go funkcją tabelaryczną, z której korzystają wszystkie zapytania użytkowników zewnętrznych.

CREATE FUNCTION dbo.AwizacjeFirmy (@firmaId INT)
RETURNS TABLE
AS
RETURN
    SELECT a.Id, a.OknoOd, a.OknoDo, a.RampaId, a.Ach
    FROM dbo.Awizacja AS a
    WHERE a.FirmaId = @firmaId;

Od SQL Server 2016 dostępna jest też wbudowana ochrona na poziomie wierszy, opisana w dokumentacji Row-Level Security. Reguła filtrująca jest wtedy dołączana do każdego zapytania automatycznie, co ogranicza ryzyko pominięcia filtra w nowym raporcie.

Czas postoju pojazdu jako wskaźnik

Czas postoju obejmuje okres od wjazdu pojazdu na teren zakładu do jego wyjazdu. Jest to najprostszy wskaźnik pokazujący, ile realnie zyskuje firma po uruchomieniu awizacji. Przed wdrożeniem warto więc zmierzyć obecny czas oczekiwania przed bramą, żeby porównanie miało punkt odniesienia.

Sam wskaźnik składa się z etapów, które mają różnych właścicieli. Wyodrębnienie ich pozwala wskazać, gdzie tracony jest czas, zamiast dyskutować o średniej.

Etap postojuZnacznik czasuKto wpływa na wynik
oczekiwanie przed bramąprzyjazd i wjazd na terenplanista i portiernia
ważeniewaga przy wjeździe i wyjeździeochrona
oczekiwanie na rampęwjazd i podstawienie pod rampęplac i planista
rozładunekpoczątek i koniec obsługizespół przyjęć

Największy udział w postoju ma zwykle oczekiwanie, a nie sam rozładunek. Kierowca, który dotrze przed wyznaczoną godziną, powinien zostać skierowany na miejsce postojowe zamiast czekać w kolejce przy bramie. Zajętość miejsc pokazuje artykuł o wizualizacji miejsc parkingowych, a dalszy przebieg przyjęcia opisuje tekst o awizacjach dostaw do magazynu.

Integracja z wagami samochodowymi rejestruje masę pojazdu przy wjeździe i wyjeździe bez ręcznego przepisywania odczytów. Znacznik czasu ważenia jest wtedy dodatkową, wiarygodną datą w historii wizyty. Szczegóły opisuje strona o wagach samochodowych w systemie awizacji.

Ilustracja integracji systemu awizacji z wagami samochodowymi rejestrującymi masę pojazdu przy wjeździe i wyjeździe
Waga samochodowa jako źródło znaczników czasu - dane, z których liczy się etap ważenia w czasie postoju

Do raportu lepiej nadaje się mediana i wysoki percentyl niż średnia. Kilka bardzo długich postojów po awarii zawyża średnią, więc typowy przebieg znika z raportu. Poniższe zapytanie liczy oba wskaźniki dla każdej rampy. Funkcję percentyla opisuje dokumentacja PERCENTILE_CONT.

SELECT DISTINCT a.RampaId,
       PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY DATEDIFF(MINUTE, a.Wjazd, a.Wyjazd))
           OVER (PARTITION BY a.RampaId) AS Mediana,
       PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY DATEDIFF(MINUTE, a.Wjazd, a.Wyjazd))
           OVER (PARTITION BY a.RampaId) AS P90
FROM dbo.Awizacja AS a
WHERE a.Wyjazd IS NOT NULL AND a.Wjazd >= @od;

Punktualność przewoźników w raporcie

Drugi wskaźnik dotyczy zachowania dostawców. Punktualność określa, czy pojazd przyjechał w oknie z ustaloną tolerancją. Przyjazd w tolerancji liczy się jako punktualny, a wcześniejszy lub późniejszy jako odchylenie. Magazyn musi zdecydować, jak traktuje przyjazd zbyt wczesny, bo zajmuje on miejsce na placu równie skutecznie jak spóźniony.

KategoriaWarunekSkutek na placu
przyjazd zbyt wczesnyprzed początkiem tolerancjipojazd zajmuje miejsce postojowe, rampa jeszcze zajęta
przyjazd punktualnyw oknie z tolerancjąbrak zakłóceń w harmonogramie
przyjazd spóźnionypo końcu tolerancjiprzesunięcie kolejnych okien albo utrata rezerwacji

Każda awizacja zostawia ślad w bazie danych, więc dane do raportu powstają same. Planowany i rzeczywisty czas przyjazdu gromadzą się bez dodatkowej pracy, podobnie jak czas rozładunku. Raport terminowości wskazuje przewoźników, którzy regularnie się spóźniają, i staje się argumentem w rozmowach kontraktowych.

SELECT p.Nazwa AS Przewoznik,
       COUNT(*) AS Wizyty,
       SUM(CASE WHEN a.Przyjazd BETWEEN DATEADD(MINUTE, -@tol, a.OknoOd)
                                    AND DATEADD(MINUTE,  @tol, a.OknoOd)
                THEN 1 ELSE 0 END) AS WTolerancji,
       100.0 * SUM(CASE WHEN a.Przyjazd BETWEEN DATEADD(MINUTE, -@tol, a.OknoOd)
                                            AND DATEADD(MINUTE,  @tol, a.OknoOd)
                        THEN 1 ELSE 0 END) / COUNT(*) AS ProcentPunktualnych
FROM dbo.Awizacja AS a
JOIN dbo.Przewoznik AS p ON p.Id = a.PrzewoznikId
WHERE a.Przyjazd IS NOT NULL AND a.OknoOd >= @od
GROUP BY p.Nazwa
ORDER BY ProcentPunktualnych;

Raporty tego typu buduje się zwykle w narzędziu raportowym z zapytaniem SQL jako źródłem danych. Sposób przygotowania takich zestawień opisuje tekst o SQL Report Builder i raportach RDL. Wnioski z raportu przekłada się na parametry systemu, na przykład tolerancję i pojemność okien.

Dostosowanie okien na podstawie wskaźników

Oba wskaźniki mają sens dopiero wtedy, gdy prowadzą do zmiany parametrów. Jeśli mediana czasu rozładunku dla dostaw wielkogabarytowych wynosi 40 minut, a okno zaplanowano na 30, każda taka dostawa przesuwa kolejne. Wystarczy wtedy wydłużyć okno dla tej grupy asortymentu, zamiast dokładać rampy.

Odwrotna sytuacja występuje przy przewoźnikach o niskiej punktualności. Rezerwacja, z której korzystają rzadko, blokuje pojemność, więc skrócenie tolerancji albo wprowadzenie rezerwy zwalnianej po kwadransie zwykle poprawia wykorzystanie ramp. Zmiany wprowadza się stopniowo i sprawdza w kolejnych tygodniach, bo skutki widać dopiero w danych.

Pełny obraz obejmuje też pojazdy, które przyjechały bez zgłoszenia. Ich udział w ruchu opisuje artykuł o transporcie nieawizowanym. Planowanie obłożenia ramp i zgodność zgłoszeń z zamówieniami omawia tekst o awizacji dostawy jako planie dnia, a zarządzanie całym procesem prezentuje strona zarządzanie awizacjami na portalu awizacji.