Przejdź do głównej zawartości

Paginacja, filtry i synchronizacja

Dla: Programista

Listy w API Prynt zwykle działają tak samo: zwracają jedną stronę wyników, łączną liczbę rekordów i informację o kolejnych stronach. Argumenty konkretnej listy, w tym dostępne filtry, pola sortowania i domyślny rozmiar strony, podaje jej strona w referencji API.

Lista ze stronami przyjmuje zwykle argument pagination z dwoma polami:

Pole Znaczenie
page Numer strony, od 1.
pageSize Liczba rekordów na stronie, od 1 do 100. Bez niego lista zwraca stronę domyślną.

Wynik takiej listy to obiekt z polami:

Pole Znaczenie
items Rekordy bieżącej strony.
totalCount Liczba wszystkich rekordów spełniających filtr.
pageInfo.currentPage Numer bieżącej strony.
pageInfo.pageSize Rozmiar strony.
pageInfo.totalPages Liczba stron.
pageInfo.hasNextPage Czy jest kolejna strona.
pageInfo.hasPreviousPage Czy jest poprzednia strona.

Rozmiar strony spoza zakresu API odrzuca błędem API_PAGE_SIZE_EXCEEDED i nie wykonuje zapytania. Duża strona podnosi też szacowany koszt zapytania: przy 100 rekordach na stronę wybieraj mniej powiązanych obiektów. Zobacz Limity.

  • Filtry przyjmuje argument filter. Klucz widzi tylko filtry opisane w referencji. Filtry po cenach i kosztach wymagają zakresu prices:read.
  • Sortowanie przyjmuje argument sort z polem field (wartość enuma z listą pól sortowania) i kierunkiem direction: ASC albo DESC.

Filtry typu DateTime (np. updatedFrom, occurredFrom, productPackingDateFrom) przyjmują chwilę w ISO 8601. Dni drukarni liczysz w jej strefie, Europe/Warsaw, więc podawaj granice z offsetem: +01:00 zimą, +02:00 latem.

Data pakowania produktu (Product.packingDate) to w Prynt początek dnia w strefie drukarni: pakowanie 3 listopada 2026 ma wartość 2026-11-02T23:00:00Z. Granica 2026-11-03T00:00:00Z (północ UTC) wypada godzinę później i ten produkt pominie. Cały 3 listopada to productPackingDateFrom: "2026-11-03T00:00:00+01:00" i productPackingDateTo: "2026-11-03T23:59:59+01:00". Obie granice są włącznie, a druga obejmuje też datę pakowania zapisaną z godziną.

Przykład: zamówienia z produktem do spakowania dziś i jutro.

query PackingSoon($from: DateTime!, $to: DateTime!) {
orders(
filter: { productPackingDateFrom: $from, productPackingDateTo: $to, archived: false }
sort: { field: COMPLETION_DATE, direction: ASC }
pagination: { page: 1, pageSize: 50 }
) {
totalCount
items {
id
displayId
completionDate
products {
id
name
quantity
packingDate
}
}
pageInfo {
hasNextPage
}
}
}

Zmienne from i to policz w strefie drukarni:

Okno terminala
# GNU date (Linux; na macOS gdate z pakietu coreutils). Zapytanie w pliku packing-soon.graphql.
FROM=$(TZ=Europe/Warsaw date -d 'today 00:00' -Iseconds) # np. 2026-11-02T00:00:00+01:00
TO=$(TZ=Europe/Warsaw date -d 'tomorrow 23:59:59' -Iseconds) # np. 2026-11-03T23:59:59+01:00
jq -n --rawfile query packing-soon.graphql --arg from "$FROM" --arg to "$TO" \
'{query: $query, variables: {from: $from, to: $to}}' |
curl -sS https://twoja-drukarnia.prynt.pl/api/v1/graphql \
-H "Authorization: Bearer $PRYNT_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @-

Zapytanie PackingSoon z tymi zmiennymi wysyłasz jak każde inne, zobacz Szybki start. Biblioteka strefy czasowej sama poda właściwy offset także w dniu zmiany czasu, kiedy początek i koniec doby mają różne offsety.

  • Lista zwraca całe zamówienia, ze wszystkimi produktami. Który produkt spakować, rozstrzygasz po packingDate.
  • Warunki product* (productStatus, productIsFsc, productNameContains, daty pakowania) dotyczą jednego produktu naraz.
  • Zamówienia z produktem bez daty pakowania zwraca productHasPackingDate: false. Razem z zakresem dat lista jest pusta, bo ten sam produkt nie może mieć daty i jej nie mieć.
  • Starsze filtry dat zamówień (createdFrom, completionFrom, paymentDueDateFrom i ich pary …To) przyjmują też samą datę, która oznacza północ UTC. Im też podawaj chwilę z offsetem.

