# Tenancy and deployment model

## One clinic, one installation

Each clinic gets its own copy of the application, its own database, and its own
domain. There is no shared deployment and no tenant identifier threaded through
the schema.

That is a deliberate choice rather than an unfinished one. Tenancy here is
**physical**: two clinics' records are in two different databases on two
different servers, so no query, no missing `WHERE`, and no bug in a scope can
show one clinic another clinic's patients. In a shared-database design that
guarantee rests on every query in the system being correct forever. For medical
records, the stronger guarantee is worth the extra deployment.

The rest of the architecture already assumes it:

- **Licensing** activates a key against *this* installation.
- **Updates** downloads and verifies releases for *this* installation.
- **Backups** dump one database.

A hosted, multi-clinic deployment would make all three mean something different.

## What `branches` is, and is not

`branches` is **inside** one clinic — a practice with a main surgery and two
satellite locations. `BelongsToBranch` scopes records between those locations.

It is **not** a tenant boundary. Two clinics are never rows in one `branches`
table, and nothing in the application is written on the assumption that they
could be.

## Running on any domain

A clinic runs the software on whatever address they like —
`app.metabolism.pk`, `emr.thesurgery.co.uk`, a bare subdomain of their own
site. Two things need setting and nothing else:

1. **`APP_URL`** in `.env` — used for links in mail, and for URLs generated
   outside a request such as from the scheduler.
2. **The web server virtual host**, pointed at `public/`, with a certificate
   for that name.

Everything generated during a request follows the request's own host. There is
no forced root URL, `SESSION_DOMAIN` is unset so the session cookie binds to
whatever host served it, and no shipped view links to a specific company.
`tests/Feature/DomainIndependenceTest.php` holds all of that in place.

### Branding

The login page names a supplier only when one is configured:

```dotenv
PRODUCT_NAME="Clinic Management System"
PRODUCT_VENDOR="Acme Health Systems"
PRODUCT_VENDOR_URL=https://acme.example
```

Leave `PRODUCT_VENDOR` at its default and no attribution is rendered at all. A
reseller shipping under their own name sets these; a clinic who wants their
login page to mention nobody leaves them alone. Neither has to edit a file that
the next update would overwrite.

## Provisioning a new clinic

Four things a person has to do, and one command.

1. Copy the release onto the server and point a virtual host at `public/`.
2. Create an empty database and a user for it.
3. Set `.env`: `APP_URL`, the database credentials, `PRODUCT_*`, and the mail
   settings.
4. Run the installer:

```bash
php artisan clinic:install --license=<key>
```

It generates the application key if there is not one, migrates, reconciles
modules, seeds branches, roles, permissions and settings, prompts for the first
Super Admin, activates the licence, and rebuilds the module manifest cache — in
that order, which is the reason it exists as one command rather than nine.
`--name`, `--email`, `--password` and `--branch` are passed through to
`install:admin` for an unattended run; omit `--password` and it is prompted for
without echo, since an option is visible in shell history and the process list.

It refuses to run on an installation that already has accounts, and exits
non-zero when it does, so a wrapper script can tell. Further administrators are
added from Users inside the application, where the action is authorised and
audited.

5. Schedule `php artisan schedule:run` every minute, for reminders, the licence
   heartbeat and update checks.

Nothing in that list is specific to a domain beyond step 3.

### What the installer deliberately leaves alone

It does not write `.env` — the credentials must already be in place for
anything to connect — and it does not cache config or routes. Both of those
carry traps documented in `DEPLOYMENT.md`, and which is right depends on the
server rather than on the software, so it prints the decision rather than
making it.

### There is no web installer

`app/Http/Middleware/EnsureInstalled.php` was written to send an un-installed
application to a browser wizard, for clinics on cPanel with no shell. The wizard
was never built: there is no `install.index` route, no controller and no view,
and the middleware is registered nowhere — which is the only reason its
redirect to a non-existent route does not take the site down. Either build the
wizard on top of `clinic:install` or delete the middleware; leaving a loaded
gun in the tree is the worst of the three.

## If a hosted tier is ever wanted

Serving many clinics from one deployment is a different product, and the honest
summary of the work is:

- A central registry of clinics, and hostname → database resolution per request.
- Per-tenant isolation of everything currently global: the cache prefix
  (settings are cached, so a shared prefix would show one clinic another's
  clinic name and timezone), `storage/app/private` (patient documents), the
  licence state file, and sessions.
- Provisioning that creates a database and runs migrations on demand.
- A scheduler that loops tenants rather than running once.
- A decision about what Licensing and Updates mean when the vendor, not the
  clinic, owns the server.

Database-per-tenant would be the design — it keeps the isolation described at
the top of this document. Row-level tenancy in a shared database would not, and
is not worth it here.
