# Permission & Scope Catalog (Phase 0 — Read-Only)

Engineering analysis artifact for future admin-managed role/scope work.  
**This document does not change application behavior.** No migrations, middleware, or UI are implied by this file.

**Sources inspected (2026-06-04, updated for Data Entry Phase B2, remittance claim, catalog hygiene):**

| Source | Purpose |
|--------|---------|
| `routes/web.php` | Route names, prefixes, `role:*` middleware groups |
| `routes/api.php` | Internal JSON endpoints used by staff forms (unauthenticated today) |
| `app/Http/Middleware/RoleMiddleware.php` | Current route gate |
| `app/Support/AuctionIntakeScope.php` | Intake field/file scopes (PHP) |
| `resources/js/support/auctionIntakeScope.js` | Intake UI scopes (Vue mirror) |
| `app/Http/Requests/StoreVehicleRequest.php`, `UpdateVehicleRequest.php`, `UpdateDataEntryVehicleRequest.php` | Vehicle form validation + admin-only / data-entry whitelist fields |
| `resources/js/Components/Vehicles/VehicleForm.vue` | Vehicle wizard sections |
| Admin/Seller/Bidder/ShippingAgent controllers | Ownership checks (`seller_id`, customer linkage) |
| `docs/ARCHITECTURE.md` §2 | Intended business role model |

**Legend**

| Column | Meaning |
|--------|---------|
| **Current role** | Who can hit the route today (`RoleMiddleware` + controller checks) |
| **Ownership** | Additional data-scope rule beyond role |
| **Future scope key** | Proposed `module.action` or `module.sections.*` identifier for Phase 1+ |
| **Status** | `existing` = production today; `future` = not built; `gap` = partial or inconsistent |

---

## 1. Current access-control overview

### 1.1 Route gate

- **Middleware:** `role:admin` | `role:seller` | `role:bidder` | `role:shipping_agent` | `role:data_entry` | `role:customer`
- **Implementation:** `Auth::user()->role === $role` (exact string, single role per user)
- **No** Laravel policies for business modules (only Jetstream `TeamPolicy`)

### 1.2 Field scope (intake only)

- **Server:** `AuctionIntakeScope::restrictPayload()`, `editableDataKeys()`, `editableFileTypes()`
- **Client:** `auctionIntakeSettings(intakeScope)` — parallel definition; drift risk
- **Pattern:** Bidder/Shipping/Data Entry controllers extend `Admin\AuctionCarController` and override `intakeScope()` only

### 1.3 Ownership patterns (orthogonal to role)

| Pattern | Used by |
|---------|---------|
| `vehicles.seller_id = auth()->id()` | Seller vehicle/document CRUD |
| `orders.seller_id` or vehicle on order items | Seller orders, shipments, invoices |
| `customerBelongsToSeller()` (`created_by`, orders, reservations) | Seller customer/remittance customer pickers |
| `vehicle_reservations.seller_id` | Seller reservations |
| Admin: no ownership filter | Full platform access on admin routes |

### 1.4 Internal API (staff forms)

Routes in `routes/api.php` (`/api/regions`, `/api/models`, `/api/countries`, catalog attributes, etc.) are **public (no auth)** today. Staff UIs call them from authenticated pages; they are **not** customer-facing listings but are **not scope-gated**.

---

## 2. Module route catalog

Future scope keys use **`module.action`**. Field-level keys in §4.

---

### 2.1 `auction_intake`

