# Dispatcher & Agent Features - Implementation Plan

## Overview

This document outlines the MVP-grade operational features for DISPATCHER, LEAD_AGENT, and FIELD_AGENT roles. All features support both web dashboard and mobile clients via the same REST API.

## Current State Analysis

### Existing Infrastructure
- ✅ Multi-tenant architecture with organization isolation
- ✅ RBAC system with roles: SUPER_ADMIN, ORG_ADMIN, DISPATCHER, LEAD_AGENT, FIELD_AGENT, CITIZEN
- ✅ Incident model with status, priority, assignment fields
- ✅ Assignment system (user/team polymorphic)
- ✅ Status events timeline (incident_status_events table)
- ✅ Comments system (polymorphic)
- ✅ Media upload support
- ✅ Notification system (FCM)
- ✅ Audit logging

### Current Endpoints
- `GET /api/v1/incidents` - List incidents (basic filtering)
- `POST /api/v1/incidents` - Create incident
- `GET /api/v1/incidents/{id}` - Get incident
- `PATCH /api/v1/incidents/{id}` - Update incident
- `POST /api/v1/incidents/{id}/assign` - Assign incident
- `POST /api/v1/incidents/{id}/status` - Change status
- `GET /api/v1/incidents/{id}/timeline` - Get timeline (status events only)
- `POST /api/v1/incidents/{id}/media` - Upload media
- `GET /api/v1/comments` - List comments
- `POST /api/v1/comments` - Create comment
- `GET /api/v1/teams` - List teams
- `GET /api/v1/users` - List users

## Database Changes

### 1. Enhance `incident_status_events` table
Add fields for timeline event types and visibility:
```sql
ALTER TABLE incident_status_events 
ADD COLUMN event_type VARCHAR(50) DEFAULT 'STATUS_CHANGE',
ADD COLUMN visibility ENUM('PUBLIC_TO_CITIZEN', 'INTERNAL_ONLY') DEFAULT 'PUBLIC_TO_CITIZEN';
```

**Event Types:**
- `STATUS_CHANGE` - Status transition (existing)
- `ASSIGNMENT` - Assignment/unassignment
- `COMMENT` - Comment added
- `INTERNAL_NOTE` - Internal dispatcher note
- `REQUEST_INFO` - Request for more info from citizen
- `MEDIA_ADDED` - Media uploaded

**Visibility:**
- `PUBLIC_TO_CITIZEN` - Visible to citizen reporter
- `INTERNAL_ONLY` - Only visible to org users (dispatchers, agents, admins)

### 2. Enhance `incident_media` table (optional MVP)
Add visibility field for media:
```sql
ALTER TABLE incident_media 
ADD COLUMN visibility ENUM('PUBLIC_TO_CITIZEN', 'INTERNAL_ONLY') DEFAULT 'PUBLIC_TO_CITIZEN',
ADD COLUMN caption TEXT NULL;
```

### 3. Add optional tracking fields to `incidents` table
```sql
ALTER TABLE incidents
ADD COLUMN triaged_by_id BIGINT UNSIGNED NULL,
ADD COLUMN triaged_at TIMESTAMP NULL,
ADD COLUMN assigned_by_id BIGINT UNSIGNED NULL,
ADD COLUMN assigned_at TIMESTAMP NULL,
ADD FOREIGN KEY (triaged_by_id) REFERENCES users(id) ON DELETE SET NULL,
ADD FOREIGN KEY (assigned_by_id) REFERENCES users(id) ON DELETE SET NULL;
```

## API Endpoints

### Enhanced Incidents List
**GET /api/v1/incidents**

