# New Shipment Workflow (DHL / Surrender)

## Purpose

Introduce a dedicated **Document Release** workflow so a seller can request:

1. **DHL** — courier delivery of physical/export documents to the customer  
2. **Surrender** — authorization to release the vehicle and begin origin shipping  

Admin reviews payment, approves/declines, and assigns a shipping agent. The agent then finalizes DHL and/or starts the vehicle shipment after Surrender approval.

## New vs legacy separation

| | Legacy | New (this workflow) |
|--|--------|---------------------|
| Tables | `shipments` (CRUD historically) | `document_release_requests`, `dhl_details`, `surrender_details`, `shipment_status_events` + shipments **linked only via** `surrender_details.shipment_id` |
| Actors | Admin/Seller manual shipment CRUD | Seller request → Admin assign → Shipping Agent execute |
| Feature flag | `LEGACY_SHIPMENT_WORKFLOW_ENABLED` (default **true**) | `NEW_SHIPMENT_WORKFLOW_ENABLED` (default **false**) |

**Rules**

- Legacy shipment workflow stays operational and untouched.
- New vehicle shipments are identified by `surrender_details.shipment_id` (no `workflow_source` column required).
- No automatic conversion of legacy shipments.
- **Never delete historical legacy shipments.**

## Current phase

**Smart defaults, Auction Intake DHL adoption, and finalized DHL ownership (complete)**

No migrations. No stored-status / lifecycle changes. GET/show remains read-only.

### Vessel name (Surrender)

Precedence (display only until Save Draft / Start):

`saved surrender_details.vessel_name` → `auction_cars.vessel` → empty  

Helper: `NewShipmentDefaults::resolveVesselName`. Prefill helper text only when the visible value came from Auction Intake.

### DHL receiver defaults

Centralized: `DhlWorkflowService::resolveReceiverSnapshot`  
Per-field precedence:

1. saved `dhl_details.receiver_snapshot`  
2. request `consignee_snapshot`  
3. live order Consignee / order consignee fields  
4. customer User / `customer_*`  
5. empty  

Persisted only via Save Draft / Finalize.

### Auction Intake → DHL adoption

- Show payload may include `dhl.adoption.available` when draft + empty path + valid Intake `auction_files.dhl[0]` on disk.  
- **No mutation on GET.**  
- Vue auto-POSTs once to `shipping-agent.document-release-requests.dhl.adopt-existing` (sessionStorage de-dupes loops).  
- Service reuses the same path (no copy/move/delete), upserts one VehicleDocument (`dhl`), appends internal timeline `dhl_intake_adopted`.  
- Finalize accepts the adopted attachment without a new upload.

### Source of truth after finalize

| Before finalize | After finalize |
|-----------------|----------------|
| Intake may seed detail via adoption | `dhl_details.attachment_path` is authoritative |
| Manage Files may edit Intake DHL | Manage Files **blocks** DHL upload/replace/delete |

Locked message: use Replace DHL Document on the assigned DHL request. Other Auction Intake file types unchanged. Internal mismatch warnings via `document_sync` for Admin / assigned agents only.

### Prior complete: S5.1D

**S5.1D — DHL document-delivery & vehicle shipment status polish (complete)**

Presentation + dropdown correctness only. Stored status values and lifecycle semantics are unchanged.

### DHL document-delivery sequence (stored → label)

| Stored | Label |
|--------|--------|
| `draft` | Draft |
| `finalized` | Documents Prepared |
| `dispatched` | Documents Dispatched |
| `delivered` | Documents Delivered |

Normal agent path: Draft → **Finalize** (→ Documents Prepared) → Documents Dispatched → Documents Delivered (terminal).  
Draft has no status-dropdown next step; finalize remains the prepare action. Forward-only; duplicate/same-status rejected. Replacement/resend stay separate.

### Vehicle release / shipment sequence (stored → label)

