Odbiór paczki (pickup) — wszystkie warianty¶
Recipient (pracownik) podchodzi do kiosku, loguje się PIN-em lub kartą, widzi listę swoich paczek DEPOSITED na TEJ maszynie, wybiera, otwiera skrytkę, zabiera paczkę, zamyka.
Sekwencja happy-path¶
sequenceDiagram
autonumber
actor R as Recipient<br/>(pracownik)
participant K as Kiosk
participant B as Backend
participant DB as Postgres
participant H as Hardware
participant MQ as RabbitMQ
R->>K: PIN 517796 (lub RFID)
K->>B: POST /pickup/verify { type: 'PIN', value }
B->>DB: authenticate credential<br/>SELECT shipments WHERE recipientId=user<br/>AND machineId=THIS AND status=DEPOSITED
B-->>K: 200 { user, shipments: [s1, s2, ...] }
K->>K: PickupScreen z listą<br/>(albo NoParcelsScreen jeśli pusto)
R->>K: tap shipment #1
K->>K: pre-confirm "Otworzymy skrytkę 7"
R->>K: tap "Otwórz teraz"
K->>K: push LockerOpenScreen<br/>onClosed=_completeBackend(shipment)
K->>H: isClosed(7) — pre-flight
H-->>K: true
K->>H: hardware.open(7) — Modbus pulse
H-->>K: opened event
K->>K: phase=open<br/>"Wyjmij paczkę i zamknij skrytkę"
R->>H: zabiera paczkę + zamyka drzwi
H-->>K: closed event
K->>K: _onCloseDetected (phase MUSI być open)
K->>K: widget.onClosed() → markPickedUpLocally + sync.flush
K->>B: POST /pickup/:id/complete
B->>DB: UPDATE shipment SET status=PICKED_UP, pickedUpAt=NOW<br/>UPDATE locker SET status=FREE
B->>MQ: publish shipment.picked_up
B-->>K: 200
K->>K: phase=done → reopen panel [Otwórz ponownie] [Zakończ]
R->>K: tap Zakończ
K->>K: pop → wraca do PickupScreen
Note over K: jeśli wszystkie shipments done → auto-pop do menu/login
Wariant 3V1 — Happy path (z perspektywy stanu)¶
Patrz sekwencja powyżej. Wynik:
| Layer | Stan |
|---|---|
| Locker | FREE |
| Shipment | PICKED_UP + pickedUpAt=NOW |
| Panel timeline | ✅ Utworzono ✅ Przypisano ✅ Złożono ✅ Odebrano |
| Kiosk UI | 🟢 reopen panel → Zakończ → wraca do listy |
| Powiadomienia | Audit notification_shipment.picked_up (placeholder: nadawca + external "zrealizowane") |
Wariant 3V2 — recipient trzyma drzwi otwarte, NIE wyciąga paczki¶
Recipient otworzył ale waha się, trzyma drzwi otwarte ponad 20s.
sequenceDiagram
actor R as Recipient
participant K as Kiosk
participant B as Backend
Note over K: phase=open, trzyma drzwi 20+s
K->>K: _openPhaseDeadline → phase=userConfirmClosed<br/>Dialog "Czy skrytka zamknięta?"
alt Recipient: "Zgłoś obsłudze"
R->>K: tap Zgłoś obsłudze
K->>B: POST /machines/me/fault-reports<br/>{ reportedBy: USER }
K->>K: phase=faultReported<br/>_phasePriorToFault=userConfirmClosed
Note over K: v1.1.21: TYLKO [Anuluj]<br/>Tytuł: "Drzwi nie zostały zamknięte"
Note over K,B: ⚠ pickup-specific hook:<br/>onUserSubmittedFault wywołuje<br/>_completeBackend + abandon-locker<br/>(zakłada: user wziął paczkę, sensor lies)
K->>B: POST /pickup/:id/complete<br/>(automatic, BUG #5)
B->>B: status=PICKED_UP, locker BROKEN
K->>K: return true → pop 'success'
end
TODO Bug #5
Obecny hook onUserSubmittedFault w pickup ZAKŁADA że recipient wziął paczkę i tylko sensor kłamie. Jeśli recipient po prostu trzymał drzwi i NIE wziął paczki, shipment niesłusznie marked PICKED_UP. Potrzebny dialog "Czy odebrałeś paczkę?" [Tak / Nie] przed _completeBackend. Częściowo naprawione v1.1.20 (close after fault no longer auto-completes), ale "Zgłoś obsłudze" path nadal autocompleted.
Wariant 3V3 — drzwi się nie otwierają (mech jam)¶
sequenceDiagram
participant K as Kiosk
participant H as Hardware
K->>H: hardware.open(N)
H-->>K: master E07
K->>K: _autoReportMachineFault('E07', MACHINE)<br/>phase=faultReported<br/>_phasePriorToFault=opening
Note over K: v1.1.21: PEŁNY card<br/>[🔄 Spróbuj ponownie] + [Anuluj]
alt Spróbuj ponownie
K->>H: re-pulse
Note over K,H: ewentualnie się otworzy
end
alt Anuluj
K->>K: pop 'fault'
Note over K: pickup_screen NIE wywołuje abandon<br/>shipment ZOSTAJE DEPOSITED<br/>locker ZOSTAJE OCCUPIED<br/>user może próbować ponownie później
end
Stan po Anuluj: bez zmian — shipment dalej DEPOSITED, locker OCCUPIED. Recipient może wrócić później lub poprosić o pomoc.
Wariant 3V4 — sensor flicker (v1.1.20 chroni)¶
sequenceDiagram
participant K as Kiosk
participant H as Hardware
Note over K: phase=open, czeka close
H-->>K: closed event (flicker — drzwi naprawdę nadal otwarte)
K->>K: _onCloseDetected: phase==open ✓<br/>widget.onClosed() → markPickedUp ✓
Note over K: w v1.1.19 i wcześniej: BUG<br/>v1.1.20 OK bo phase===open
Note over K: ALE: jeśli fault watchdog już fired:<br/>phase=failed/faultReported
H-->>K: closed event (po fault)
K->>K: _onCloseDetected: phase!=open ✗<br/>SUPPRESSED<br/>fault dialog zostaje
Note over K: user widzi fault → Anuluj<br/>shipment ZOSTAJE DEPOSITED<br/>(naprawione w v1.1.20)
Wariant 3V5 — multi-shipment pickup¶
sequenceDiagram
actor R as Recipient
participant K as PickupScreen
Note over K: lista [s1, s2, s3] (3 paczki dla recipient'a)
R->>K: tap s1 → LockerOpenScreen → close → done
R->>K: tap s2 → LockerOpenScreen → close → done
R->>K: tap s3 → LockerOpenScreen → close → done
K->>K: _items.every(x.done) → auto-pop po 1s do login
Każda paczka osobno. Lista odświeża się jeden-po-jednym (kiosk lokalnie markuje it.done).
Wariant 3V6 — recipient wylogowuje się z paczkami w środku¶
Recipient odebrał 1 z 3 paczek i kliknął "Wyloguj" (lub auto-bounce po inactivity). 2 zostałe paczki zostają DEPOSITED + OCCUPIED. Recipient wraca później, paczki czekają.
Wariant 3V7 — paczka z innej maszyny¶
sql
-- pickup/verify zwraca TYLKO shipments z THIS machine:
SELECT * FROM Shipment
WHERE recipientId = :user
AND machineId = :thisMachineId -- ← filtr
AND status = 'DEPOSITED'
Paczki na innej maszynie są niewidoczne (po prawidłowemu — nie można odebrać paczki tutaj jeśli jest gdzieś indziej). Panel pokazuje na której maszynie czeka.
Wariant 3V8 — brak paczek do odbioru¶
sequenceDiagram
actor R as Recipient
participant K as Kiosk
participant B as Backend
R->>K: PIN
K->>B: POST /pickup/verify
B-->>K: 200 { user, shipments: [] }
K->>K: NoParcelsScreen<br/>"Nie masz teraz paczek do odbioru"
Note over K: po 5s auto-pop do login<br/>(albo Wyloguj button)
Wariant 3V9 — sieć padnie podczas pickup¶
sequenceDiagram
participant K as Kiosk
participant SR as SyncService outbox
participant B as Backend
Note over K: drzwi zamknięte, _completeBackend leci
K->>K: offlineRepo.markPickedUpLocally(shipmentId)<br/>(lokalne cached_shipments: PICKED_UP)
K->>SR: queue 'pickup.completed'
K->>K: UI dalej leci jakby OK
Note over K: po sieci wróci ↓
SR->>B: POST /machines/me/replay [events]
B->>B: idempotent markPickedUp<br/>(WHERE status='DEPOSITED')
B-->>SR: 200
Note over K,SR: panel widzi PICKED_UP z opóźnieniem
Phase machine LockerOpenScreen — pickup vs deposit¶
Pickup i deposit dzielą TĘ SAMĄ LockerOpenScreen z różnymi onClosed callback:
- Deposit
onClosed = POST /shipments/:id/deposited→ status DEPOSITED - Pickup
onClosed = _completeBackend → POST /pickup/:id/complete→ status PICKED_UP
Reopen panel różni się: - Deposit: [Otwórz ponownie] [Zakończ] [🔴 Anuluj — paczki nie ma w skrytce] - Pickup: [Otwórz ponownie] [Zakończ] (brak czerwonego — pickup nie ma odpowiednika "package missing")
onUserSubmittedFault różni się:
- Deposit: brak hook'a (caller sam decyduje co zrobić z fault)
- Pickup: automatycznie wywołuje _completeBackend + abandon-locker (BUG #5 do naprawy)
Macierz wariantów pickup¶
| Wariant | Trigger | Locker | Shipment | Fault report | Uwagi |
|---|---|---|---|---|---|
| 3V1 | happy | FREE | PICKED_UP | — | webhook etap 7: "zrealizowane" |
| 3V2 | trzyma drzwi → Zgłoś obsłudze | BROKEN | PICKED_UP* | USER | ⚠ BUG #5 — auto-completed |
| 3V3 | drzwi mech jam → Anuluj | OCCUPIED (bez zmian) | DEPOSITED (bez zmian) | MACHINE | retry dostępny |
| 3V4 | sensor flicker (v1.1.20 OK) | poprawnie | poprawnie | — | guard chroni |
| 3V5 | multi-shipment | każdy FREE | każdy PICKED_UP | — | tap po jednym |
| 3V6 | wylogowanie z paczkami | OCCUPIED | DEPOSITED | — | czeka na powrót |
| 3V7 | paczka z innej maszyny | bez zmian | bez zmian | — | niewidoczna |
| 3V8 | brak paczek | bez zmian | bez zmian | — | NoParcelsScreen |
| 3V9 | sieć padnie przy markPickedUp | FREE po sync | PICKED_UP po sync | — | outbox replay |
* patrz Bug #5
→ Dalej: Powiadomienia i webhooki