| Route name | Method | Controller | Current role | Ownership | Future scope key | Status |
|------------|--------|------------|--------------|-----------|------------------|--------|
| `admin.auction-cars.index` | GET | `Admin\AuctionCarController@index` | admin | — | `auction_intake.view` | existing |
| `admin.auction-cars.create` | GET | same | admin | — | `auction_intake.create` | existing |
| `admin.auction-cars.store` | POST | same | admin | — | `auction_intake.create` | existing |
| `admin.auction-cars.batch-update` | POST | same | admin | — | `auction_intake.update` | existing |
| `admin.auction-cars.assign-seller` | POST | same | admin | — | `auction_intake.assign_seller` | existing |
| `admin.auction-cars.edit` | GET | same | admin | — | `auction_intake.view` | existing |
| `admin.auction-cars.update` | PUT | same | admin | — | `auction_intake.update` | existing |
| `admin.auction-cars.bl-upload` | POST | same | admin | — | `auction_intake.upload_file` | existing |
| `admin.auction-cars.bl-delete` | POST | same | admin | — | `auction_intake.delete_file` | existing |
| `admin.auction-cars.files.upload` | POST | same | admin | — | `auction_intake.upload_file` | existing |
| `admin.auction-cars.files.delete` | POST | same | admin | — | `auction_intake.delete_file` | existing |
| `admin.auction-cars.pc-preview` | GET | same | admin | — | `auction_intake.view_file` | existing |
| `bidder.auction-cars.index` | GET | `Bidder\AuctionCarController` | bidder | — | `auction_intake.view` | existing |
| `bidder.auction-cars.batch-update` | POST | same | bidder | Field scope: bidder keys only | `auction_intake.update` | existing |
| `bidder.auction-cars.files.upload` | POST | same | bidder | File: `as` only | `auction_intake.upload_file` | existing |
| `bidder.auction-cars.files.delete` | POST | same | bidder | File: `as` only | `auction_intake.delete_file` | existing |
| `shipping-agent.auction-cars.index` | GET | `ShippingAgent\AuctionCarController` | shipping_agent | — | `auction_intake.view` | existing |
| `shipping-agent.auction-cars.batch-update` | POST | same | shipping_agent | Field scope: shipping keys only | `auction_intake.update` | existing |
| `shipping-agent.auction-cars.files.*` | POST/GET | same | shipping_agent | Shipping file types | `auction_intake.upload_file` / `delete_file` / `view_file` | existing |
| `data-entry.auction-cars.index` | GET | `DataEntry\AuctionCarController@index` | data_entry | Read-only grid; `AuctionIntakeScope::DataEntry` | `auction_intake.view` | existing |
| `data-entry.dashboard` | GET | `DataEntry\DashboardController@index` | data_entry | — | `analytics.data_entry_dashboard.view` | existing |
| `data-entry.vehicles.index` | GET | `DataEntry\VehicleController@index` | data_entry | — | `vehicles.view` | existing |
| `data-entry.vehicles.show` | GET | same | data_entry | — | `vehicles.view` | existing |
| `data-entry.vehicles.edit` | GET | same | data_entry | — | `vehicles.update` | existing |
| `data-entry.vehicles.update` | PUT | same | data_entry | Whitelist: `UpdateDataEntryVehicleRequest` | `vehicles.update` | existing |

**Data Entry auction queue (read-only):**

- Route: `data-entry.auction-cars.index` only — no create/update/batch/file routes under `data-entry/*` for intake.
- `AuctionIntakeScope::DataEntry`: `editableDataKeys()` and `editableFileTypes()` empty; `readOnlyGrid: true` in Vue; commercial cost/profit/reservation columns masked via `maskCommercialPayload()`.
- UI hides: seller column, add row, batch save, file upload/delete, reserve, vehicle link actions, financial filters, export, calculated totals.

**Intake UI actions (admin-only today, hidden in Vue via `intakeScope`):**

| Action | Current role | Future scope key | Status |
|--------|--------------|------------------|--------|
| Add intake row | admin, bidder | `auction_intake.create_row` | existing (not shipping_agent) |
| Assign seller | admin | `auction_intake.assign_seller` | existing |
| Create/view vehicle link | admin | `vehicles.create` + `auction_intake.link_vehicle` | existing |
| Reserve from intake | admin | `reservations.create` | existing |

**Gaps:** Bidder cannot access admin master-data settings routes but uses open `/api/regions` etc. Data Entry has read-only intake context only — no intake write routes (`gap` for future scoped read APIs vs open `/api/*`).

---

### 2.2 `vehicles`

| Route name | Method | Controller | Current role | Ownership | Future scope key | Status |
|------------|--------|------------|--------------|-----------|------------------|--------|
| `admin.vehicles.index` | GET | `Admin\VehicleController` | admin | — | `vehicles.view` | existing |
| `admin.vehicles.create` | GET | same | admin | — | `vehicles.create` | existing |
| `admin.vehicles.store` | POST | same | admin | Auction link: admin only in validation | `vehicles.create` | existing |
| `admin.vehicles.show` | GET | same | admin | — | `vehicles.view` | existing |
| `admin.vehicles.edit` | GET | same | admin | — | `vehicles.update` | existing |
| `admin.vehicles.update` | PUT | same | admin | Locked vehicle: seller/chassis rules in FormRequest | `vehicles.update` | existing |
| `admin.vehicles.destroy` | DELETE | same | admin | — | `vehicles.delete` | existing |
| `admin.vehicles.documents.*` | * | `Admin\VehicleDocumentController` | admin | — | `vehicles.manage_documents` | existing |
| `seller.vehicles.*` | * | `Seller\VehicleController` | seller | `seller_id = auth` | `vehicles.view` / `create` / `update` / `delete` | existing |
| `seller.vehicles.documents.*` | * | `Seller\VehicleDocumentController` | seller | `vehicle.seller_id = auth` | `vehicles.manage_documents` | existing |
| `data-entry.vehicles.index` | GET | `DataEntry\VehicleController` | data_entry | — | `vehicles.view` | existing |
| `data-entry.vehicles.show` | GET | same | data_entry | — | `vehicles.view` | existing |
| `data-entry.vehicles.edit` | GET | same | data_entry | — | `vehicles.update` | existing |
| `data-entry.vehicles.update` | PUT | same | data_entry | Enrichment whitelist + immutable-field checks | `vehicles.update` | existing |

