# 04 — Client Management

## Overview

Read-mostly oversight of end-user **clients** (the event organisers using OotboApps Client). Lists, detail page with event history, and an "Add Client" creation surface that lets ops create a client account on their behalf.

**Status:** Live

> This is a window into the *shared* `users` table (filtered by `user_type='client'`), not VisoAdmin-owned data. Most writes to clients happen in the public Client app via OotboAPI; VisoAdmin only does manual creation + read.

---

## User Stories

| ID | As a | I want to | So that |
|----|------|-----------|---------|
| CLI-01 | Ops | See a paginated list of all clients filtered by phone | I can find a specific client |
| CLI-02 | Ops | Open a client detail page with basic info + events history | I can debug their issues |
| CLI-03 | Ops | See counts of total / upcoming / past / cancelled events for a client | I can gauge engagement |
| CLI-04 | Ops | Manually create a client account when they can't onboard themselves | Ops-assisted signup is supported |
| CLI-05 | Ops | Check whether a phone number is already a client before creating | Avoid duplicate accounts |

---

## Screens & Flows

```
┌─────────────────────────┐
│ /user/clientlist        │
│ clientlist.blade.php    │
└────────────┬────────────┘
             │  DataTable AJAX
             ▼
┌─────────────────────────────────────────────┐
│ /user/ajaxUserList?user_type=client         │
│ → EventUsers::getUsersList() (raw SQL)      │
│   filters: mobile, service, location        │
└────────────┬────────────────────────────────┘
             │ Click "View" per row
             ▼
┌─────────────────────────┐    ┌────────────────────────────────┐
│ /user/clientDetails/{id}│───▶│ /user/ajaxClientDetails        │
│ clientDetails.blade.php │    │   → basic + events + status    │
└─────────────────────────┘    └────────────────────────────────┘

ADD CLIENT
┌─────────────────────────┐    POST /user/check-user        ┌────────────────┐
│ /user/add-client        │───────────────────────────────▶ │ phone exists?  │
│ addClient.blade.php     │                                 └───────┬────────┘
└─────────────────────────┘                                         │ no
                            POST /user/create-account               ▼
                            ──────────────────────────▶ INSERT INTO users
                                                       (user_type='client',
                                                        password = bcrypt(phone))
```

### Routes & Actions

| Route | Method | Handler | Description |
|-------|--------|---------|-------------|
| `/user/clientlist` | GET | `UsersController::clientlist()` | List page |
| `/user/ajaxUserList` | GET | `UsersController::ajaxUserList()` | DataTable JSON (multi-user-type — same endpoint serves vendors too) |
| `/user/clientDetails/{id}` | GET | `UsersController::clientDetails()` | Detail page |
| `/user/ajaxClientDetails` | POST | `UsersController::ajaxClientDetails()` | Detail JSON (basic / about_events / event_list) |
| `/user/add-client` | GET | `UsersController::addClientForm()` | Add form |
| `/user/check-user` | POST | `UsersController::checkUser()` | Phone-exists check |
| `/user/create-account` | POST | `UsersController::createAccount()` | INSERT client row |

---

## Data Model

The shared `users` table (filtered to clients):

```php
// App\Models\ClientUser → table 'users'
protected $fillable = [
  'id', 'name', 'display_name', 'uuid',
  'phone', 'user_type', 'email', 'password',
];

// Read columns observed:
{
  id:               int,
  uuid:             string,
  name:             string,
  display_name:     string,
  phone:            string,             // login key
  email_id:         string|null,
  user_type:        'client'|'vendor',  // we filter for 'client'
  password:         bcrypt,             // server sets = bcrypt(phone) on admin-create
  status:           int,                // 0 inactive / 1 active
  last_login_from:  string|null,        // 'app' | 'web' | null
  created_at:       datetime,
}
```

### Detail Page Computed Counts

```php
// from ajaxClientDetails
total_events:     COUNT(events WHERE client_id = id)
upcoming_events:  COUNT(events WHERE client_id = id AND event_date > today)
past_events:      COUNT(events WHERE client_id = id AND event_date < today)
cancelled_events: hardcoded "0"  // see Known Issues
```

The `event_list` array is fetched joined to `cities`.

---

## Validations & Business Rules

| Rule | Detail |
|------|--------|
| Phone uniqueness | `checkUser` queries `users WHERE phone=? AND user_type='client'` |
| Default password | Server-side: `bcrypt($request->phone)` — predictable |
| UUID generated server-side | `Str::uuid()` |
| `display_name` defaults to `name` | On admin-create only |
| No email validation on admin-create | `email_id` is taken raw |
| Detail page lookups by numeric `id`, not UUID | URL is `/clientDetails/{id}` |

---

## API Endpoints

| Method | Path | Auth | Request | Response | Consumer |
|--------|------|------|---------|----------|----------|
| GET | `/user/clientlist` | session + permission 5 | — | HTML | Browser |
| GET | `/user/ajaxUserList` | session (AJAX whitelisted) | `draw, start, length, user_type=client, mobile?, service?, location?` | DataTable JSON | Client list page |
| GET | `/user/clientDetails/{id}` | session + permission 5 | path id | HTML | Browser |
| POST | `/user/ajaxClientDetails` | session (AJAX whitelisted) | `user=<id>` | `{ basic_details, about_events, event_list }` | Detail page JS |
| GET | `/user/add-client` | session + permission 5 | — | HTML | Browser |
| POST | `/user/check-user` | session (AJAX whitelisted) | `phone` | `{ userExists, phone? }` | Add form |
| POST | `/user/create-account` | session (AJAX whitelisted) | `phone, user_type=client, name, email_id` | `{ status, msg, body: { user_id } }` | Add form |

---

## Upstream Impact

- **Shared `users` table** — populated by OotboAPI signup endpoints when end users register via the app.
- **`events` table** — written by OotboAPI when clients create events (or by Feature 09 RM Create Event).
- **`cities` table** — joined into the event-list for the detail page.

---

## Downstream Impact

- **Feature 09 (RM Create Event)** — the RM form needs a `client_id`; that id refers to a row here.
- **Feature 18 (User Reports)** — `user_reports.client_id` joins back to this.
- **Feature 06 (Event Oversight)** — every event has a `client_id`; the detail page links here.

---

## Impact of Changes

| If you change... | Risk to... | Level | Type |
|-----------------|------------|-------|------|
| `users.phone` becomes non-unique | `checkUser` returns wrong duplicates | High | Data |
| `users.user_type` enum changes | Client/vendor split breaks across many filters | Critical | Data |
| `users.email_id` rename → `users.email` | All detail pages + create payloads | High | Data |
| `users.last_login_from` rename | Detail page "last_login" badge breaks | Low | UI |
| Adding required columns to `users` | `createAccount` insert fails (only writes 7 columns) | High | Data |
| Removing the `cancelled_events: '0'` hardcode without filling in the real query | Misleads ops who rely on the count | Medium | Data |

---

## Known Issues

- **`cancelled_events` is hardcoded to "0"** in `ajaxClientDetails` — see line 333 in `UsersController.php`. The real query was never written.
- **Password = bcrypt(phone)** for admin-created clients. Same predictability problem as Feature 03.
- **No email format validation** server-side on `createAccount`.
- **`ajaxUserList` is shared with vendor list** — branching on `$request->user_type`. Changing either side risks regressing the other.
