# Kaabe SaaS POS 1.0.0 — Staging acceptance checklist (InMotion)

Run on the **staging** installation only (`STAGING-INMOTION.md`). Use test data only. Create two test businesses:
**Alpha** (plan *Professional*) and **Beta** (plan *Free*). Every line must be ✅ before staging is accepted.
Each check was already executed in the isolated test environment (see `TESTING-REPORT.md`); here it proves the
InMotion account behaves the same.

| # | Check | How | Expected result | ✅ |
|---|---|---|---|---|
| 1 | Super Admin login | `https://admin.<base>` → e-mail + password | Signed in; wrong password is rejected | ☐ |
| 2 | 2FA | First login → Account security → scan secret → 6-digit code; sign out/in | 2FA enabled; code asked at every login; a wrong code is rejected | ☐ |
| 3 | Create test business | Businesses → **Provision business**: Alpha, slug `alpha`, plan Professional, owner `alphaowner`, branch "Main", auto-activate. Repeat for Beta (`beta`, Free) | Business page opens, state *pending* | ☐ |
| 4 | Automatic provisioning | Wait ≤ 2 min (cron) → business → *Provisioning* tab | All steps OK; state **ready**, status **active**; cPanel → MySQL Databases shows `<user>_t00001` and user `<user>_t00001` with privileges on that database only; `alpha.<base>` exists with an SSL certificate | ☐ |
| 5 | Owner login | Business → Users → *Temporary password* for `alphaowner` (shown once) → `https://alpha.<base>` | Sign-in accepted | ☐ |
| 6 | Owner password change | Continue after step 5 | POS forces a new password; after that the business dashboard opens; the temporary password no longer works | ☐ |
| 7 | Create branch | Super Admin → Alpha → Branches → add "Alpha Branch 2" | Created; adding branches beyond 3 on Professional is refused (limit 3) | ☐ |
| 8 | Create user | Super Admin → Alpha → Users → add a *Cashier* for "Main" | Temporary password shown once; cashier signs in, must change password, cannot open Store Config or Employees | ☐ |
| 9 | Create product | POS → Items → New item "Test Rice", category "Food", cost 20, price 30, quantity 10 at Main | Item saved; appears in search | ☐ |
| 10 | Create customer | POS → Customers → New "Test Customer" | Saved; appears in search | ☐ |
| 11 | Purchase / receive stock | POS → Receivings → add "Test Rice" ×20 → Complete | Receiving saved | ☐ |
| 12 | Make a sale | POS → Sales → add "Test Rice" ×3 → Cash 90 → Complete | Receipt shows 3 × 30 = 90 | ☐ |
| 13 | Verify inventory | Items → "Test Rice" → Inventory | 10 + 20 − 3 = **27** at Main; Branch 2 unchanged | ☐ |
| 14 | Central sales total | Wait ≤ 5 min (or Super Admin → Sales → Alpha → *Sync now*) | Super Admin → Sales shows Alpha today = **1 sale, 90.00**, matching POS → Reports → Summary sales (today) | ☐ |
| 15 | API key | Alpha POS → Store Config → API → *Add API key* (read/write). Terminal: `curl -s -o /dev/null -w "%{http_code}\n" -H "x-api-key: <key>" https://alpha.<base>/index.php/api/v1/items` | `200`; without the header `403`; the same key on `https://beta.<base>/index.php/api/v1/items` → `403`; on Beta (Free) *Add API key* is refused | ☐ |
| 16 | Module permissions | Beta (Free) POS: open `https://beta.<base>/index.php/appointments`, `/suppliers`, `/receivings`, `/giftcards` | Menu items are hidden and the URLs go to the *no access* page; the same pages work on Alpha | ☐ |
| 17 | Plan limits | Beta (Free): Super Admin → Beta → Branches → add a 2nd branch; Users → add a 3rd user | Both refused with "Your Free plan allows up to …" | ☐ |
| 18 | Suspend business | Super Admin → Alpha → Status → **Suspended**, reason "Staging test" | Status suspended; audit log entry | ☐ |
| 19 | POS blocked immediately | Refresh the open Alpha POS tab; open the login page; call the API (step 15) | Blocked page with the reason (HTTP 403) within a few seconds; API 403; Beta keeps working | ☐ |
| 20 | Reactivate | Super Admin → Alpha → Status → **Active** | Status active | ☐ |
| 21 | POS works again | Sign in to Alpha POS; make one more sale | Works; central total updates at the next sync | ☐ |
| 22 | Backup | Terminal: `bash ~/kaabe/scripts/backup-all.sh` then `cd ~/kaabe-backups/<newest> && sha256sum -c SHA256SUMS && ls -l` | Central + Alpha + Beta dumps, config and registry; all checksums **OK**; files `-rw-------` | ☐ |
| 23 | Restore | cPanel → MySQL Databases: create `<user>_restoretest` + user with ALL PRIVILEGES. Terminal: `gzip -dc ~/kaabe-backups/<newest>/alpha__<user>_t00001.sql.gz \| mysql -u <user>_restoretest -p <user>_restoretest`, then compare (below) | Import without errors; comparison PASS; then drop `<user>_restoretest` and its user | ☐ |
| 24 | Tenant isolation | Open Alpha's item URL (`/index.php/items/view/<id>`) on Beta's subdomain; search Alpha's customer name in Beta; open `https://unknown.<base>` | Beta never shows Alpha data; unknown subdomain → "No business is registered at this address" (404) | ☐ |

### Restore comparison (step 23)

No business password is needed: give the temporary restore user read access to Alpha's database for the comparison.

1. cPanel → MySQL Databases → *Add User To Database*: user `<user>_restoretest` → database `<user>_t00001` → tick **SELECT only**.
2. Terminal:
   ```bash
   cd ~/kaabe/migration
   export M7_DB_PASSWORD='<restoretest password>'
   php m7_profile.php --host=localhost --db=<user>_t00001      --user=<user>_restoretest --label=live     --out=$HOME/restorecheck
   php m7_profile.php --host=localhost --db=<user>_restoretest --user=<user>_restoretest --label=restored --out=$HOME/restorecheck
   php m7_compare.php $HOME/restorecheck/live.json $HOME/restorecheck/restored.json ; echo "exit=$?"   # exit=0 → identical
   ```
   Make no sales on Alpha between the backup and the comparison (or the live side will legitimately differ).
3. Remove the SELECT privilege, then delete `<user>_restoretest` (database and user).

### Also confirm once
- `http://admin.<base>` and `http://alpha.<base>` redirect to `https://` (301).
- Super Admin → **Audit** shows the actions above; **Health** shows both businesses reachable.
- cPanel → Cron Jobs: the `schedule:run` line is present; `~/kaabe/platform/storage/logs/` has no errors after the tests.

**Accepted by:** ______________________ **Date:** ____________ **Staging URL:** ______________________