**Data Entry vehicle edit (Phase B2 — implemented):**

| Layer | Behavior |
|-------|----------|
| **Routes** | Edit/update only — no `data-entry.vehicles.create` or `destroy` |
| **FormRequest** | `UpdateDataEntryVehicleRequest` — role gate `data_entry`; whitelisted persist keys; rejects tampered changes to immutable fields |
| **UI** | Shared `VehicleForm` + `dataEntryMode` — hides seller, pricing, status/stock/flags; strips forbidden keys on submit |
| **Section grants (config)** | `basic_info`, `specifications`, `images`, `location_status` — **not** `seller_assignment`, **not** `pricing` |

**Allowed persist keys:** `make_id`, `model_id`, `model_code`, `make`, `model`, `year`, `mileage`, `condition`, `chassis` (when permitted), legacy spec columns, `dynamic_attributes`, images, `country_id`, `location`, `stock_country`.

**Immutable / blocked keys:** `seller_id`, `auction_car_id`, `price`, `discount_*`, `status`, `stock_status`, `is_featured`, `is_clearance`, `is_locked`, `is_ready_for_sale`, `vehicle_type*`. Auction-linked vehicles also lock make/model/chassis to intake source; locked vehicles block chassis changes.

**Gaps:**

- Seller has full vehicle form (specs, images) — overlaps intended **Data Entry** role (`gap`).
- Admin vehicle update has no server-side section scopes (full FormRequest rules) (`gap`).
- Data Entry has **server-side section whitelist** for update; admin/seller unchanged (`existing` for data_entry only).
- Legacy `app/Http/Controllers/VehicleController.php` still contains role checks (`gap` — verify if routed).

**Future intended:** Data Entry gets vehicle create + `vehicle_master_data.*`; Seller gets narrowed section scopes (§4.7).

---

### 2.3 `vehicle_master_data`

All routes **`role:admin`** only today.

| Route prefix / names | Resource | Future scope key |
|---------------------|----------|------------------|
| `admin.countries.*` | Countries | `vehicle_master_data.countries.manage` |
| `admin.regions.*` | Regions | `vehicle_master_data.regions.manage` |
| `admin.auction-houses.*` | Auction houses | `vehicle_master_data.auction_houses.manage` |
| `admin.ports.*` | Ports | `vehicle_master_data.ports.manage` |
| `admin.makes.*`, `admin.makes.models.store`, `admin.models.*` | Makes/models | `vehicle_master_data.makes_models.manage` |
| `admin.attributes.*`, `admin.attributes.values.*` | Attributes | `vehicle_master_data.attributes.manage` |
| `admin.make-attributes.batch` | Make↔attribute | `vehicle_master_data.attributes.manage` |

**Future intended:** Grant to **Data Entry** role; read-only subset to **Bidder** for intake dropdowns (`vehicle_master_data.regions.view` — `future`).

**Gaps:** Bidder/shipping_agent rely on unauthenticated `/api/*` instead of scoped read APIs (`gap`).

---

### 2.4 `reservations`

| Route name | Method | Current role | Ownership | Future scope key | Status |
|------------|--------|--------------|-----------|------------------|--------|
| `admin.reservations.index` | GET | admin | — | `reservations.view` | existing |
| `admin.reservations.store` | POST | admin | — | `reservations.create` | existing |
| `admin.reservations.destroy` | DELETE | admin | — | `reservations.release` | existing |
| `admin.reservations.checkout` | POST | admin | — | `reservations.checkout` | existing |
| `admin.reservations.approve-checkout` | POST | admin | — | `reservations.approve_checkout` | existing |
| `admin.reservations.pay-balance` | POST | admin | — | `reservations.pay_balance` | existing |
| `admin.checkout-approvals.*` | * | admin | — | `reservations.approve_checkout` | existing |
| `seller.reservations.index` | GET | seller | `seller_id` on reservation | `reservations.view` | existing |
| `seller.reservations.store` | POST | seller | own customers/vehicles/pool rules | `reservations.create` | existing |
| `seller.reservations.destroy` | DELETE | seller | own reservation | `reservations.release` | existing |
| `seller.reservations.checkout` | POST | seller | own → `pending_approval` | `reservations.checkout` | existing |
| `seller.reservations.pay-balance` | POST | seller | own converted order | `reservations.pay_balance` | existing |

**Frozen (customer):** reservation checkout routes listed in §7 — do not scope-refactor.

**Future intended:** Data Entry has **no** reservation actions. Scope UI should not grant seller `reservations.*` without ownership flag.

---

### 2.5 `orders`

