# Jetstream App — System Workflow & Lifecycle Architecture

Engineering reference for current behavior, entity boundaries, lifecycle flows, and known fragile areas.  
For schema-level detail, see [`docs/system-blueprint.md`](system-blueprint.md).

---

## 1. System Overview

### Purpose

Laravel + Inertia/Vue platform for **Japanese vehicle export operations**: auction intake → sellable inventory → customer reservation → order/payment → shipment → delivery.

The system merges two inventory origins:

1. **Auction intake** (`auction_cars`) — admin-managed sourcing, cost build-up, and seller assignment.
2. **Vehicle catalog** (`vehicles`) — customer-facing listings, optionally 1:1 linked to an auction car.

Commercial activity is reservation-driven: sellers/admins hold stock for a customer, then checkout creates an order with split wallet + external payments.

### Primary business flow

```mermaid
flowchart LR
  A[Auction intake] --> B[Vehicle materialization]
  B --> C[Reservation]
  C --> D[Reservation invoice]
  C --> E[Checkout / Order]
  E --> F[Payments]
  F --> G[Partial invoices]
  F --> H[Shipment milestones]
  H --> I[Order completed]
```

**Orchestration services (central paths):**

| Service | Role |
|---------|------|
| `AuctionVehicleSyncService` | Auction car → vehicle normalization |
| `VehicleReservationService` | Reserve, release, pricing snapshot, reservation invoice trigger |
| `CustomerPurchaseService` | Customer CIF checkout, order creation, balance payments |
| `PaymentService` | Payment ledger, order status sync, reservation conversion |
| `WalletService` | Credit/debit ledger, remittance allocation |
| `InvoiceService` | PDF generation, persistence, email |
| `OrderService` | Legacy/seller-specific order helpers (partially superseded) |

---

## 2. Role Responsibilities and Access Scope

This section defines the **intended business responsibilities** per role. It is the target model for future scope management.  
**Current production** still uses coarse role checks (`users.role`) plus hard-coded scopes such as `AuctionIntakeScope` for intake only — not a full permission system yet.

### Scope model (target)

Access should eventually be managed by **Admin** from a dedicated **role/scope management** UI (not implemented). The target model supports three layers:

| Layer | Examples |
|-------|----------|
| **Module** | auction intake, vehicles, reservations, orders, payments, invoices, remittances, settings, analytics, master data |
| **Action** | view, create, update, delete, assign, reserve, approve, generate, resend, allocate |
| **Field / section** | bidder identity block, shipping cost columns, vehicle specifications, remitter/consignee forms |

**Implementation principles (future):**

- Analysis-first: map modules, actions, and field groups before wiring enforcement.
- Do **not** hard-code role names in business logic; resolve scopes from configuration/DB.
- **Customer-facing systems are frozen** — public listings, customer checkout, customer dashboard, invoice delivery to customers, payment capture, and remittance settlement logic must not change as a side effect of internal scope work.
- Internal roles (Bidder, Shipping Agent, Data Entry) gain access through scoped admin/staff routes, not customer routes.

```mermaid
flowchart TB
  A[Admin scope management] --> M[Module scopes]
  M --> AC[Action scopes]
  AC --> FS[Field/section scopes]
  FS --> R[Bidder / Shipping / Data Entry / Seller / Admin]
  CF[Customer-facing modules] -.->|frozen| X[No scope refactor impact]
```

---

### Role summary

| Role | Primary responsibility | Data-entry stage | Commercial / ops |
|------|------------------------|------------------|------------------|
| **Admin** | Full platform control | All intake + vehicle stages | All reservations, checkout, payments, invoices, remittances, settings, analytics |
| **Seller** | Sales and customer handling | **Not** primary vehicle data entry | Pool/assigned reservations, customers, remittances, partial invoices, shipment updates |
| **Bidder** | Auction intake base capture | Stage 1 | None |
| **Shipping Agent** | Logistics/shipping completion | Stage 2 | None |
| **Data Entry** | Vehicle catalog enrichment | Stage 3 | None (no reservation/checkout) |
| **Customer** | Purchase and order self-service | N/A | Own orders/payments/invoices only |

---

### 1. Admin

**Responsibility:** Full operational control and oversight across the staged pipeline.

| Domain | Intended access |
|--------|-----------------|
| **Users & access** | Manage users, roles, and scopes (target: role-scope management page) |
| **Auction intake** | Full CRUD, all cost/logistics fields, all document types, seller assignment |
| **Vehicles** | Full CRUD, link/unlink auction cars, override locks where business rules allow |
| **Reservations & checkout** | Create/release reservations; checkout on behalf of customers; approve seller checkouts |
| **Orders & shipments** | Full lifecycle management |
| **Payments & invoices** | Record/mark payments; list/download invoices; generate partial invoices |
| **Remittances** | Create/upload remittance records; review seller submissions and seller claim requests; verify to credit customer wallets |
| **Settings & master data** | Company, banking, transport defaults, catalog configuration |
| **Analytics** | Dashboard revenue/profit/inventory metrics |

**Current enforcement:** `role = admin` on admin route prefix; `AuctionIntakeScope::Admin` for intake field/file scope.

---

### 2. Seller

**Responsibility:** Customer-facing sales operations for assigned inventory — **not** the primary vehicle catalog data-entry role.

