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
{ "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(defaultSDIna 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.