# 06 — Event Oversight

## Overview

Lists, filters, and inspects events created by clients (via the public app OR by an RM in Feature 09). The "control tower" view of platform activity — every event flows through here.

**Status:** Live

---

## User Stories

| ID | As a | I want to | So that |
|----|------|-----------|---------|
| EVT-01 | Ops | List all events, filtered by status (all / quoted / non-quoted / cancelled / past), creation date range, event date range, mobile number, or human event ID | I can find any event |
| EVT-02 | Ops | Open the event detail page | I see every quote, vendor, service, and chat |
| EVT-03 | Ops | Edit the event's name, description, guest count, date, time, area | I can correct client mistakes |
| EVT-04 | Ops | Edit a service's description, expected_event_date, expected_start_time | I can fix per-service issues |
| EVT-05 | Ops | View vendor-by-vendor quote breakdown for the event | I can audit pricing |
| EVT-06 | Ops | Read the chat history between client and a specific vendor | I can resolve disputes |

---

## Screens & Flows

```
┌──────────────────────────┐
│ /event/list              │
│ list.blade.php           │
│ Filters: status, ranges, │
│   mobile, human id       │
└────────────┬─────────────┘
             │ DataTable AJAX
             ▼
┌──────────────────────────┐
│ /event/getEventData      │
│ → EventModel::getEventList│
│  (raw SQL with filters)  │
└────────────┬─────────────┘
             │  Click "Event Details"
             ▼
┌────────────────────────────────────────────────────────────────┐
│ /event/details/{id}    detail.blade.php                        │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────────┐   │
│ │ Event card  │ │ Services    │ │ Vendor quotes (per svc)  │   │
│ │ + edit form │ │  (edit ea.) │ │  + chat panel            │   │
│ └─────┬───────┘ └─────┬───────┘ └──────────┬───────────────┘   │
│       │               │                    │                   │
│       │PUT update-event│POST updateservices│GET event/chats    │
│       ▼               ▼                    ▼                   │
│  events table  event_services table   chat table               │
└────────────────────────────────────────────────────────────────┘
```

### Routes & Actions

| Route | Method | Handler | Description |
|-------|--------|---------|-------------|
| `/event/list` | GET | `EventController::index()` | List page |
| `/event/getEventData` | GET | `EventController::getEventData()` | DataTable JSON (with filter object) |
| `/event/details/{id}` | GET | `EventController::eventDetails()` | Detail page |
| `/event/update-event/{id}` | PUT | `EventController::updateEvent()` | Update event basic fields |
| `/event/services` | GET | `EventController::eventServices()` | Get one service row (for edit modal) |
| `/event/updateservices` | POST | `EventController::updateServices()` | Update a service row (raw SQL UPDATE) |
| `/event/chats` | GET | `EventController::eventChats()` | Fetch chat thread for given convo + event |
| `/event/ajax-vendor-quotes-chat` | POST | (Commented out in controller — route exists but handler disabled) | — |

---

## Data Model

### `events`

```php
// App\Models\EventModel → table 'events'
protected $fillable = [
  'event_name', 'uuid', 'human_usable_id', 'client_id', 'type',
  'cities_id', 'state_id', 'country_id', 'category', 'area',
  'description', 'saved_vendor_id', 'event_date',
  'event_start_time', 'guest_count', 'status', 'event_title_id',
  'is_cancelled', 'cancel_date_time',
];

{
  id:               int,
  uuid:             string,        // generated by OotboAPI or VisoAdmin
  human_usable_id:  string,        // "EVN" + 9 digits, displayed to users
  client_id:        int,           // FK users.id
  type:             'personal'|'professional',
  category:         int,           // FK categories.id
  country_id:       int,
  state_id:         int,
  cities_id:        int,           // note: plural typo
  event_name:       string,
  description:      string,
  event_date:       date,
  event_start_time: time,
  guest_count:      int,
  area:             string,        // address line
  status:           int,
  saved_vendor_id:  int|null,      // hired vendor
  is_cancelled:     0 | 1,
  cancel_date_time: datetime|null,
  created_at:       datetime,
  updated_at:       datetime,
}
```

### `event_services`

```php
// App\Models\EventServices → table 'event_services'
{
  id, uuid,
  event_id:             string,    // UUID, joins on events.uuid in RM flow
                                   // BUT EventModel uses integer events.id in list flow
                                   // -- be careful which is which
  service_id:           int,       // FK services.id
  amount:               decimal,
  description:          string,
  expected_event_date:  date,
  expected_start_time:  time,
}
```

> **Schema dual-use**: `event_services.event_id` is sometimes referenced as integer (`events.id`) and sometimes as UUID (`events.uuid`). Feature 09 RM Create Event stores UUIDs; OotboAPI may do otherwise. Verify before doing joins on raw integer keys.