| Domain | Intended access |
|--------|-----------------|
| **Reservations** | From **auction pool** (unlinked auction rows) and from **own assigned vehicles** |
| **Customers** | Manage own customers; create proper customer accounts with **remitter** and **consignee** profiles |
| **Checkout** | Submit checkout for admin approval (`pending_approval` path) |
| **Remittances** | Submit direct remittances for admin verification; browse and **claim** admin PDF imports (customer allocation + claim proof); credited only after admin verify |
| **Orders** | View own sales; update **order shipment info** where permitted |
| **Invoices** | Generate and resend **partial payment** invoices (not reservation invoice policy changes) |
| **Vehicles** | View/edit **own** listings for sales readiness; may add listing media — but **Stage 3 catalog completion** is owned by Data Entry, not Seller |

**Explicit non-responsibilities:** Bidder/shipping intake fields; platform settings; admin-only approval gates; primary make/model/attribute master-data maintenance.

**Current enforcement:** `role = seller` + `vehicles.seller_id = auth` for inventory; seller route controllers for pool, reservations, checkout, remittances.

---

### 3. Bidder

**Responsibility:** Early **auction intake base data** only (Stage 1).

| Domain | Intended access |
|--------|-----------------|
| **Auction intake** | Identity and base cost fields: make/model/chassis, purchase context, destination, `auction_price`, `auction_fee` |
| **Documents** | Auction sheet (`as`) only |
| **Settings** | Related **base-data settings** required to populate bidder fields (e.g. auction houses, regions referenced by intake — scoped read/use, not full settings admin) |

**Field scope (current):** `AuctionIntakeScope::Bidder` — cannot edit shipping/logistics columns, `selling_price`, seller assignment, or shipping-stage documents.

**Route prefix (current):** `bidder/data-entry/auctions`

---

### 4. Shipping Agent

**Responsibility:** **Logistics and shipping-stage** intake completion (Stage 2).

| Domain | Intended access |
|--------|-----------------|
| **Auction intake** | Logistics/shipping cost fields, `selling_price`, `vessel`, `sailing_date` |
| **Documents** | Shipping document types where assigned (`ec`, `coc`, `bl`, `dhl`, `keys`, `pc`, `psi`, `bill`, `inv`) |
| **Restrictions** | Cannot change bidder identity block; cannot add new intake rows; cannot assign sellers or create reservations |

**Field scope (current):** `AuctionIntakeScope::ShippingAgent` — identity fields preserved from baseline on save.

**Route prefix (current):** `shipping-agent/data-entry/auctions`

---

### 5. Data Entry

**Responsibility:** **Stage 3 vehicle catalog enrichment** — detailed vehicle information after intake, without commercial or financial workflows.

| Domain | Intended access |
|--------|-----------------|
| **Dashboard** | Staff landing page (`data-entry.dashboard`) |
| **Auction intake (read-only)** | Browse auction queue for context; identity fields read-only; no cost/commercial columns, file management, seller assignment, or batch save |
| **Vehicle catalog** | View all vehicles; **edit enrichment fields only** on existing records (no create/delete yet) |
| **Vehicle enrichment (allowed)** | Basic descriptive info (where safe), specifications, dynamic attributes, images, stock location (`country_id`, `location`, `stock_country`) |
| **Vehicle enrichment (blocked)** | Seller assignment, pricing/discounts, listing/stock lifecycle (`status`, `stock_status`, featured/clearance), reservation/lock fields, `auction_car_id` changes |
| **Master data** | Planned — not routed yet (`vehicle_master_data.*` remains in `future_role_grants`) |
| **Restrictions** | No reservation, checkout, payment, invoice generation, remittance, or seller-sales workflows; no admin routes |

**Current enforcement (implemented):**

- `users.role = data_entry` on route prefix `data-entry/*` (`RoleMiddleware`)
- `AccessResolver` + `config/permissions.php` active grants: `vehicles.view`, `vehicles.update`, section grants except `seller_assignment` and `pricing`, `auction_intake.view`, `analytics.data_entry_dashboard.view` — **not middleware-enforced yet**
- Auction queue: `DataEntry\AuctionCarController` extends admin intake with `AuctionIntakeScope::DataEntry` (read-only grid, commercial payload masked)
- Vehicle edit: `DataEntry\VehicleController` + `UpdateDataEntryVehicleRequest` whitelist + immutable-field tamper checks; reuses `VehicleForm` with `dataEntryMode` (UI hides forbidden sections)

**Route prefix (current):** `data-entry/` — dashboard, vehicles (index/show/edit/update), auctions (index only).

