# Shiprex Lite — Architecture

## Multi-tenancy
- **Shared database**, every tenant-owned row carries `company_id`.
- A **company** = one SaaS instance (a shipping company). It has exactly **one manager** user
  (the operation manager) in the MVP.
- The **root** super-admin (`users.role = 'root'`, `company_id = null`) manages every company.
- Tenant scoping is enforced in `middleware/tenant.js` (`resolveTenant`):
  - manager → locked to their own `company_id`;
  - root → must pass `?company_id=` for tenant routes (otherwise uses `/admin/*`).

## Auth
- JWT Bearer tokens (`utils/jwt.js`). Payload: `{ id, email, role, company_id }`.
- `requireAuth` validates the token; `requireRoot` gates the admin console.

## Order engine (mirrors Pro concepts)
- **Manual fee (Lite)**: the operator enters `fees` on each order. The destination `city` is still
  resolved to a zone (`zones.service.resolveZoneForCity`) and stored on `zone_id` for display/info,
  but it does **not** set the fee. Zone-based auto-pricing is a **Pro** feature (PRP-005). (Pro KB §9.2/§12.)
- **Daily cap (Lite)**: `orders.service.create` enforces `ORDER_DAILY_CAP` (default 30) orders per
  company per UTC day; rejection returns `429 { code:'DAILY_CAP', meta:{cap,used,resets_at} }`.
  Companies on `plan='pro'` are uncapped. Usage: `GET /api/orders/usage/today`. (PRP-001.)
- **Status choke point**: all status changes go through `orders.service.updateStatus`, which:
  1. blocks changes on terminal `collected`;
  2. enforces the transition graph (`config/constants.STATUS_TRANSITIONS`) — root may override;
  3. writes an `order_actions` audit row.
  This is the single place to later hang downstream effects (SMS, invoicing, stock) when those Pro
  modules are introduced. (Pro KB §9.3.)
- **Active vs terminal** statuses (`ACTIVE_STATUSES` / `TERMINAL_STATUSES`) drive the dashboard split.

## Feature gating (the Lite funnel)
- `config/constants.FEATURES` maps each module to `{ enabled, plan, label }`.
- `GET /api/features` exposes the manifest; the client renders enabled modules and locked
  "Upgrade to Pro" entries from it.

## Data model (MVP)
- `companies` — tenant: name, slug, contact, email, phone, country, currency, plan, status.
- `users` — root + managers; `company_id` null for root.
- `zones` — per-company pricing areas; `is_default` is the fallback for unknown cities.
- `orders` — receiver, city, cod, auto-priced fees, status; unique `reference` per company.
- `order_actions` — per-order audit trail.

## Conventions
- JSON envelope everywhere: `{ success, data | message, errors?, meta? }` (`utils/response.js`).
- Validation via `zod` at the route boundary.
- Money stored as `DECIMAL(12,2)`.
- All timestamps UTC (`timezone: 'Z'`).

## Deliberate deviations from Pro
- Clean status names (no `statues` misspelling); no `DID()/UNDID()` id offset.
- No CakePHP plugin/hook engine — modules are plain Express routers; feature flags replace toggles.
