Przejdź do głównej zawartości

Status produkcji dla klienta

Dla: Programista

Klient drukarni chce wiedzieć, co dzieje się z jego zamówieniem: czy już się drukuje, ile zostało i kiedy będzie gotowe. Ten przepis zasila taki widok w panelu klienta albo w sklepie internetowym: pobiera zamówienia, w których coś się zmieniło od poprzedniego razu — także zadanie na hali, które ruszyło, stanęło albo się skończyło — i zapisuje dla każdego status do pokazania klientowi.

  • Klucz z zakresami orders:read i production:read. orders:read daje zamówienia, production:read — stan ich produkcji. Bez production:read pola production i productionStatus nie istnieją w schemacie klucza, a zapytanie z nimi API odrzuca błędem API_SCOPE_MISSING.
  • Klienta API z przepisu Obsługa błędów i ponowień. Kod niżej wysyła żądania przez niego.
  • Miejsce na znacznik synchronizacji (np. plik albo wiersz w bazie) i tabelę statusów zamówień, jeden wiersz na zamówienie, zastępowany przy każdej zmianie.

Stan produkcji jest częścią zamówienia, więc nie potrzebujesz osobnej listy zadań:

  • Operation.productionStatus mówi, gdzie stoi operacja: NOT_STARTED (nic jeszcze nie zrobiono; zadanie może być już zaplanowane), IN_PROGRESS (ktoś teraz nad nią pracuje), PAUSED (praca wstrzymana albo część ilości zrobiona, a reszta czeka) albo COMPLETED.
  • production zamówienia, produktu i elementu podsumowuje ich operacje: status całości, liczbę operacji (operationCount, completedOperationCount) i percent — jaką część pracy już zrobiono. Status całości to IN_PROGRESS, gdy coś trwa albo gdy część operacji jest zakończona, a reszta czeka na swoją kolej; PAUSED — gdy któraś operacja stanęła w połowie (wstrzymane zadanie albo część ilości zrobiona), a nic nie trwa.
  • percent waży operacje planowanym czasem maszyn — szacunkiem drukarni (timePerUnitSeconds × quantity każdej operacji), a nie czasem zmierzonym na hali. Operacja bez szacunku waży tyle, co średnia operacja z szacunkiem, a gdy żadna go nie ma, każda waży tyle samo; 100 pojawia się dopiero po zakończeniu wszystkich operacji. remainingSeconds to planowany czas tego, co zostało, policzony tylko z operacji z szacunkiem.
  • Każda zmiana statusu zadania — rozpoczęcie, wstrzymanie, wznowienie, zakończenie, anulowanie — przesuwa updatedAt zamówienia. Lista orders z filtrem updatedFrom zwraca więc każde zamówienie, w którym coś się zmieniło na hali, tak samo jak zamówienie zmienione w biurze.
  • Każda operacja ma też własny zegar updatedAt, a lista operations — filtr updatedFrom. Przydaje się, gdy potrzebujesz samych operacji, np. do ekranu przy maszynie (sekcja Widok maszyny niżej).
  1. Pierwsza synchronizacja pobiera wszystkie zamówienia: updatedFrom jest wtedy pusty. Zamówienia zarchiwizowane i zakończone dawno temu możesz pominąć filtrem archived: false albo po statusie.

  2. Zapisuj status po identyfikatorze zamówienia: wstaw wiersz albo zastąp go w całości. Zamówienie pobrane dwa razy niczego nie psuje.

  3. Kolejną porcję zaczynaj od updatedAt ostatniego zamówienia, a po udanym końcu zapisz znacznik: najpóźniejszy updatedAt. Następna synchronizacja zaczyna od znacznika minus 5 minut. Szczegóły algorytmu: Paginacja, filtry i synchronizacja.

Status zamówienia (Order.status) zmienia drukarnia: przyjmuje zamówienie, a potem oznacza je jako gotowe albo wysłane. Hala bywa szybsza: zamówienie wciąż ma status ACCEPTED, a pierwsze zadanie już ruszyło. Dlatego kod pokazuje klientowi etap z obu źródeł:

Etap dla klienta Kiedy
Przyjęte CREATED albo ACCEPTED, a produkcja nie ruszyła (production.status = NOT_STARTED)
W produkcji IN_PRODUCTION albo ACCEPTED z rozpoczętą produkcją — z procentem percent
Gotowe READY
Wysłane SENT; RECEIVED — odebrane
Anulowane CANCELLED

production.status = PAUSED w zamówieniu, które jest w produkcji, oznacza, że praca stanęła w połowie operacji i nic teraz nie trwa: zadanie wstrzymano albo zrobiono część ilości, a reszta czeka na maszynę albo na kolejną zmianę. Kod zaznacza to w statusie (paused), żeby klient nie zobaczył zatrzymanego paska postępu bez wyjaśnienia. Przerwa między operacjami (druk skończony, foliowanie jeszcze nie ruszyło) to zwykły tok produkcji: status pozostaje IN_PROGRESS.

Lista tasks pokazuje zadania hali — po jednym na każde podjęcie pracy nad operacją — z terminem, początkiem i końcem pracy (startedAt, completedAt) i maszyną (machineId; nazwy maszyn da słownik machines z zakresem reference:read). Filtruj ją po operacji, maszynie albo statusie, a z orders:read także po zamówieniu (orderId); ma też własny updatedFrom. Nazwa zadania (title) zawiera numer zamówienia, więc — jak operacja zadania — wymaga orders:read. API nie pokazuje, kto pracował nad zadaniem, ani ile czasu mu to zajęło: dane pracowników drukarni nie wychodzą przez klucz.

Zadanie wskazuje zamówienie, produkt i element przez swoją operację: operation { element { name } product { name } order { displayId } }. Działa to także dla zadania operacji podjętej ponownie — po zakończeniu części ilości hala zaczyna nowe zadanie, a poprzednie dalej wskazuje tę samą operację.

Funkcja synchronizacji przyjmuje znacznik z poprzedniego razu (null przy pierwszym) i zapis statusu, a zwraca nowy znacznik. Zapisz go dopiero po udanym końcu: przerwana synchronizacja powtórzy się wtedy od poprzedniego.

Przykłady pokazują adres zastępczy. Wpisz adres swojej drukarni, żeby go podmienić.

Wymaga curl, jq i skryptu prynt-call.sh z przepisu Obsługa błędów i ponowień w tym samym katalogu. Każdy status wypisuje jako jedną linię JSON, a znacznik trzyma w pliku ostatnia-synchronizacja-produkcji.txt.

