Przejdź do treści

Etap 0 — Tworzenie paczki

Paczka jest tworzona PRZED tym jak fizycznie dotrze do kiosku — przez API zewnętrznego systemu (np. ErgoFlow, system HR, zewnętrzna apka zamówień) lub bezpośrednio z panelu web przez admina.

Flow A — przez API (typowy)

sequenceDiagram
    autonumber
    participant Ext as External System<br/>(ErgoFlow / order app)
    participant API as Backend API<br/>(NestJS)
    participant DB as Postgres
    participant MQ as RabbitMQ
    participant W as NotificationWorker
    participant R as Recipient (pracownik)

    Ext->>API: POST /shipments<br/>{ recipientId, notes? }<br/>Bearer JWT (admin)
    API->>API: walidacja DTO + auth
    API->>DB: INSERT Shipment<br/>status=CREATED, trackingId=SB-...<br/>expiresAt=NOW+7d
    API->>DB: INSERT AuditLog<br/>action=shipment.create
    API->>MQ: publish "shipment.created"<br/>{ shipmentId, trackingId, recipientId }
    API-->>Ext: 201 Created<br/>{ id, trackingId, qrPayload, ... }
    MQ->>W: deliver shipment.created
    W->>DB: INSERT AuditLog<br/>system.notification_shipment.created<br/>(placeholder: wouldSend email_via_smtp_in_etap7)
    Note over W,R: 📌 Etap 7 plan:<br/>SMTP do recipient<br/>"Stworzono przesyłkę {trackingId}"

Flow B — przez panel web (ręcznie)

Identyczna ścieżka — UI w /dashboard/lockers/parcel/shipments/new woła ten sam POST /shipments. Operator może opcjonalnie pre-przypisać maszynę i skrytkę (wtedy w nadaniu kurier wybiera tylko rozmiar zamiast wpisywać kod).

Body request

```json POST /shipments Authorization: Bearer Content-Type: application/json

{ "recipientId": "uuid-of-pracownik", "notes": "opcjonalna notatka", "machineId": null, "lockerId": null, "expiresAt": null // domyślnie NOW + 7 dni } ```

Body response

```json HTTP 201 Created

{ "id": "318d9738-a9f4-4bbc-a208-71a207b04dd7", "trackingId": "SB-SDI-449359995354", "status": "CREATED", "createdById": "...", "recipientId": "...", "machineId": null, "lockerId": null, "qrPayload": "{\"t\":\"SB-SDI-449359995354\",\"e\":1780316014}", "expiresAt": "2026-06-01T12:13:34.810Z", "createdAt": "2026-05-25T12:13:34.812Z", "updatedAt": "2026-05-25T12:13:34.822Z", "assignedAt": null, "depositedAt": null, "pickedUpAt": null, "cancelledAt": null } ```

Stan po Etap 0

Layer Wartość
Shipment.status CREATED
Shipment.lockerId NULL
Shipment.machineId NULL (chyba że pre-assign)
Shipment.depositedAt NULL
Panel timeline ✅ Utworzono / — Przypisano skrytkę / — Złożono / — Odebrano
Recipient (email) brak powiadomienia (placeholder etap 7)
External system brak webhook (placeholder etap 7)

Generowanie trackingId

Format: SB-{prefix}-{12 cyfr}, np. SB-SDI-449359995354.

  • prefix — z ENV var SHIPMENT_TRACKING_PREFIX (default SDI na produkcji)
  • 12 cyfr — kryptograficzne randomBytes(8) mod 10¹² z padding do 12 znaków
  • Unikalność: retry 5 razy jeśli kolizja w DB

last6 (ostatnie 6 cyfr) używane przez kiosk w /deposit/lookup?last6=… — kurier nie musi wpisywać całego 12-cyfrowego ID.

QR payload

json { "t": "SB-SDI-449359995354", // trackingId "e": 1780316014 // unix timestamp expiresAt }

Signed JWT (HMAC-SHA256 z QR_SIGNING_SECRET). Generowany przy create, immutable. Wydrukowany na etykiecie paczki — pickup może być przez QR scanner zamiast PIN/RFID (etap 7+).

Eventy emitowane

Event Routing key Treść
shipment.created shipment.created { shipmentId, trackingId, recipientId, createdById }

NotificationWorker subskrybuje shipment.# (wildcard) i loguje każdy event do AuditLog z polem wouldSend jako placeholder.

Audit log

Po stworzeniu są DWA wpisy w AuditLog:

createdAt action actorType payload
T+0 shipment.create USER pełny Shipment row
T+0.01 system.notification_shipment.created SYSTEM { wouldSend, routingKey, shipmentId, trackingId, recipientId, createdById }

Następny krok

Paczka jest teraz CREATED + bez maszyny. Kurier idzie do dowolnej maszyny SmartBox, wpisuje kod nadania (ostatnie 6 cyfr trackingId), i kiosk rezerwuje wolną skrytkę odpowiedniego rozmiaru.

Ładowanie (deposit)