See [§3 Staged Data-Entry Workflow](#3-staged-data-entry-workflow) for stage sequencing and [Operational entry points — Data Entry](#data-entry-1) below.

---

### Customer (frozen boundary)

Customer role scope is **out of scope** for internal role-scope refactors. Do not alter:

- Public vehicle listings and inquiry/purchase modes
- Customer checkout and reservation checkout
- Customer dashboard, orders, payments, invoice download
- Payment webhooks and wallet/remittance settlement behavior

---

### Operational entry points (current implementation)

The tables below map **today’s routes/controllers** to outcomes. They supplement — not replace — the responsibility model above.

#### Admin

| Area | Entry | Outcome |
|------|-------|---------|
| **Auction intake** | `Admin\AuctionCarController` | CRUD auction rows, cost fields, files; auto-sync minimal vehicle on save |
| **Seller assignment** | `assignSeller()` | Sets `vehicles.seller_id` via `syncMinimalVehicle()` |
| **Reservations** | `Admin\VehicleReservationController` | Create/release reservation for any customer |
| **Checkout** | `Admin\ReservationCheckoutController` | Admin completes checkout on behalf of customer → order + payments → reservation **`converted`** immediately |
| **Checkout approval** | `Admin\CheckoutApprovalController` | Approve/hold/decline seller-submitted checkouts |
| **Orders / shipments** | `Admin\OrderController`, `ShipmentController` | Manual lifecycle progression after payment |
| **Payments** | `Admin\PaymentController` | Mark bank transfers paid → triggers `syncOrderPaymentTotals()` |
| **Invoices** | `Admin\InvoiceController` | List/download; manual partial-payment invoice generation |
| **Dashboard** | `Admin\DashboardController` + `AdminDashboardService` | Inventory + revenue/profit analytics |

#### Seller

| Area | Entry | Outcome |
|------|-------|---------|
| **Auction pool** | `Seller\AuctionPoolController` | Browse unreserved auction cars with **no linked vehicle** |
| **Inventory** | `Seller\VehicleController` | CRUD own listings (`seller_id = auth`) — sales-oriented, not primary data entry |
| **Reservations** | `Seller\VehicleReservationController` | Reserve for own customers; pricing = `admin_price + commission` |
| **Checkout** | `Seller\SellerCheckoutController` | Create order + pending payments → reservation **`pending_approval`** |
| **Pay balance** | Same controller | Additional payments after conversion |
| **Orders / invoices** | Seller controllers | View own sales; generate/resend partial invoices |

Seller checkout **requires admin approval** before payments are completed and reservation is fully converted (unless admin performs checkout directly).

#### Data Entry {#data-entry-1}

| Area | Entry | Outcome |
|------|-------|---------|
| **Dashboard** | `DataEntry\DashboardController` | Staff landing; no commercial metrics |
| **Auction queue (read-only)** | `DataEntry\AuctionCarController@index` | Browse intake rows via shared admin grid UI; `AuctionIntakeScope::DataEntry` — no editable keys, no files, commercial fields masked, no seller/reserve/vehicle actions |
| **Vehicle catalog (read)** | `DataEntry\VehicleController@index`, `@show` | Same query payload as admin index/show; read-only catalog components (`VehicleCatalogIndex` / `VehicleCatalogShow`) |
| **Vehicle enrichment (edit)** | `DataEntry\VehicleController@edit`, `@update` | `UpdateDataEntryVehicleRequest` whitelists enrichment fields; blocks seller/pricing/lifecycle tampering; `VehicleForm` `dataEntryMode` hides forbidden UI sections |

**Not available to Data Entry:** vehicle create/delete, document management, reservations, checkout, remittances, settings, master-data admin routes, admin prefix.

#### Customer

| Area | Entry | Outcome |
|------|-------|---------|
| **Public catalog** | `PublicController` | Browse published vehicles; inquiry vs direct purchase depends on auction completion |
| **Direct purchase** | `Customer\CheckoutController` | CIF checkout (vehicle + freight/inspection/insurance) |
| **Reservation checkout** | Reservation checkout routes | Uses locked `seller_price`; materializes vehicle if missing |
| **Orders** | `Customer\OrderController` | Timeline, payment summary |
| **Payments** | `Customer\PaymentController` | Pay balance, gateway webhook |
| **Invoices** | `Customer\InvoiceController` | Download only (reservation invoice emailed at reserve time) |

---

## 3. Staged Data-Entry Workflow

Auction intake is designed as a **multi-role, staged pipeline** on a single `auction_cars` record. Stages accumulate data over time; they do not require a fully enriched vehicle catalog row before commercial activity can begin.

### Stage overview

```mermaid
flowchart LR
  B[Bidder intake] --> S[Shipping Agent intake]
  S --> DE[Data Entry enrichment]
  DE --> A[Admin oversight]
  B -.-> A
  S -.-> A
  A --> V[Minimal vehicle]
  A --> R[Reservation]
  R --> I[Reservation invoice]
```

| Stage | Role | Scope | Route prefix |
|-------|------|-------|--------------|
| **1 — Auction capture** | **Bidder** | Identity, auction house, destination, core auction costs | `bidder/data-entry/auctions` |
| **2 — Logistics & costing** | **Shipping Agent** | Shipping/logistics charges, sailing, selling price, shipping documents | `shipping-agent/data-entry/auctions` |
| **3 — Vehicle enrichment** | **Data Entry** | Vehicle catalog enrichment on existing records; read-only auction queue for context | `data-entry/` (vehicles + auctions index) |
| **Oversight** | **Admin** | All stages, seller assignment, reservation, checkout, vehicle CRUD | `admin/data-entry/auctions` |

Field-level enforcement is implemented in `app/Support/AuctionIntakeScope.php` via `editableDataKeys()`, `restrictPayload()`, and per-role file-type permissions.

### Stage 1 — Bidder

**Purpose:** Capture the minimum auction purchase identity and base cost inputs early.

**Typical editable fields:**

- Catalog keys: `make_id`, `model_id`, `vehicle_type_id`, `chassis_no`
- Context: `purchase_date`, `region_id`, `city`, `auction_house_id`, `dest_country_id`
- Base costs: `auction_price`, `auction_fee`

**Files:** `as` (auction sheet) only.

**Cannot edit:** shipping/logistics cost columns, `selling_price`, seller assignment, or shipping-stage documents.

### Stage 2 — Shipping Agent

**Purpose:** Complete logistics-related costs and shipping metadata after bidder intake.

**Typical editable fields:**

- Logistics costs: `inspection`, `transportation`, `h_charge`, `van`, `insurance`, `freight`, `bl`, `extra`, `shipper`, `storage`, `storage_is_manual_override`
- Commercial/shipping: `selling_price`, `vessel`, `sailing_date`

**Files:** `ec`, `coc`, `bl`, `dhl`, `keys`, `pc`, `psi`, `bill`, `inv` (not `as`).

**Cannot edit:** bidder identity fields (make/model/chassis/auction price identity block is preserved from baseline on save).

**Cannot:** add new intake rows (`canAddRows()` is false for shipping agent).

### Stage 3 — Data Entry

**Purpose:** Complete customer-facing and invoice-enrichment vehicle details that are not sourced from auction intake alone. Primary owner of Stage 3 catalog enrichment — see [§2 — Data Entry](#5-data-entry).

**Implemented scope (Phase B — foundation + enrichment edit):**

| Capability | Status |
|------------|--------|
| Distinct `users.role = data_entry` + `data-entry/*` routes | ✓ |
| Staff dashboard | ✓ |
| Read-only auction queue (`data-entry.auction-cars.index`) | ✓ — context only; no intake edit |
| Vehicle index/show (read) | ✓ |
| Vehicle edit/update (enrichment fields only) | ✓ — `UpdateDataEntryVehicleRequest` |
| Vehicle create/delete, documents, master data | Not routed |
| Reservations / checkout / remittances | Blocked (no routes) |

**Allowed vehicle fields (server whitelist):** make/model identity (unless auction-linked or locked), year, mileage, condition, chassis (when not locked), legacy spec columns, `dynamic_attributes`, images, `country_id` / `location` / `stock_country`.

**Blocked vehicle fields (server immutable checks):** `seller_id`, `auction_car_id`, price/discounts, `status`, `stock_status`, `is_featured`, `is_clearance`, lock/readiness flags, `vehicle_type*`. Auction-linked rows also lock make/model/chassis to intake source.

**UI:** Shared `VehicleForm` with `dataEntryMode` — hides seller assignment, pricing, listing/stock status, and featured/clearance; omits forbidden keys from submit payload.

**Still planned:** vehicle create (from auction or direct), document management, master-data admin routes, full `AccessResolver` middleware enforcement.

**Today:** Admin still owns vehicle create/delete and master data. Seller may still edit own listings for sales — Data Entry is the intended primary enrichment role for the full catalog.

### Admin across all stages

Admin responsibilities and full access are defined in [§2 — Admin](#1-admin). Intake enforcement uses `AuctionIntakeScope::Admin`. Admin can:

- View and edit **all** intake fields and file types
- Assign/clear seller (`assignSeller()` → `syncMinimalVehicle()`)
- Create reservations and run checkout
- Manage vehicles, orders, payments, and invoices regardless of intake completion stage

Non-admin roles write only their allowed keys; other columns are preserved from the existing `auction_cars` baseline on update.

---

### Commercial activity before full vehicle enrichment

The system **intentionally allows** the following before Stage 3 (full vehicle details) is complete:

| Entity | Can exist early? | Notes |
|--------|------------------|-------|
| `auction_cars` | Yes | After Stage 1 (and progressively Stage 2) |
| `vehicles` (minimal) | Yes | Draft from seller assign, or materialized on reservation via `ensureForReservation()` |
| `vehicle_reservations` | Yes | Requires make/model (+ pricing); auction-only path materializes vehicle in transaction |
| Reservation invoice | Yes | Auto-fired in `afterCommit` on reserve |

**Design intent:** Operations (reserve → invoice → checkout) must not be blocked waiting for full catalog attribute entry. Missing detailed specs at reservation time is an **accepted** state when earlier intake stages have not yet populated them.

---

### Data expected at reservation time

When a reservation is created (and its invoice generated), the following are **expected to be available** if intake/reservation prerequisites are met:

**From `auction_cars` (via normalization):**

| Data | Source |
|------|--------|
| Make / model (IDs + names) | `make_id`, `model_id`, relations |
| Chassis | `chassis_no` |
| Body type | `vehicle_type_id` + denormalized `vehicle_type` |
| Year variants | Derived from `purchase_date` |
| Model code | Model relation or chassis prefix |
| Destination | `dest_country_id` on `auction_cars` only (not copied to vehicle stock fields) |
| Stock location | `region_id` / `city` → `vehicles.location`; region’s `country_id` → `vehicles.country_id` / `stock_country` |
| Pricing base | `selling_price` → reservation `admin_price` / materialized `price` |

**From materialized `vehicles` (reservation path):**

| Data | Source |
|------|--------|
| `rec_no` | Auto-generated on vehicle create (invoice “auction order”) |
| Lock / ownership | `status`, `is_locked`, `seller_id` |
| Core denormalized fields | Synced from auction via `AuctionVehicleSyncService::normalizedAttributes()` |

**From `vehicle_reservations`:**

| Data | Source |
|------|--------|
| Invoice amount | `seller_price ?? admin_price` |
| Invoice number | Zero-padded reservation ID |
| Customer / seller | `user_id`, `seller_id` |

**From invoice service defaults (no order yet):**

| Data | Source |
|------|--------|
| Consignee | Customer’s default consignee profile, or customer address fallback |
| Transport | Default `RORO`, setting `invoice_transport_from`, consignee destination |
| Company / banking | `Setting` keys at generation time (stored in invoice `metadata`) |

---

### Vehicle fields that may be intentionally missing

These are **not required** for reservation or reservation invoice generation. Empty values render as `-` on the PDF unless/until Stage 3 (or later admin/seller vehicle editing) fills them:

| Category | Examples | Why missing is acceptable early |
|----------|----------|--------------------------------|
| Dynamic catalog attributes | Color, transmission, fuel type, engine CC | Live on `vehicle_attribute_values`, not `auction_cars` |
| Boolean features | Option packs / feature flags | Same; invoice `features` line may be `-` |
| Legacy spec columns | `doors`, `grade`, detailed mileage | Not mapped from minimal auction normalization |
| Rich media | `primary_image`, gallery, documents | Added during listing/enrichment |
| Order/shipping snapshot | Port pair, consignee from order | No `order` exists at reservation invoice time |
| Published listing state | `status = published` | Minimal/reserved vehicles are operational, not necessarily public listings |

**Reservation invoice behavior:** `InvoiceService::resolveVehicleDetails()` uses the **best available** vehicle + auction data at generation time and stores the assembled context in `invoices.metadata`. Gaps in detailed specs reflect incomplete data-entry stage, not a blocked workflow.

**Important:** Early invoices are **commercial reservation documents** (price, customer, core vehicle identity), not a guarantee that the full attribute catalog is complete.

---

### Invoice readiness vs reservation blocking

**Current behavior:**

- Reservation is **not** gated on full vehicle attribute completeness.
- Reservation invoice is **not** blocked when color/transmission/features are empty.
- Invoice PDF content is frozen in `metadata` at generation; later attribute entry does not retroactively change that PDF.

**Future recommendation (documentation only — not implemented):**

- Add **invoice readiness / data completeness indicators** on intake and vehicle admin UI (e.g. core identity ✓, pricing ✓, optional specs ○).
- Use indicators for **operator guidance**, not hard reservation blocks.
- Optionally surface “partial spec” warnings on reservation invoice preview without preventing generation.

This preserves fast reservation/invoicing while making incomplete Stage 3 data visible to staff.

---

## 4. Core Entity Responsibilities

### `auction_cars`

- **Owns:** sourcing costs, logistics fields, `selling_price`, intake files, destination country.
- **Derives:** `total` (calculated on save), `status` (`pending` | `completed` when linked vehicle + selling price exist).
- **Does not own:** seller (removed; seller lives on `vehicles.seller_id`).
- **Links:** 1:1 optional `vehicle`; 1:N reservations via `auction_id` / `auction_car_id`.

### `vehicles`

- **Owns:** sellable catalog record, denormalized make/model/chassis, stock lifecycle, seller ownership.
- **Hybrid spec model:** legacy columns (`color`, `transmission`, etc.) + dynamic `vehicle_attribute_values`.
- **Flags:** `is_locked`, `is_ready_for_sale` (synced from auction completion), `stock_status`, `status`.
- **Auto:** `rec_no` on create; make/model backfill from FK on save; country sync from `country_id` / `stock_country`.

### `vehicle_reservations`

- **Owns:** time-bound hold, pricing snapshot (`admin_price`, `seller_price`, `commission_amount`), wallet hold amount.
- **Status:** `active` → `expired` | `converted` | `pending_approval` | `on_hold`.
- **Target:** `vehicle_id` and/or `auction_car_id` (legacy `auction_id` kept in sync).
- **Scope `active()`:** status active **and** `expires_at > now()`.

### `orders`

- **Owns:** commercial contract snapshot (price, shipping, party snapshots, deposit rules).
- **Sources:** `checkout`, `reservation`, `public`, `seller_checkout` (string, not enum-enforced everywhere).
- **Status flow:** `reserved` → `payment_pending` → `paid` → shipment stages → `completed`.
- **Links:** customer (`user_id`), seller, vehicle, reservation, items, payments.

### `payments`

- **Owns:** individual payment attempts/records.
- **Status:** `pending`, `completed`, `failed`, `refunded` (string constants in `PaymentService`).
- **Drives:** `PaymentService::syncOrderPaymentTotals()` — primary order/reservation state machine.

### `invoices`

- **Types:** `reservation`, `partial_payment`.
- **Owns:** PDF path, rendered context in `metadata` (assembled at generation), email timestamp.
- **Reservation invoices:** Context captured at reserve time from best available vehicle/auction/customer data (see [§3](#3-staged-data-entry-workflow)); not blocked on full attribute catalog.
- **Partial invoices:** Numbering uses max `KT-*` suffix scan (reservation zero-padded IDs do not affect sequence).

### `exchange_rates`

- **Owns:** daily USD/JPY rate (`date`, `rate`).
- **Populated by:** `ExchangeRateService` (API fetch + DB cache + latest-rate fallback).
- **Read by:** `AuctionCar` accessor (DB exact-date only), `AdminDashboardService` (SQL join on purchase date).

### `remittances`

Two parallel seller paths feed one admin verification gate. Wallet credit happens **only** on admin verify (`Admin\RemittanceController::verify` → `WalletService::credit` on the row’s `customer_id`). Settlement logic is frozen; claim work added listing, proof separation, and claim-specific reject behavior without changing verify/credit rules.

**Proof files (separate purposes):**

| Column pair | Role |
|-------------|------|
| `deposit_slip_path` / `deposit_slip_name` | **Source proof** — admin PDF import bank slip, or sole proof on seller-submitted / admin-manual rows. Never replaced by seller claim upload. |
| `claim_slip_path` / `claim_slip_name` | **Claim proof** — seller-uploaded file when claiming an `admin_pdf` import. Cleared when admin rejects a claim back to pool. |

**Origin (`origin` column):**

| Value | Created by |
|-------|------------|
| `admin_pdf` | Admin bulk PDF import (`storeBulk`) — incomplete pool row |
| `admin_manual` | Admin manual create (`store`) |
| `seller_submitted` | Seller direct remittance (`Seller\RemittanceController::store`) |

**Claim workflow (implemented):**

```mermaid
flowchart LR
    A[Admin PDF import] --> B[PDF import pool unclaimed]
    B --> C[Seller claims customer + claim proof]
    C --> D[Claim pool claimed-pending]
    D -->|Admin verify| E[Wallet credit to customer]
    D -->|Admin reject claim| B
    S[Seller direct remittance] --> D2[Pending seller submission]
    D2 -->|Admin verify| E
```

1. **Admin PDF import** — Creates `origin = admin_pdf`, `is_complete = false`, `seller_id` null, `status = pending`. Source PDF stored in `deposit_slip_*`.
2. **Seller claim pool** — Sellers see unclaimed imports and claimed-pending rows awaiting admin (`sellerClaimListing` scope). Unclaimed: `seller_id` null, `is_complete = false`. Claimed-pending: `seller_id` + `claimed_at` set, still `status = pending`.
3. **Seller claim** — Atomic claim sets `seller_id`, `customer_id`, `received_by_seller`, `claim_slip_*`, `claimed_at`, `is_complete = true`; `status` stays `pending`. Row **remains in seller claim pool**, not in seller “own remittances” until verified.
4. **Seller direct remittance** — Separate path: `origin = seller_submitted`, proof in `deposit_slip_*`, pending admin verify; appears in seller own list immediately.
5. **Admin UI tabs** — Pending Seller Submissions | Pending Claim Requests | PDF Import Pool | Verified | Rejected. Claim tab shows dual proof links, allocated customer, claiming seller, `claimed_at`.
6. **Admin verify** — Unchanged endpoint; credits **selected `customer_id`** wallet. Claimed `admin_pdf` rows then appear in seller own remittance history (`status = verified`).
7. **Admin reject (claim only)** — When `origin = admin_pdf`, `claimed_at` and `seller_id` set, `status = pending`: clears claim fields and `seller_id` / `customer_id` / `received_by_seller`, deletes claim proof file, sets `is_complete = false`, keeps `status = pending` and **unchanged** `deposit_slip_*` — row returns to available claim pool. Optional rejection note logged; not stored on row for pool reset. **Terminal reject** (`status = rejected`) unchanged for seller-submitted and other non-claim rows.

**Seller listing filters:**

| Seller view | Includes |
|-------------|----------|
| Available to Claim | Unclaimed + claimed-pending `admin_pdf` (`status = pending`) |
| My Submitted Remittances | `seller_submitted` (any status) + verified claimed `admin_pdf` only |

### Related lifecycle entities

| Entity | Role |
|--------|------|
| `wallet_transactions` | Ledger credits/debits; reservation and checkout holds |
| `remittances` / `remittance_allocations` | Incoming funds → wallet credits on admin verify. See [§4 `remittances`](#remittances) for claim workflow, dual proof, and listing rules. |
| `shipments` | Logistics progression tied to order + vehicle |
| `consignees` / `remitters` | Customer party profiles; snapshotted onto orders |
| `bank_accounts` | Seller/admin bank transfer targets |

---

## 5. Vehicle Lifecycle Architecture

### Auction intake flow

1. Admin (or scoped role) creates/updates `auction_cars` with make/model/chassis/costs.
2. Model hooks recalculate `total` and derive `status`.
3. `AuctionCarController::syncAuctionVehicle()` calls `syncMinimalVehicle()` **without seller** — updates existing linked vehicle only; does not create new vehicle without seller.

**Intake → vehicle mapping** is centralized in `AuctionVehicleSyncService::normalizedAttributes()`.

### Minimal vehicle generation triggers

| Trigger | Method | Vehicle state |
|---------|--------|---------------|
| Admin assigns seller | `syncMinimalVehicle($auctionCar, $sellerId)` | `draft`, unlocked |
| Auction row saved (existing vehicle) | `syncMinimalVehicle()` | Preserves existing status flags |
| **Reservation created** (auction-only) | `ensureForReservation()` inside `VehicleReservationService` transaction | `reserved`, locked, invoice-ready |
| Customer reservation checkout GET | `ensureForReservation()` | Same normalization |
| Admin/seller reservation checkout | `ensureForReservation()` | Same normalization |

**Important:** Reservation invoice fires in `afterCommit` — vehicle must exist **before** invoice generation. `ensureForReservation()` runs inside the reservation DB transaction.

### Seller assignment flow

- Admin-only: `AuctionIntakeScope::canAssignSeller()`.
- Writes `vehicles.seller_id`; does **not** write seller on `auction_cars`.
- Creates draft vehicle when make/model/chassis complete.

### Reservation flow

```
reserveForCustomer()
  ├─ validate target (vehicle or auction_car)
  ├─ compute seller_price = base + commission
  ├─ [auction-only] ensureForReservation() → vehicle_id
  ├─ lock vehicle (status=reserved, is_locked=1)
  ├─ create reservation (active, expires_at)
  ├─ optional wallet debit
  └─ afterCommit → reservation invoice (best available data; see [§3](#3-staged-data-entry-workflow))
```

**Pricing base (`admin_price`):**

- Vehicle target: `auctionCar.selling_price ?? vehicle.price`
- Auction target: `auctionCar.selling_price`

### Reservation release / reset flow

| Path | Reservation | Vehicle (unconverted) | Wallet |
|------|-------------|------------------------|--------|
| Admin or seller `release()` | → `expired` | `seller_id` cleared, `status = draft`, unlocked, `is_ready_for_sale = false` | **No refund** |
| Admin or seller `release()` (converted / has order) | → `expired` | → `available`, unlocked (seller retained) | **No refund** |
| Scheduled `reservations:expire` | → `expired` | **Not reset** | No refund |
| `expireStaleForTarget()` (on new reserve) | stale → `expired` | **Not reset** | — |

**Fragmentation:** scheduled expiry and stale-expire paths do not unlock vehicles or clear seller ownership; manual `release()` does (via `VehicleReservationService::release()`).

### Pool visibility logic

**Seller auction pool** (`AuctionPoolController`) shows `auction_cars` where:

- No active non-expired reservation
- No reservation with `order_id`
- No linked `vehicle`

Once reserved, `ensureForReservation()` creates/links a vehicle → auction leaves pool.

**Public catalog** (`VehicleFilterService`, `PublicController`):

- Published listings: `stock_status = available`, status in (`published`, `reserved`)
- `purchase_mode`: `direct_purchase` only when linked auction is `completed`; else inquiry-only (reservation checkout bypasses this)

### Admin / system-owned flow

- Vehicles may have `seller_id = null` until assignment or reservation materialization.
- Admin can reserve/checkout without seller approval gate.
- Admin checkout sets reservation **`converted`** immediately; seller checkout sets **`pending_approval`**.

---

## 6. Invoice & Payment Flow

### Invoice generation lifecycle

| Type | When | Amount | Number format |
|------|------|--------|---------------|
| **Reservation** | Auto after reservation commit | `seller_price ?? admin_price` | Zero-padded reservation ID (`000042`) |
| **Partial payment** | Manual POST on completed payment | `payment.amount` | Global sequential `KT-110001+` (max of existing `KT-*` rows; not affected by reservation IDs like `000042`) |

**Pipeline (`InvoiceService`):**

1. Build context from live reservation/order/vehicle/consignee/settings.
2. Render PDF via **Browsershot** (Headless Chrome) from `resources/views/invoices/pdf.blade.php`.
3. Store under `storage/app/invoices/{Y}/{m}/{number}.pdf`.
4. Persist `invoices` row; email via `CustomerInvoiceMail` if SMTP + customer email configured.

### Partial invoice behavior

- One partial invoice per completed payment (duplicate rejected).
- **Not** auto-generated on payment completion — admin/seller must trigger.
- Does not affect order/payment state.

### Payment → order lifecycle

`PaymentService::syncOrderPaymentTotals()` is the **authoritative payment state machine**:

```
paid_amount = SUM(completed payments)
remaining_balance = order_total - paid_amount

if remaining = 0  → status = paid
elif paid > 0     → status = payment_pending
else              → status = reserved

if first completed payment + reservation_id → reservation = converted
if remaining = 0 → vehicle stock_status/status = sold
```

Seller checkout creates **pending** payments; admin approval completes them → triggers sync.

### Current architectural limitations

| Limitation | Impact |
|------------|--------|
| Invoice `metadata` frozen at creation | Later vehicle attribute edits do not update existing PDFs; reservation invoices may show `-` for specs not yet entered (see [§3](#3-staged-data-entry-workflow)) |
| Reservation invoice requires normalized vehicle | Fixed at generation layer; spec attributes still empty if not on auction car / vehicle attributes |
| Browsershot + hardcoded Windows Chrome paths | PDF generation environment-coupled; DomPDF installed but unused |
| Partial invoice numbering is global `KT-*` | Not order-scoped; sequence derived from max `KT-*` suffix only |
| No invoice on payment unless manual | Operations gap for customer self-serve partial payments |

---

## 7. Analytics & Financial Logic

### Profit calculation (dashboard)

**File:** `AdminDashboardService`

- **Revenue:** `SUM(orders.total_price)` for sold-status orders.
- **Profit per order:** `orders.total_price - (auction_cars.total / (exchange_rates.rate - 3))` when rate > 3 and auction total present; else **0**.
- **Join path:** `COALESCE(vehicles.auction_car_id, vehicle_reservations.auction_car_id, vehicle_reservations.auction_id)`.
- **Rate join:** `DATE(exchange_rates.date) = DATE(auction_cars.purchase_date)`.

Uses **order total**, not `selling_price`. Divisor always `rate - 3` (no 2026-05-06 cutoff).

### Profit / cost (auction intake UI)

**File:** `AuctionCar` accessors

- `exchange_rate`: exact-date DB lookup only (no API fallback in accessor).
- `usd_cost`: `total / divisor` where divisor = `(rate - 3)` before 2026-05-06, else `rate`.
- `profit`: `selling_price - usd_cost`.

`ExchangeRateService::preloadRatesForDates()` populates DB during admin list load; accessor still reads DB only.

### Selling price fallback chains (by context)

| Context | Fallback chain |
|---------|----------------|
| Reservation pricing | `auctionCar.selling_price ?? vehicle.price ?? 0` |
| Vehicle sync | `selling_price ?? total ?? 0` |
| Catalog/filter SQL | `COALESCE(auction_cars.selling_price, vehicles.price)` |
| Customer reservation checkout | reservation price → vehicle discount → auction selling_price → auction total |
| Dashboard profit | **Does not use selling_price** |

### Exchange-rate dependency summary

```mermaid
flowchart TD
  ER[(exchange_rates)]
  ERS[ExchangeRateService]
  AC[AuctionCar accessors]
  ADS[AdminDashboardService]

  ERS -->|API + cache write| ER
  AC -->|exact date read| ER
  ADS -->|SQL join on purchase_date| ER
```

**Inconsistency:** three different rate consumption strategies (API fallback, exact DB, SQL join with fixed `- 3` divisor).

---

## 8. Current Technical Debt / Fragile Areas

### Duplicated lifecycle logic

- Vehicle materialization previously duplicated across checkout controllers; now centralized in `AuctionVehicleSyncService`, but **reservation locking** still split between `VehicleReservationService` and checkout controllers.
- Order creation paths: `CustomerPurchaseService` (primary), `OrderService` (seller legacy), inline controller logic in admin/seller checkout.
- Release/reset: admin vs seller vs scheduler behave differently.

### Normalization concerns

- `Vehicle` uses FK + denormalized strings + dynamic attributes + legacy columns — invoice reads mostly denormalized/legacy paths.
- `auction_id` vs `auction_car_id` on reservations (dual columns kept in sync manually).
- `Vehicle::getPriceAttribute()` overrides stored price when auction `selling_price` exists — can surprise reporting.

### Invoice snapshot concerns

- Reservation invoice context is stored in `metadata` at generation; optional vehicle specs may be incomplete by design (see [§3](#3-staged-data-entry-workflow)).
- Partial-payment and re-download paths should be verified to ensure they read persisted metadata rather than recomputing from live entities.
- No immutable party/vehicle snapshot at invoice time beyond what's stored in `metadata` and linked IDs.

### Fragmented transitions

- Reservation conversion triggered by: admin checkout, payment sync, seller mark-paid, full-payment vehicle loop — overlapping paths in `PaymentService`.
- Seller checkout approval vs payment completion ordering relies on admin action.
- `VehicleReservationService::reserve()` legacy method unused; `convertToOrder()` rarely called directly.

### Other fragile areas

- Wallet debits on reservation release are **not reversed**.
- `reservations:expire` command expires rows but leaves vehicles locked/reserved.
- `OrderService::backfillHybridFields()` defined but unused.
- Invoice PDF depends on Browsershot/Chrome availability.
- Dashboard profit returns 0 silently when join data missing ( diverges from intake UI profit).

---

## 9. Recommended Future Refactor Directions

### Service centralization

| Target | Recommendation |
|--------|----------------|
| Vehicle materialization | Single entry: `AuctionVehicleSyncService` (in progress) |
| Reservation lifecycle | Expand `VehicleReservationService` to own lock/unlock/convert/expiry side effects |
| Order creation | Route all paths through one factory (likely `CustomerPurchaseService` + thin adapters) |
| Checkout | Shared checkout orchestrator for admin/seller/customer variance (approval gate only) |

### Lifecycle consistency

- Unified **release/expiry handler**: reservation expired → unlock vehicle → optional wallet credit policy.
- Single reservation conversion function called from payment sync and checkout.
- Deprecate `auction_id` column after migration verification.

### Snapshot architecture

- Store invoice context JSON at creation (vehicle, consignee, transport, amounts) and render PDF from snapshot.
- Order party fields already partially snapshotted — extend same pattern to invoices.

### Financial consistency

- Extract shared `ProfitCalculator` with explicit policy: revenue base, cost base, rate divisor, cutoff date.
- Align `AdminDashboardService` with `AuctionCar` accessor rules or document intentional divergence.

### Maintainability

- Remove unused `OrderService` / `VehicleReservationService::reserve()` or mark `@deprecated` with callers grep-enforced.
- Replace DomPDF dependency or document Browsershot as required infra.
- Add integration tests around: auction-only reserve → invoice vehicle fields, payment sync → conversion, seller checkout approval.

### Access control (future)

- Implement admin **role/scope management** UI before expanding staff roles — module, action, and field/section scopes (see [§2](#2-role-responsibilities-and-access-scope)).
- Migrate from hard-coded `users.role` + `AuctionIntakeScope` checks to configurable scope resolution; keep customer-facing modules frozen.
- Wire **Data Entry** section scopes through middleware/`AccessResolver` dual enforcement (grants exist in config; routes still use `role:data_entry` only).

**Phase 1A (complete, read-only):** `AccessResolver` + `config/permissions.php` mirror current role grants from [`docs/permission-scope-catalog.md`](permission-scope-catalog.md). **Not enforced** in middleware or controllers yet. Active `data_entry` grants include vehicle view/update (enrichment sections only) and read-only auction intake view.

**Data Entry role foundation (complete):** `users.role = data_entry`, `data-entry/*` route group, `DataEntryLayout`, dashboard, read-only auction queue, vehicle catalog read + restricted edit (Phase B2). User creation supports `data_entry` via `StoreManagedUserRequest`.

**Remittance claim workflow (complete):** Dual proof model, seller claim pool, admin claim-review tabs, reject-to-pool — see [§4 `remittances`](#remittances). Wallet verify/credit path unchanged.

---

## Quick Reference — Status Constants

```
AuctionCar:       pending | completed
Vehicle:          draft | published | reserved | sold  (+ stock_status: available | sold)
Reservation:      active | expired | converted | pending_approval | on_hold
Order:            reserved | payment_pending | paid | … shipment … | completed | cancelled
Payment:          pending | completed | failed | refunded
Invoice:          reservation | partial_payment
```

---

## Related Docs

- [`docs/system-blueprint.md`](system-blueprint.md) — full schema appendix
- [`docs/VEHICLE_AUCTION_ARCHITECTURE_ANALYSIS.md`](VEHICLE_AUCTION_ARCHITECTURE_ANALYSIS.md) — column usage audit
- [`docs/shipment_handling_flow.md`](shipment_handling_flow.md) — shipment progression

*Last aligned to codebase behavior including §2 role responsibilities/access scope (target model), staged data-entry workflow with implemented Data Entry role (read-only auction queue + Phase B2 vehicle enrichment edit), completed remittance claim workflow (dual proof, admin claim tabs, reject-to-pool), centralized `AuctionVehicleSyncService::ensureForReservation()`, unified `VehicleReservationService::release()`, and `KT-*` partial invoice numbering.*
