# Caresoft Visitor Management System (VMS)

QR-based hospital visitor management. Plain PHP 8.3 + PDO + MySQL + Bootstrap 5.
No frameworks, no Composer.

Verified working end to end: schema loads clean, the full visitor journey issues a pass,
gate check-in/check-out records duration, all three portals return 200 with zero PHP notices.

---

## The three levels

| Level | Who | Entry point | Can do |
|---|---|---|---|
| 1 | Caresoft | `/superadmin/login.php` | Create clients, set plan + validity + active/inactive, configure the Caresoft HIS link per client, view cross-client usage and sync logs |
| 2 | Hospital admin | `/client/login.php` | Live inside count, MIS reports, gates + QR posters, wards, purposes, users, blocked list, marketing/education content, settings |
| 3 | Gate user | `/client/login.php` → `/gate/index.php` | Scan pass, allow entry, mark exit, walk-in registration, deny with reason, shift log |

Visitors need no login: `/visitor/index.php?g=<gate-qr-token>`

---

## Visitor journey

1. Scan gate QR → signup (full name + mobile)
2. WhatsApp OTP → verified (rate limited, SMS fallback, resend timer)
3. Details: purpose, patient UHID (live lookup from HIS), ward/bed, relation, **email optional**,
   marketing/education content blocks, consent tick, opt-in tick
4. Gate pass issued with pass number + QR, valid till a purpose-driven expiry

Guards enforced at issue time: blacklist, one open pass per mobile, ward/patient visitor limit,
subscription validity.

---

## Install

```bash
mysql -u root -p < db/schema.sql
mysql -u root -p < db/seed.sql        # demo tenant, optional
cp config/config.sample.php config/config.php   # then edit
chmod -R 775 storage
```

Point the web root at the project root (or map `/` to `public/index.php` for the marketing site).
Required PHP extensions: `pdo_mysql`, `mbstring`, `curl`.

QR codes are generated locally by `inc/qrcode.php` — no outbound call, no GD, no Composer.

Set in `config/config.php`: DB credentials, `APP_BASE_URL`, `APP_KEY`, and the WhatsApp provider.

**Seed logins** (change immediately): platform `admin@caresoft.in / Admin@123`;
hospital `VMS-DEMO / admin / Admin@123`; gate `VMS-DEMO / gate1 / Admin@123`.

### WhatsApp OTP

`WA_PROVIDER` supports `meta` (Cloud API), `gupshup`, `interakt`, or `log`.
`log` writes the OTP to `storage/logs/otp.log` — use it for UAT only, never production.
You need an approved template (default name `visitor_otp`) with the OTP as the body parameter.

---

## Step 1: Caresoft HIS integration

Configured per client at Level 1 (`/superadmin/caresoft.php`), with a connection test,
manual ward/doctor pull and a "probe a patient" tool. Everything degrades to manual entry
when the link is off or the HIS is unreachable — no screen breaks.

**The HIS side is built.** `his-api/` is a working reference implementation of all four
endpoints — auth, rate limiting, logging and the JSON contract are done; the HIS team maps
their tables in one file (`his-api/lib/map.php`) and runs `his-api/sql/mssql-objects.sql`.
See `his-api/README-HIS.md`. A MySQL demo fixture is included so the whole path can be
exercised before the real HIS is wired up.

The four calls:

```
GET  {base}/vms/patient?uhid=UH0044219&hospital_code=XX
  → {"ok":true,"patient":{"uhid","ipd_no","name","ward_code","ward_name",
                          "bed_no","doctor_name","status","visitors_allowed"}}

GET  {base}/vms/wards?hospital_code=XX
  → {"ok":true,"wards":[{"code","name","type","floor"}]}

GET  {base}/vms/doctors?hospital_code=XX
  → {"ok":true,"doctors":[{"code","name","speciality"}]}

POST {base}/vms/visitor-log
  ← pass_no, visitor_name, mobile, email, relation, purpose, uhid, ipd_no,
    ward_code, bed_no, issued_at, valid_till, in_at, out_at, status
  → {"ok":true,"ref":"..."}
```

