# Stoccaggio e uscita

Depositare la merce in magazzino, poi spedirla: configurazione → quotare → creare lo stoccaggio → pagare → elencare gli articoli ancora in giacenza → stimare l’uscita → creare l’uscita → pagare → tracciare → annullare.

Questa guida documenta esclusivamente le operazioni seguenti. Eseguitele in sequenza. L’autenticazione usa un account **cliente**.

Sostituite `YOUR_HOST` e `ACCESS_TOKEN` con i valori del vostro ambiente.

## 1. Accesso (cliente)

```bash
curl -X POST https://YOUR_HOST/api/v1/user/customer/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"your_password"}'
```

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL usa la stessa intestazione su `POST /api/graphql`.

**Verifica:** il login restituisce `access_token`. Una chiamata successiva senza di esso restituisce `401`.

## 2. Configurazione dello stoccaggio

**REST:** `GET /api/v1/customer/storage-orders/config` — [Manuale REST](/api/documentation#/paths/v1-customer-storage-orders-config/get)

```bash
curl https://YOUR_HOST/api/v1/customer/storage-orders/config \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

**GraphQL:** `customerStorageOrderConfig` ([Manuale GraphQL](/api/graphql/documentation#/customer/customerStorageOrderConfig))

**Verifica:** avete annotato un `id` di magazzino e, se il catalogo non è vuoto, un `id` di imballaggio.

## 3. Quotare lo stoccaggio

**REST:** `POST /api/v1/customer/storage-orders/calculate-price`

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders/calculate-price \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": 7,
    "start_date": "2026-09-10",
    "end_date": "2026-10-10",
    "items": [{
      "qty": 1,
      "length": 30,
      "width": 20,
      "height": 15,
      "dimension_unit": 2,
      "weight": 2,
      "weight_unit": 2
    }]
  }'
```

**Verifica:** `success` o `result` è true e avete un prezzo. `warehouse_id` / date mancanti danno `400`.

## 4. Creare l’ordine di stoccaggio

**REST:** `POST /api/v1/customer/storage-orders`

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-store-001" \
  -d '{
    "warehouse_id": 7,
    "start_date": "2026-09-10",
    "end_date": "2026-10-10",
    "items": [{
      "description": "Box A",
      "qty": 1,
      "length": 30,
      "width": 20,
      "height": 15,
      "dimension_unit": 2,
      "weight": 2,
      "weight_unit": 2,
      "value": 100
    }]
  }'
```

**Verifica:** la risposta ha `data.id`. Conservate quell’id dell’ordine di stoccaggio.

## 5. Pagare lo stoccaggio

**REST:** `POST /api/v1/customer/storage-orders/{id}/pay`

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders/1024/pay \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Facoltativo: prima `GET /api/v1/customer/storage-orders/{id}/payment-info`.

**REST:** `GET /api/v1/customer/storage-orders/{id}` — [Manuale REST](/api/documentation#/paths/v1-customer-storage-orders-id/get)

**GraphQL:** `customerStorageOrderShow` ([Manuale GraphQL](/api/graphql/documentation#/customer/customerStorageOrderShow))

**Verifica:** l’ordine di stoccaggio è pagato / confermato. `402` significa ricaricare il portafoglio, poi ritentare.

L’uscita sotto funziona solo dopo che i colli sono **ricevuti** in magazzino. Per una prova, aspettate che il personale (o una ricezione di test) li abbia contrassegnati come ricevuti, poi proseguite.

## 6. Elencare gli articoli ancora in giacenza

**REST:** `GET /api/v1/customer/shipout-orders/available-items` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-available-items/get)

```bash
curl "https://YOUR_HOST/api/v1/customer/shipout-orders/available-items?warehouse_id=7" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

**GraphQL:** `customerShipoutAvailableItems` ([Manuale GraphQL](/api/graphql/documentation#/customer/customerShipoutAvailableItems))

**Verifica:** avete annotato uno o più `storage_package_ids` (esempio `5001`). Un elenco vuoto significa che nulla è ancora ricevuto — non create un’uscita. `403` significa che l’uscita è disabilitata per questo cliente.

## 7. Stimare e creare l’uscita

**REST:** `GET /api/v1/customer/shipout-orders/services?warehouse_id=7` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-services/get)

Annotate un `service_code`.

**REST:** `POST /api/v1/customer/shipout-orders/services/{serviceCode}/estimate` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-services-serviceCode--estimate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/services/intl_express/estimate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": 7,
    "delivery_postcode": "M5V2H1",
    "delivery_country": "CA",
    "packages": [{
      "weight": 2.5,
      "length": 30,
      "width": 20,
      "height": 15,
      "weight_unit": 2,
      "dimension_unit": 2
    }]
  }'