### `event_services_vendor_quote` (read-only here)

```php
{
  id,
  event_services_id: int,
  vendor_id:         int,
  amount:            decimal,
  vendor_service_description: string,
  is_hired:          0|1,
  created_at, updated_at,
}
```

### `event_vendor_discount` (read-only here)

```php
{
  id, event_id, vendor_id,
  discount_type:  'percentage' | 'flat',
  discount_value: decimal,
  updated_at,
}
```

---

## Validations & Business Rules

| Rule | Detail |
|------|--------|
| Cancellation badge | `eventCancellation(is_cancelled, date)` → "Cancelled Event" / "Upcoming Event" / "Past Event" |
| Filter "quoted" / "non-quoted" | Uses subquery `SELECT GROUP_CONCAT(DISTINCT(es.event_id)) ... WHERE qu.id is not null` |
| Filter "past" | `event_date < today AND is_cancelled = 0` |
| Filter "cancelled" | `is_cancelled = 1` |
| Date range filter | Split on " to " — expects rfc-ish "YYYY-MM-DD to YYYY-MM-DD" |
| Update validation | `event_name max 100`, `description max 500`, `guest_count int nullable`, `event_date date`, `event_start_time H:i:s`, `area max 150` |
| Service-update is a raw `DB::update("...")` | with positional `?` placeholders — safe-ish |
| Chat fetcher requires `userId[]`, `convId`, `eventid` | Passed by detail page JS |
| `chat_time_format` for messages | `h:i:s A` |

---

## API Endpoints

| Method | Path | Auth | Request | Response | Consumer |
|--------|------|------|---------|----------|----------|
| GET | `/event/list` | session + permission 2 | — | HTML | Browser |
| GET | `/event/getEventData` | session (AJAX whitelisted) | `draw, start, length, filter={...}` | DataTable JSON | Event list page |
| GET | `/event/details/{id}` | session + permission 2 | path id | HTML | Browser |
| PUT | `/event/update-event/{id}` | session + permission 2 | `event_name, description, guest_count?, event_date, event_start_time, area` | redirect back | Detail page form |
| GET | `/event/services?serviceId=<id>` | session + permission 2 | `serviceId` | `event_services` row | Edit-service modal |
| POST | `/event/updateservices` | session + permission 2 | `serviceid, description, datetime` | redirect back | Edit-service modal |
| GET | `/event/chats` | session + permission 2 | `userId[], convId, eventid, client` | `[{message, time, class, pic}, ...]` | Detail page chat panel |

---

## Upstream Impact

- **`events` table** — written by both OotboAPI (client app event creation) and Feature 09 (RM Create Event).
- **`event_services`, `event_services_vendor_quote`, `event_vendor_discount`** — written by OotboAPI (quote submit) and Feature 09.
- **`conversation`, `chat`, `chat_service`** — written by Viso-Chat and OotboAPI.
- **`cities`, `states`, `countries`** (Feature 12) — joined on display.
- **`users`** — `events.client_id` and quote `.vendor_id` join here.

---

## Downstream Impact

- **Feature 10 (Critical Events)** — slices this same data by zero-quote + upcoming.
- **Feature 11 (Analytics)** — events counts come from here.
- **Feature 07 (Coordinator)** — reuses `getEventVendorQuotesDetails` (same model method) to render its detail view.

---

## Impact of Changes

| If you change... | Risk to... | Level | Type |
|-----------------|------------|-------|------|
| `events.cities_id` rename (drop the typo) | Filter, list join, detail join | Critical | Data |
| `events.human_usable_id` format | Coordinator lookup, filter "event id", display | High | Data |
| `event_services.event_id` integer/UUID semantics | RM event detail page + standard event detail page interoperability | Critical | Data |
| Adding columns required to `events` | `RmController::storeEvent` insert must add them, list query may still work | High | Data |
| Removing `is_cancelled` | Filter logic + critical events + analytics break | Critical | Data |
| Changing the `chat`/`chat_service` schema | `eventChats` query + Coordinator chat panel | High | Data |
| Renaming `saved_vendor_id` | Vendor detail "About Events" counts break | High | Data |

---

## Known Issues

- **`event/ajax-vendor-quotes-chat`** route exists in `web.php` but the handler is commented out in `EventController.php`. Dead route.
- **`getEventVendorQuotesDetails` query** is a raw `DB::raw` with `$id` interpolated. The `$id` is from a route param, so still controlled — but the pattern is fragile.
- **The eventDetails detail page loops `foreach($eventVendor as $row)` and re-queries `getEventVendorQuotesDetails($id)` inside the loop** (line 96 of `EventController`). N+1 query for no reason — performance trap.
- **`chat_initiated` count** for vendors is computed via a hard-coded subquery. If the `chat_service` table grows, this slows.