| Route name | Current role | Ownership | Future scope key | Status |
|------------|--------------|-----------|------------------|--------|
| `admin.orders.index` | admin | — | `orders.view` | existing |
| `admin.orders.show` | admin | — | `orders.view` | existing |
| `admin.orders.update` | admin | — | `orders.update` | existing |
| `admin.orders.shipments.*` | admin | — | `orders.manage_shipments` | existing |
| `seller.orders.*` | seller | `order.seller_id` or vehicle ownership | `orders.view` / `update` / `manage_shipments` | existing |
| `seller.orders.markPaid` | seller | own order | `orders.mark_paid` | existing |
| `seller.vehicles.orders.store` | seller | own vehicle | `orders.create_from_reservation` | existing |

**Gaps:** Seller `markPaid` overlaps payment semantics — scope catalog should treat as `payments.record` alias (`gap` — document only, do not change).

---

### 2.6 `payments`

| Route name | Current role | Ownership | Future scope key | Status |
|------------|--------------|-----------|------------------|--------|
| `admin.payments.index` | admin | — | `payments.view` | existing |
| `admin.payments.mark-paid` | admin | — | `payments.mark_paid` | existing |

**Frozen:** Customer payment process/webhook/history (§7). **Do not** add scope gates that alter payment sync or wallet behavior.

**Future intended:** Seller may get `payments.view` on own orders only (`future` — not separate routes today).

---

### 2.7 `invoices`

| Route name | Current role | Ownership | Future scope key | Status |
|------------|--------------|-----------|------------------|--------|
| `admin.invoices.index` | admin | — | `invoices.view` | existing |
| `admin.invoices.download` | admin | — | `invoices.download` | existing |
| `admin.payments.invoice.generate` | admin | — | `invoices.generate_partial` | existing |
| `seller.invoices.*` | seller | own sale context | `invoices.view` / `download` / `generate_partial` | existing |

**Frozen:** Reservation invoice auto-generation, customer invoice download (§7). Partial invoice **generation rules** must not change under scope work.

**Future intended:** `invoices.resend` scope for seller (`future` — verify if resend exists; currently download + generate only) (`gap`).

---

### 2.8 `remittances`

**Implemented:** Admin PDF import → seller claim pool → seller claim (customer allocation + **claim proof**) → admin **Pending Claim Requests** tab → verify (wallet credit) or **reject claim to pool**. Seller direct remittance remains a separate `seller_submitted` path.

**Dual proof model:**

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

Admin and seller UIs expose separate view/download routes for each proof type.

| Route name | Current role | Ownership | Future scope key | Status |
|------------|--------------|-----------|------------------|--------|
| `admin.remittances.index` | admin | — | `remittances.view` | existing |
| `admin.remittances.store` | admin | — | `remittances.create` | existing |
| `admin.remittances.store-bulk` | admin | — | `remittances.create_bulk` | existing |
| `admin.remittances.update` | admin | — | `remittances.update` | existing |
| `admin.remittances.verify` | admin | — | `remittances.verify` | existing |
| `admin.remittances.reject` | admin | — | `remittances.reject` | existing — terminal reject; claim branch returns row to pool |
| `admin.remittances.slip.view` / `.download` | admin | — | `remittances.view_slip` | existing — source / `deposit_slip_*` |
| `admin.remittances.claim-slip.view` / `.download` | admin | — | `remittances.view_claim_slip` | existing — seller claim proof (download uses same scope) |
| `seller.remittances.index` | seller | own rows (`sellerOwnedRemittances`) | `remittances.view` | existing |
| `seller.remittances.pool` | seller | global claim listing | `remittances.view_pool` | existing |
| `seller.remittances.store` | seller | linked customer optional | `remittances.create` | existing — `seller_submitted` |
| `seller.remittances.claim` | seller | unclaimed `admin_pdf` only | `remittances.claim` | existing |
| `seller.remittances.source-slip.view` | seller | claim-listing rows | `remittances.view_source_slip` | existing |
| `seller.remittances.claim-slip.view` | seller | own claimed rows | `remittances.view_claim_slip` | existing |

**Admin index tabs (client-filtered on serialized flags):**

| Tab | Predicate (summary) |
|-----|---------------------|
| Pending Seller Submissions | `origin = seller_submitted`, `status = pending` |
| Pending Claim Requests | `origin = admin_pdf`, `seller_id` + `claimed_at` set, `status = pending` |
| PDF Import Pool | `origin = admin_pdf`, `seller_id` null, `!is_complete`, `status = pending` |
| Verified | `status = verified` |
| Rejected | `status = rejected` (terminal reject only; claim reject returns to pool) |

**Claim workflow scope keys (implemented actions; enforcement still `role:*` + controllers):**