Auth is `apikey` (X-API-Key / X-API-Secret headers), `bearer` or `basic` — selectable per client.
Every call is logged in `caresoft_sync_log` with URL, HTTP code, response and latency.

`visitors_allowed` from the HIS overrides the local ward limit, so the ICU rule lives in one
place. The VMS counts anyone inside **plus** anyone holding a live unused pass, so a ward set
to 1 cannot have two attendants collect passes and arrive together.

Pushes that fail during an HIS outage are backfilled by `cron/push_pending.php`.

---

## Files

```
config/config.php          environment: DB, WhatsApp, keys
inc/db.php                 PDO + query/insert/update helpers + audit
inc/helpers.php            escaping, CSRF, flash, settings, pass numbers, QR
inc/auth.php               all three levels + licence enforcement
inc/whatsapp.php           OTP generate/verify + Meta/Gupshup/Interakt adapters + cURL
inc/caresoft.php           HIS client: patient lookup, ward/doctor sync, visit push
inc/layout.php             Bootstrap shell for the admin portals
public/index.php           marketing landing page + demo request form
visitor/                   QR → signup → OTP → details → pass (public, no login)
superadmin/                Level 1
client/                    Level 2
gate/                      Level 3
cron/push_pending.php      re-pushes visits that missed the HIS during an outage
his-api/                   reference HIS-side endpoints (deploy on the HIS server)
db/schema.sql, db/seed.sql
```

---

## Before go-live

- Change all seed passwords, set a real `APP_KEY`, set `APP_ENV = 'prod'`
- Force HTTPS (the session cookie only sets `secure` when HTTPS is on)
- `qr_img()` calls a public QR generator. If the hospital blocks outbound traffic,
  drop in a local PHP QR library and change that one function.
- Add a nightly cron for `expire_stale_passes()` if you want expiry independent of page loads
- Schedule `cron/push_pending.php` every 15 minutes once the HIS link is live
- Set a retention policy on `otp_log`, `audit_log` and `caresoft_sync_log`

---

## Not built yet (next phases)

Visitor photo capture, pre-approved/appointment passes, WhatsApp pass delivery (currently the pass
is a link on screen), staff/vendor ID card printing, turnstile and boom-barrier integration,
face recognition for repeat attendants, and a hospital-group roll-up dashboard across units.

---

## SEO / AEO / GEO

The marketing site carries a search, answer-engine and generative-engine layer.
`inc/seo.php` is written to be dependency-free so the same module drops into the
other Caresoft PHP sites — only `public/seo-config.php` changes per site.

**One source of truth.** Visible FAQ copy and FAQ structured data are rendered from
the same array in `seo-config.php`, so they cannot drift apart — the usual reason
FAQ rich results get pulled.

- **SEO** — titles, meta descriptions, canonicals, Open Graph and Twitter cards,
  `robots.txt`, dynamic `sitemap.xml`, single-`h1` hierarchy, trailing-slash and
  `index.php` canonicalisation
- **AEO** — `FAQPage` schema with ten self-contained 40–90 word answers, a `HowTo`
  for the visitor journey, a speakable definitional lede high on the page,
  `max-snippet:-1` so answers can be quoted in full
- **GEO** — `/llms.txt` generated from the same config, an `Organization` and
  `SoftwareApplication` graph with explicit category, audience and feature list,
  and a set of short checkable facts a model can cite
- **Programmatic pages** — `/visitor-management-system-<city>` for 12 cities, each
  with location-specific framing, its own leading FAQs, `Service` + `BreadcrumbList`
  schema, and cross-links. Same pattern as the per-state `/hms/` pages on caresoft.co.in.

**Private surfaces are locked out of the index.** Every visitor, gate, client and
superadmin page sends `X-Robots-Tag: noindex, nofollow, noarchive, nosnippet` plus a
matching meta tag, and `Referrer-Policy: no-referrer` — a gate pass URL carries a
single-use token, and without that header the token travels in the `Referer` to any
third-party asset the page loads.

Before go-live: set the real sales phone and email in `seo-config.php`, add
`/assets/og-vms.png` (1200×630) and `/assets/logo.png`, and submit the sitemap in
Search Console.
