Spaces:
Sleeping
Ticketing System - Frontend Integration Guide
Quick reference for PM, Sales Manager, and Dispatcher ticket management.
User Roles & Permissions
| Role | View Tickets | Create Tickets | Assign Tickets | View Expenses | Approve Expenses |
|---|---|---|---|---|---|
| Platform Admin | All | β | β | All | β |
| Project Manager | Their projects | β | β | Their projects | β |
| Dispatcher | Contractor's projects | β | β | Contractor's projects | β |
| Sales Manager | Their client's projects | View only | β | View only | β |
Ticket Basics
Ticket Types
- installation - New customer service setup (from sales orders)
- support - Fix existing customer issues (from incidents)
- infrastructure - Project rollout work (from tasks)
Ticket Status Flow
open β assigned β in_progress β pending_review β completed
β cancelled
Ticket Sources
- sales_order - Customer wants new service
- incident - Customer has problem with existing service
- task - Infrastructure work needed
Priority Levels
urgent > high > normal > low
Core Endpoints
1. List Tickets
GET /api/v1/tickets?skip=0&limit=50
Query Params:
project_id(UUID) - Filter by projectstatus(enum) - open, assigned, in_progress, pending_review, completed, cancelledticket_type(enum) - installation, support, infrastructurepriority(enum) - urgent, high, normal, lowproject_region_id(UUID) - Filter by regionsource(enum) - sales_order, incident, taskfrom_date(date) - Created after (YYYY-MM-DD)to_date(date) - Created before (YYYY-MM-DD)is_overdue(bool) - true/false
Response:
{
"total": 150,
"skip": 0,
"limit": 50,
"tickets": [
{
"id": "uuid",
"project_id": "uuid",
"source": "sales_order",
"source_id": "uuid",
"ticket_name": "John Doe - Fiber Installation",
"ticket_type": "installation",
"service_type": "ftth",
"work_description": "Install fiber for customer",
"status": "open",
"priority": "normal",
"scheduled_date": "2024-03-20",
"scheduled_time_slot": "morning",
"due_date": "2024-03-20T17:00:00Z",
"sla_violated": false,
"is_overdue": false,
"project_region_id": "uuid",
"notes": "Customer prefers morning",
"created_at": "2024-03-15T10:00:00Z",
"updated_at": "2024-03-15T10:00:00Z",
"is_open": true,
"can_be_assigned": true
}
]
}
2. Get Single Ticket
GET /api/v1/tickets/{ticket_id}
Response: Same as ticket object above with full details.
3. Create Ticket from Sales Order
POST /api/v1/tickets/from-sales-order
Request:
{
"sales_order_id": "uuid",
"priority": "normal",
"scheduled_date": "2024-03-20",
"scheduled_time_slot": "morning",
"work_description": "Install fiber for customer",
"notes": "Customer prefers morning visit"
}
Response: Ticket object
Rules:
- Sales order must be
pendingstatus - One sales order = one active ticket (prevents duplicates)
- If cancelled ticket exists, it gets reactivated instead
4. Create Ticket from Task
POST /api/v1/tickets/from-task
Request:
{
"task_id": "uuid",
"priority": "normal",
"scheduled_date": "2024-03-20",
"scheduled_time_slot": "morning",
"notes": "Infrastructure work"
}
Response: Ticket object
5. Create Ticket from Incident
POST /api/v1/tickets/from-incident
Request:
{
"incident_id": "uuid",
"priority": "urgent",
"scheduled_date": "2024-03-20",
"scheduled_time_slot": "morning",
"notes": "Customer reports no internet"
}
Response: Ticket object
Important: Support tickets don't create new subscriptions (service already exists).
6. Bulk Create from Sales Orders
POST /api/v1/tickets/bulk-from-sales-orders
Request:
{
"sales_order_ids": ["uuid1", "uuid2", "uuid3"],
"priority": "normal",
"notes": "Batch promotion - March installations"
}
Response:
{
"total": 3,
"successful": 2,
"failed": 1,
"errors": ["Sales order uuid3: Already has ticket"],
"created_ticket_ids": ["ticket-uuid1", "ticket-uuid2"]
}
7. Update Ticket
PUT /api/v1/tickets/{ticket_id}
Request:
{
"priority": "urgent",
"work_description": "Updated description",
"notes": "Additional notes"
}
Rules: Can only update open or assigned tickets.
8. Reschedule Ticket
POST /api/v1/tickets/{ticket_id}/reschedule
Request:
{
"scheduled_date": "2024-03-25",
"scheduled_time_slot": "afternoon",
"reason": "Customer requested later date"
}
9. Cancel Ticket
POST /api/v1/tickets/{ticket_id}/cancel
Request:
{
"reason": "Customer cancelled service"
}
Rules: Can only cancel open or assigned tickets.
10. Get Ticket Stats
GET /api/v1/tickets/stats?project_id=uuid
Response:
{
"total_tickets": 150,
"open_tickets": 20,
"assigned_tickets": 30,
"in_progress_tickets": 15,
"completed_tickets": 80,
"cancelled_tickets": 5,
"overdue_tickets": 3,
"installation_tickets": 100,
"support_tickets": 30,
"infrastructure_tickets": 20,
"urgent_tickets": 5,
"high_tickets": 15,
"normal_tickets": 100,
"low_tickets": 30,
"avg_completion_time_hours": 4.5,
"completion_rate": 53.33
}
Ticket Assignments
11. Assign Ticket to Agent
POST /api/v1/ticket-assignments/tickets/{ticket_id}/assign
Request:
{
"user_id": "agent-uuid",
"execution_order": 1,
"planned_start_time": "2024-03-20T09:00:00Z",
"notes": "Customer prefers morning visit"
}
Response:
{
"id": "assignment-uuid",
"ticket_id": "ticket-uuid",
"user_id": "agent-uuid",
"assigned_by_user_id": "dispatcher-uuid",
"action": "assigned",
"execution_order": 1,
"planned_start_time": "2024-03-20T09:00:00Z",
"assigned_at": "2024-03-19T14:00:00Z",
"responded_at": null,
"journey_started_at": null,
"arrived_at": null,
"ended_at": null,
"is_active": true,
"notes": "Customer prefers morning visit"
}
Rules:
- Ticket must be
openstatus - Agent must be on project team
- Agent capacity: max 4 active tickets
- No duplicate assignments
12. Assign Ticket to Team
POST /api/v1/ticket-assignments/tickets/{ticket_id}/assign-team
Request:
{
"user_ids": ["agent-a-uuid", "agent-b-uuid", "agent-c-uuid"],
"required_team_size": 3,
"notes": "Infrastructure team for pole installation"
}
Response:
{
"ticket_id": "ticket-uuid",
"assignments": [
{ "id": "assignment-1-uuid", "user_id": "agent-a-uuid" },
{ "id": "assignment-2-uuid", "user_id": "agent-b-uuid" },
{ "id": "assignment-3-uuid", "user_id": "agent-c-uuid" }
],
"required_team_size": 3,
"successful": 3,
"failed": 0
}
Team Rules:
- Any team member can complete the ticket
- Completing closes ALL active assignments
- All agents must have capacity
13. Get Ticket Assignments
GET /api/v1/ticket-assignments/tickets/{ticket_id}/assignments
Response:
{
"current_assignments": [
{
"id": "uuid",
"user_id": "agent-uuid",
"user_name": "John Agent",
"assigned_at": "2024-03-19T14:00:00Z",
"responded_at": "2024-03-19T14:30:00Z",
"journey_started_at": "2024-03-20T08:00:00Z",
"arrived_at": "2024-03-20T09:15:00Z",
"is_active": true
}
],
"past_assignments": [
{
"id": "uuid",
"user_id": "previous-agent-uuid",
"user_name": "Previous Agent",
"assigned_at": "2024-03-18T10:00:00Z",
"ended_at": "2024-03-18T11:00:00Z",
"action": "dropped",
"is_active": false
}
]
}
14. Get Agent's Work Queue
GET /api/v1/ticket-assignments/users/{user_id}/assignment-queue
Response:
{
"user_id": "agent-uuid",
"active_count": 3,
"max_capacity": 4,
"can_accept_more": true,
"assignments": [
{
"id": "assignment-uuid",
"ticket_id": "ticket-uuid",
"ticket_name": "John Doe - Installation",
"execution_order": 1,
"planned_start_time": "2024-03-20T09:00:00Z",
"priority": "high"
}
]
}
Ticket Expenses
15. Get Ticket Expenses
GET /api/v1/ticket-expenses?ticket_id=uuid
Response:
{
"expenses": [
{
"id": "expense-uuid",
"ticket_id": "ticket-uuid",
"ticket_assignment_id": "assignment-uuid",
"incurred_by_user_id": "agent-uuid",
"incurred_by_user_name": "John Agent",
"category": "transport",
"description": "Taxi fare to customer site",
"quantity": 1,
"unit": "trip",
"unit_cost": 500.00,
"total_cost": 500.00,
"receipt_document_id": "doc-uuid",
"is_approved": false,
"is_paid": false,
"location_verified": true,
"notes": "Shared with 2 team members",
"created_at": "2024-03-20T10:00:00Z"
}
],
"total": 1,
"page": 1,
"page_size": 50,
"pages": 1
}
Expense Categories:
transport- Travel costsmaterials- Materials purchasedmeals- Food/drinksaccommodation- Lodgingother- Other expenses
16. Approve/Reject Expense
POST /api/v1/ticket-expenses/{expense_id}/approve
Request:
{
"is_approved": true,
"rejection_reason": null
}
Or reject:
{
"is_approved": false,
"rejection_reason": "Receipt not clear, please resubmit"
}
17. Get Expense Stats
GET /api/v1/ticket-expenses/stats?ticket_id=uuid
Response:
{
"total_expenses": 25,
"total_amount": 12500.00,
"approved_count": 20,
"approved_amount": 10000.00,
"pending_count": 3,
"pending_amount": 1500.00,
"rejected_count": 2,
"rejected_amount": 1000.00,
"paid_count": 15,
"paid_amount": 7500.00,
"by_category": {
"transport": 5000.00,
"materials": 4000.00,
"meals": 1500.00
}
}
Ticket Images/Photos
18. Get Ticket Equipment
GET /api/v1/tickets/{ticket_id}/equipment
Response:
[
{
"id": "uuid",
"unit_identifier": "HW12345678",
"equipment_type": "ONT",
"equipment_name": "Huawei HG8145V5",
"status": "installed",
"issued_at": "2025-11-15T08:00:00Z",
"installed_at": "2025-11-15T14:30:00Z",
"agent_name": "John Technician",
"agent_id": "agent-uuid"
}
]
Database Models
Ticket Model
{
"id": UUID,
"project_id": UUID,
"source": "sales_order" | "incident" | "task",
"source_id": UUID,
"ticket_name": str,
"ticket_type": "installation" | "support" | "infrastructure",
"service_type": "ftth" | "fixed_wireless" | "dsl" | etc,
"work_description": str,
"status": "open" | "assigned" | "in_progress" | "pending_review" | "completed" | "cancelled",
"priority": "low" | "normal" | "high" | "urgent",
"scheduled_date": date,
"scheduled_time_slot": str,
"due_date": datetime,
"sla_target_date": datetime,
"sla_violated": bool,
"started_at": datetime,
"completed_at": datetime,
"is_invoiced": bool,
"project_region_id": UUID,
"work_location_latitude": decimal,
"work_location_longitude": decimal,
"work_location_verified": bool,
"dedup_key": str, // Permanent unique identifier
"required_team_size": int,
"notes": str,
"version": int, // Optimistic locking
"created_at": datetime,
"updated_at": datetime
}
TicketAssignment Model
{
"id": UUID,
"ticket_id": UUID,
"user_id": UUID, // Agent
"assigned_by_user_id": UUID, // Dispatcher/PM
"action": "assigned" | "accepted" | "rejected" | "dropped",
"execution_order": int,
"planned_start_time": datetime,
"assigned_at": datetime,
"responded_at": datetime, // When agent accepted/rejected
"journey_started_at": datetime,
"arrived_at": datetime,
"ended_at": datetime, // When assignment closed
"is_self_assigned": bool,
"is_active": bool, // ended_at IS NULL
"journey_start_location": {lat, lng, accuracy},
"arrival_location": {lat, lng, accuracy},
"arrival_verified": bool,
"journey_location_history": [{lat, lng, timestamp, speed}], // GPS breadcrumbs
"notes": str
}
TicketExpense Model
{
"id": UUID,
"ticket_assignment_id": UUID,
"ticket_id": UUID,
"incurred_by_user_id": UUID,
"category": "transport" | "materials" | "meals" | "accommodation" | "other",
"description": str,
"quantity": decimal,
"unit": str,
"unit_cost": decimal,
"total_cost": decimal,
"receipt_document_id": UUID,
"location_verified": bool,
"is_approved": bool,
"approved_by_user_id": UUID,
"approved_at": datetime,
"rejection_reason": str,
"is_paid": bool,
"paid_to_user_id": UUID,
"paid_at": datetime,
"payment_reference": str,
"payment_recipient_type": "agent" | "vendor",
"payment_method": "send_money" | "till_number" | "paybill" | etc,
"payment_details": {}, // M-Pesa/bank details
"notes": str
}
Key Business Rules
Ticket Creation
- One source (sales order/incident/task) = one ticket ever (permanent deduplication)
- Cancelled tickets can be reactivated instead of creating duplicates
- Installation tickets create subscriptions on completion
- Support tickets update existing subscriptions (no new subscription)
Ticket Assignment
- Agent capacity: max 4 active tickets
- Team assignments: any member can complete, closes all assignments
- Self-assignment: agents can pick from available pool in their region
Ticket Status Transitions
openβ Can assign, update, reschedule, cancelassignedβ Can start journey, update, reschedule, cancelin_progressβ Can complete, cannot cancelcompletedβ Cannot modifycancelledβ Can reactivate by creating new ticket from same source
Expenses
- Must be linked to assignment
- Require approval before payment
- Support M-Pesa, bank transfer, cash payments
- Track location verification
Common UI Flows
Flow 1: Create & Assign Installation Ticket
POST /tickets/from-sales-order- Create ticketPOST /ticket-assignments/tickets/{id}/assign- Assign to agent- Agent accepts and completes work
GET /tickets/{id}- View completed ticket
Flow 2: View Ticket Details
GET /tickets/{id}- Get ticketGET /ticket-assignments/tickets/{id}/assignments- Get assignmentsGET /ticket-expenses?ticket_id={id}- Get expensesGET /tickets/{id}/equipment- Get equipment used
Flow 3: Manage Agent Workload
GET /ticket-assignments/users/{agent_id}/assignment-queue- Check capacity- If capacity available:
POST /ticket-assignments/tickets/{id}/assign GET /tickets/stats- Monitor overall workload
Flow 4: Approve Expenses
GET /ticket-expenses?ticket_id={id}- List expenses- Review receipts and details
POST /ticket-expenses/{expense_id}/approve- Approve/rejectGET /ticket-expenses/stats- View expense summary
Error Handling
Common error codes:
400- Validation error (missing fields, invalid data)403- Forbidden (no permission for this project/ticket)404- Not found (ticket/assignment doesn't exist)409- Conflict (duplicate ticket, agent at capacity, ticket already assigned)
Error response format:
{
"detail": "Error message explaining what went wrong"
}
Tips for Frontend
- Polling: Poll ticket status every 30-60s for real-time updates
- Filters: Use query params to filter tickets by status, priority, region
- Pagination: Default limit is 50, adjust based on UI needs
- Computed Properties: Use
is_overdue,can_be_assigned, etc. for UI state - Team Size: Check
required_team_sizevsassigned_team_sizefor team tickets - GPS Tracking: Display
journey_location_historyon map for route visualization - Expense Approval: Filter by
is_approved: falsefor pending expenses - Stats Dashboard: Use
/statsendpoints for analytics/charts
Quick Reference: HTTP Methods
GET- Retrieve data (list, single item, stats)POST- Create new resource or trigger actionPUT- Update existing resourceDELETE- Not used (soft deletes via cancel/drop)
All endpoints require authentication via Bearer token in Authorization header.