| Action | Future scope key | Maps to |
|--------|------------------|---------|
| Seller view pool source slip | `remittances.view_source_slip` | `seller.remittances.source-slip.view` |
| Seller view claim pool | `remittances.view_pool` | `seller.remittances.pool` |
| Seller claim + allocate customer | `remittances.claim` | `seller.remittances.claim` |
| Seller / admin view claim proof | `remittances.view_claim_slip` | claim-slip view routes |
| Admin verify claim / submission | `remittances.verify` | `admin.remittances.verify` (unchanged wallet path) |
| Admin reject claim to pool | `remittances.reject` | `admin.remittances.reject` (claim branch only) |

**Frozen:** `verify()` / `creditRemittanceWallet()` / `WalletService` settlement behavior — scope work documents who may invoke endpoints, not how credit is calculated.

**Notes:** Rejection reason optional on admin reject modal. **Reject-to-pool (claim branch only):** when `origin = admin_pdf`, `seller_id` + `claimed_at` set, `status = pending` — clears claim fields (`seller_id`, `customer_id`, `received_by_seller`, `claim_slip_*`, `claimed_at`), sets `is_complete = false`, keeps `status = pending` and unchanged `deposit_slip_*`; optional note logged; row returns to **PDF Import Pool**. **Terminal reject** (`status = rejected`) unchanged for seller-submitted and other non-claim rows. `admin_manual` pending rows are outside the five-tab claim filters (admin Complete / verify path unchanged).

---

### 2.9 `customers`

| Route name | Current role | Ownership | Future scope key | Status |
|------------|--------------|-----------|------------------|--------|
| `admin.users.index/create/store/show` | admin | — | `users_access.manage_users` (customers subset) | existing |
| `admin.customers.consignees` | admin | — | `customers.view_consignees` | existing |
| `admin.customers.search` | admin | — | `customers.search` | existing |
| `seller.customers.*` | seller | linked customers | `customers.view` / `create` | existing |
| `seller.customers.consignees` | seller | linked | `customers.manage_consignees` | existing |
| `seller.customers.wallet.credit` | seller | linked | `customers.wallet_credit` | existing |
| `seller.customers.search` | seller | — | `customers.search` | existing |

**Future intended:** Seller creates customer with remitter/consignee — scopes `customers.create`, `customers.manage_remitter` (`future` — remitter routes may be embedded in customer UI).

**Frozen:** Customer self-service consignee/remitter routes (§7).

---

### 2.10 `settings`

| Route name | Current role | Future scope key | Status |
|------------|--------------|------------------|--------|
| `admin.settings.reservation.*` | admin | `settings.reservation.manage` | existing |
| `admin.settings.invoice.*` | admin | `settings.invoice.manage` | existing |
| `admin.bank-accounts.*` | admin | `settings.banking.manage` | existing |
| `admin.deposit-settings.*` | admin | `settings.deposits.manage` | existing |
| `admin.pricing-rules.*` | admin | `settings.pricing_rules.manage` | existing |
| `admin.shipping-rates.*` | admin | `settings.shipping_rates.manage` | existing |

**Future intended:** Bidder read-only access to auction houses/regions referenced by intake (`settings.auction_reference.view` — `future`).

---

### 2.11 `analytics`

| Route name | Current role | Future scope key | Status |
|------------|--------------|------------------|--------|
| `admin.dashboard` | admin | `analytics.dashboard.view` | existing |
| `seller.dashboard` | seller | `analytics.seller_dashboard.view` | existing |
| `bidder.dashboard` | bidder | `analytics.bidder_dashboard.view` | existing |
| `shipping-agent.dashboard` | shipping_agent | `analytics.shipping_dashboard.view` | existing |
| `data-entry.dashboard` | data_entry | `analytics.data_entry_dashboard.view` | existing |

**Gaps:** Nav placeholders for profit reports (`route: null` in `adminNavigation.js`) — not routed (`future`).

---

### 2.12 `users_access`

| Route name | Current role | Future scope key | Status |
|------------|--------------|------------------|--------|
| `admin.users.*` | admin | `users_access.manage_users` | existing |
| `users.wallet-balance` | auth (admin/seller privileged in controller) | `customers.view_wallet_balance` | existing |

**Creatable roles today (`StoreManagedUserRequest`):** `seller`, `customer`, `bidder`, `shipping_agent`, `data_entry`.

**Future intended:**

| Feature | Future scope key | Status |
|---------|------------------|--------|
| Role/scope management page | `users_access.manage_scopes` | future |
| Assign role templates to users | `users_access.assign_roles` | future |

---

### 2.13 Seller-only: `auction_pool`

| Route name | Current role | Future scope key | Status |
|------------|--------------|------------------|--------|
| `seller.auction-pool.index` | seller | `auction_intake.view_pool` or `reservations.pool_view` | existing |

Pool = unlinked auction cars without active reservation — supports seller reservation from intake.

---

## 3. Shared / auth routes (staff-relevant)

