# POS System — End-to-End Testing Checklist

Work through this top-to-bottom. Each section depends on the one above it.  
Mark `[x]` as you go. Note failures with a short comment.

---

## 0. Prerequisites

- [ ] Docker Desktop is running
- [ ] You are in the project root: `c:\development\simple-pos-system`
- [ ] All `.env` files are filled in correctly (see below)

### .env quick reference

**`cloud_backend/.env`**
```
SECRET_KEY=<long random string>
DATABASE_URL=sqlite:////app/data/cloud.db
ACCESS_TOKEN_EXPIRE_MINUTES=60
ADMIN_USERNAME=sysadmin
ADMIN_PASSWORD=<your password>
```

**`local_backend/.env`**
```
SITE_ID=<filled in after step 2.4>
SITE_KEY=<filled in after step 2.4>
CLOUD_URL=http://cloud_backend:8001
SECRET_KEY=<long random string>
LICENSE_GRACE_HOURS=24
```

**`sysadmin_panel/.env`**
```
VITE_CLOUD_URL=http://localhost:8001
```

**`waiter_pwa/.env`**
```
VITE_API_URL=http://<local-backend-ip>:8000
```

**`manager_dashboard/.env`**
```
VITE_API_URL=http://<local-backend-ip>:8000
```

---

## 1. Cloud Backend

### 1.1 Start
```bash
docker compose up cloud_backend -d
```
- [ ] Container starts without errors (`docker compose logs cloud_backend`)

### 1.2 Health check
```
GET http://localhost:8001/health
```
- [ ] Returns `{"status": "ok"}`

### 1.3 Sysadmin login
```
POST http://localhost:8001/api/auth/login
Body: { "username": "sysadmin", "password": "changeme" }
```
- [ ] Returns a JWT `access_token`
- [ ] Save token for next steps

### 1.4 List sites (empty)
```
GET http://localhost:8001/api/sites/
Header: Authorization: Bearer <token>
```
- [ ] Returns `[]` (empty list)

---

## 2. Sysadmin Panel

### 2.1 Start
```bash
docker compose up sysadmin_panel -d
```
- [ ] Container starts, `npm install` completes, dev server on port 5175

### 2.2 Open in browser
```
http://localhost:5175
```
- [ ] Redirects to `/login`
- [ ] Login form appears (username + password, NOT pin pad)

### 2.3 Login
- [ ] Enter `sysadmin` / `changeme` → redirects to `/sites`
- [ ] Sites page shows "0 registered", "Register New Site" button visible
- [ ] Summary counts (Active / Locked / Expired) visible

### 2.4 Register a site
- Click "Register New Site"
- [ ] Form fields: Restaurant Name, Owner Name, Contact Email, License Expiry
- [ ] Fill in test data, set expiry date to at least 1 year from now
- [ ] Submit → success screen shows
- [ ] **Site ID** and **Secret Key** are displayed
- [ ] Warning banner "Copy this secret key now" is visible
- [ ] "Copy .env vars" button works (copies to clipboard)
- Copy the `SITE_ID` and `SITE_SECRET` values
- Paste them into `local_backend/.env` as `SITE_ID=` and `SITE_KEY=`