| Stored | Label |
|--------|--------|
| `shipment_pending` | Shipment Pending |
| `processing` | Shipping Booked |
| `documents_ready` | Shipping Documents Ready |
| `released_from_origin` | Vehicle Released from Origin |
| `shipped` | Vessel Departed |
| `in_transit` | Vehicle In Transit |
| `arrived` | Arrived at Destination Port |
| `delivered` | Vehicle Delivered |
| `completed` | Shipment Completed |
| `cancelled` | Shipment Cancelled (Admin-only; not in agent dropdown) |

Allowed-next dropdown options come from `NewShipmentWorkflow::SHIPMENT_TRANSITIONS` / `DHL_TRANSITIONS` (helpers mirror the service — backend remains authoritative). Practical skips already on the map are preserved. High-impact agent confirms: Vessel Departed, Vehicle Delivered, Shipment Completed.

### Separation

- DHL UI uses **document delivery** wording only (`Update Document Delivery Status`).
- Surrender/shipment UI uses **vehicle release / shipment** wording only (`Update Vehicle Shipment Status`).
- No migrations or stored-value renames in S5.1D.

### Prior complete: S5.1B

Adds a read-only **Shipment Operations** section to the existing Shipping Agent dashboard when:

- `NEW_SHIPMENT_WORKFLOW_ENABLED=true`
- Agent has `document_release.assigned.view`

Detail pages remain the only processing surfaces (DHL finalize, Surrender draft/start, status updates). The dashboard links into those pages; it does not change lifecycle transitions, notifications, or payments.

### Statistics (current agent, approved + assigned only)

| Stat | Definition |
|------|------------|
| Assigned Releases | Approved DHL (not `delivered`) + approved Surrender (no completed/cancelled linked shipment) |
| Pending DHL Documents | Approved DHL where detail is missing or still `draft` / not finalized|
| Ready to Start | Approved Surrender with `surrender_details.shipment_id` null and draft fields complete / not legacy-blocked |
| Active Vehicle Shipments | Linked new-workflow shipment status in active set (not completed/cancelled) |
| Arriving Soon | Active shipments with ETA from today through next 7 days (`app.timezone`) |
| Completed This Month | Linked shipments with `completed` status event (or shipment `updated_at` fallback) in the current calendar month |

### Urgency indicators (presentation only — not stored statuses)

- Approved Surrender older than 3 days with no shipment → Needs Attention  
- ETD passed while status before `shipped` → ETD Overdue  
- ETA passed while status before `arrived` → Arrival Overdue  
- ETA within 7 days → Arriving Soon  

### Query service

`ShippingAgentOperationsDashboardService` builds bounded dashboard payloads with eager loading and assignment ownership (`assigned_shipping_agent_id = auth user`). Legacy shipment rows are never listed as active new-workflow work.

### Prior complete: S5.1A

Shipment summary cards, status badges, and `ShipmentProgressTimeline` (presentation polish).

### Prior complete: S5

**S5 — Surrender processing, Start Shipping Process, lifecycle, timeline, notifications (complete)**

### Admin approval vs Shipping Agent Start

- Admin **approve** authorizes Surrender and creates an empty `SurrenderDetail`.
- Approve does **not** create a `Shipment` row.
- The assigned Shipping Agent must explicitly click **Start Shipping Process** (confirmation dialog).
- Only then is a `shipments` row created, linked, and timeline/notifications fired.

### Draft vs Start

| | Draft save | Start Shipping Process |
|--|------------|------------------------|
| Partial fields | Allowed | Required: shipping_line, origin_port, destination_port, etd, surrender_date |
| Creates shipment | No | Yes |
| Notifies | No | Admin + Seller + Customer |
| Payment revalidation | No | Yes (`PaymentService` + eligibility) |

Operational Vehicle Release fields (draft / start): shipping line, vessel name, voyage number, surrender/release date, origin port, destination port, ETD, ETA, reference, internal notes.

Vessel/voyage may stay nullable at start.

**Confirmation Document (retired):** The nullable `surrender_details.confirmation_document_path` column remains for backward compatibility/history only. The active Vehicle Release workflow does **not** accept new uploads. Existing paths (if any) stay readable as a historical attachment for Admin / assigned Agent; files are not deleted.