**Query Parameters:**
- `status` - Filter by status (REPORTED, TRIAGED, ASSIGNED, etc.)
- `priority` - Filter by priority (LOW, MEDIUM, HIGH, CRITICAL)
- `service_type` - Filter by service type
- `incident_type_id` - Filter by incident type
- `unassigned` - Boolean (true = only unassigned incidents)
- `mine` - Boolean (true = only incidents assigned to current user)
- `team` - Integer (team_id - incidents assigned to this team)
- `assigned_to_id` - Integer (user_id or team_id)
- `assigned_to_type` - String (user or team)
- `search` - String (search in title/description)
- `from` - Date (created_at >= from)
- `to` - Date (created_at <= to)
- `min_lat`, `max_lat`, `min_lng`, `max_lng` - Bounding box filter
- `sort` - String (newest, oldest, priority, aging)
- `per_page` - Integer (default: 15)

**Response:** Paginated IncidentResource collection

**Permissions:**
- DISPATCHER: All incidents in org
- LEAD_AGENT: Team incidents + own incidents
- FIELD_AGENT: Only assigned incidents
- CITIZEN: Only own incidents

### Unassign Incident
**POST /api/v1/incidents/{id}/unassign**

**Request Body:**
```json
{
  "note": "Optional unassignment note"
}
```

**Response:** IncidentResource

**Permissions:** DISPATCHER, ORG_ADMIN only

### Enhanced Timeline
**GET /api/v1/incidents/{id}/timeline**

**Query Parameters:**
- `visibility` - Filter by visibility (PUBLIC_TO_CITIZEN, INTERNAL_ONLY, all)
- `type` - Filter by event type

**Response:**
```json
{
  "timeline": [
    {
      "id": 1,
      "event_type": "STATUS_CHANGE",
      "from_status": "REPORTED",
      "to_status": "TRIAGED",
      "user": {
        "id": 5,
        "name": "Dispatcher Name"
      },
      "notes": "Triage notes",
      "visibility": "PUBLIC_TO_CITIZEN",
      "created_at": "2025-01-15T10:30:00Z",
      "metadata": {}
    },
    {
      "id": 2,
      "event_type": "INTERNAL_NOTE",
      "user": {
        "id": 5,
        "name": "Dispatcher Name"
      },
      "notes": "Internal note text",
      "visibility": "INTERNAL_ONLY",
      "created_at": "2025-01-15T11:00:00Z",
      "metadata": {}
    }
  ]
}
```

**Visibility Rules:**
- Citizens see only PUBLIC_TO_CITIZEN events
- Org users see all events

### Add Timeline Event (Internal Note)
**POST /api/v1/incidents/{id}/timeline**

**Request Body:**
```json
{
  "event_type": "INTERNAL_NOTE",
  "notes": "Internal dispatcher note",
  "visibility": "INTERNAL_ONLY"
}
```

**Event Types:**
- `INTERNAL_NOTE` - Internal note (dispatchers/agents)
- `REQUEST_INFO` - Request more info from citizen

**Response:** Timeline event object

**Permissions:**
- DISPATCHER: Can add to any incident in org
- LEAD_AGENT: Can add to team/assigned incidents
- FIELD_AGENT: Can add to assigned incidents

### Enhanced Media Upload
**POST /api/v1/incidents/{id}/media**

**Request:** multipart/form-data
- `media[]` - Array of files
- `visibility` - String (PUBLIC_TO_CITIZEN, INTERNAL_ONLY) - default: PUBLIC_TO_CITIZEN
- `caption` - String (optional)

**Response:**
```json
{
  "message": "Media uploaded successfully",
  "media": [...]
}
```

### Enhanced User Listing (Agents)
**GET /api/v1/users**

**Query Parameters:**
- `role` - Filter by role name (FIELD_AGENT, LEAD_AGENT, DISPATCHER)
- `team_id` - Filter by team membership
- `organization_id` - Filter by organization (super admin only)

**Response:** Paginated UserResource collection

**Permissions:**
- DISPATCHER: Can list agents in their org
- ORG_ADMIN: Can list all users in org
- SUPER_ADMIN: Can list all users

### Enhanced Team Endpoints
**GET /api/v1/teams/{id}/members**

**Response:** Array of team members with roles

**Permissions:** DISPATCHER, LEAD_AGENT, ORG_ADMIN

## Permissions Matrix