| Route name | Current access | Notes |
|------------|----------------|-------|
| `dashboard` | all authenticated | Redirects by `users.role` |
| Jetstream `profile.*` | authenticated | Not module-scoped |

---

## 4. Field & section scopes

Future keys: **`module.sections.<slug>`** with access `read` | `write` | `hidden`.

Server source of truth should eventually match UI; today intake is dual-defined (PHP + JS).

---

### 4.1 Auction intake — bidder fields

**Future scope key:** `auction_intake.sections.bidder_identity`  
**Current enforcement:** `AuctionIntakeScope::Bidder` → `editableDataKeys()`  
**Status:** existing

| Payload key | In bidder scope |
|-------------|-----------------|
| `make_id`, `model_id`, `vehicle_type_id`, `chassis_no` | yes |
| `purchase_date`, `region_id`, `city`, `auction_house_id`, `dest_country_id` | yes |
| `auction_price`, `auction_fee` | yes |
| File type `as` | yes |

All other intake keys preserved from baseline on save (not writable).

---

### 4.2 Auction intake — shipping fields

**Future scope key:** `auction_intake.sections.shipping_logistics`  
**Current enforcement:** `AuctionIntakeScope::ShippingAgent`  
**Status:** existing

| Payload key | In shipping scope |
|-------------|-------------------|
| `inspection`, `transportation`, `h_charge`, `van`, `insurance`, `freight`, `bl`, `extra`, `shipper`, `storage`, `storage_is_manual_override` | yes |
| `selling_price`, `vessel`, `sailing_date` | yes |
| File types `ec`, `coc`, `bl`, `dhl`, `keys`, `pc`, `psi`, `bill`, `inv` | yes |

Identity/bidder keys: read-only baseline (UI: `showIdentityFieldsReadOnly`).

---

### 4.3a Auction intake — Data Entry (read-only context)

**Future scope key:** `auction_intake.view` (no separate section key — read-only by empty `editableDataKeys()`)  
**Current enforcement:** `AuctionIntakeScope::DataEntry` + `data-entry.auction-cars.index` only  
**Status:** existing

| Capability | Data Entry |
|------------|------------|
| View intake grid | yes (identity + logistics context; commercial columns masked) |
| Edit any intake field | no |
| Upload/delete files | no |
| Add rows, batch save, assign seller, reserve, vehicle actions | no |

Commercial/reservation columns stripped in serialized payload (`maskCommercialPayload()`). Vue: `readOnlyGrid: true`, `showFinancialFilters: false`.

---

### 4.3 Auction intake — admin-only fields

**Future scope key:** `auction_intake.sections.admin_control`  
**Current enforcement:** Admin scope = all keys; UI flags for seller column, reserve, vehicle actions  
**Status:** existing

| Capability | Admin only today |
|------------|------------------|
| All intake data keys including `admin_notes`, `status` | yes |
| All file types | yes |
| `assignSeller` | yes |
| Add rows (not shipping_agent) | yes |
| Create vehicle / reserve from intake | yes (UI + routes) |

**Future intended:** Map admin_control sub-actions to separate scope keys (`assign_seller`, `create_row`) in scope UI — not separate field sections.

---

### 4.4 Vehicle — basic info

**Future scope key:** `vehicles.sections.basic_info`  
**Form:** `VehicleForm.vue` wizard step `basic`  
**Status:** existing (no server section filter)

| Fields | Admin | Seller | Data Entry |
|--------|-------|--------|------------|
| `make_id`, `model_id`, `model_code`, `make`, `model` | write | write | write (locked if `auction_car_id`) |
| `year`, `mileage`, `condition`, `chassis` | write | write | write (chassis blocked if `is_locked` or auction-linked) |
| Auction-linked: make/model/chassis locked | admin create | N/A (no auction link) | enforced server-side on update |

**Locked vehicle (`is_locked`):** chassis/seller immutable — `UpdateVehicleRequest` (admin/seller); Data Entry uses `UpdateDataEntryVehicleRequest` (`existing`).

---

### 4.5 Vehicle — specifications

**Future scope key:** `vehicles.sections.specifications`  
**Form:** wizard step `specifications` + `dynamic_attributes` (non-boolean)  
**Status:** existing

| Fields | Admin | Seller | Data Entry |
|--------|-------|--------|------------|
| `dynamic_attributes[]` | write | write | write |
| Legacy columns `color`, `transmission`, etc. | write | write | write |

**Future intended:** Primary **Data Entry** section; Seller **read/write limited** to sales-safe subset (`future` — policy TBD in scope UI).

---

### 4.6 Vehicle — images

**Future scope key:** `vehicles.sections.images`  
**Form:** wizard step `images`  
**Status:** existing

| Fields | Admin | Seller | Data Entry |
|--------|-------|--------|------------|
| `primary_image`, `additional_images[]` | write | write | write |

