Roadmapa i status etapów¶
Pełny przegląd etapów budowy SmartBoxa — od infrastruktury (etap 0) po wydawanie ilościowe i redesign inwentaryzacji (etap 23–26). Etapy 0–6 i 10 mają osobne, szczegółowe strony (m.in. Wydawanie ilościowe); etapy 7–9 i 11–26 są opisane tutaj (oraz w dedykowanych dokumentach Komunikacja kiosk ↔ agent i Kiosk Helper).
Konwencja etapów
Każdy etap startuje od „planu do akceptacji", potem leci w kawałkach X.1, X.2… Commity: etap-X.Y: / etap-X.Y polish: / etap-X.Y fixes:. Wersje kiosku w flutter-machine-ui/lib/core/version.dart, agenta SmartConf osobno, panel — stopka „powered by SDI Solution" + badge wersji.
Skrót statusu¶
| Etap | Temat | Status |
|---|---|---|
| 0 | Infrastruktura (Docker/Traefik/Keycloak/Postgres) | wdrożone |
| 1 | Backend core (NestJS, model, auth) | |
| 2 | Panel web (Next.js 15) | |
| 3 | Kiosk (Flutter) + OTA | |
| 4 | Mobile API + tryb offline | |
| 5 | Hardware kiosku (Modbus) | (droga nieaktywna — patrz etap 8–9, 20) |
| 6 | Integracja ErgoFlow | |
| 7 | Wydawanie/uzupełnianie + raporty | |
| 8 | Prymitywy DynaBox (bęben) | |
| 9 | SmartConf: tryb serwisowy + discovery + live-konfiguracja | |
| 10 | Kiosk — branding Temrex (ekrany) | |
| 11 | KioskKit + utwardzenie OTA + instalator one-click | |
| 12 | DynaBox: drzwi fizyczne vs skrytki wirtualne | |
| 13 | Inwentaryzacja (operator) + auto-rollout OTA | |
| 14 | MachineApiLog — audyt wywołań /v1 | |
| 15 | Realna mapa skrytek na kiosku (Diagnostyka) | |
| 16 | Job API — masowe otwieranie (uzup./inwent.) | |
| 17 | Job API — pojedyncza skrytka (wydaj/nadaj/odbierz) + flaga BROKEN | |
| 18 | Pre-flight gotowości maszyny + tożsamość zgłaszającego | |
| 19 | 1 skrytka = 1 funkcja + NONE domyślnie | |
| 20 | Przesyłki: nazwy zamiast UUID + usunięcie adresu Modbus | |
| 21 | Dwa tryby serwisowe (komunikat vs zamknij kiosk) | |
| 22 | Kiosk Helper + generator instalatora per-maszyna + hasła userów | |
| 23–24 | Wiele sztuk w skrytce — pojemność/strategia/ważność + per-egzemplarz | |
| 25 | Ilość wydania per produkt (domyślna + krotność/blokada) | |
| 26 | Redesign inwentaryzacji (tabela bez scrolla, zdjęcie produktu) | |
| 27 | Raporty (9 raportów + wykresy + filtry dat/maszyny + rzut bębna) | |
| 28 | Media offline-first (zdjęcia + PDF hostowane S3/MinIO+dysk, /media/<id>, cache w kiosku) |
|
| 29 | Offline hardening (kiosk działa offline: odbiór/nadanie/wydanie/uzup./inwent. + replay) + tryby over-limit + flagi offline | |
| 30 | Cache-first kiosk (ekrany z lokalnego cache natychmiast + odświeżanie w tle — dużo szybciej, kiosk 1.5.71) |
Etapy 7–9¶
Etap 7 — Wydawanie/uzupełnianie + raporty¶
- Backend: rozdział wydawania na
issue→open→confirm(rezerwacja skrytki bez konsumpcji stanu do potwierdzenia zamknięcia); katalog z limitami (remaining/usedQty/limitQty); skonsolidowana strona „Raporty i audyt" (ruchy magazynowe IN/OUT + akceptacje dokumentów).1 skrytka = 1 sztukadla slotów (DynaBox: większa pojemność komórki). - Kiosk (1.1.52–1.1.54): wydawanie + uzupełnianie korzystają z
LockerOpenScreen(dialogi recovery, watchdog, retry-gate); pokaz uprawnień; siatka diagnostyki na żywo. - Pliki:
dispensing/kiosk-dispense.service.ts,dispensing/inventory.service.ts,dispensing/reports.service.ts,screens/dispense_screen.dart,screens/replenish_screen.dart.
Etap 8 — Prymitywy DynaBox¶
- Kiosk (1.1.53): prymitywy bębna DynaBox w
LockerOpenScreen(adresacja po logicznym N, sekwencja obrotu, obsługa krawędziUnlocked, „Bęben się obraca"); każda gałąź bramkowanamachineType=='DynaBox', by ścieżka LockerBox została bajt-w-bajt. - Pliki:
screens/locker_open_screen.dart,hardware/locker_hardware.dart. - Modbus i SmartConf jeszcze współistnieją w kodzie na tym etapie.
Etap 9 — SmartConf: tryb serwisowy + discovery + live-konfiguracja¶
- Backend:
MachinezyskujemaintenanceMode(+maintenanceAuto),smartconfUrl/smartconfApiKey/smartconfMachineType/smartconfLockerCount/smartconfConnectedAt.GET /machines/mezwracamaintenanceMode+ bloksmartconf.PATCH :id/smartconf(połącz),PATCH :id/maintenance(wyjście bramkowane żywym połączeniem),POST :id/discover-smartconf. - Web: panel „Połączenie ze SmartConf / Tryb serwisowy" (discovery po IP/zakresie, połącz, pobierz skrytki, wyjdź z trybu).
- Kiosk (1.2.0+): pełnoekranowy Service Mode; live-rekonfiguracja SmartConf z
/machines/me; skan localhost + /24 kart sieciowych; PIN bramkujący róg serwisowy (serviceModePin, domyślnie 222222). - To etap, w którym SmartConf staje się preferowaną drogą do sprzętu — patrz Komunikacja kiosk ↔ agent.
Etapy 11–18¶
Etap 11 — KioskKit + utwardzenie OTA + instalator one-click¶
- Kiosk (1.5.0–1.5.9): wspólna biblioteka komponentów
KioskKit+ tokenyKioskShapena wszystkie skórki; usuniętolib/templates; pasek statusu offline; mutex single-instance; tray; instalator one-click (install/update), agent OTA,update-host.ps1. - Pliki:
lib/kit/, skrypty instalatora (poprzednik dzisiejszego Helpera).
Etap 12 — DynaBox: drzwi fizyczne vs skrytki wirtualne¶
- Backend: rozdział drzwi fizycznych (
MachineDoor, wiersze bębna) od skrytek wirtualnych (Locker.physicalDoorNumber); alokacja po logicznym N → mapuje na drzwi; awaria drzwi → cały wiersz wyłączony (auto+ręcznie). - Web: pasek drzwi fizycznych + macierz K×wiersze + wyłączanie wiersza; realny układ bębna (18-kolumnowy) jak w SmartConf; rozmiary komórek z
/v1/compartments. - Kiosk (1.5.17–1.5.18): geometria bębna (
drumColumns + doorsPerColumn).
Etap 13 — Inwentaryzacja + auto-rollout OTA¶
- Kiosk (1.5.19): ekran inwentaryzacji (lista kolumn → modal ilości → akordeon sekwencyjnych otwarć, max 3).
- OTA auto-rollout: maszyny same się aktualizują (kanał
STABLE/BETAper maszyna), bez ręcznego pushu.
Etap 14 — MachineApiLog (audyt /v1)¶
- Kiosk (1.5.20): ring-buffer wszystkich wywołań SmartConf
/v1(mutacje zawsze, idle-polle próbkowane, w oknie capture wszystko); drain + relay do backendu. - Backend: tabela
MachineApiLog+ GET dla panelu; Web: podgląd logu /v1 na maszynie (retencja 7 dni).
Etap 15 — Realna mapa skrytek na kiosku¶
- Kiosk (1.5.21–1.5.22): „Diagnostyka → Skrytki" to realny układ jak na webie (DynaBox macierz, LockerBox siatka), stan na żywo z SmartConf. Kiosk startuje od razu na SmartConf (koniec wieszania „Łączę ze SmartConf").
Etap 16 — Job API: masowe otwieranie¶
- Agent SmartConf (0.3.35):
POST /v1/jobs/open {doors[]}+GET /v1/jobs/{id}(poll 700 ms) +POST /:id/cancel. Agent zarządza współbieżnością (≤3, DynaBox→1), kolejką, cyklemopening→unlocked→open→done, re-ryglem, timeoutami (not_opened/not_closed). - Kiosk (1.5.26+): uzupełnianie + inwentaryzacja przez Job API.
Etap 17 — Job API: pojedyncza skrytka + flaga BROKEN¶
- Kiosk (1.5.42–1.5.43):
JobOpenScreen— wydawanie/nadawanie/odbiór przez Job API; agent prowadzi cykl, kiosk pollu je diody + master status; na błąd „Czy ponowić?". - Backend:
LockerStatus.BROKEN— wyklucza skrytkę z wydania/nadania/odbioru (pre-check blokuje), ale dostępna do inwentaryzacji/uzupełnienia.
Etap 18 — Pre-flight + tożsamość zgłaszającego¶
- Pre-flight gotowości maszyny przed Job-open (DrumOffline, brak zaległych jobów, master ready).
KioskFaultz tożsamością zgłaszającego (kto zgłosił awarię).
Etapy 19–22¶
Etap 19 — 1 skrytka = 1 funkcja + NONE domyślnie ¶
- Najważniejsza zmiana semantyki: każda skrytka ma dokładnie jedną funkcję;
NONE= skonfigurowana ale nieprzypisana (nie bierze udziału w niczym). 4 realne:PARCEL,DISPENSE,KEY_RENTAL,DOCUMENT. - Backend: 2 migracje (enum-add osobno; potem dane
{}→{NONE}×340 + CHECKcardinality=1);lockerFn(); egzekwowanie wszędzie (deposit/assign tylko[PARCEL], inventory tylko[DISPENSE]);availableFunctionsz realnych funkcji (NONE odfiltrowane — naprawia ukryty bug pomijający DISPENSE). - Web: single-select + „Brak"; Kiosk (1.5.48): brak fallbacku na
['PARCEL']. - Po migracji 340 skrytek = NONE — operator musi je przetagować (bulk-picker), inaczej maszyna nie przyjmie paczek.
Etap 20 — Przesyłki: nazwy + usunięcie Modbus¶
- Web/Backend: lista PRZESYŁEK pokazuje nazwę odbiorcy + maszyny (nie UUID).
- Usunięcie adresu Modbus skrytek end-to-end (DB
DROP COLUMN, schema, DTO, web, README) — droga Modbus jest martwa, SmartConf jedyną aktywną. Patrz Komunikacja.
Etap 21 — Dwa tryby serwisowe ¶
- Rozdzielenie trybu serwisowego: „komunikat" (
maintenanceMode— kiosk widoczny + baner) vs „zamknij kiosk" (kioskClosed— kiosk zamknięty, dostęp do Windows, zostaje tylko Helper). - Sterowanie z panelu i z traya Helpera, zsynchronizowane przez backend. Szczegóły → Kiosk Helper.
Etap 22 — Kiosk Helper + generator instalatora + hasła ¶
- Kiosk Helper (dzisiejsze) — jeden proces-nadzorca zastępujący 4 taski PowerShell. Pełny opis, audyt i instalacja → Kiosk Helper.
- Generator instalatora per-maszyna (zaszyty klucz API) + uniwersalny.
- Hasła userów z panelu — własne lub generowane; fix „temporary=true blokuje login direct-grant" + poprawny klient
smartbox-web.
Etapy 23–27¶
Etap 23–24 — Wiele sztuk w skrytce ¶
- Odejście od „1 skrytka = 1 sztuka": pojemność per skrytka, stan śledzony per egzemplarz (
StockUnit), strategia wydania per maszyna (FIFO/LIFO/FEFO/RANDOM), ważność per produkt (BLOCK/WARN). Wydawanie wieloilościowe i wieloskrytkowe, inwentaryzacja + uzupełnianie liczbowe. Cały stan przez jeden chokepointInventoryService.createMovement. Pełny opis → Wydawanie ilościowe. - Kiosk 1.5.55–1.5.56, backend + web (v0.73). Inwariant per-sztuka zweryfikowany na produkcji (
Σ quantity == count StockUnit IN_STOCK).
Etap 25 — Ilość wydania per PRODUKT¶
Item.defaultDispenseQty(domyślna ilość na wydanie, np. rękawice = 2) +dispenseQtyLocked. Na kiosku stepper „− N +" na karcie produktu (klik „Wydaj" → od razu wydanie). Zablokowana = wydanie w krotności (2 → 2, 4, 6, 8… domin(stan, limit)), „pojedynczo" = krok 1.- Kiosk 1.5.57/1.5.59, web. Fixy zapisu produktu:
@Type(()=>Number)na ilości + listagroups: [{id,name}](produkt w grupie 400-ował przezgroupIds:[undefined]).
Etap 26 — Redesign ekranu inwentaryzacji ¶
- Wymóg klienta: wszystkie skrytki kolumny na jednym ekranie bez scrolla (DynaBox). Lewy panel: zdjęcie + nazwa produktu + podsumowanie na żywo (Powinno / Stwierdzono / Różnica). Tabela: etykieta „Wiersz X (#N)", stepper −/+ tylko po otwarciu skrytki, stała wysokość i stała kolejność wierszy (u góry 1, na dole N). LockerBox z >15 skrytkami w kolumnie → scroll po prawej.
- Kiosk 1.5.58–1.5.61, backend (
photoUrlwinventoryProducts). Chrome przez KioskTheme/KioskKit, stałe kolory statusów.
Etap 27 — Raporty ¶
- Zakładka Raporty (
wydawanie/raporty) przebudowana w dashboard z 5 kartami (Najpopularniejsze / Trendy / Maszyny / Alerty stanów / Audyt), wspólny pasek filtrów (presety zakresu 7/30/90 dni / 12 mies. + własne daty, wybór maszyny) i wykresy recharts (kolumnowe / liniowe / warstwowe / kołowe). 9 raportów: top 10 produktów, top 10 użytkowników, wydania miesięcznie, przyjęcia vs wydania, trend roczny, stan maszyn (pojemność/stan/puste), rzut bębna (puste skrytki na czerwono — reużytydrum-layout), alerty stanów (brak/poniżej progu), wypożyczenia vs zwroty. - Backend: nowe końcówki
GET /dispensing/reports/{timeseries,machines-status,drum/:id,stock-alerts}(agregacje$queryRawdate_trunc+FILTER), istniejącyusagezasila top 10. Web (server fetch → klientReportsDashboard). Pełny opis → Raporty. - Uwaga: „Wypożyczenia i zwroty" bazuje na ruchach
RETURN; dedykowany flowKEY_RENTALnie jest jeszcze wdrożony, więc słupki zwrotów mogą być zerowe.
Etap 30 — Cache-first kiosk (perf) ¶
- Audyt wykazał: logowanie (PIN/RFID) i media (zdjęcia/PDF) były cache-first (szybkie), ale wydawanie/odbiór/inwentaryzacja/uzupełnianie/wyszukiwarka odbiorcy były network-first — czekały na rundę do backendu (do 8 s timeout na słabym łączu) zanim cokolwiek pokazały.
- Przerobione na stale-while-revalidate: render z lokalnego cache (SQLite) natychmiast, sieć dociąga w tle i podmienia. Brak sieci = zostaje cache, bez spinnera. Odbiór paczek:
PickupScreenotwiera się z cache od razu i sam re-syncuje z/pickup/by-userw tle (zdejmuje nieaktualne pozycje — obawa v1.1.1 obsłużona bez blokowania otwarcia). Kiosk 1.5.71 → auto-rollout na 5 maszyn.
Etap 28 — Media offline-first ¶
- Zdjęcia produktów/kategorii + PDF-y hostowane u nas (S3/MinIO za portem
MediaStorage, fallback na dysk w dev/VM), content-addressed (sha256), serwowaneGET /media/:id(stream przez backend — MinIO in-cluster only). Admin wgrywa plik w kartotece (zamiast wklejać URL); kiosk pobiera raz i renderuje z dysku offline (KioskMediaImage+MediaCache, precache na syncu). Zgodne z kontraktem przyszłej platformy [[billvis-deploy-contract]] (ENVS3_*). Pełny opis → Media offline-first. - Wdrożone 2026-06-17: backend (migracja
20260618_etap28_media) + web na VM, kiosk 1.5.70 STABLE → auto-rollout na 5 maszyn. Na VM aktywny DiskStorage (/data/media) — MinIO dojdzie później (wtedy ENVS3_*→ auto-switch, zero zmian w kodzie).
Etap 29 — Offline hardening ¶
- Cel: kiosk działa w pełni offline dla wszystkich funkcji (odbiór, nadanie, wydanie, uzupełnianie, inwentaryzacja), buforując zdarzenia do outboxa i odtwarzając je przez chokepointy przy synchronizacji — z zachowaniem integralności stanu i limitów. Plus załatanie dziur już istniejących w offline. Pełny spec + audyt → Offline hardening (Fazy 0→5) · Audyt offline.
- Fazy 0→5 (kolejność 0→1→4→2→5.1→5.2→3, wydanie z twardym budżetem na końcu): łączność/timeouty +
ApiException, koperta outboxa (eventId/actorUserId) + autoryzacja replay, nadanie offline (OFF-tracking, bezLockerLease), uzupełnianie + inwentaryzacja (OCC guard), wydanie offline z twardym budżetem (snapshot uprawnień per-user +offline_usage_ledger, fail-closed). Kiosk 1.5.62→1.5.69, SQLite kiosku v5. Migration-free poza Fazą 3. - Rozszerzenie (po fazach): instancyjne tryby przekroczenia limitu
DispenseSetting.overLimitMode(RESET / CARRY_OVER — długLimitOveragena następny okres) + jednolite flagiofflineReplayedAtna wszystkich zreplayowanych rekordach (Movement/InventoryDocument/Shipment) z odznakami w panelu (Raporty / przesyłki / inwentaryzacje). - Wdrożone 2026-06-17: backend (2 migracje) + web na VM, kiosk 1.5.69 STABLE → auto-rollout na 5 maszyn. Pozostaje: test offline na żywej maszynie.
Co jest do zrobienia¶
Otwarte / w toku
- etap-24.7: retencja egzemplarzy — cron prune
StockUnito statusieDISPENSEDpo oknie retencji (żeby per-sztuka nie zapchała bazy); trwały ślad zostaje wMovement. - etap-24.7: podgląd egzemplarzy w panelu + raport „wkrótce przeterminowane" (dla produktów z
tracksExpiry). - Pojemności skrytek > 1 w praktyce — po migracji wszystkie sloty
capacity=1; operator podnosi pojemność per skrytka w panelu, by realnie korzystać z wielu sztuk. - Przetagować 340 skrytek
NONE(etap-19) w panelu na realne funkcje (bulk-picker) — bez tego maszyna nie przyjmie paczek. - Helper: czysty kanał WebSocket w kiosku — usunąć most plikowy (
rfid.txt/rfid-bridge.log/update-state.json) na rzeczws://127.0.0.1:8810/v1/events. Most działa, to porządkowy krok (→ Kiosk Helper). - Helper: rollout IKEA + ewentualne pozostałe maszyny (LockerBox i DynaBox zrobione).
- Podpis instalatora Helpera (cert → koniec ostrzeżenia SmartScreen). Skrypt buildu ma hook
-SignCertThumbprint. - Helper na Ubuntu (Faza 5) — odłożone; rdzeń jest cross-platform, brakuje shella per-OS (evdev RFID, systemd autostart).
- Inwentaryzacja w panelu — parytet funkcji z uzupełnianiem (etap-13 częściowo).
- etap-29: test offline na żywej maszynie — po dociągnięciu OTA 1.5.69: wyjmij sieć → przejdź każdy flow (odbiór/nadanie/wydanie/uzup./inwent.) → wepnij sieć → sprawdź, że replay daje stan zgodny, a flagi/konflikty/over-limit są widoczne w panelu (Raporty / przesyłki / inwentaryzacje, log
/v1kind=offline-replay). - Walidacja DynaBox double-UNLOCKED na żywym sprzęcie (etap-8 flaga).
- Brakujące ekrany Temrex (13 projektów) — placeholdery renderują paletę + ramę Temrex (etap-10).