### 2.5 Verify site in list
- [ ] Navigate to `/sites` → new site card appears
- [ ] Status shows yellow "No Heartbeat" (expected — local backend hasn't connected yet)
- [ ] Click site card → SiteDetailPage shows correct name, owner, expiry
- [ ] Lock/Unlock/Extend License buttons are visible

---

## 3. Local Backend

### 3.1 Start
```bash
docker compose up backend -d
```
- [ ] Container starts without errors
- [ ] Logs show: `Application startup complete`
- [ ] Logs show cloud sync attempt: either `Cloud sync OK` or `Cloud sync failed` (if cloud URL not yet reachable from inside Docker — check `CLOUD_URL=http://cloud_backend:8001`)

### 3.2 Health check
```
GET http://localhost:8000/api/system/health
```
- [ ] Returns `{"status": "ok"}` (this endpoint is exempt from license check)

### 3.3 Heartbeat appears in sysadmin panel
- [ ] In Sysadmin Panel → SiteDetailPage: "Last seen" updates to recent timestamp
- [ ] Status indicator turns green "Active"
- [ ] Last IP field shows the Docker container IP

> Note: Cloud sync runs immediately on startup, so this should update within seconds.

### 3.4 Auth — create initial manager
If no manager user exists in the DB yet, seed one via the seed script or direct API call:
```bash
docker compose exec backend python seed.py
```
- [ ] Seed runs without errors (or manager already exists)

### 3.5 Auth — login as manager
```
POST http://localhost:8000/api/auth/login
Body: { "username": "manager", "pin": "1234" }
```
- [ ] Returns `{ access_token, user: { id, username, role: "manager" } }`

### 3.6 Auth — login as waiter
```
POST http://localhost:8000/api/auth/login
Body: { "username": "<waiter-username>", "pin": "<pin>" }
```
- [ ] Returns token with `role: "waiter"`

### 3.7 Tables endpoint
```
GET http://localhost:8000/api/tables/
Header: Authorization: Bearer <manager-token>
```
- [ ] Returns list of tables (may be empty if not seeded)

### 3.8 Products endpoint
```
GET http://localhost:8000/api/products/
Header: Authorization: Bearer <manager-token>
```
- [ ] Returns list of products and categories

---

## 4. Manager Dashboard

### 4.1 Start
```bash
docker compose up manager_dashboard -d
```
- [ ] Dev server starts on port 5174

### 4.2 Open in browser
```
http://localhost:5174
```
- [ ] Redirects to `/login`
- [ ] PIN pad + username field visible

### 4.3 Login
- [ ] Enter manager username + PIN → redirects to `/dashboard`
- [ ] Dashboard shows table grid (empty or with existing tables)
- [ ] No console errors

### 4.4 Tables page
- [ ] Navigate to Tables → existing tables shown
- [ ] Create a new table (e.g. Table 10) → appears in list
- [ ] Table groups section visible

### 4.5 Products page
- [ ] Navigate to Products → existing products shown
- [ ] Create a new category
- [ ] Create a new product in that category, assign a price
- [ ] Toggle product availability → greyed out when unavailable
- [ ] Assign a printer zone to a product (if printer is configured)
- [ ] Product image upload works on an existing product

### 4.6 Waiters page
- [ ] Navigate to Waiters → existing waiters shown
- [ ] Create a new waiter (username + PIN)
- [ ] Block/unblock a waiter works
- [ ] Reset PIN works

### 4.7 Dashboard live grid
- [ ] Open an order from the Waiter PWA (step 5 below), then return here
- [ ] Table card updates within 30 seconds (polling interval)
- [ ] Click table → OrderDetailPage loads with correct items

### 4.8 Reports page
- [ ] Navigate to Reports → no crash
- [ ] Date picker works, summary loads (may be empty)

### 4.9 Settings page
- [ ] Navigate to Settings → no crash
- [ ] Printer config visible

---

## 5. Waiter PWA

### 5.1 Open on phone (or browser)
```
http://<local-machine-ip>:5173
```
- [ ] App loads, redirects to `/login`
- [ ] Username field + PIN pad visible

### 5.2 Login as waiter
- [ ] Enter waiter credentials → redirects to table list
- [ ] Only active tables visible (not deactivated ones)

### 5.3 Open a table order
- [ ] Tap a table → if no open order, "Open Order" button appears
- [ ] Tap "Open Order" → order is created, table turns active

### 5.4 Add items
- [ ] Category tabs visible at top
- [ ] Tap a product → added to order
- [ ] Products with options show option selector
- [ ] Products with removable ingredients show toggle list
- [ ] Item with note field → can add freetext note

### 5.5 Verify print routing
- [ ] After adding items: check printer (if connected) received the ticket
- [ ] If no physical printer: check `docker compose logs backend` — should show print attempt log (success or failure, NOT a crash)

### 5.6 View order total
- [ ] Order total shows correct sum (base price + selected option deltas)
- [ ] Individual item prices shown

### 5.7 Partial payment
- [ ] Select some items → "Mark as Paid" → those items turn grey/paid
- [ ] Order status changes to "partially_paid"
- [ ] Remaining total updates

### 5.8 Full payment and close
- [ ] Mark all items as paid → order status = "paid"
- [ ] "Close Order" button becomes active
- [ ] Tap "Close Order" → confirmation prompt
- [ ] Confirm → order closes, table returns to "available"

### 5.9 Multi-waiter scenario
- [ ] From Manager Dashboard, add a second waiter to the order
- [ ] Second waiter can see and manage the table in their PWA

### 5.10 Blocked waiter
- [ ] From Manager Dashboard, block a waiter
- [ ] That waiter is immediately redirected to login on their next PWA request (401 interceptor)

---

## 6. License / Lock Testing

### 6.1 Lock site from sysadmin panel
- [ ] In Sysadmin Panel → SiteDetailPage → Lock Site → enter reason → confirm
- [ ] Site shows "Locked" status with red indicator
- [ ] Within the next sync cycle (or restart local backend to force immediate sync):
  - Local backend logs show lock status received
  - All local backend endpoints return HTTP 423 (except `/api/system/health`)
  - Waiter PWA shows error / cannot load data
  - Manager Dashboard shows error

### 6.2 Unlock
- [ ] In Sysadmin Panel → Unlock Site
- [ ] After next sync: local backend resumes normal operation

### 6.3 Expired license
- [ ] In Sysadmin Panel → Extend License → set expiry date in the PAST → save
- [ ] After local backend sync: returns HTTP 402 on all endpoints
- [ ] Restore to future date → normal operation resumes

### 6.4 Cloud unreachable (grace period)
- [ ] Stop cloud backend: `docker compose stop cloud_backend`
- [ ] Local backend continues to serve requests (last known state + 24h grace)
- [ ] Logs show repeated `Cloud sync failed` warnings
- [ ] Restart cloud backend → next sync succeeds

---

## 7. Known Issues to Watch For

- Product image upload only works on existing products (not at creation time)
- Printer zone must be assigned via Manager Dashboard before items will route to a printer
- Some manager/PWA interactions have rough edges (noted in CLAUDE_CODE_INSTRUCTIONS.md)
- `SITE_KEY` in `local_backend/.env` must match the secret generated at site registration (copy it immediately, it's shown only once)

---

## Done?

All steps checked → the system is fully operational end-to-end.  
Log any failures as GitHub issues or in `CLAUDE_CODE_INSTRUCTIONS.md` under a new "Phase 5 Known Issues" section.