Documents: separate `vehicles.manage_documents` action — **not** granted to Data Entry today.

---

### 4.7 Vehicle — seller assignment

**Future scope key:** `vehicles.sections.seller_assignment`  
**Form:** wizard step `seller` (admin only in UI)  
**Status:** existing

| Field | Admin | Seller | Data Entry |
|-------|-------|--------|------------|
| `seller_id` | write | forced to `auth` on create; not shown | **hidden + blocked server-side** |

**Future intended:** Admin + optional ops role; **not** Data Entry unless explicitly granted (`future`).

---

### 4.8 Vehicle — pricing

**Future scope key:** `vehicles.sections.pricing`  
**Form:** `price`, `discount_percentage`, `discount_amount` in basic step; location step has status flags  
**Status:** existing

| Field | Admin | Seller | Data Entry | Notes |
|-------|-------|--------|------------|-------|
| `price` | write | write | **blocked** | Overwritten from auction `selling_price` when linked |
| `discount_*` | write | write | **blocked** | |
| `status`, `stock_status`, `is_featured`, `is_clearance` | write | write | **blocked** | Listing lifecycle — not enrichment |

**Location subsection** (`vehicles.sections.location_status`): `country_id`, `location`, `stock_country` — Data Entry **write**; status fields above remain admin/seller only.

**Future intended:** Data Entry may eventually edit catalog price on draft vehicles under separate policy (`future` — explicitly excluded in Phase B2).

---

## 5. Role → module matrix (current behavior)

`✓` = route group exists. `(f)` = field-scoped subset. `(o)` = ownership required.

| Module | admin | seller | bidder | shipping_agent | data_entry |
|--------|-------|--------|--------|----------------|------------|
| auction_intake | ✓ full | pool only | ✓ (f) | ✓ (f) | ✓ view only (read-only queue) |
| vehicles | ✓ | ✓ (o) | — | — | ✓ view + update (enrichment) |
| vehicle_master_data | ✓ | — | — (api gap) | — (api gap) | — (future) |
| reservations | ✓ | ✓ (o) | — | — | — |
| orders | ✓ | ✓ (o) | — | — | — |
| payments | ✓ | partial (o) | — | — | — |
| invoices | ✓ | ✓ (o) | — | — | — |
| remittances | ✓ | ✓ (o) | — | — | — |
| customers | ✓ | ✓ (o) | — | — | — |
| settings | ✓ | — | — | — | — |
| analytics | ✓ | ✓ | ✓ | ✓ | ✓ dashboard |
| users_access | ✓ | — | — | — | — |

**`data_entry` column:** implemented — `role:data_entry` routes + `config/permissions.php` active grants (Phase B2). `AccessResolver` mirrors grants but is **not** middleware-enforced yet.

---

## 6. Frozen boundary — customer & public (do not modify)

Listed for completeness only. **No scope-refactor work** on these routes or their controllers/services.

### Public (unauthenticated)

| Route | Module |
|-------|--------|
| `home`, `vehicles.show` | public_listings |
| `vehicle-catalog.*` | public_listings |
| `webhooks.payments` | payment_logic |

### Customer (`role:customer`)

| Area | Route prefix / names |
|------|----------------------|
| Dashboard | `customer.dashboard` |
| Checkout / purchase | `customer.checkout.*`, `customer.purchase.*`, legacy `/checkout/*` |
| Orders | `customer.orders.*` |
| Payments | `customer.payments.*` |
| Consignees / remitters | `customer.consignees.*`, `customer.remitters.*` |
| Invoices | `customer.invoices.*` |
| Documents | `customer.vehicle.documents.*` |

**Recommendation:** Mark module `customer_self_service` as **non-configurable** in any future scope UI.

---

## 7. Gaps, unknowns, and no-code recommendations

### 7.1 Gaps

| ID | Gap | Severity |
|----|-----|----------|
| G1 | Single `users.role` — no composite grants | High for Data Entry + Bidder same person |
| G2 | Intake scopes duplicated PHP + JS | Medium drift risk |
| G3 | Vehicle form has no server-side section scopes (admin/seller) | Medium — Seller = full catalog edit today; **Data Entry has whitelist FormRequest** |
| G4 | Open `/api/*` for master data | Medium — not customer-facing but unscoped |
| G5 | Many FormRequests `authorize(): true` | Medium — rely on route middleware only (`UpdateDataEntryVehicleRequest` excepted) |
| G6 | Scope keys not wired to `AccessResolver` middleware | Low — routes live under `role:*`; resolver read-only; catalog aligned with live claim routes |
| G7 | Data Entry missing vehicle create + master data routes | Expected — Phase B3+ |
| G8 | Legacy `VehicleController` | Low — confirm unused routes |
| G9 | Admin remittance tabs filter client-side on paginated index | Low — server-side tab filter optional later |

