Komunikaty agencji celnej: kontrakt JSON i przetwarzanie w bazie - dla wdrożeniowców
Agencja celna zgłasza towar i odbiera zwolnienie poza magazynem, a moduł Dozór celny dostaje z tego tylko komunikat JSON. Komunikat trafia do kolejki, jest przetwarzany w jednej transakcji i zmienia statusy partii. W wersji demonstracyjnej punkt API przyjmujący komunikaty nie istnieje: komunikat powstaje przez symulację albo przez import pliku w tym samym kontrakcie. Ekran kolejki opisuje strona użytkownika.
Na tej stronie
Kanały odbioru
Kolumna KANAL w kolejce przyjmuje trzy wartości kodowe (API, FTPS, PLIK). Procedura zapisu komunikatu dla każdej innej wartości ustawia PLIK. W bazie demonstracyjnej działają dwa sposoby wprowadzenia komunikatu: przycisk Symuluj komunikat agencji, który zapisuje komunikat z kanałem API, oraz przycisk Wczytaj komunikat z pliku (JSON), który zapisuje go z kanałem PLIK. Punkt API przyjmujący komunikaty od agencji nie jest częścią wersji demo, a komunikaty z kanałem FTPS w danych demonstracyjnych są tylko wpisami w kolejce.
Przy wczytywaniu pliku ekran przekazuje agencję wybraną w filtrze Agencja celna. Gdy agencji nie wskazano, a komunikat jest poprawnym JSON, agencję przypisuje pole agencjaNip. Gdy NIP nie pasuje do żadnej aktywnej agencji, komunikat trafia do kolejki bez agencji i pozostaje przetwarzalny.
Kontrakt JSON
Komunikat to jeden obiekt JSON. Poniższe przykłady używają danych z bazy demonstracyjnej: numeru dokumentu i kontenera z dokumentu przygotowanego pod demonstrację PZC oraz agencji demonstracyjnej. Wartości klienta i indeksów są przykładowe, a w prawdziwym komunikacie muszą istnieć w kartotece klienta.
Komunikat ZGLOSZENIE:
{
"typKomunikatu": "ZGLOSZENIE",
"typDokumentu": "T1",
"mrn": "26PLDEMO000000901H",
"klientNip": "1234567890",
"agencjaNip": "5250000001",
"kodProcedury": "71",
"pozycje": [
{ "lp": 1, "indeks": "DEMO-3001", "ilosc": 120, "jm": "szt", "masaBruttoKg": 960.5, "nrKontenera": "DEMU2000015" },
{ "lp": 2, "indeks": "DEMO-3002", "ilosc": 80, "jm": "szt", "masaBruttoKg": 410 }
]
}
Komunikat ZWOLNIENIE do tego samego dokumentu różni się rodzajem i obowiązkową datą zwolnienia:
{
"typKomunikatu": "ZWOLNIENIE",
"typDokumentu": "T1",
"mrn": "26PLDEMO000000901H",
"klientNip": "1234567890",
"agencjaNip": "5250000001",
"kodProcedury": "71",
"dataZwolnienia": "2026-10-17T09:42:00+02:00",
"pozycje": [
{ "lp": 1, "indeks": "DEMO-3001", "ilosc": 120, "jm": "szt" },
{ "lp": 2, "indeks": "DEMO-3002", "ilosc": 80, "jm": "szt" }
]
}
| Pole | Typ | Wymagane | Znaczenie |
|---|---|---|---|
| typKomunikatu | tekst | tak | ZGLOSZENIE albo ZWOLNIENIE; wielkość liter bez znaczenia. |
| typDokumentu | tekst | tak | T1, DSK albo MRN. |
| mrn | tekst | tak | Numer dokumentu celnego; nazwa pola jest ta sama także dla T1 i DSK. Spacje na brzegach są obcinane, litery zamieniane na wielkie. |
| klientNip | tekst | tak, gdy brak klientId | NIP klienta; myślniki i spacje są pomijane przy porównaniu z kartoteką kontrahentów. |
| klientId | liczba lub tekst | tak, gdy brak klientNip | Identyfikator kontrahenta; używany wyłącznie przy pustym klientNip, w demo. |
| agencjaNip | tekst | nie | Wskazuje agencję z kartoteki przy zapisie komunikatu do kolejki. |
| kodProcedury | tekst, 2 znaki | nie | Kod procedury; zapisywany przy tworzeniu dokumentu i aktualizowany przy ZGLOSZENIE, gdy podany. |
| dataZwolnienia | tekst ISO 8601 | tak dla ZWOLNIENIE | Czytane jest pierwszych 10 znaków jako data; reszta (godzina, strefa) jest pomijana. |
| pozycje | tablica | zalecane | Pozycje dokumentu. Bez pozycji zmienia się status dokumentu, ale statusy partii nie. |
| pozycje[].lp | liczba | nie | Numer pozycji; widoczny w komunikacie błędu. |
| pozycje[].indeks | tekst | tak | Indeks z kartoteki asortymentu klienta. |
| pozycje[].ilosc | liczba | tak | Ilość zgłoszona; trafia do pozycji nowo tworzonego dokumentu. |
| pozycje[].jm | tekst | nie | Jednostka miary; przy braku brana z kartoteki indeksu. |
| pozycje[].masaBruttoKg | liczba | nie | Masa brutto w kg. |
| pozycje[].nrKontenera | tekst | nie | Zawęża dopasowanie partii do kontenera. |
Komunikat symulowany z ekranu ma zawsze dwa pola klienta (klientNip oraz klientId), a przy przetwarzaniu liczy się NIP, jeśli jest niepusty. Data zwolnienia w symulacji to bieżąca data i godzina z zapisaną na sztywno strefą +02:00.
Hash treści i duplikaty
Przy zapisie do kolejki procedura liczy SHA-256 z odczytanej treści komunikatu i zapisuje wynik w kolumnie HASH_TRESCI z unikalnym indeksem UQ_x_cel_import_HASH. Jeśli komunikat o identycznej treści już jest w kolejce, nowy wiersz nie powstaje, a procedura zwraca OK z komunikatem: Ten komunikat był już odebrany (data) - duplikat rozpoznany po hashu treści, stan bez zmian. Zwracany jest identyfikator i status istniejącego wiersza, więc ponowne wczytanie tego samego pliku nie przetwarza komunikatu drugi raz.
Hash obejmuje całą treść znak po znaku, także białe znaki. Ten sam komunikat zapisany z innym wcięciem daje inny hash i zostanie potraktowany jak nowy. Przed ponownym wgraniem skorygowanego komunikatu nie trzeba więc kasować starego wiersza.
SELECT ID, KANAL, NUMER, STATUS_PRZETW,
LOWER(CONVERT(varchar(64), HASH_TRESCI, 2)) AS HASH_SHA256
FROM dbo.x_cel_import
ORDER BY ID DESC;
Przetwarzanie komunikatu
Przetwarzanie wykonuje jedna procedura, wywoływana zaraz po zapisie do kolejki oraz przyciskiem Ponów wybrany komunikat. Procedura działa z XACT_ABORT i limitem oczekiwania na blokadę 5000 ms.
Kolejność kroków jest stała.
- Walidacja struktury (50101-50105) - treść musi być poprawnym JSON, a pola rodzaju komunikatu oraz typu i numeru dokumentu muszą mieć dopuszczalne wartości; ZWOLNIENIE wymaga dataZwolnienia.
- Klient (50106) - kontrahent jest szukany w kartotece po NIP bez myślników i spacji, a przy pustym NIP po identyfikatorze klientId.
- Mapowanie indeksów (50107) - każdy indeks z pozycji musi istnieć w kartotece asortymentu tego klienta; pierwsza pozycja bez dopasowania przerywa przetwarzanie.
- Transakcja - dopiero po trzech powyższych krokach procedura otwiera transakcję, czyta dokument z blokadą aktualizacji i zmienia dane.
W transakcji procedura szuka dokumentu celnego po typie i numerze oraz po kliencie. Dla istniejącego dokumentu ZGLOSZENIE podnosi status dokumentu do 2, jeśli był niższy, a ZWOLNIENIE ustawia status 3 i datę zwolnienia. Dla nieznanego dokumentu zachowanie zależy od rodzaju komunikatu: ZGLOSZENIE tworzy nowy dokument, a ZWOLNIENIE kończy się błędem 50108. Nowy dokument dostaje status 2, dzisiejszą datę przyjęcia, magazyn z wiersza kolejki, uwagę Utworzony z importu komunikatu agencji. oraz pozycje z komunikatu. Partii nie tworzy, bo te powstają dopiero na PZC.
Następnie procedura zmienia statusy partii dokumentu. Komunikat ZGLOSZENIE przestawia na 2 partie ze statusem 1, a ZWOLNIENIE przestawia na 3 partie ze statusem 1 albo 2. Partia podlega zmianie, gdy jej indeks występuje w pozycjach komunikatu i, jeśli pozycja podaje nrKontenera, gdy kontener się zgadza. Dla każdej zmienionej partii powstaje wpis historii ze źródłem IMPORT, uzasadnieniem w postaci Zgłoszenie lub Zwolnienie towaru wraz z typem i numerem dokumentu oraz identyfikatorem komunikatu. Partie zablokowane (status 5) nie są zmieniane.
Dopasowanie pozycji i wynik częściowy
Dopasowanie pozycji do partii odbywa się po indeksie, a opcjonalnie po kontenerze. Po zmianie statusów procedura sprawdza, czy każda pozycja komunikatu ma partię do dopasowania na dokumencie. Sprawdzenie dotyczy tylko dokumentów, które mają już jakieś partie. Pozycja bez takiej partii, a także pozycja, której jedyną partią jest partia zablokowana, trafia do uwag, a komunikat dostaje wynik Częściowy. Treść uwag zapisuje się w kolumnie BLAD i na karcie komunikatu pod nazwą Uwagi przetwarzania.
Wynik przetwarzania trafia do kolumny STATUS_PRZETW jako 1 (Przetworzony), 3 (Częściowy) albo 2 (Błąd). Status 0 (Oczekuje) mają komunikaty zapisane, ale jeszcze nieprzetworzone. Przy wyniku Częściowym zmiany statusów partii, które się powiodły, zostają zapisane. Przy Błędzie transakcja jest cofana w całości.
Błędy przetwarzania
| Kod | Treść komunikatu | Przyczyna |
|---|---|---|
| 50101 | Błąd struktury: treść komunikatu nie jest poprawnym JSON. | Treść nie przechodzi ISJSON. |
| 50102 | Błąd struktury: typKomunikatu musi być ZGLOSZENIE albo ZWOLNIENIE. | Brak pola albo inna wartość. |
| 50103 | Błąd struktury: typDokumentu musi być T1, DSK albo MRN. | Brak pola albo inna wartość. |
| 50104 | Błąd struktury: brak numeru dokumentu (mrn). | Puste pole mrn. |
| 50105 | Błąd struktury: komunikat ZWOLNIENIE wymaga dataZwolnienia. | Brak daty albo data w nierozpoznanym formacie. |
| 50106 | Nieznany klient (NIP lub identyfikator klienta nie występuje w kartotece kontrahentów). | Żaden aktywny kontrahent roli WMS nie pasuje. |
| 50107 | Pozycja N: indeks X nie istnieje w kartotece klienta (skrót klienta). Transakcja została cofnięta, statusy partii nie zmieniły się. | Indeks spoza kartoteki asortymentu klienta. |
| 50108 | Nieznany dokument celny - zwolnienie nie ma czego zwolnić (najpierw zgłoszenie albo dokument w rejestrze). | ZWOLNIENIE dla dokumentu, którego nie ma w rejestrze klienta. |
| 1222 | Partie są właśnie zatwierdzane w wydaniu (blokada wiersza) - ponów komunikat za chwilę. | Przekroczony limit oczekiwania na blokadę wiersza partii. |
Kod 1222 to systemowy błąd SQL Server, nie błąd aplikacyjny: procedura zamienia go na czytelny tekst i zapisuje komunikat ze statusem Błąd. Wynika z tego, że wiersze partii czytane w transakcji importu i w zatwierdzeniu WZC blokują się wzajemnie, a oczekiwanie trwa najwyżej 5 sekund. Po takim błędzie wystarczy ponowić komunikat. Szczegóły blokad opisuje strona kontrola wydania i transakcje.
Ponowne przetwarzanie zapisanej treści
Przycisk Ponów wybrany komunikat wywołuje tę samą procedurę przetwarzania, ale z zapisaną już treścią, więc nie tworzy nowego wiersza w kolejce i nie liczy hasha od nowa. Ekran nie pozwala ponowić komunikatu o statusie Przetworzony. Po błędzie 50107 typowa naprawa to dopisanie brakującego indeksu w kartotece klienta i ponowienie tego samego komunikatu, bez zmiany treści. Po błędzie 50108 wystarczy najpierw przetworzyć komunikat ZGLOSZENIE albo założyć dokument w rejestrze.
Transport pliku przez UTF-16LE
Treść komunikatu wczytywanego z pliku przeglądarka przesyła do bazy jako ciąg szesnastkowy kodowania UTF-16LE, bez przedrostka 0x. Dzięki temu treść nie koliduje z separatorem parametrów w zapytaniu ani z apostrofem. Procedura odczytuje ją wywołaniem CONVERT do varbinary z formatem 2, a potem CAST do nvarchar. Każdy znak zajmuje dwa bajty z młodszym bajtem na początku, na przykład nawias klamrowy otwierający to 7B00, a litera A to 4100.
Jeżeli konwersja się nie powiedzie, procedura zwraca komunikat Nie udało się odczytać pliku komunikatu. Pusty plik daje komunikat Plik komunikatu jest pusty. Plik może mieć najwyżej 200 kB, powinien być zapisany w UTF-8 i mieć rozszerzenie .json albo .txt. Hash liczony jest z już odczytanego tekstu, nie z bajtów pliku, więc kodowanie pliku nie zmienia hasha tego samego tekstu.
Zapytania diagnostyczne
Pierwsze zapytanie pokazuje stan kolejki według statusów, a drugie wymienia komunikaty z błędem lub wynikiem częściowym razem z treścią błędu. Oba tylko czytają dane.
SELECT i.STATUS_PRZETW,
CASE i.STATUS_PRZETW WHEN 0 THEN 'Oczekuje' WHEN 1 THEN 'Przetworzony'
WHEN 2 THEN 'Błąd' WHEN 3 THEN 'Częściowy' END AS STATUS,
COUNT(*) AS LICZBA, MIN(i.DATA_ODBIORU) AS NAJSTARSZY
FROM dbo.x_cel_import i
GROUP BY i.STATUS_PRZETW
ORDER BY i.STATUS_PRZETW;
SELECT i.ID, i.DATA_ODBIORU, i.KANAL, i.TYP_KOMUNIKATU, i.TYP_DOKUMENTU, i.NUMER,
i.STATUS_PRZETW, i.BLAD, ISJSON(i.TRESC) AS CZY_JSON
FROM dbo.x_cel_import i
WHERE i.STATUS_PRZETW IN (2, 3)
ORDER BY i.DATA_ODBIORU DESC;
Najczęstsze pytania
Czy w wersji demo agencja może wysłać komunikat przez API?
Nie. Punkt API przyjmujący komunikaty nie jest częścią wersji demo. Komunikat tworzy się przyciskiem Symuluj komunikat agencji albo wczytuje z pliku JSON w tym samym kontrakcie.
Co się stanie przy wczytaniu tego samego pliku drugi raz?
Baza rozpozna duplikat po hashu SHA-256 treści, nie doda wiersza do kolejki i zwróci komunikat o duplikacie. Stan partii pozostanie bez zmian.
Dlaczego komunikat ZWOLNIENIE kończy się błędem 50108?
Dokumentu o podanym typie, numerze i kliencie nie ma w rejestrze. Najpierw trzeba przetworzyć komunikat ZGLOSZENIE, który tworzy dokument, albo dodać dokument w rejestrze.
Czy błąd w jednej pozycji cofa cały komunikat?
Tak, błąd 50107 i inne błędy rzucone w transakcji cofają wszystkie zmiany tego komunikatu. Komunikat dostaje status Błąd, a statusy partii się nie zmieniają.
Jaki jest limit rozmiaru pliku komunikatu?
Ekran przyjmuje pliki .json i .txt do 200 kB w kodowaniu UTF-8.
Słownik pojęć
- ZGLOSZENIE
- Rodzaj komunikatu agencji: zgłoszenie dokumentu z numerem; przestawia partie ze statusu Pod dozorem na Zgłoszony.
- ZWOLNIENIE
- Rodzaj komunikatu agencji: zwolnienie towaru z datą; przestawia partie ze statusu Pod dozorem albo Zgłoszony na Zwolniony.
- Kolejka importu
- Tabela komunikatów od agencji ze statusem przetwarzania: Oczekuje, Przetworzony, Błąd albo Częściowy.
- Hash treści
- Skrót SHA-256 treści komunikatu z unikalnym indeksem; rozpoznaje duplikaty.
- Wynik częściowy
- Komunikat przetworzony, w którym część pozycji nie miała partii do dopasowania albo trafiła na partię zablokowaną.
- UTF-16LE szesnastkowo
- Sposób przesłania treści pliku do bazy: każdy znak jako dwa bajty zapisane cyframi szesnastkowymi.