Przejdź do treści

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