# System Architecture

**Document type:** Technical architecture blueprint  
**Audience:** Engineers rebuilding, extending, or operating the platform  
**Scope:** As implemented today — not a user manual  
**Companion docs:** `PROJECT_MASTER_DOCUMENTATION.md`, `docs/CURSOR_DEVELOPER_CONTEXT.md`, `docs/ARCHITECTURE.md`

---

## Table of Contents

1. [High Level Architecture](#high-level-architecture)
2. [Core Domains](#core-domains)
3. [Service Layer Architecture](#service-layer-architecture)
4. [Database Architecture](#database-architecture)
5. [Event & Lifecycle Flows](#event--lifecycle-flows)
6. [Permission Architecture](#permission-architecture)
7. [Financial Architecture](#financial-architecture)
8. [Exchange Rate Architecture](#exchange-rate-architecture)
9. [Deployment Architecture](#deployment-architecture)
10. [Rebuild Recommendations](#rebuild-recommendations)

---

# High Level Architecture

## Platform Shape

Monolithic **Laravel 12** application serving a **Vue 3 + Inertia** SPA. HTTP requests flow through middleware (auth, role, optional scope enforcement), controllers delegate to **domain services**, services mutate **Eloquent models** and return DTOs/redirects for Inertia.

There is no separate microservice tier. Background work is limited to Artisan commands (e.g. reservation expiry) and queue-ready infrastructure (Laravel `jobs` table) where used.

## Commercial Pipeline

The platform's value chain is a **linear inventory-to-cash pipeline** with parallel **settlement** (wallet/remittance) and **documentation** (invoice) paths.

```mermaid
flowchart LR
    subgraph Sourcing
        AI[Auction Intake]
    end

    subgraph Inventory
        V[Vehicles]
    end

    subgraph Commercial
        R[Reservations]
        O[Orders]
    end

    subgraph Settlement
        P[Payments]
        W[Wallet]
        RM[Remittances]
    end

    subgraph Documentation
        INV[Invoices]
    end

    subgraph Fulfillment
        SH[Shipments]
    end

    AI --> V
    V --> R
    R --> O
    O --> P
    P --> W
    RM --> W
    P --> INV
    O --> SH
```

## Layered View

```mermaid
flowchart TB
    subgraph Presentation
        VUE[Vue 3 Pages / Components]
        INERTIA[Inertia.js]
    end

    subgraph Application
        ROUTES[routes/web.php + api.php]
        MW[Middleware: auth, role, scope, user.active]
        CTRL[Controllers per role prefix]
        FR[Form Requests]
    end

    subgraph Domain
        SVC[Services]
        POL[Policies]
        SUPPORT[Support: AuctionIntakeScope, RoleScopePolicy]
    end

    subgraph Data
        MODELS[Eloquent Models]
        DB[(MySQL / SQLite)]
        DISK[Local / S3 Storage]
    end

    subgraph External
        FX[exchangerate.host API]
        SMTP[Mail / SMTP]
        CHROME[Chrome via Browsershot]
        GATEWAY[Payment Gateway Webhook]
    end

    VUE <--> INERTIA
    INERTIA <--> ROUTES
    ROUTES --> MW --> CTRL
    CTRL --> FR
    CTRL --> SVC
    SVC --> POL
    SVC --> MODELS
    MODELS --> DB
    SVC --> DISK
    SVC --> FX
    SVC --> SMTP
    SVC --> CHROME
    GATEWAY --> CTRL
```

## Role Boundaries (Route Prefixes)

| Role | Prefix | Primary domains touched |
|------|--------|-------------------------|
| Admin | `/admin` | All domains |
| Seller | `/seller` | Vehicles (own), Reservations, Orders, Customers, Remittances, Invoices |
| Customer | `/customer` + public | Orders, Payments, Wallet (read), Invoices (download) |
| Bidder | `/bidder` | Auction Intake (stage 1) |
| Shipping Agent | `/shipping-agent` | Auction Intake (stage 2) |
| Data Entry | `/data-entry` | Vehicles (enrichment), Master Data, Auction (read-only) |
| Public | `/` | Vehicles (published listings) |

## Staged Auction Intake (Cross-Cutting)

Auction intake is a **multi-actor pipeline** on one `auction_cars` row before inventory is commercially active:

```mermaid
flowchart LR
    B[Bidder<br/>identity + base cost] --> SA[Shipping Agent<br/>logistics + selling_price]
    SA --> AD[Admin<br/>assign seller]
    AD --> V[Vehicle materialized]
    V --> DE[Data Entry<br/>catalog enrichment]
```

Field-level enforcement: `App\Support\AuctionIntakeScope` enum + batch grant enforcer.

---

# Core Domains

## 1. Vehicle Domain

### Purpose

Represents **sellable inventory** exposed to customers and managed by sellers/admins. May be created directly or **materialized from an auction car** (1:1 optional link).

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `Vehicle` | `vehicles` | Listing record: make/model, price, status, seller, lock flags |
| `VehicleImage` | `vehicle_images` | Gallery |
| `VehicleDocument` | `vehicle_documents` | Per-vehicle files |
| `VehicleAttributeValue` | `vehicle_attribute_values` | Dynamic specs |
| `Make`, `VehicleModel` | `makes`, `models` | Catalog hierarchy |

### Business Rules

- `status`: `draft` → `published` → `reserved` → `sold`
- `stock_status`: `available` | `sold`
- `is_locked`: set during active reservation
- Public visibility: `published` or `reserved` with `stock_status = available`
- Price accessor prefers linked `AuctionCar.selling_price` when present
- Sold when `stock_status = sold` OR paid order exists
- Release (non-converted, no order) clears `seller_id`, sets `draft`

### Relationships

```mermaid
erDiagram
    MAKES ||--o{ MODELS : has
    MAKES ||--o{ VEHICLES : classifies
    MODELS ||--o{ VEHICLES : classifies
    AUCTION_CARS ||--o| VEHICLES : materializes
    USERS ||--o{ VEHICLES : sells
    VEHICLES ||--o{ VEHICLE_IMAGES : has
    VEHICLES ||--o{ VEHICLE_DOCUMENTS : has
    VEHICLES ||--o{ VEHICLE_ATTRIBUTE_VALUES : specs
    VEHICLES ||--o{ VEHICLE_RESERVATIONS : held_by
    VEHICLES ||--o{ ORDER_ITEMS : sold_via
```

---

## 2. Auction Domain

### Purpose

**Sourcing queue** — captures JPY cost build-up, logistics, and selling price before/during vehicle materialization.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `AuctionCar` | `auction_cars` | Intake row: costs, dates, files, selling_price |
| `AuctionHouse` | `auction_houses` | Source reference, storage pricing |
| `ExchangeRate` | `exchange_rates` | USD/JPY by purchase date (lookup, not stored on car) |

### Business Rules

- DB status: `pending` | `completed` (auto-derived when vehicle linked + `selling_price` set)
- `total`: sum of JPY cost fields + 10% surcharge on selected fees
- `usd_cost` accessor: `total / divisor` (divisor = `rate - 3` if purchase before 2026-05-06, else `rate`)
- Seller assignment (admin-only) triggers `AuctionVehicleSyncService::syncMinimalVehicle`
- UI operational status (`available`, `reserved`, `in_process`, `sold`) derived from reservations/orders/payments

### Relationships

```mermaid
erDiagram
    AUCTION_HOUSES ||--o{ AUCTION_CARS : sources
    REGIONS ||--o{ AUCTION_CARS : location
    COUNTRIES ||--o{ AUCTION_CARS : destination
    USERS ||--o{ AUCTION_CARS : assigned_seller
    AUCTION_CARS ||--o| VEHICLES : links
    EXCHANGE_RATES }o--|| AUCTION_CARS : purchase_date_lookup
```

---

## 3. Reservation Domain

### Purpose

**Temporal hold** on stock for a specific customer before financial checkout. Bridges inventory and orders.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `VehicleReservation` | `vehicle_reservations` | Hold record with pricing snapshot |

### Status Machine

| Status | Meaning |
|--------|---------|
| `active` | Hold in force |
| `expired` | Released or TTL passed |
| `converted` | Checkout completed; order linked |
| `pending_approval` | Seller checkout submitted |
| `on_hold` | Admin paused approval |

### Business Rules

- Unified path: `reserveForCustomer()` — locks vehicle, optional wallet debit, auto reservation invoice
- Legacy path: `reserve()` — auction-only, no auto invoice
- Duplicate guard: one active/converted/pending_approval per target
- Seller pricing: `seller_price = base + commission`
- Expiry: `reservation_expiry_days` setting (default 3)
- `release()` unlocks vehicle; no wallet refund of debits
- Cron `reservations:expire` marks expired only (does not full release)

### Relationships

```mermaid
erDiagram
    VEHICLE_RESERVATIONS }o--|| USERS : customer
    VEHICLE_RESERVATIONS }o--|| USERS : seller
    VEHICLE_RESERVATIONS }o--o| VEHICLES : targets
    VEHICLE_RESERVATIONS }o--o| AUCTION_CARS : targets
    VEHICLE_RESERVATIONS ||--o| ORDERS : converts_to
    VEHICLE_RESERVATIONS ||--o{ INVOICES : reservation_type
```

---

## 4. Order Domain

### Purpose

**Purchase contract** after checkout — financial snapshot, consignee/remitter, CIF components, shipment linkage.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `Order` | `orders` | Header: totals, status, parties, ports |
| `OrderItem` | `order_items` | Line items (typically one vehicle) |
| `Shipment` | `shipments` | Logistics milestones per order/vehicle |

### Status Flow

```
reserved → payment_pending → paid → shipment_pending → shipped → in_transit → arrived → delivered → completed
```

(`cancelled` supported via resolution logic.)

### Business Rules

- Sources: `admin_reservation_checkout`, `seller_checkout`, `reservation`, `checkout`, customer purchase
- `paid_amount` / `remaining_balance` synced by `PaymentService`
- Consignee/remitter fields snapshotted on order
- CIF: vehicle price + freight + inspection + insurance
- Shipment status flow drives post-payment milestones

### Relationships

```mermaid
erDiagram
    ORDERS }o--|| USERS : customer
    ORDERS }o--o| USERS : seller
    ORDERS }o--o| VEHICLE_RESERVATIONS : from
    ORDERS ||--o{ ORDER_ITEMS : contains
    ORDERS ||--o{ PAYMENTS : paid_by
    ORDERS ||--o{ SHIPMENTS : fulfilled_by
    ORDER_ITEMS }o--|| VEHICLES : references
```

---

## 5. Payment Domain

### Purpose

**Payment ledger** for orders — records intent, completion, allocation to vehicles, and gateway reconciliation.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `Payment` | `payments` | Amount, method, status, proof, `payment_data` JSON |
| `PaymentAllocation` | `payment_allocations` | Payment → vehicle amount |
| `PaymentHistory` | `payment_histories` | Audit trail |

### Statuses

`pending` | `completed` | `failed` | `refunded`

### Business Rules

- Methods normalized: `wallet`, `stripe`/`card`, `bank`/`bank_transfer`
- Seller checkout: payments start `pending`, `allocate_on_create = false`
- Admin checkout: payments `completed` immediately
- `syncOrderPaymentTotals()` drives order status and reservation conversion
- First `completed` payment converts reservation, assigns vehicle `seller_id`
- Full payment marks vehicles sold
- Admin `markPaid` sets `approved_by` and completes payment
- Webhook: `POST /webhooks/payments` (public)

### Relationships

```mermaid
erDiagram
    PAYMENTS }o--|| ORDERS : for
    PAYMENTS ||--o{ PAYMENT_ALLOCATIONS : splits
    PAYMENTS }o--o| WALLET_TRANSACTIONS : wallet_ref_in_json
    PAYMENTS ||--o{ REMITTANCE_ALLOCATIONS : funded_by
    PAYMENTS ||--o{ INVOICES : partial_type
```

---

## 6. Wallet Domain

### Purpose

**Customer prepaid balance** ledger — credits from verified remittances, debits for reservations/checkout, FIFO linkage to remittance sources.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `WalletTransaction` | `wallet_transactions` | Credit/debit rows (no separate balance column) |
| `RemittanceAllocation` | `remittance_allocations` | Links wallet debit to remittance credit and payment |

### Business Rules

- Balance = sum(credits) − sum(debits) — computed, not stored
- Credit sources: `remittance`, `manual`
- Debit sources: `reservation`, `checkout`, `manual`
- Debit enforces sufficient balance (throws validation exception)
- FIFO: on debit, consumes oldest remittance credits first
- Payment completion attaches allocations via `wallet_transaction_id` in `payment_data`

### Relationships

```mermaid
erDiagram
    WALLET_TRANSACTIONS }o--|| USERS : belongs_to
    WALLET_TRANSACTIONS ||--o{ REMITTANCE_ALLOCATIONS : credit_side
    WALLET_TRANSACTIONS ||--o{ REMITTANCE_ALLOCATIONS : debit_side
    REMITTANCE_ALLOCATIONS }o--o| REMITTANCES : sources
    REMITTANCE_ALLOCATIONS }o--o| PAYMENTS : settles
    REMITTANCE_ALLOCATIONS }o--o| ORDERS : applies_to
```

---

## 7. Remittance Domain

### Purpose

Records **funds received** from customers (seller-submitted or admin-imported), verified by admin, crediting customer wallets.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `Remittance` | `remittances` | Amount, rates, slips, origin, claim state |

### Statuses & Origins

| Status | `pending`, `verified`, `rejected` |
| Origin | `seller_submitted`, `admin_manual`, `admin_pdf` |

### Business Rules

- `funds_received_usd = conversion / average_rate`
- Complete when: `seller_id` + `customer_id` + `received_by_seller`
- Verify (admin) → idempotent wallet credit via `WalletService`
- Admin PDF imports enter **claim pool**; sellers claim with customer + claim slip
- Reject claim returns remittance to pool
- Seller delete: own `seller_submitted` + `pending` + no credit/allocations only (`SellerRemittanceDeletePolicy`)

### Relationships

```mermaid
erDiagram
    REMITTANCES }o--o| USERS : seller
    REMITTANCES }o--o| USERS : customer
    REMITTANCES }o--o| USERS : verified_by
    REMITTANCES ||--o{ WALLET_TRANSACTIONS : credits_on_verify
    REMITTANCES ||--o{ REMITTANCE_ALLOCATIONS : traced
```

---

## 8. Invoice Domain

### Purpose

**PDF documentation** for reservation charges and partial payments — stored, emailable, downloadable.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `Invoice` | `invoices` | Type, number, PDF path, email timestamp |

### Types

| Type | Trigger |
|------|---------|
| `reservation` | Auto after `reserveForCustomer` (idempotent) |
| `partial_payment` | Manual admin/seller on completed payment |

### Business Rules

- PDF via Browsershot + `resources/views/invoices/pdf.blade.php`
- Storage: `invoices/Y/m/{invoice_number}.pdf` on `local` disk
- Email via `CustomerInvoiceMail` when SMTP configured
- Reservation number: zero-padded reservation ID
- Partial number: `KT-{n}` sequential (min 110001)
- Download regenerates PDF from stored metadata

### Relationships

```mermaid
erDiagram
    INVOICES }o--|| USERS : customer
    INVOICES }o--o| VEHICLE_RESERVATIONS : reservation_type
    INVOICES }o--o| PAYMENTS : partial_type
    INVOICES }o--o| ORDERS : context
```

---

## 9. Access Control Domain

### Purpose

**Scope-based authorization** for staff roles — module/action/section grants with per-user overrides.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `RoleScopeGrant` | `role_scope_grants` | Role template scopes |
| `RoleScopeGrantAudit` | `role_scope_grant_audits` | Template change history |
| `UserScopeOverride` | `user_scope_overrides` | Per-user grant/revoke |
| `UserScopeOverrideAudit` | `user_scope_override_audits` | Override change history |

### Config Artifacts

- `config/permissions.php` — scope catalog, defaults, route maps
- `config/scope_manifest.php` — UI metadata, dependencies, risk

### Business Rules

- Admin: all scopes, bypasses middleware
- Customer: `role:customer` only; no scope middleware; no overrides
- Editable staff: seller, bidder, shipping_agent, data_entry
- Locked scopes cannot be granted (financial, lifecycle, settings)
- Override-only: `checkout.bypass_approval`, `payments.bypass_approval` (seller, per-user)
- Enforcement: `Enforce*ScopeMiddleware` when env flags true
- Runtime: `EffectiveScopeGrantResolver::hasEffectiveGrant()`

### Relationships

```mermaid
erDiagram
    ROLE_SCOPE_GRANTS }o--|| USERS : role_slug_logical
    USER_SCOPE_OVERRIDES }o--|| USERS : target_user
    USER_SCOPE_OVERRIDES }o--o| USERS : created_by
```

---

## 10. User Management Domain

### Purpose

**Identity and account lifecycle** — Jetstream auth, single role per user, admin-managed staff/customer accounts, active/disabled state.

### Key Entities

| Entity | Table | Role |
|--------|-------|------|
| `User` | `users` | Identity, role, seller adjustment, active status |
| `Team` | `teams` | Jetstream personal teams on create |

### Business Rules

- Roles: `admin`, `seller`, `customer`, `bidder`, `shipping_agent`, `data_entry`
- Admin UI manages all except `admin` role
- `created_by` set on store; not rewritten on edit
- `is_active` / `disabled_at` / `disabled_by` via `UserAccountStatusService`
- Disabled: blocked at login + logged out on next request
- Seller `exchange_rate_adjustment`: admin intake display only

### Relationships

```mermaid
erDiagram
    USERS ||--o{ TEAMS : owns
    USERS ||--o{ USERS : created_by
    USERS ||--o{ USERS : disabled_by
```

---

# Service Layer Architecture

Services own **multi-step transactions**, **cross-entity rules**, and **side effects**. Controllers should remain thin orchestrators.

```mermaid
flowchart TB
    CTRL[Controller] --> VRS[VehicleReservationService]
    CTRL --> AVS[AuctionVehicleSyncService]
    CTRL --> CAS[CheckoutApprovalService]
    CTRL --> PS[PaymentService]
    CTRL --> WS[WalletService]
    CTRL --> IS[InvoiceService]
    CTRL --> ERS[ExchangeRateService]
    CTRL --> ESG[EffectiveScopeGrantResolver]

    VRS --> AVS
    VRS --> IS
    VRS --> WS
    CAS --> PS
    PS --> WS
    PS --> VRS
```

---

## VehicleReservationService

**File:** `app/Services/VehicleReservationService.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `reserve()` | `AuctionCar`, customer `User`, seller `User`, wallet amount | `VehicleReservation` (legacy auction-only path) |
| `reserveForCustomer()` | Array: vehicle/auction IDs, users, pricing, flags | `VehicleReservation`; locks vehicle; optional wallet debit; triggers reservation invoice after commit |
| `release()` | `VehicleReservation` | Status → `expired`; unlocks vehicle; clears seller on draft path |
| `resolveOperationalVehicle()` | Reservation, optional seller price | `Vehicle` (materialize if needed) |
| `convertToOrder()` | `VehicleReservation` | Marks reservation converted |
| `lockVehicleForReservation()` | `Vehicle` | Sets reserved + locked flags |

**Responsibilities:** Reservation lifecycle, vehicle locking, pricing snapshot, wallet debit at reserve, invoice trigger, vehicle materialization delegation.

**Dependencies:** `AuctionVehicleSyncService`, `InvoiceService`, `WalletService`, settings.

---

## AuctionVehicleSyncService

**File:** `app/Services/AuctionVehicleSyncService.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `syncMinimalVehicle()` | `AuctionCar`, optional `sellerId`, `assignSellerId` flag | `Vehicle` or null — draft vehicle when seller assigned |
| `ensureForReservation()` | `AuctionCar`, `sellerId`, optional `sellerPrice` | `Vehicle` — reserved, locked, priced for checkout |

**Responsibilities:** 1:1 auction→vehicle mapping; catalog field normalization; seller assignment side effects.

**Does not:** Publish vehicles, handle payments, or manage reservations directly.

---

## CheckoutApprovalService

**File:** `app/Services/CheckoutApprovalService.php`

| Constant | Value |
|----------|-------|
| `BYPASS_SCOPE_CHECKOUT` | `checkout.bypass_approval` |
| `BYPASS_SCOPE_PAYMENTS` | `payments.bypass_approval` |

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `approvePendingCheckout()` | Reservation, approver `User` | Completes pending payments; reservation → `converted` |
| `approvePendingCheckoutWithBypass()` | Reservation, seller `User`, audit context | Same as approve; used for bypass scope |
| `completePaymentsWithBypass()` | Payment IDs, `Order`, approver, audit context | Marks payments completed with metadata |
| `completePendingOrderPayments()` | `Order`, approver, optional audit | Completes all pending payments on order |

**Responsibilities:** Seller checkout approval gate, bypass auto-completion, row-level locking, audit notes in `payment_data` and order notes.

**Dependencies:** `PaymentService`.

---

## PaymentService

**File:** `app/Services/PaymentService.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `createPayment()` | order ID, amount, method, payment data | Payment array; allocates if completed |
| `updatePaymentStatus()` | `Payment`, status | Updates payment; on completed → allocate + sync order |
| `allocatePayment()` | `Payment` | `PaymentAllocation` to vehicle |
| `syncOrderPaymentTotals()` | `Order` | Recalculates paid/remaining; updates order status; converts reservation; marks vehicles sold |
| `getPaymentSummary()` | order ID | Summary array |
| `getCustomerPaymentSummary()` | user ID | Cross-order customer summary |
| `processRefund()` | `Payment`, amount, reason | Refund payment row |
| `reconcileGatewayResponse()` | `Payment`, gateway payload | Gateway reconciliation |

**Responsibilities:** Payment state machine, order financial sync, reservation conversion trigger, remittance allocation attachment on wallet payments.

**Dependencies:** `WalletService` (indirect via payment_data), `VehicleReservationService` (conversion side effect).

---

## WalletService

**File:** `app/Services/WalletService.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `getBalance()` | user ID | Float balance (computed) |
| `credit()` | user ID, amount, source, reference, createdBy, metadata | `WalletTransaction` credit |
| `debit()` | user ID, amount, source, reference, createdBy, metadata | `WalletTransaction` debit; FIFO `RemittanceAllocation` rows |

**Responsibilities:** Ledger integrity, insufficient-funds guard, FIFO remittance consumption on debit.

**Does not:** Verify remittances (caller credits after verify).

---

## InvoiceService

**File:** `app/Services/InvoiceService.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `createAndSendReservationInvoice()` | `VehicleReservation`, optional createdBy | `Invoice` or existing (idempotent); PDF; email |
| `createAndSendPartialPaymentInvoice()` | `Payment`, createdBy | `Invoice`; PDF; email |
| `downloadResponse()` | `Invoice` | HTTP download response (regenerates PDF) |
| `formatInvoice()` | `Invoice` | Display array for UI |

**Responsibilities:** Invoice numbering, PDF render (Browsershot), storage, SMTP delivery, settings-driven company/bank context.

**Dependencies:** `MailSettingsService`, `Setting` model, Chrome/Browsershot.

---

## ExchangeRateService

**File:** `app/Services/ExchangeRateService.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `getRateByDate()` | date | Float rate or null; DB → API → `insertOrIgnore` |
| `preloadRatesForDates()` | iterable dates | Bulk prefetch into memory cache + DB |

**Responsibilities:** USD/JPY historical rates; in-request memory cache; external API fallback; never overwrites existing DB rows.

**Config:** `config/services.php` → `exchangerate_host`.

---

## EffectiveScopeGrantResolver

**File:** `app/Services/EffectiveScopeGrantResolver.php`

| Method | Inputs | Outputs / Effects |
|--------|--------|-------------------|
| `effectiveGrantsForRole()` | role slug | List of scope keys |
| `hasEffectiveGrant()` | `Authenticatable` user, scope key | Boolean |

**Responsibilities:** Runtime permission resolution — admin bypass, role grants (DB or config), user overrides (grant/revoke), customer config-only.

**Dependencies:** `RoleScopeGrantService`, `UserScopeOverrideService`, `PermissionCatalog`, `RoleScopePolicy`.

---

# Database Architecture

Tables grouped by domain. Relationships described; no column-level dumps.

## Identity & Access

| Table | Purpose |
|-------|---------|
| `users` | All personas; role; seller adjustment; active status; `created_by` |
| `teams`, `team_user`, `team_invitations` | Jetstream teams |
| `personal_access_tokens` | Sanctum API tokens |
| `role_scope_grants` | Persisted role permission templates |
| `role_scope_grant_audits` | Role grant change audit |
| `user_scope_overrides` | Per-user grant/revoke |
| `user_scope_override_audits` | Override change audit |

**Hub:** `users.id` referenced across all commercial domains.

## Catalog & Master Data

| Table | Purpose |
|-------|---------|
| `makes`, `models` | Vehicle taxonomy |
| `attributes`, `attribute_values` | Dynamic spec definitions |
| `model_attributes`, `make_attributes` | Default spec mappings |
| `countries`, `regions`, `ports` | Geography |
| `auction_houses` | Auction sources |

## Sourcing & Inventory

| Table | Purpose |
|-------|---------|
| `auction_cars` | Intake queue (1:1 optional → vehicle) |
| `vehicles` | Sellable listings |
| `vehicle_images`, `vehicle_documents` | Media |
| `vehicle_attribute_values` | Instance specs |
| `exchange_rates` | Daily USD/JPY rates |

```mermaid
erDiagram
    AUCTION_CARS ||--o| VEHICLES : auction_car_id
    VEHICLES }o--|| MAKES : make_id
    VEHICLES }o--|| MODELS : model_id
```

## Commercial

| Table | Purpose |
|-------|---------|
| `vehicle_reservations` | Holds pre-checkout |
| `orders` | Purchase contracts |
| `order_items` | Line items |
| `shipments` | Logistics milestones |
| `consignees`, `remitters` | Party profiles |

```mermaid
erDiagram
    VEHICLE_RESERVATIONS ||--o| ORDERS : order_id
    ORDERS ||--o{ ORDER_ITEMS : has
    ORDERS ||--o{ SHIPMENTS : has
```

## Financial

| Table | Purpose |
|-------|---------|
| `payments` | Order payment ledger |
| `payment_allocations` | Payment → vehicle splits |
| `payment_histories` | Payment audit |
| `wallet_transactions` | Customer wallet ledger |
| `remittances` | Incoming funds records |
| `remittance_allocations` | Wallet FIFO traceability |
| `invoices` | Generated PDF records |

```mermaid
erDiagram
    ORDERS ||--o{ PAYMENTS : has
    PAYMENTS ||--o{ PAYMENT_ALLOCATIONS : allocates
    USERS ||--o{ WALLET_TRANSACTIONS : owns
    REMITTANCES ||--o{ WALLET_TRANSACTIONS : credit_ref
    WALLET_TRANSACTIONS ||--o{ REMITTANCE_ALLOCATIONS : links
```

## Configuration

| Table | Purpose |
|-------|---------|
| `settings` | Key-value platform config |
| `bank_accounts`, `deposit_settings` | Payment config |
| `pricing_rules`, `shipping_rates`, `shipping_countries` | Pricing/shipping |
| `cache`, `jobs` | Laravel infrastructure |

## Referential Integrity Patterns

- Financial tables use **nullable FKs with `nullOnDelete`** for actor references (`created_by`, `approved_by`, `verified_by`)
- Wallet balance is **never denormalized** — always derived from `wallet_transactions`
- Order totals (`paid_amount`, `remaining_balance`) are **synced** by `PaymentService`, not user-edited
- Auction `exchange_rate` is **not stored** on `auction_cars` — computed at read time

---

# Event & Lifecycle Flows

## Vehicle Lifecycle

```mermaid
stateDiagram-v2
    [*] --> AuctionPending: AuctionCar created
    AuctionPending --> AuctionCompleted: selling_price + vehicle linked
    AuctionCompleted --> VehicleDraft: Seller assigned / synced
    VehicleDraft --> VehiclePublished: Published
    VehiclePublished --> VehicleReserved: Reservation active
    VehicleReserved --> OrderCreated: Checkout
    OrderCreated --> PaymentPending: Partial/zero payment
    PaymentPending --> Paid: remaining_balance = 0
    Paid --> VehicleSold: stock_status sold
    VehicleSold --> Shipped: Shipment milestones
    Shipped --> Completed: delivered/completed
    VehicleReserved --> VehicleDraft: Release (no order)
```

| Stage | Primary mutator |
|-------|-----------------|
| Auction → Vehicle | `AuctionVehicleSyncService` |
| Reserve | `VehicleReservationService::lockVehicleForReservation` |
| Checkout | Admin/Seller/Customer checkout controllers |
| Sold | `PaymentService::syncOrderPaymentTotals` |
| Shipment | `Admin\ShipmentController`, `Seller` order updates |

---

## Reservation Lifecycle

```mermaid
stateDiagram-v2
    [*] --> active: reserveForCustomer / reserve
    active --> expired: release() / cron expiry
    active --> pending_approval: seller checkout
    pending_approval --> converted: admin approve / bypass
    pending_approval --> on_hold: admin hold
    pending_approval --> active: admin decline
    on_hold --> pending_approval: admin release hold
    converted --> [*]: terminal (order exists)
    expired --> [*]
```

| Transition | Service / Controller |
|------------|---------------------|
| Create | `VehicleReservationService` |
| Release | `VehicleReservationService::release` |
| Seller checkout | `SellerCheckoutController` → `pending_approval` |
| Approve | `CheckoutApprovalService` |
| Decline | `CheckoutApprovalController` + service |

---

## Checkout Lifecycle

```mermaid
sequenceDiagram
    participant S as Seller/Admin
    participant C as CheckoutController
    participant VRS as VehicleReservationService
    participant PS as PaymentService
    participant CAS as CheckoutApprovalService

    S->>C: checkout(reservation)
    C->>VRS: resolveOperationalVehicle
    C->>PS: createPayment (pending or completed)
    alt Seller path (no bypass)
        C->>VRS: status pending_approval
        Note over S: Awaits admin
        CAS->>PS: complete pending payments
        CAS->>VRS: converted
    else Admin path / bypass
        CAS->>PS: complete immediately
        CAS->>VRS: converted
    end
```

| Path | Payment initial status | Reservation end state |
|------|------------------------|----------------------|
| Admin checkout | `completed` | `converted` |
| Seller checkout | `pending` | `pending_approval` |
| Seller + `checkout.bypass_approval` | `completed` (after create) | `converted` |
| Customer CIF checkout | gateway-dependent | N/A (may skip reservation) |

---

## Payment Lifecycle

```mermaid
stateDiagram-v2
    [*] --> pending: createPayment
    pending --> completed: markPaid / approve / bypass / gateway
    pending --> failed: decline / gateway fail
    completed --> refunded: processRefund
    completed --> [*]: allocations synced
```

**Side effects on `completed`:**

1. `allocatePayment()` → `payment_allocations`
2. Attach remittance allocations (wallet payments)
3. `syncOrderPaymentTotals()` → order status, reservation conversion, vehicle sold

---

## Remittance Lifecycle

```mermaid
stateDiagram-v2
    [*] --> pending: seller submit / admin create / PDF import
    pending --> verified: admin verify → wallet credit
    pending --> rejected: admin reject
    pending --> pending: seller claim (pool)
    rejected --> [*]
    verified --> [*]
```

| Origin | Initial state | Pool? |
|--------|---------------|-------|
| `seller_submitted` | pending, seller set | No |
| `admin_manual` | pending or verified on create | No |
| `admin_pdf` | pending, incomplete | Yes — claimable |

---

## Invoice Lifecycle

```mermaid
flowchart LR
    R[Reservation created] --> RI[createAndSendReservationInvoice]
    RI --> PDF[PDF stored]
    PDF --> EM[Email if SMTP]
    P[Payment completed] --> MAN[Manual generate partial]
    MAN --> PDF
    PDF --> DL[Customer/Seller/Admin download]
```

Idempotency: reservation invoice returns existing record if already created.

---

# Permission Architecture

## Components

```mermaid
flowchart TB
    subgraph Static
        PC[config/permissions.php]
        SM[config/scope_manifest.php]
    end

    subgraph Persistent
        RSG[(role_scope_grants)]
        USO[(user_scope_overrides)]
    end

    subgraph Runtime
        ESG[EffectiveScopeGrantResolver]
        SRE[ScopeRouteEnforcer]
        MW[Enforce*ScopeMiddleware]
    end

    subgraph UI
        AC[AccessControlController]
        INERTIA[Shared scope props]
    end

    PC --> ESG
    RSG --> ESG
    USO --> ESG
    ESG --> MW
    ESG --> SRE
    ESG --> INERTIA
    AC --> RSG
    AC --> USO
```

## Role Grants

- **Definition:** `config/permissions.php` → `role_grants` (defaults)
- **Persistence:** `role_scope_grants` table (admin-editable)
- **Resolution:** DB rows if present for editable role, else config defaults
- **Admin:** implicit full grant set; not stored per-user

## User Overrides

- **Table:** `user_scope_overrides` — `(user_id, scope_key, effect)`
- **Effects:** `grant` | `revoke` | `inherit` (delete row)
- **Eligible users:** seller, bidder, shipping_agent, data_entry only
- **Audit:** `user_scope_override_audits`

## Runtime Resolution

```mermaid
flowchart TD
    START[hasEffectiveGrant user, scope] --> ADMIN{role = admin?}
    ADMIN -->|yes| ALLOW[true]
    ADMIN -->|no| CUST{role = customer?}
    CUST -->|yes| CFG[config grants only]
    CUST -->|no| BASE[role grants DB or config]
    BASE --> OVR{user override?}
    OVR -->|grant| ALLOW
    OVR -->|revoke| DENY[false]
    OVR -->|none/inherit| CHECK{scope in base?}
    CHECK -->|yes| ALLOW
    CHECK -->|no| DENY
    CFG --> CHECK2{scope in customer list?}
    CHECK2 -->|yes| ALLOW
    CHECK2 -->|no| DENY
```

## Enforcement Surfaces

| Layer | Mechanism |
|-------|-----------|
| Route | `role:{role}` middleware |
| Route | `scope.{role}` middleware (env-gated) |
| Controller | Ownership checks (`seller_id`, customer linkage) |
| Form Request | Section field whitelists |
| Intake | `AuctionIntakeScope` editable keys / file types |
| UI | Inertia shared grant booleans (not sole enforcement) |

## Locked vs Override-Only

| Category | Examples | Assignable? |
|----------|----------|-------------|
| Locked | `remittances.verify`, `payments.mark_paid`, `auction_intake.assign_seller` | Never |
| Override-only | `checkout.bypass_approval`, `payments.bypass_approval` | Per-user only |
| Grantable | `vehicles.view`, `reservations.checkout`, exports | Role template + override |

---

# Financial Architecture

## Money Flow Overview

```mermaid
flowchart TB
    subgraph Inbound
        RM[Remittance verified]
        RM --> WC[Wallet CREDIT]
    end

    subgraph Held
        WC --> WB[(Wallet Balance computed)]
    end

    subgraph Outbound
        WB --> WD1[Wallet DEBIT reservation]
        WB --> WD2[Wallet DEBIT checkout]
        WD2 --> PAY[Payment completed]
        EXT[External payment bank/card] --> PAY
    end

    subgraph Allocation
        WD2 --> RA[RemittanceAllocation FIFO]
        PAY --> PA[PaymentAllocation]
        RA --> PAY
    end

    subgraph Documentation
        PAY --> INV[Partial invoice]
        RES[Reservation] --> INV2[Reservation invoice]
    end
```

## Wallet Credits

| Source | Trigger | Reference |
|--------|---------|-----------|
| `remittance` | Admin verify remittance | `reference_type=remittance`, `reference_id` |
| `manual` | Admin manual credit | configurable |

**Idempotency:** Remittance verify checks for existing credit before creating.

## Wallet Debits

| Source | Trigger | Pre-check |
|--------|---------|-----------|
| `reservation` | `reserveForCustomer` with wallet amount | Balance ≥ amount |
| `checkout` | Checkout wallet payment | Balance ≥ amount |
| `manual` | Admin action | Balance ≥ amount |

On debit: **FIFO allocation** creates `remittance_allocations` linking debit to oldest remittance credits.

## Payment Allocations

`PaymentService::allocatePayment()`:

- Creates `payment_allocations` row: payment → vehicle → amount
- Used for order/vehicle balance tracking and customer history

## Remittance Allocations

`WalletService::allocateDebitByFifo()`:

- For each debit, consumes credit transactions oldest-first (remittance credits prioritized by `received_date`)
- Links: `credit_wallet_transaction_id`, `debit_wallet_transaction_id`, optional `remittance_id`, `order_id`, `payment_id`

On payment completion: `attachRemittanceAllocationsToPayment` binds allocations to payment via `payment_data.wallet_transaction_id`.

## Invoice Generation

| Type | Amount basis | Auto? |
|------|--------------|-------|
| Reservation | Reservation pricing snapshot | Yes (unified reserve) |
| Partial payment | Completed payment amount | No (manual trigger) |

PDF content pulls company/bank/transport from `settings` table keys (`invoice_bill_from_*`, `invoice_bank_*`, etc.).

---

# Exchange Rate Architecture

## Storage Model

```
exchange_rates
├── date (unique)
└── rate (decimal USD→JPY)
```

**Not stored on `auction_cars`** — rate is a computed accessor at serialization time.

## Lookup Pipeline

```mermaid
flowchart LR
    PD[purchase_date] --> MEM[Memory cache]
    MEM --> DB[(exchange_rates)]
    DB -->|miss| API[exchangerate.host /historical]
    API --> INSERT[insertOrIgnore]
    INSERT --> RATE[rate float]
```

`ExchangeRateService::getRateByDate()` — never overwrites existing DB row.

Bulk listing: `preloadRatesForDates()` before auction intake serialization.

## USD Cost Divisor (Model)

```
if purchase_date < 2026-05-06:
    divisor = rate - 3
else:
    divisor = rate

usd_cost = auction_car.total / divisor
```

Used in `AuctionCar` accessor, dashboard profit aggregates.

## Seller Adjustment (Display Layer)

```
users.exchange_rate_adjustment  (decimal -999.9 .. 999.9, sellers only)

Admin intake UI:
    effective_rate = exchange_rate + seller.adjustment
    buying_cost_display = total / effective_rate
```

**Does not** affect: `AuctionCar::usd_cost`, invoices, customer pricing, payment amounts.

Bidder intake UI masks adjustment entirely.

---

# Deployment Architecture

## Application Tier

| Component | Role |
|-----------|------|
| **Laravel 12** | HTTP kernel, routing, Eloquent, queues, mail |
| **PHP 8.2+** | Runtime |
| **Jetstream 5** | Auth, teams, profile, Sanctum session |
| **Fortify** | Login/register/password; custom `authenticateUsing` for disabled users |

## Presentation Tier

| Component | Role |
|-----------|------|
| **Inertia 2** | Server-driven SPA bridge; shared props via `HandleInertiaRequests` |
| **Vue 3** | Page components under `resources/js/Pages/` |
| **Vite 7** | Asset bundling; `npm run build` for production |
| **Tailwind 3** | Styling |
| **Ziggy** | Named routes in JavaScript |

## Data Tier

| Component | Role |
|-----------|------|
| **MySQL** | Production database (typical) |
| **SQLite** | Local/test (`:memory:` in PHPUnit) |

**Migration policy:** forward-only `php artisan migrate` — never `migrate:fresh` in shared/production environments.

## Storage Tier

| Disk | Content |
|------|---------|
| `local` | Invoice PDFs (`invoices/Y/m/`), vehicle documents, remittance slips |
| `public` | Public assets, vehicle images (as configured) |

## PDF Generation Tier

| Component | Role |
|-----------|------|
| **Browsershot** | Headless Chrome wrapper |
| **Chrome/Chromium** | Must be available on server for invoice PDF |
| **Blade template** | `resources/views/invoices/pdf.blade.php` |

## External Integrations

| Integration | Endpoint / Config |
|-------------|-------------------|
| exchangerate.host | `config/services.php` → `exchangerate_host` |
| SMTP | `MailSettingsService` / `.env` mail vars |
| Payment gateway | `POST /webhooks/payments` |

## Environment Flags (Permission Enforcement)

```
PERMISSIONS_ENFORCE_DATA_ENTRY=true
PERMISSIONS_ENFORCE_BIDDER=true
PERMISSIONS_ENFORCE_SHIPPING_AGENT=true
PERMISSIONS_ENFORCE_SELLER=true
PERMISSIONS_LOG_ONLY_ENABLED=false
```

## Request Path (Authenticated Admin Example)

```
Browser → nginx/Apache → public/index.php
  → Middleware: sanctum, session, verified, user.active
  → Middleware: role:admin
  → Admin\UserController
  → Service layer
  → Eloquent → MySQL
  → Inertia::render → Vue page
```

---

# Rebuild Recommendations

If rebuilding as **V2**, preserve proven domain boundaries and refactor known debt.

## What Should Remain

| Area | Rationale |
|------|-----------|
| **Pipeline model** | Auction → Vehicle → Reservation → Order is core business truth |
| **Service-oriented financial core** | `PaymentService`, `WalletService`, `WalletService` FIFO are battle-tested |
| **Ledger-based wallet** | Computed balance from transactions — auditable |
| **Scope catalog + effective resolver** | Foundation for granular staff permissions |
| **Staged auction intake** | Matches real-world bidder/shipping/data-entry handoff |
| **Separate customer boundary** | Customer flows frozen and isolated — keep as module |
| **Reservation as pre-order hold** | Correct commercial model for export sales |
| **Remittance verify → wallet credit** | Settlement pattern works |

## What Should Be Refactored

| Area | Current issue | V2 direction |
|------|---------------|--------------|
| **Dual reservation APIs** | `reserve()` vs `reserveForCustomer()` | Single unified API with explicit modes |
| **Reservation expiry cron** | Marks expired without `release()` | Expiry job calls full release transaction |
| **Wallet refund on release** | Not implemented | Explicit refund policy + transactions |
| **Order status resolution** | Multiple inferred paths in `Order` model | Explicit state machine service |
| **Exchange rate display split** | Model `usd_cost` vs UI seller adjustment | Single documented cost engine with pluggable adjustments |
| **Controller ownership checks** | Scattered `seller_id` tests | Domain policies + query scopes |
| **API routes unauthenticated** | `/api/*` helpers without auth | Authenticated or signed catalog API |
| **README_ECOMMERCE drift** | Cart model vs reservation model | Archive or rewrite docs to match pipeline |

## What Should Be Separated into Services/Modules

```mermaid
flowchart TB
    subgraph CorePlatform
        IAM[Identity & Access Module]
        NOTIFY[Notification Module]
    end

    subgraph Commercial
        INTAKE[Auction Intake Module]
        INV[Inventory Module]
        COMM[Commercial Module<br/>Reservation + Order]
    end

    subgraph Finance
        FIN[Finance Module<br/>Payment + Wallet + Remittance + Invoice]
    end

    subgraph Fulfillment
        SHIP[Shipment Module]
    end

    subgraph CustomerBoundary
        CUST[Customer Self-Service Module]
    end

    INTAKE --> INV
    INV --> COMM
    COMM --> FIN
    COMM --> SHIP
    CUST --> COMM
    CUST --> FIN
    IAM --> Commercial
    IAM --> Finance
```

| Module | Boundaries |
|--------|------------|
| **Identity & Access** | Users, roles, scopes, overrides, teams — no business logic |
| **Auction Intake** | `auction_cars`, staged scopes, files, cost engine |
| **Inventory** | Vehicles, catalog, images, documents |
| **Commercial** | Reservations, checkout, orders — no payment ledger internals |
| **Finance** | Payments, wallet, remittances, invoices — strict audit |
| **Fulfillment** | Shipments, logistics milestones |
| **Customer** | Public + customer routes only; stable API contract |

## Technical Debt Inventory

| Debt | Severity | Notes |
|------|----------|-------|
| Triple enforcement (role + scope + controller) | Medium | Consolidate to resolver + policies |
| Ownership not in scope layer | Medium | `customerBelongsToSeller()` in controllers |
| `PaymentService` god-method sync | High | Extract order state machine |
| Invoice depends on Chrome on app server | Medium | Queue PDF generation; object storage |
| No event sourcing for financial tables | Low | Audit tables exist but not full event log |
| Test migration subsets fragile | Medium | Shared `RefreshDatabaseState` conflicts |
| Customer payment history page missing Vue | Low | Route exists without view |
| Future actions in catalog unused | Low | Clean catalog or implement |
| Admin nav placeholders (profit tools) | Low | Remove or implement |
| Seller adjustment display-only | Medium | Unify cost calculation or document permanently |

## V2 Technology Choices (Suggested)

| Layer | Current | V2 option |
|-------|---------|-----------|
| Monolith | Laravel monolith | Modular monolith or domain packages |
| Frontend | Inertia + Vue | Keep Inertia or separate API + Vue SPA |
| PDF | Browsershot sync | Queued job + S3 + email job |
| Permissions | DB + config hybrid | Full DB catalog with versioned migrations |
| Events | Implicit in services | Domain events for payment verified, reservation converted |

## Migration Strategy (V2 Data)

If rebuilding alongside V1:

1. **Never** truncate financial tables — migrate with dual-write period
2. Map `wallet_transactions` as source of truth for balances
3. Preserve `remittance_allocations` for audit trail
4. Keep `exchange_rates` immutable append-only
5. Status enums must map 1:1 or provide compatibility layer

---

## Document Index

| Document | Purpose |
|----------|---------|
| `docs/SYSTEM_ARCHITECTURE.md` | This file — rebuild blueprint |
| `PROJECT_MASTER_DOCUMENTATION.md` | Full behavioral reference |
| `docs/CURSOR_DEVELOPER_CONTEXT.md` | AI/developer safety rules |
| `docs/ARCHITECTURE.md` | Lifecycle and workflow detail |
| `docs/system-blueprint.md` | Schema appendix |
| `docs/permission-scope-catalog.md` | Route→scope engineering catalog |
| `config/permissions.php` | Runtime permission config |

---

*End of System Architecture document*
