Przejdź do treści

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 issueopenconfirm (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 sztuka dla 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ędzi Unlocked, „Bęben się obraca"); każda gałąź bramkowana machineType=='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: Machine zyskuje maintenanceMode (+ maintenanceAuto), smartconfUrl/smartconfApiKey/smartconfMachineType/smartconfLockerCount/smartconfConnectedAt. GET /machines/me zwraca maintenanceMode + blok smartconf. 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 + tokeny KioskShape na wszystkie skórki; usunięto lib/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/BETA per 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ą, cyklem opening→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). KioskFault z 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 + CHECK cardinality=1); lockerFn(); egzekwowanie wszędzie (deposit/assign tylko [PARCEL], inventory tylko [DISPENSE]); availableFunctions z 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 chokepoint InventoryService.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… do min(stan, limit)), „pojedynczo" = krok 1.
  • Kiosk 1.5.57/1.5.59, web. Fixy zapisu produktu: @Type(()=>Number) na ilości + lista groups: [{id,name}] (produkt w grupie 400-ował przez groupIds:[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 (photoUrl w inventoryProducts). 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żyty drum-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 $queryRaw date_trunc + FILTER), istniejący usage zasila top 10. Web (server fetch → klient ReportsDashboard). Pełny opis → Raporty.
  • Uwaga: „Wypożyczenia i zwroty" bazuje na ruchach RETURN; dedykowany flow KEY_RENTAL nie 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: PickupScreen otwiera się z cache od razu i sam re-syncuje z /pickup/by-user w 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), serwowane GET /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]] (ENV S3_*). 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 ENV S3_* → 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, bez LockerLease), 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ług LimitOverage na następny okres) + jednolite flagi offlineReplayedAt na 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 StockUnit o statusie DISPENSED po oknie retencji (żeby per-sztuka nie zapchała bazy); trwały ślad zostaje w Movement.
  • 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 rzecz ws://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 /v1 kind=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).

Powiązane