### DISPATCHER
| Action | Scope | Notes |
|--------|-------|-------|
| View incidents | All in org | Can filter by status, priority, unassigned, etc. |
| Triage incidents | All in org | Update status: REPORTED → TRIAGED |
| Assign incidents | All in org | Assign to user or team |
| Unassign incidents | All in org | Remove assignment |
| Update status | All in org | Enforce valid transitions |
| Add internal notes | All in org | INTERNAL_ONLY visibility |
| Request info | All in org | Send request to citizen |
| View timeline | All in org | See all events (PUBLIC + INTERNAL) |
| Upload media | All in org | Can mark as INTERNAL_ONLY |
| List agents | Org users | Filter by role (FIELD_AGENT, LEAD_AGENT) |
| List teams | Org teams | View team members |

### LEAD_AGENT
| Action | Scope | Notes |
|--------|-------|-------|
| View incidents | Team + assigned | Incidents assigned to team or self |
| Update status | Team + assigned | IN_PROGRESS, RESOLVED (for assigned) |
| Add timeline updates | Team + assigned | Progress updates, internal notes |
| Upload media | Team + assigned | Work photos/videos |
| View timeline | Team + assigned | See all events |
| Reassign within team | Team incidents | If policy allows (optional MVP) |
| Claim incident | Team incidents | If policy allows (optional MVP) |

### FIELD_AGENT
| Action | Scope | Notes |
|--------|-------|-------|
| View incidents | Assigned only | Only incidents assigned to them |
| Update status | Assigned only | ASSIGNED → IN_PROGRESS → RESOLVED |
| Add timeline updates | Assigned only | Progress updates, comments |
| Upload media | Assigned only | Work photos/videos |
| View timeline | Assigned only | See all events |
| Cannot assign | - | Cannot assign incidents to others |

## Status Transition Rules by Role

### DISPATCHER
- REPORTED → TRIAGED ✅
- TRIAGED → ASSIGNED ✅
- Any → REJECTED ✅
- RESOLVED → CLOSED ✅

### LEAD_AGENT
- ASSIGNED → IN_PROGRESS ✅ (for team/assigned)
- IN_PROGRESS → RESOLVED ✅ (for assigned)
- Cannot triage or close

### FIELD_AGENT
- ASSIGNED → IN_PROGRESS ✅ (for assigned)
- IN_PROGRESS → RESOLVED ✅ (for assigned)
- Cannot triage, assign, or close

## Policy Updates

### IncidentPolicy Enhancements

**viewAny()** - Already scoped by organization, but needs role-based filtering:
- DISPATCHER: All in org
- LEAD_AGENT: Team + assigned
- FIELD_AGENT: Assigned only
- CITIZEN: Own only

**update()** - Enhanced to check:
- DISPATCHER: Any in org
- LEAD_AGENT: Team/assigned incidents
- FIELD_AGENT: Assigned incidents only

**assign()** - Already correct (DISPATCHER, ORG_ADMIN only)

**New: unassign()** - DISPATCHER, ORG_ADMIN only

**New: addTimelineEvent()** - Based on update permissions

## Notification Updates

### Assignment Notifications
- ✅ Already implemented: Notify assigned user/team members

### Status Change Notifications
- ✅ Already implemented: Notify reporter + org admins
- **Enhancement:** Notify dispatcher when agent marks RESOLVED

### New Notifications
- **Request Info:** Notify citizen when dispatcher requests more info
- **Internal Note:** Optional - notify team members (optional MVP)

## Timeline Event Types

1. **STATUS_CHANGE** - Status transition (existing)
   - Visibility: Usually PUBLIC_TO_CITIZEN
   - Metadata: `{ from_status, to_status }`

