# SaaS One — combined multi-project host

One Laravel host that drives **three projects, each in its own database**, with a
super-admin "choose a project → sign in as a dev/test user" flow.

## Projects (config/database.php + .env)

| Project | Connection  | Database (.env)            | Vertical |
|---------|-------------|----------------------------|----------|
| Static  | `proj_static` | `PROJ_STATIC_DB` = static_test09022026 | base ERP |
| Shifa   | `proj_shifa`  | `PROJ_SHIFA_DB`  = shifa_one_demo      | human clinics (ClinicCore) |
| Vet     | `proj_vet`    | `PROJ_VET_DB`    = vet_one_demo        | veterinary (VetClinic) |

The host's **default `mysql` connection points at the static DB**, where the
super-admin lives. `SESSION_DRIVER=file`, so swapping connections never breaks
the session.

## Flow

1. Super-admin signs in normally (`/login`) — resolved against the static DB.
2. `/saas` → **chooser** (super-admin only): three project cards.
3. `/saas/select/{key}` → records the project in session.
4. `/saas/users` → **dev/test user picker**, queried from the project DB via
   `User::on('proj_xxx')` (super-admin still authenticated on static).
5. `POST /saas/login-as/{id}` → impersonates that project user
   (`saas_impersonating=true`).
6. From then on `App\Http\Middleware\ResolveProjectConnection` (in the `web`
   group) swaps the **default** connection to the project's for every request,
   so `/home` and the whole ERP run against the project DB.
7. `/saas/exit` → logout + back to the chooser.

## Where it lives — `Modules/SaasSwitcher` (portable)
The whole feature is now a **self-contained module** so it can be copied to the
cloud version without touching core files. Its service provider registers, at
boot: the per-project DB connections (cloned from `mysql`), the connection-switch
middleware (appended to the `web` group), routes and views. Core `config/database.php`,
`app/Http/Kernel.php` and `routes/web.php` are left untouched.

To deploy elsewhere: copy `Modules/SaasSwitcher`, add `"SaasSwitcher": true` to
`modules_statuses.json`, set `PROJ_*_DB` in `.env`, `composer dump-autoload`.
See `Modules/SaasSwitcher/README.md`. Only `.env` (`PROJ_*_DB`, host `DB_DATABASE`)
is host-specific.

Entry point: **`https://localhost/saas-one/public/saas`** (after super-admin login).

## Verified
- All 3 connections resolve (static 131 / shifa 7 / vet 6 users).
- Routes register, controller + middleware autoload, default connection = static.
- **All 3 DBs already carry the full union schema** (static has `clc_patients`,
  shifa has `vc_pets`, vet has `clc_patients`) — they're forks where both the
  clinic and vet migrations ran. So the union of modules will NOT hit
  missing-table fatals against any project DB; projects differ by *data* +
  *enabled modules*, not schema. This de-risks the multi-DB switch.
- Each project DB has business + user rows for impersonation/`/home`
  (static: 4 biz, shifa: biz 8, vet: biz 10).
- Still to do in a browser: full login → choose → impersonate → `/home` render
  via the XAMPP vhost (CLI can't exercise the HTTP/auth stack end to end).

## Remaining (next passes)
1. **Union of modules.** The host carries shifa-one's module set. To fully drive
   every project, copy in the modules the others have and this one lacks:
   - from `vet-one`: `VetSite`, `VetStaffPortal`, `IdexxLab`
   - from `static-purchase`: `Gym`, `Hms`, `Vrc`, `ProductCompat`
   NOTE: `TpsTransfer` was renamed to `AdvancedTransfer` (already present here from
   shifa-one). Its **latest version lives in vet-one** — source `AdvancedTransfer`
   from `vet-one` if refreshing it. Reconcile divergent forks of shared `app/` files.
2. **Per-project module enablement.** `modules_statuses.json` is global; ideally
   each project activates only its own modules at runtime (custom module
   activator keyed on `session('saas_project')`). Until then the union is
   enabled and modules rely on `Schema::hasTable` guards when a table is absent
   in the active DB.
3. **Exit/return UX.** Exiting impersonation currently logs out (you re-login as
   super-admin). Could be upgraded to stash/restore the super-admin session
   (the ERP already has a `previous_user_id` impersonation mechanism to reuse).
4. **Browser E2E.** Validate the full login → choose → impersonate → /home path
   against each live DB via the XAMPP vhost.