production-status.sh
#!/usr/bin/env bash
# production-status.sh — jedna synchronizacja statusów produkcji (curl, jq; prynt-call.sh z przepisu
# „Obsługa błędów i ponowień”): zamówienia zmienione od poprzedniej synchronizacji — także te, na
# których hala zaczęła, wstrzymała albo skończyła pracę — jako status dla klienta drukarni. Każdy
# status wypisuje jako linię JSON, którą zapisujesz u siebie po `orderId`:
# ./production-status.sh >>statusy.ndjson
# Znacznik trzyma w pliku ostatnia-synchronizacja-produkcji.txt i zapisuje go dopiero na końcu.
set -euo pipefail
marker_file=ostatnia-synchronizacja-produkcji.txt
since=$(cat "$marker_file" 2>/dev/null || true)
body=$(mktemp) result=$(mktemp)
trap 'rm -f "$body" "$result"' EXIT
# Zamówienia zmienione od ostatniego razu — każda zmiana statusu zadania na hali też przesuwa
# `Order.updatedAt` — ze stanem produkcji całego zamówienia i jego produktów (production:read).
# 20 zamówień na stronę: z produktami i ich postępem mieści się w limicie kosztu zapytania.
query='query ChangedOrdersProduction($from: DateTime, $page: Int!) {
orders(
filter: { updatedFrom: $from }
sort: { field: UPDATED_AT, direction: ASC }
pagination: { page: $page, pageSize: 20 }
) {
items {
id displayId status completionDate updatedAt
production { status percent remainingSeconds }
products { id number name status production { status percent } }
}
pageInfo { hasNextPage }
}
}'
# Status zamówienia dla klienta drukarni: etap (z jego statusu, a w produkcji — ze stanu
# produkcji; przyjęte, na którym hala już zaczęła pracę, jest „W PRODUKCJI”), procent pracy
# zrobionej na maszynach i produkty.
client_status='{
"CREATED": "PRZYJĘTE", "ACCEPTED": "PRZYJĘTE", "IN_PRODUCTION": "W PRODUKCJI", "READY": "GOTOWE",
"SENT": "WYSŁANE", "RECEIVED": "ODEBRANE", "CANCELLED": "ANULOWANE"
} as $stages
| (.status == "IN_PRODUCTION" or (.status == "ACCEPTED" and .production.status != "NOT_STARTED")) as $making
| {
orderId: .id,
number: .displayId,
stage: (if $making then $stages.IN_PRODUCTION else $stages[.status] end),
paused: ($making and .production.status == "PAUSED"),
percent: .production.percent,
plannedCompletion: .completionDate,
products: [.products[] | { number, name, status, percent: .production.percent }]
}'
from=null latest=$since page=1
if [[ -n $since ]]; then
# Zapas na zmiany zapisane tuż przed poprzednią synchronizacją: 5 minut wcześniej.
from=$(jq -n --arg since "$since" '$since | capture("^(?<time>[^.]+?)(?<ms>\\.[0-9]+)?Z$")
| ((.time + "Z") | fromdateiso8601 - 300 | todateiso8601 | rtrimstr("Z")) + (.ms // ".000") + "Z"')
fi
while :; do
jq -n --arg query "$query" --argjson from "$from" --argjson page "$page" \
'{ query: $query, variables: { from: $from, page: $page } }' >"$body"
./prynt-call.sh "$body" >"$result"
jq -c ".data.orders.items[] | $client_status" "$result"
# Czasy z API mają jeden format (np. 2026-10-05T10:41:02.000Z), więc porównujemy je jako tekst.
latest=$(jq -r --arg latest "$latest" '[$latest, .data.orders.items[].updatedAt] | map(select(. != "")) | max // ""' "$result")
[[ $(jq -r '.data.orders.pageInfo.hasNextPage' "$result") == true ]] || break
last=$(jq -c '.data.orders.items[-1].updatedAt' "$result")
# Dalej czytamy od czasu ostatniego zamówienia, nie od kolejnego numeru strony; kolejną stronę
# bierzemy tylko wtedy, gdy cała strona ma ten sam czas zmiany.
if [[ $last == "$from" ]]; then
page=$((page + 1))
else
from=$last page=1
fi
done
echo "$latest" >"$marker_file"
Okno terminala
./production-status.sh >>statusy.ndjson # wczytaj linie do panelu klienta: po orderId

Ekran przy maszynie albo tablica działu pokazuje, co maszyna ma do zrobienia — także operacje, nad którymi nikt jeszcze nie pracuje, więc nie mają zadania. Takie operacje zwraca lista operations (zakres orders:read):

  • gdzie leży operacja: maszyna (machineId), element, produkt i zamówienie (elementId, productId, orderId), archiwum zamówienia (orderArchived) i jego status (orderStatus, orderStatusIn);
  • stan produkcji (productionStatus, productionStatusIn) — tylko z zakresem production:read. Filtr działa dokładnie jak pole productionStatus operacji. Bez tego zakresu API odrzuca zapytanie z tym filtrem błędem API_SCOPE_MISSING;
  • kolejność: bez sort lista idzie po terminie zamówienia (completionDate), od najbliższego, a zamówienia bez terminu są na końcu (ORDER_COMPLETION_DATE). Operacje z tym samym terminem mają stałą kolejność według id, więc strony się nie nakładają.

Każda operacja zna swoje miejsce w zamówieniu: elementId, productId i orderId są w niej od razu, a element, product i order pobierzesz jako obiekty. readyToStart (zakres production:read) mówi, czy hala może ją już zacząć — szczegóły niżej, w Czy operację można zacząć.

query MachineView($machineId: ID!, $page: Int!) {
operations(
filter: {
machineId: $machineId
orderArchived: false
orderStatusIn: [CREATED, ACCEPTED, IN_PRODUCTION]
productionStatusIn: [NOT_STARTED, PAUSED, IN_PROGRESS]
}
sort: { field: ORDER_COMPLETION_DATE, direction: ASC }
pagination: { page: $page, pageSize: 40 }
) {
totalCount
pageInfo {
hasNextPage
}
items {
id
description
quantity
quantityCompleted
unit
sequenceOrder
productionStatus
updatedAt
element {
name
}
product {
id
name
packingDate
}
order {
displayId
status
completionDate
isDeadline
clientId
}
readyToStart
}
}
}

Status zamówienia nadaje biuro: przyjmuje zamówienie, a potem oznacza je jako gotowe albo wysłane. Hala bywa szybsza — zamówienie CREATED może już być w produkcji. Dlatego pracę maszyny wybiera stan produkcji (productionStatusIn), a orderStatusIn: [CREATED, ACCEPTED, IN_PRODUCTION] tylko odcina zamówienia gotowe, wysłane i anulowane. Bez CREATED ekran zgubiłby zamówienia, które hala zaczęła przed biurem.

Lista działa w dwóch trybach:

  1. Ekran maszyny na żywo. Backend Twojej aplikacji co mniej więcej 15 sekund pobiera dla każdej maszyny jedną albo dwie strony po 40 operacji. Limit integracji to domyślnie 120 żądań na minutę: dziesięć ekranów po dwie strony co 15 sekund to 80 żądań na minutę, więc zostaje zapas na synchronizację. Klucz zostaje w backendzie — przeglądarka ekranu pyta Twój serwer, nie Prynt.
  2. Lustro operacji. Lista z updatedFrom i sortowaniem UPDATED_AT (same pola proste, bez obiektów) pobiera tylko operacje zmienione od poprzedniej synchronizacji — np. do tablicy działu albo raportów z własnej bazy. Operation.updatedAt przesuwa zmiana samej operacji i każda zmiana statusu jej zadania. Zmiana zamówienia (termin, status) go nie przesuwa, więc synchronizuj też orders z updatedFrom. Usunięta operacja nie zgłasza się w updatedFrom: znika z ekranu na żywo, a z lustra usunie ją okresowe pełne przejście — bez updatedFrom, ale z sort: { field: UPDATED_AT, direction: ASC }, bo to sortowanie idzie indeksem. Szczegóły: Paginacja, filtry i synchronizacja.

Czytaj readyToStart (zakres production:read). To ta sama reguła, której używa kolejka maszyn w Prynt: operację można zacząć, gdy wszystkie wcześniejsze operacje jej elementu (o niższej pozycji sequenceOrder) są skończone — zakończone (endedAt) z całą ilością.

  • Pozycje materiałowe — papier, karton albo narzędzie wydawane z magazynu i każda operacja na maszynie magazynu — nie mają zadania i niczego nie wstrzymują: reguła je pomija.
  • readyToStart może być false, choć poprzednia operacja ma productionStatus = COMPLETED: hala zakończyła ją bez całej ilości. previousOperation pokaże wtedy, na co operacja czeka.
  • Operacja bez pozycji (sequenceOrder = null, starsze dane) zawsze jest gotowa.
  • readyToStart zmienia się, gdy hala skończy inną operację elementu — bez ruchu updatedAt tej operacji. Przesuwa się wtedy Order.updatedAt, więc lustro operacji synchronizuj razem z orders(updatedFrom) i po zmianie zamówienia odśwież jego operacje.
  • Szczegóły dla działu obsługi klienta: Operation.productionStatus i Operation.tasks w zamówieniu (order(id:)) pokażą, która operacja stoi i od kiedy.
  • Status i pliki w jednym widoku: produkt w statusie FILES_TO_CHECK albo WAITING_FOR_CLIENT_RESPONSE czeka na pliki albo odpowiedź klienta — przepis Pliki do zamówienia pokazuje, jak je wysłać.
  • Zamówienie ze sklepu: przepis Zamówienie ze sklepu internetowego zakłada zamówienie, którego status potem pokazujesz.