### One active new shipment

Service-enforced (row locks), not a destructive DB unique index:

- One active new-workflow shipment per order + vehicle  
- Active = any status except `completed` / `cancelled`  
- Identified via existing `surrender_details` → `shipment_id` link  

### Legacy coexistence

If the same order+vehicle already has an **active legacy** shipment (not linked via surrender, status ≠ `delivered`):

- Start is **blocked** with a clear validation message  
- Legacy row is never adopted or mutated  

### Lifecycle & transitions (Shipping Agent)

```
shipment_pending → processing → documents_ready → released_from_origin
  → shipped → in_transit → arrived → delivered → completed
```

(Display: Shipment Pending → Shipping Booked → Shipping Documents Ready → Vehicle Released from Origin → Vessel Departed → Vehicle In Transit → Arrived at Destination Port → Vehicle Delivered → Shipment Completed.)

Allowed skips (explicit map only):

- `shipment_pending` → `documents_ready` / `released_from_origin`
- `processing` → `released_from_origin`
- `shipped` → `arrived`

No backward transitions in agent UI. Cancel is **Admin-only** (not in agent dropdown).

Milestone column mapping (cautious):

- Start → `shipping_approved_date` / `buy_date` / `ship_ok_date` from surrender_date  
- `shipped` → `departure_date`  
- `arrived` → `arrival_date`  
- `delivered` / `completed` → timeline authoritative; **do not** set `local_dispatch_date` (avoids legacy order DHL/delivered sync side effects)

### Timeline / customer visibility

Customer-visible events: `shipping_process_started`, `released_from_origin`, `shipped`, `in_transit`, `arrived`, `delivered`, `completed`.

Internal: draft saves, `shipment_created`, assignment, payment notes, agent notes.

### Notification policy

| Event | Admin | Seller | Customer |
|-------|-------|--------|----------|
| Start | ✓ | ✓ | ✓ |
| released_from_origin / shipped / arrived / delivered / completed | ✓ | ✓ | ✓ |
| processing / documents_ready / in_transit | — | — | portal timeline only |

All mail after `DB::afterCommit()`. Failures do not roll back shipment state. Idempotency = one-shot status transitions.

### DHL document delivery remains separate

- DHL finalize prepares documents (stored status `finalized` = Documents Prepared) and syncs courier PDF only  
- Agent may then mark `dispatched` / `delivered` (document delivery only)  
- Does **not** create vehicle shipments  
- Customer early DHL download unchanged  
- DHL and vehicle shipment status domains stay independent  

## Feature flags

```env
NEW_SHIPMENT_WORKFLOW_ENABLED=false
LEGACY_SHIPMENT_WORKFLOW_ENABLED=true
```

## Shipping Agent scopes (defaults)

- `document_release.assigned.view`
- `document_release.dhl.manage`
- `document_release.surrender.manage`
- `shipments.status.update`

Revocable via Access Control.

## Status domains

- **Request:** `pending` | `approved` | `declined`
- **DHL detail:** `draft` | `finalized` | `dispatched` | `delivered`
- **New shipment:** `shipment_pending` | `processing` | `documents_ready` | `released_from_origin` | `shipped` | `in_transit` | `arrived` | `delivered` | `completed` | `cancelled`
- **Legacy `Shipment::STATUS_FLOW`:** unchanged (`shipment_pending`, `shipped`, `in_transit`, `arrived`, `delivered`)

## Prior phases

- **S4** DHL finalize + file sync + customer email  
- **S3** Admin review & assignment  
- **S2** Seller request + eligibility  
- **S1** Foundation tables/constants  

## Planned next

- Admin cancel/override corrections  
- Audited legacy cutover when ready  

S5.1B notes:

- No migration for the operations dashboard  
- No status/lifecycle/business-logic changes  
- Legacy Auction Intake dashboard metrics and links remain unchanged  

## Warning

**Legacy shipment module removal is future work only.**  
**Database safety:** only forward `php artisan migrate`. Never `migrate:fresh` / wipe production data as a “fix”.