Przechodź po stronach, dopóki pageInfo.hasNextPage jest true:

// Pobiera wszystkie strony listy. `query` to Twoje zapytanie ze zmienną $page,
// `field` to nazwa pola listy w odpowiedzi, `post` wysyła żądanie i zwraca `data`.
async function fetchAll(post, query, field, variables = {}) {
const items = [];
for (let page = 1; ; page += 1) {
const data = await post(query, { ...variables, page });
const list = data[field];
items.push(...list.items);
if (!list.pageInfo.hasNextPage) return items;
}
}

Dane mogą się zmieniać w trakcie przechodzenia po stronach: nowy rekord przesuwa pozostałe o jedną pozycję. Dlatego:

  • sortuj po polu, które się nie zmienia, jeśli lista na to pozwala;
  • zapisuj rekordy po id (wstaw albo zaktualizuj), więc rekord pobrany dwa razy niczego nie zepsuje;
  • powtarzaj pełne przejście okresowo, jeśli musisz wykryć rekordy, które zniknęły z filtra albo zostały usunięte.

Lista, która ma filtr po czasie zmiany, pozwala pobierać tylko rekordy zmienione od poprzedniej synchronizacji. Wszystkie takie listy działają tak samo:

Element Znaczenie
Pole updatedAt rekordu Kiedy rekord albo cokolwiek w nim ostatnio się zmieniło. Co dokładnie się liczy, opisuje pole w referencji.
Filtr updatedFrom Rekordy zmienione w tej chwili lub później (granica włącznie).
Sortowanie UPDATED_AT, ASC Rekordy w kolejności zmian; rekordy z tym samym czasem zmiany w stałej kolejności według id.

updatedAt przesuwa tylko prawdziwa zmiana: zapis tych samych wartości (ponowienie, odesłanie do Prynt tego, co pobrałeś) go nie zmienia, więc synchronizacja w obie strony się nie zapętla. Zmiana zapisana przez Twoją integrację wraca w kolejnej porcji tak samo jak zmiana z Prynt.

Pierwsza synchronizacja pobiera wszystko: bez updatedFrom albo z updatedFrom: null (tak najprościej, gdy przekazujesz go zmienną). Kolejne:

  • zaczynaj od znacznika minus kilka minut, czyli od najpóźniejszego updatedAt z poprzedniej synchronizacji. Zapas łapie zmiany zapisane tuż przed nią, a widoczne w API dopiero po niej;
  • kolejną porcję czytaj od updatedAt ostatniego rekordu, a nie od następnego numeru strony. Rekord zmieniony w trakcie przeskakuje na koniec listy, a przy numerach stron przesunąłby pozostałe. Kolejną stronę z tym samym updatedFrom bierz tylko wtedy, gdy cała strona ma ten sam updatedAt;
  • zapisuj rekordy po id: rekord pobrany podwójnie nie szkodzi, a pominięty tak;
  • zapisz nowy znacznik dopiero po udanym końcu, żeby przerwana synchronizacja powtórzyła się od poprzedniego;
  • po wydaniu Prynt, które dodaje pole do istniejącego typu, zrób pełne przejście listy: nowe pole nie przesuwa updatedAt rekordów, więc synchronizacja przyrostowa nie przyniesie jego wartości dla rekordów zmienionych wcześniej. Nowe pola zapowiada historia zmian API.

Jedno zapytanie może pobrać kilka list naraz, każdą z jej własnym filtrem updatedFrom. To jedno żądanie limitu, a koszt rośnie tylko o pola obiektowe: lista z samymi id, updatedAt i pageInfo kosztuje 3 punkty bez względu na rozmiar strony. Tak działa sonda zmian, która co kilkanaście sekund sprawdza kilka list i pobiera szczegóły tylko zmienionych rekordów — zastępuje subskrypcje, których klucz API nie otwiera. Zobacz przepis Zmiany na żywo w ekranach integracji.

