# Tenancy, Permissions & Policies

## Two separate login paths

- **Mobile apps** authenticate via **Sanctum tokens** (`auth:sanctum`).
- The **Livewire panel** authenticates via a normal **`web` session guard**
  (`Auth::attempt()`).

They share the same `users` table and `type` column, but a mobile Sanctum token doesn't produce a
Livewire session and vice versa.

### ⚠️ Staff currently can't log into the Livewire panel

The Livewire login only allows `UserTypesEnum::USER` (admin) and `UserTypesEnum::RESTAURANT`
(owner) — `UserTypesEnum::STAFF` is excluded. A staff account, which can already log into the
mobile app, currently gets rejected at the Livewire login screen. If staff are meant to use the
real-time panel, that gate needs to change.

## Resolving "the current restaurant"

There's no tenant middleware — every place that needs "which restaurant is this user" resolves it
explicitly, checking both paths:

- **Owner**: has their own `restaurants` row.
- **Staff** (and, per the seeder, usually the owner too): attached via the `restaurant_users`
  pivot instead.

The `User` model already has a helper that encodes this fallback (owned restaurant, or first
restaurant from the pivot) — everything elsewhere in the codebase that needs "current restaurant"
goes through the same owner-or-pivot logic rather than trusting `$user->restaurant` alone, since
that would silently break for staff.

## Permissions (Spatie)

Two roles beyond `admin`: **`restaurant`** (owner) and **`staff`**.

| Permission | Owner | Staff |
|---|---|---|
| `accept orders` | ✅ | ✅ |
| `decline orders` | ✅ | ✅ |
| `assign driver` | ✅ | ✅ |
| `view available drivers` | ✅ | ✅ |
| `view orders` / `view order` | ✅ | ✅ |
| `edit restaurants` | ✅ | ❌ |
| `edit dispatch settings` / `edit pricing settings` | ✅ | ❌ |
| `create product` / `edit product` | ✅ | ✅ |
| `view driver` / `edit driver` / `delete driver` | ✅ | ❌ |
| `create category` / `edit category` / `delete category` | ✅ | ❌ |

### ⚠️ Being in `restaurant_users` doesn't grant permissions by itself

Attaching a staff member to the pivot and giving them a Spatie role are two separate steps. The
existing staff-assignment endpoint does both — creates the pivot row *and* assigns the `staff`
role. A staff member with only the pivot row (no role) will pass "belongs to this restaurant"
checks but fail every permission check, unable to do anything. If you build anything that creates
staff accounts, both steps are required.

## Policies

Two policy classes exist and cover order/chat authorization:

**`OrderPolicy`**

| Method | Rule |
|---|---|
| `belongsToRestaurant` | owner or staff of the order's restaurant — the base check most others build on |
| `accept` / `decline` | belongs to restaurant + the matching permission |
| `offerDriver` | belongs to restaurant + `assign driver` |
| `viewAvailableDrivers` | belongs to restaurant + `view available drivers` |
| `view` | belongs to restaurant + `view order`, **or** the order's own client, **or** the assigned driver |
| `chat` | same parties as `view`, but **no permission required** — staff without `view order` can still open threads they participate in |
| `claim` / `reject` / `markOnTheWay` / `markDelivered` | driver-only, ownership of that specific order |

**`ConversationPolicy`** — `view`/`sendMessage` both reduce to "is this user a participant of this
thread." Participation is resolved once when the conversation is created, from the order's
client/restaurant-side/driver — this also enforces tenant isolation for free.

There's no `RestaurantPolicy`/`ProductPolicy` — those are gated by plain permission checks instead
of a policy class.

## Settings, again

Repeating this because it's an easy mistake: settings are per-restaurant. Reading "the" settings
row without scoping to the current restaurant's id will silently read the wrong tenant's row (or
the legacy null-tenant one). See [DATABASE_SCHEMA.md](../../DATABASE_SCHEMA.md).