```

**REST:** `POST /api/v1/customer/shipout-orders/services/{serviceCode}/orders` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-services-serviceCode--orders/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/services/intl_express/orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-shipout-001" \
  -d '{
    "warehouse_id": 7,
    "storage_package_ids": [5001],
    "delivery_name": "Jane Recipient",
    "delivery_telephone": "5555555555",
    "delivery_email": "jane@example.com",
    "delivery_address_1": "123 King St W",
    "delivery_city": "Toronto",
    "delivery_province": "ON",
    "delivery_country": "CA",
    "delivery_postcode": "M5V2H1"
  }'
```

**GraphQL:** `customerCreateShipoutOrder` ([Manuale GraphQL](/api/graphql/documentation#/customer/customerCreateShipoutOrder))

**Verifica:** la risposta ha un `id` di uscita. I colli di stoccaggio selezionati sono bloccati su questa richiesta.

## 8. Pagare l’uscita, tracciare, eventi

**REST:** `POST /api/v1/customer/shipout-orders/{id}/pay` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-id--pay/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/8001/pay \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**GraphQL:** `customerPayShipout` ([Manuale GraphQL](/api/graphql/documentation#/customer/customerPayShipout))

**REST:** `GET /api/v1/customer/shipout-orders/{id}` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-id/get)

Quando esiste un numero di tracking:

```bash
curl https://YOUR_HOST/api/v1/tracking/SR123456789012
```

[Manuale REST](/api/documentation#/operations/getPublicTracking) · [Manuale GraphQL](/api/graphql/documentation#/tracking/trackingPublic)

**REST:** `PUT /api/v1/webhook-settings` — [Manuale REST](/api/documentation#/paths/v1-webhook-settings/put)

**GraphQL:** `webhookSettingsUpdate` ([Manuale GraphQL](/api/graphql/documentation#/webhooks/webhookSettingsUpdate)).

Verificate **v2**: `HMAC_SHA256(timestamp + "." + raw_body, secret)` contro `X-Webhook-Signature-V2`. Rimuovete i duplicati su `X-Webhook-Event-Id`. Rispondete **2xx in meno di 3 secondi**.

```php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_V2'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, $sharedSecret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
```

**Verifica:** il pagamento registra un importo (oppure `402` / `422` con un motivo chiaro). Il tracking pubblico trova la spedizione una volta che esiste un numero.

## 9. Annullare un’uscita di prova

**REST:** `POST /api/v1/customer/shipout-orders/{id}/cancel` — [Manuale REST](/api/documentation#/paths/v1-customer-shipout-orders-id--cancel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/8001/cancel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**GraphQL:** `customerCancelShipout` ([Manuale GraphQL](/api/graphql/documentation#/customer/customerCancelShipout))

Questo rilascia il blocco dei colli di stoccaggio. Lo stoccaggio stesso si annulla con `POST /api/v1/customer/storage-orders/{id}/cancel` finché è ancora consentito.

**Verifica:** `422` significa che questo stato non può essere annullato. Dopo un annullamento riuscito dell’uscita, il passo 6 elenca di nuovo i colli.

## Elenco di verifica

- [ ] La configurazione dello stoccaggio restituisce un `id` di magazzino.
- [ ] La quotazione dello stoccaggio restituisce un prezzo.
- [ ] La creazione dello stoccaggio restituisce `data.id`.
- [ ] Il pagamento dello stoccaggio va a buon fine, **oppure** avete confermato che il portafoglio va ricaricato.
- [ ] Gli articoli disponibili elencano i colli ricevuti (`storage_package_ids`).
- [ ] La stima dell’uscita restituisce un prezzo o `has_items_needing_quote`.
- [ ] La creazione dell’uscita restituisce un `id` e blocca quei colli.
- [ ] Il pagamento dell’uscita va a buon fine (oppure `402` / `422` è compreso).
- [ ] Il tracking pubblico trova la spedizione una volta che esiste un numero di tracking.
- [ ] L’annullamento dell’uscita rilascia i colli, **oppure** questo stato non può essere annullato.