Ilości i czas ruchu magazynowego (stockMovements) po zapisaniu się nie zmieniają — tak samo cena, dostawca i dokument dostawy przyjęcia (unitPrice, netValue, supplierId, documentNumber, deliveredAt), ustawiane raz przy księgowaniu — więc lista ruchów nie ma updatedFrom. Ma filtr occurredFrom i sortowanie OCCURRED_AT: czytaj ją tak samo, tylko od occurredAt, czyli chwili zaksięgowania ruchu. Zasady są te same: znacznik minus kilka minut, kolejna porcja od occurredAt ostatniego ruchu, zapis po id (ruch pobrany drugi raz pomijasz). Stany materiałów (papers, paints, foils) synchronizujesz przez updatedFrom: każdy ruch przesuwa updatedAt materiału.

Dwie rzeczy zmieniają ruch bez nowego occurredAt, więc synchronizacja przyrostowa ich nie widzi: usunięcie w Prynt zamówienia, zamówienia zakupu albo dostawcy zeruje w ruchu powiązanie (orderId, purchaseOrderId, supplierId), a usunięcie materiału usuwa jego ruchy. Okresowe pełne przejście (jak dla usuniętych rekordów wyżej) wyłapie oba. Stan początkowy karty materiału założonej przed wersją 0.34.0 i ręczne zmiany stanu sprzed wersji 0.28.0 nie są ruchami (stan wpisany przy zakładaniu nowszej karty jest ruchem CORRECTION od 0) — bieżący stan bierz z listy materiałów, nie z sumy ruchów.

Stan produkcji jest częścią zamówienia: Operation.productionStatus, zadania operacji (Operation.tasks) i podsumowania production zamówienia, produktu i elementu. Każda zmiana statusu zadania na hali — rozpoczęcie, wstrzymanie, wznowienie, zakończenie, anulowanie — przesuwa updatedAt zamówienia, więc stan produkcji synchronizujesz tą samą listą orders z updatedFrom. Lista tasks ma własny updatedFrom, gdy potrzebujesz samych zadań, np. do tablicy hali; updatedAt zadania przesuwa każda jego zmiana w Prynt, także taka, której API nie pokazuje.

Każda operacja ma też własny zegar, Operation.updatedAt. Przesuwa go:

  • zmiana samej operacji: ilość, czas, maszyna, pozycja w elemencie, wykonana ilość, zakończenie;
  • każda zmiana statusu jej zadania: utworzenie, rozpoczęcie, wstrzymanie, wznowienie, zakończenie, anulowanie.

Zmiana zamówienia, produktu albo elementu nie przesuwa updatedAt operacji (przesuwa updatedAt zamówienia). Nie przesuwa go też zapis technologii bez zmian. Po pobraniu zmienionego zamówienia porównaj więc updatedAt jego operacji z zapisanymi u siebie i przelicz tylko te, które się zmieniły. Operacje zapisane przed wersją 0.33.0 mają updatedAt równy chwili tej aktualizacji.

Same operacje, bez zamówień, synchronizujesz listą operations: ma filtr updatedFrom i sortowanie UPDATED_AT, jak każda lista wyżej. Zmiana zamówienia (termin, status) nie wraca w niej, więc obok niej synchronizuj orders — także dlatego, że Operation.readyToStart zmienia się po zakończeniu innej operacji elementu, bez ruchu updatedAt tej operacji. Pełne przejście listy operations (bez updatedFrom) rób z sort: { field: UPDATED_AT, direction: ASC }: to sortowanie idzie indeksem, a domyślne — po terminie zamówienia — musi łączyć operacje z zamówieniami. Jak zbudować z niej ekran maszyny, pokazuje przepis Status produkcji dla klienta.

Usunięta operacja nie ma już updatedAt, więc nic jej nie zgłosi: znika z element { operations } zmienionego zamówienia i z listy operations. Okresowe pełne przejście (jak dla usuniętych rekordów wyżej) wyłapie operacje, których brak przeoczyłeś.

productInvoices i purchaseInvoices mają updatedFrom, a updatedAt faktury przesuwa także dołączenie dokumentu (file, także nowego w miejsce poprzedniego) albo jego usunięcie. Zapis faktury bez zmian nie przesuwa updatedAt. Faktura zakupu zapisana przed wersją 0.28.0 ma updatedAt równy chwili jej dodania.