2. **ASSIGNMENT** - Assignment/unassignment
   - Visibility: PUBLIC_TO_CITIZEN (citizen should know who's working on it)
   - Metadata: `{ assignable_type, assignable_id, assigned_by }`

3. **COMMENT** - Comment added (link to comments table)
   - Visibility: Based on comment visibility (if we add it)
   - Metadata: `{ comment_id }`

4. **INTERNAL_NOTE** - Internal dispatcher/agent note
   - Visibility: INTERNAL_ONLY
   - Metadata: `{}`

5. **REQUEST_INFO** - Request more info from citizen
   - Visibility: PUBLIC_TO_CITIZEN
   - Metadata: `{ requested_fields: [] }`

6. **MEDIA_ADDED** - Media uploaded
   - Visibility: Based on media visibility
   - Metadata: `{ media_id, media_type }`

## Implementation Assumptions

1. **Timeline Consolidation:** Timeline will aggregate:
   - Status events (incident_status_events)
   - Assignments (assignments table)
   - Comments (comments table)
   - Internal notes (new timeline events)

2. **Backward Compatibility:** Existing status events remain valid, default to PUBLIC_TO_CITIZEN

3. **Media Visibility:** Default to PUBLIC_TO_CITIZEN, can be marked INTERNAL_ONLY

4. **Team Assignment:** When assigned to team, all team members can view/update (if policy allows)

5. **Citizen Access:** Citizens only see PUBLIC_TO_CITIZEN timeline events and media

6. **No Breaking Changes:** All changes are additive, existing endpoints continue to work

## Example API Requests

### Dispatcher: List Unassigned Incidents
```http
GET /api/v1/incidents?unassigned=true&status=REPORTED,TRIAGED&sort=priority
Authorization: Bearer {token}
```

### Dispatcher: Assign to Agent
```http
POST /api/v1/incidents/123/assign
Authorization: Bearer {token}
Content-Type: application/json

{
  "assignable_type": "user",
  "assignable_id": 45,
  "notes": "High priority, needs immediate attention"
}
```

### Dispatcher: Add Internal Note
```http
POST /api/v1/incidents/123/timeline
Authorization: Bearer {token}
Content-Type: application/json

{
  "event_type": "INTERNAL_NOTE",
  "notes": "Waiting for equipment delivery",
  "visibility": "INTERNAL_ONLY"
}
```

### Field Agent: List My Assignments
```http
GET /api/v1/incidents?mine=true&status=ASSIGNED,IN_PROGRESS
Authorization: Bearer {token}
```

### Field Agent: Start Work
```http
POST /api/v1/incidents/123/status
Authorization: Bearer {token}
Content-Type: application/json

{
  "status": "IN_PROGRESS",
  "notes": "Arrived on site, assessing damage"
}
```

### Lead Agent: List Team Incidents
```http
GET /api/v1/incidents?team=5&status=ASSIGNED,IN_PROGRESS
Authorization: Bearer {token}
```

## Testing Requirements

### Feature Tests
1. **Tenant Isolation:**
   - Dispatcher from Org A cannot see incidents from Org B
   - Field agent cannot access incidents from other orgs

2. **Role Permissions:**
   - Dispatcher can assign/unassign
   - Field agent cannot assign
   - Lead agent can update team incidents
   - Field agent can only update assigned incidents

3. **Timeline Visibility:**
   - Citizen sees only PUBLIC_TO_CITIZEN events
   - Org users see all events
   - Internal notes not returned to citizen endpoints

4. **Status Transitions:**
   - Enforce valid transitions per role
   - Reject invalid transitions

5. **Filtering:**
   - `mine=true` returns only assigned incidents for agents
   - `unassigned=true` returns only unassigned incidents
   - `team=X` returns team incidents for lead agents

## Migration Strategy

1. **Phase 1:** Database migrations (add fields, no breaking changes)
2. **Phase 2:** Model updates (add fillable fields, relationships)
3. **Phase 3:** Policy updates (enhance permissions)
4. **Phase 4:** Controller updates (new endpoints, enhanced filtering)
5. **Phase 5:** Timeline consolidation (aggregate events)
6. **Phase 6:** Tests (feature tests for all scenarios)

## Notes

- All endpoints support both web and mobile clients
- No mobile-only logic - same API for all clients
- Maintain backward compatibility
- Additive changes only - no breaking changes
- Timeline visibility enforced at API level, not just UI

