# SaasSwitcher

A drop-in module that turns one Laravel host into a **multi-project SaaS**: the
super-admin signs in, chooses a project (each project = its own database), then
signs in as a dev/test user inside that project.

## Why a module
Everything is self-contained so it ports to the cloud version by copying this
folder. The service provider registers, at boot, all of:
- **one DB connection per project** — cloned from the host's default `mysql`
  connection (host/user/pass/charset) with the database swapped, so **no edit
  to `config/database.php`** is needed;
- the **connection-switch middleware** (appended to the `web` group, so no edit
  to `app/Http/Kernel.php`);
- **routes** (`Routes/web.php` via `start.php`) and **views**.

## Per-business module visibility (entitlements)
Module visibility is driven **per business by `business.module_entitlements`**
(a DB column), NOT by a cookie. This is **entirely self-contained in this
module — no core-script files are changed.** The old cookie-based
`ProjectActivator` is now a no-op `FileActivator` subclass (kept only because
`config/modules.php` references the class name), so **all enabled modules boot
for every project** and the entitlement layer hides/blocks what a tenant isn't
entitled to.

The gate is `Modules\SaasSwitcher\Support\ModuleEntitlementService`
(global helper `module_entitled('VetClinic')`, defined in `Support/helpers.php`
and loaded by the service provider). Enforced in three places (all in modules):
- **menu** — each hub/satellite `modifyAdminMenu()` / `*MenuItems()` returns early
  when not entitled;
- **routes** — the `module:<Name>` middleware (alias registered by this module's
  provider) 403s direct-URL access;
- **staff APIs** — `StaffAuth` adds the same check.

Because it is a DATA check (not a `can()` permission), it is immune to the
`Admin#<business>` `Gate::before` bypass in `App\Providers\AuthServiceProvider`.
Values: `NULL` = fully entitled (fail-open); `[]` = entitled to nothing (clean
base ERP); `[list]` = exactly those modules.

Gateable modules + per-project default sets live in this module's
`Config/config.php` (`saasswitcher.gateable_modules` / `saasswitcher.project_modules`).
Super-admins manage entitlements per project at **`/saas/entitlements`**, or via:
```
php artisan entitlements:seed            # all projects (reads project_modules)
php artisan entitlements:seed --dry-run  # preview
```

## "Switch project" banner (impersonation)
While a super-admin is impersonating a project, a fixed banner (bottom-left)
shows the active project + a **Switch project** button (→ `/saas/exit`). It is
injected via `Http/Controllers/DataController@get_additional_script` (the ERP's
`additional_html` hook) — no core view edited. The ERP only calls that hook for
**installed** modules, so each project DB needs the system property
`saasswitcher_version` set (any value), e.g.:
```sql
INSERT INTO system (`key`,`value`) VALUES ('saasswitcher_version','1.0.0');
```
(run once per project database).

## Deploy to another install (cloud)
1. Copy `Modules/SaasSwitcher` into the target's `Modules/`.
2. Add `"SaasSwitcher": true` to `modules_statuses.json` (or enable via the UI),
   and enable the union of vertical modules you want available.
3. Run `php artisan entitlements:seed` so each project DB gets its module
   entitlements (+ vertical permissions). See "Per-business module visibility".
4. Set the `saasswitcher_version` system property in each project DB (for the banner).
3. Set the host's default `DB_DATABASE` to the **host project** DB (the one with
   the super-admin), and set the per-project databases in `.env`:
   ```
   PROJ_STATIC_DB=...
   PROJ_SHIFA_DB=...
   PROJ_VET_DB=...
   ```
4. `composer dump-autoload` (or `php artisan module:enable SaasSwitcher`).
5. Visit `/<public>/saas` as the super-admin.

Projects are declared in `Config/config.php` → `projects[]` (key, label,
connection, database, vertical, icon, color, `is_host`). Add/rename projects
there; the connection is created automatically.

## Flow
`/saas` (chooser, super-admin only, host DB) → `/saas/select/{key}` →
`/saas/users` (picker, queried on the project connection via `User::on()`) →
`POST /saas/login-as/{id}` (impersonate; sets `saas_impersonating`) → `/home`
(the `web`-group middleware now points the default connection at the project DB
for the rest of the session) → `/saas/exit` (logout + back to chooser).

## Files
- `Config/config.php` — projects + session keys
- `Providers/SaasSwitcherServiceProvider.php` — registers connections, middleware, views
- `Support/ProjectRegistry.php` — reads the project catalogue from config
- `Http/Controllers/SaasController.php` — chooser / select / users / loginAs / exit
- `Http/Middleware/ResolveProjectConnection.php` — impersonation-gated connection swap
- `Routes/web.php`, `Resources/views/{choose,users}.blade.php`

## Notes
- `SESSION_DRIVER=file` is assumed so the connection swap never disturbs the
  super-admin session.
- The super-admin stays authenticated on the host connection while browsing the
  chooser/picker; only **after** impersonation does the default connection
  follow the impersonated project user.
