Przejdź do treści

Powiadomienia i integracja zewnętrzna

Każda zmiana stanu paczki emituje event do RabbitMQ. NotificationWorker subscrybuje wszystkie shipment.* i obecnie loguje do AuditLog z polem wouldSend: 'email_via_smtp_in_etap7' — faktyczne SMTP i webhook do external system są planowane na etap 7.

Pipeline eventów

flowchart LR
    A[ShipmentsService<br/>state change] -->|publish| B[(RabbitMQ<br/>exchange: smartbox.events)]
    B -->|routing key shipment.#| C[NotificationWorker]
    B -->|routing key machine.#| D[OpsAlertWorker<br/>placeholder]
    C -->|AuditLog INSERT<br/>wouldSend=email...| E[(Postgres)]
    C -.->|📌 ETAP 7| F[SMTP server]
    C -.->|📌 ETAP 7| G[External webhook<br/>POST configured URL]
    F -.-> H[Recipient inbox]
    G -.-> I[Zewnętrzny system zamówień<br/>oznacza order jako<br/>do-odbioru / zrealizowane]

Eventy emitowane

Wszystkie zdefiniowane w backend/src/infra/amqp/event-types.ts:

Routing key Trigger Payload Subscriber action (obecny) Subscriber action (etap 7)
shipment.created POST /shipments { shipmentId, trackingId, recipientId, createdById } Audit log placeholder Email do recipient: "Stworzono przesyłkę {trackingId}"
shipment.assigned deposit/begin-existing lub admin assign { shipmentId, machineId, lockerId, emittedAt } Audit log Email do recipient: "Skrytka {N} przyjmie Twoją paczkę"
shipment.deposited POST /shipments/:id/deposited { shipmentId, machineId, lockerId, emittedAt } Audit log Email do recipient + webhook external "do odbioru"
shipment.picked_up POST /pickup/:id/complete { shipmentId, machineId, lockerId, pickedUpAt } Audit log Email do nadawcy + webhook external "zrealizowane"
shipment.cancelled admin cancel { shipmentId, cancelledAt, reason } Audit log Email do obu + webhook external "anulowane"
shipment.expired cron >expiresAt (TODO) { shipmentId, expiredAt, lastLocker } Audit log Email do recipient + ops + webhook
machine.heartbeat każde 30s z kiosku { machineId, status, lastSeenAt } (drop — tylko keep-alive)
machine.offline MachineWorker cron flip ONLINE→OFFLINE { machineId, code, lastSeenAt, thresholdSec } Audit log placeholder Email do ops "Maszyna {code} offline"
machine.task.open_locker admin emergency open { machineId, lockerId, taskId } dispatched do kiosku przez task poll

Sequence: shipment.deposited → recipient + external

Stan obecny (etap 5.1):

sequenceDiagram
    participant SS as ShipmentsService.markDeposited
    participant MQ as RabbitMQ
    participant NW as NotificationWorker
    participant DB as Postgres AuditLog

    SS->>MQ: publish 'shipment.deposited'
    MQ->>NW: deliver
    NW->>DB: INSERT AuditLog<br/>{ action: 'system.notification_shipment.deposited',<br/>payload: { wouldSend: 'email_via_smtp_in_etap7',<br/>routingKey, shipmentId, machineId, lockerId } }
    Note over NW: ⛔ KONIEC — nic więcej się nie dzieje<br/>(placeholder dla etap 7)

Stan po etap 7 (plan):

sequenceDiagram
    participant SS as ShipmentsService.markDeposited
    participant MQ as RabbitMQ
    participant NW as NotificationWorker
    participant SMTP as SMTP server
    participant EXT as External system<br/>(np. ErgoFlow webhook URL)

    SS->>MQ: publish 'shipment.deposited'
    MQ->>NW: deliver
    par Email do recipient
        NW->>SMTP: SEND mail<br/>To: recipient@firma.pl<br/>Subject: "Twoja paczka czeka"<br/>Body: "skrytka {N}, kod: {trackingId}"
    and Webhook do external
        NW->>EXT: POST {externalWebhookUrl}<br/>{ event: 'shipment.deposited',<br/>shipmentId, trackingId, machineCode,<br/>lockerNumber, depositedAt }
        EXT->>EXT: oznacza order jako<br/>"do odbioru u nas"
        EXT-->>NW: 200 OK (lub retry 3x na fail)
    end
    NW->>DB: INSERT AuditLog (zachowane dla audytu)

Konfiguracja webhook (planowana — etap 7)

Każdy shipment może mieć opcjonalny webhookUrl przekazany przy create:

json POST /shipments { "recipientId": "...", "webhookUrl": "https://erp.firma.pl/api/orders/123/smartbox-event", "webhookSecret": "shared-secret-for-hmac" }

Backend będzie POSTował do tego URL na każdy event paczki z HMAC-SHA256 signature w nagłówku X-SmartBox-Signature. External potwierdza 200 OK, na 4xx/5xx kolejka retry z exponential backoff (3 próby, potem dead-letter).

Alternatywnie webhook na poziomie maszyny lub globalnej konfiguracji (jeden endpoint dla wszystkich paczek od tego klienta).

Auto-fault reports (już działa)

Niezależnie od shipment events, kiosk wysyła fault reports do panelu:

sequenceDiagram
    participant K as Kiosk
    participant B as Backend
    participant P as Panel<br/>(System → Zgłoszenia)

    Note over K: USER fault: operator klika "Zgłoś obsłudze"<br/>LUB MACHINE fault: master E07 auto-detected
    K->>B: POST /machines/me/fault-reports<br/>{ reportedBy: USER|MACHINE,<br/>currentPhase, faultCode,<br/>sensorByte, masterMW100,<br/>timeline: [...] }
    B->>B: INSERT KioskFaultReport
    Note over P: admin/courier widzi natychmiast<br/>w /dashboard/system/fault-reports

Typy fault codes: - E07 — master timeout (drzwi nie otworzyły się / nie zamknęły) - E07-DRIVER — driver-side timeout (brak ACK z mastera) - E16 — generyczny master fault - HID_BRIDGE_DOWN — RFID bridge stopped responding (Flutter BridgeHealthMonitor) - (puste) — USER report bez specyficznego kodu

Webhook bridge health (już działa)

BridgeHealthMonitor (v1.1.19) na froncie Fluttera monitoruje rfid-bridge.log. Jeśli auto-restart 3× nie pomógł:

sequenceDiagram
    participant FH as Flutter<br/>BridgeHealthMonitor
    participant K as Kiosk
    participant B as Backend

    Note over FH: rfid-bridge.log nie advancuje >60s<br/>auto-restart attempt 1/3, 2/3, 3/3<br/>wszystkie fail
    FH->>B: POST /machines/me/fault-reports<br/>{ reportedBy: MACHINE,<br/>faultCode: 'HID_BRIDGE_DOWN',<br/>currentPhase: 'login',<br/>reporterMessage: "...",<br/>timeline: [{ kind: 'bridge.health.failed', ... }] }
    Note over K: UI: czerwony banner<br/>"Czytnik kart nie odpowiada"

Maszyna offline

sequenceDiagram
    participant MW as MachineWorker<br/>(cron 1min)
    participant DB as Postgres
    participant MQ as RabbitMQ

    Note over MW: SELECT machines WHERE status=ONLINE<br/>AND lastSeenAt < NOW - 2min
    MW->>DB: UPDATE status=OFFLINE
    MW->>DB: INSERT AuditLog system.machine_offline
    MW->>MQ: publish 'machine.offline'<br/>{ machineId, code, lastSeenAt }
    Note over MQ: 📌 etap 7: email do ops

W panelu maszyna ma status badge OFFLINE (czerwony). Cron flipuje automatycznie z powrotem na ONLINE jak tylko heartbeat wróci.

Audit log — kompletna historia

Każdy event + każda akcja użytkownika w panelu/kiosku jest logowana w AuditLog:

actorType Kto Przykłady action
USER operator/admin z panelu shipment.create, shipment.assign_locker, locker.emergency_open
MACHINE kiosk via API key shipment.deposit_started (TODO), pickup.completed
SYSTEM worker / cron system.notification_shipment.*, system.machine_offline, system.ota_auto_cancelled

Panel: /dashboard/system/audit z filtrami (actorType, action, entityType, range dat).

Podsumowanie obecny vs etap 7

Co Stan obecny (etap 5.1) Etap 7 (plan)
Email do recipient po DEPOSITED ❌ tylko audit placeholder ✅ SMTP
Email do nadawcy po PICKED_UP ✅ SMTP
Webhook external po DEPOSITED ✅ POST z HMAC sign
Webhook external po PICKED_UP (status: zrealizowane) ✅ POST
Webhook external po CANCELLED / EXPIRED ✅ POST
Fault reports w panelu ✅ działa
Auto-fault HID_BRIDGE_DOWN ✅ działa (v1.1.19)
MachineWorker auto-offline + audit ✅ działa
Email ops na machine.offline ❌ tylko audit placeholder ✅ SMTP
Expiration cron + email ❌ brak cron'a

→ Dalej: Edge cases