### 7.2 Unknowns (verify before Phase 1)

- Whether legacy `VehicleController` routes are still registered anywhere outside `web.php`.
- Exact seller remitter creation path (embedded in customer UI vs separate routes).
- Whether partial invoice **resend** exists as distinct action or only regenerate/download.

### 7.3 No-code recommendations (Phase 1 prep)

1. **Adopt scope key naming** from this catalog (`module.action`, `module.sections.*`) in any new docs/PRs — do not introduce alternate names.
2. **Keep customer modules out of scope matrices** — hard-coded deny in design reviews.
3. **Treat ownership separately** — document `requires_ownership: seller_id` flags alongside role grants in Phase 1 resolver design.
4. **Merge intake field catalogs** — Phase 1 should generate JS from PHP (or single JSON export) to close G2.
5. **Do not gate payment verify, reservation invoice, or wallet credit** — only document who may *invoke* existing endpoints.
6. **Split seller vehicle sections** in future UI matrix before granting Data Entry role — avoid conflating sales listing edits with catalog enrichment.

---

## 8. Phased alignment (reference only)

| Phase | Deliverable | This catalog |
|-------|-------------|--------------|
| **0** | This document | ✓ |
| **1A** | Read-only `AccessResolver` + `config/permissions.php` (not enforced) | ✓ |
| **1B** | Optional log-only resolver checks behind feature flag | — |
| **2** | Dual enforcement alongside `role:*` | Use future scope keys |
| **3** | Admin scope UI + DB | Edit §5 matrix |
| **4** | Data Entry persona — foundation + enrichment edit | ✓ Phase B (dashboard, read-only auction queue, vehicle edit); create/master data — |
| **5** | Remittance claim workflow | ✓ §2.8 (implemented; resolver enforcement future) |

---

## 10. Phase 1A implementation (read-only — not enforced)

**Added files (mirrors this catalog; does not gate requests):**

| File | Purpose |
|------|---------|
| `config/permissions.php` | Scope keys, module groupings, `role_grants` for current roles |
| `app/Support/PermissionCatalog.php` | Read-only config accessor |
| `app/Services/AccessResolver.php` | `can()`, `allowedModules()`, `allowedActions()` |
| `app/Console/Commands/ResolveAccessCommand.php` | `php artisan access:resolve` debug helper |
| `tests/Unit/AccessResolverTest.php` | Unit tests for role grant mirroring |

**Behavior:**

- `AccessResolver` is **not** registered in middleware, controllers, FormRequests (except `UpdateDataEntryVehicleRequest` role check), or Inertia shared props.
- `users.role` → flat grant list in config (ownership rules still only in controllers).
- **`data_entry` active grants (Phase B2):** `analytics.data_entry_dashboard.view`, `vehicles.view`, `vehicles.update`, vehicle section grants (`basic_info`, `specifications`, `images`, `location_status` — excludes `seller_assignment`, `pricing`), `auction_intake.view`.
- **`seller` remittance claim grants (live):** `remittances.view_pool`, `remittances.claim`, `remittances.view_source_slip`, `remittances.view_claim_slip` (plus `remittances.view`, `remittances.create`). Distinct from `auction_intake.view_pool` (auction reservation pool).
- **`admin` remittance claim review:** `remittances.view_claim_slip` plus existing verify/reject/update scopes (reject claim branch returns row to pool).
- `future_actions` remittance keys (`complete_claim`, `allocate_customer`, `approve_allocation`) remain **planned only** — live claim flow uses `remittances.claim` + admin `remittances.reject`.
- `future_role_grants.data_entry` documents remaining template (vehicle create/delete, `vehicle_master_data.*`, etc.).
- Remaining `future_actions` (scope management, invoice resend, settings auction reference) are **denied** for all current roles in Phase 1A.

**Debug commands (manual only):**

```bash
php artisan access:resolve
php artisan access:resolve admin
php artisan access:resolve seller --scope=reservations.create
php artisan access:resolve data_entry
php artisan access:resolve --user=1
```

**Next phase (1B+):** Wire resolver in log-only mode or dual-check alongside `RoleMiddleware` — not started.

---

## 9. Related documentation

- [`docs/ARCHITECTURE.md`](ARCHITECTURE.md) — §2 Role Responsibilities and Access Scope (business intent)
- [`app/Support/AuctionIntakeScope.php`](../app/Support/AuctionIntakeScope.php) — intake field enforcement
- [`resources/js/support/auctionIntakeScope.js`](../resources/js/support/auctionIntakeScope.js) — intake UI mirror

---

*Phase 0 catalog — read-only analysis. Updated for Data Entry Phase B2, remittance claim workflow, and config catalog hygiene (live remittance claim scopes in module_actions + role_grants). No application behavior changed by this document.*
