kamau1's picture
feat(tasks): add comprehensive tasks API with notification integration and documentation
a15e3da

Tasks API - Frontend Integration Guide

What Are Tasks?

Tasks = Work items that need to be done (planning layer) Tickets = Work orders assigned to field agents (execution layer)

Flow: Create Task β†’ Convert to Ticket β†’ Assign to Agent β†’ Track Expenses


Endpoints

1. Create Task

POST /api/v1/tasks
Permission: manage_tasks

Request:

{
  "project_id": "uuid",
  "task_title": "Install fiber at Location X",
  "task_description": "Detailed description",
  "task_type": "installation",  // or "delivery", "maintenance", "survey", etc.
  "location_name": "Pole 45, Ngong Road",
  "project_region_id": "uuid",  // optional
  "task_latitude": -1.2921,  // optional
  "task_longitude": 36.8219,  // optional
  "priority": "normal",  // urgent, high, normal, low
  "scheduled_date": "2025-12-01",
  "notes": "Optional notes"
}

Response:

{
  "id": "uuid",
  "project_id": "uuid",
  "task_title": "Install fiber at Location X",
  "task_description": "...",
  "task_type": "installation",
  "status": "pending",
  "priority": "normal",
  "scheduled_date": "2025-12-01",
  "is_completed": false,
  "is_overdue": false,
  "has_location": true,
  "project_title": "Project Name",
  "region_name": "Nairobi",
  "created_by_name": "John Doe",
  "created_at": "2025-11-27T10:00:00Z"
}

2. List Tasks

GET /api/v1/tasks?project_id={id}&status={status}&page=1&page_size=50
Permission: view_tasks

Query Params:

  • project_id - Filter by project
  • status - pending, assigned, in_progress, completed, cancelled, blocked
  • task_type - installation, delivery, maintenance, etc.
  • priority - urgent, high, normal, low
  • project_region_id - Filter by region
  • scheduled_date - Filter by date
  • search - Search in title/description
  • page - Page number (default: 1)
  • page_size - Items per page (default: 50)

Response:

{
  "items": [
    {
      "id": "uuid",
      "task_title": "...",
      "status": "pending",
      "priority": "high",
      "scheduled_date": "2025-12-01",
      "is_overdue": false,
      "project_title": "Project Name",
      "region_name": "Nairobi"
    }
  ],
  "total": 150,
  "page": 1,
  "page_size": 50,
  "total_pages": 3
}

3. Get Single Task

GET /api/v1/tasks/{task_id}
Permission: view_tasks

Response: Same as create response


4. Update Task

PUT /api/v1/tasks/{task_id}
Permission: manage_tasks

Request: Same fields as create (all optional)


5. Update Task Status

PATCH /api/v1/tasks/{task_id}/status
Permission: manage_tasks

Request:

{
  "status": "in_progress",  // pending, assigned, in_progress, completed, cancelled, blocked
  "reason": "Optional reason for status change"
}

6. Start Task

POST /api/v1/tasks/{task_id}/start
Permission: manage_tasks

Request:

{
  "started_at": "2025-11-27T10:00:00Z",  // optional, defaults to now
  "notes": "Starting work"
}

7. Complete Task

POST /api/v1/tasks/{task_id}/complete
Permission: manage_tasks

Request:

{
  "completed_at": "2025-11-27T15:00:00Z",  // optional, defaults to now
  "completion_notes": "Work completed successfully"
}

8. Cancel Task

POST /api/v1/tasks/{task_id}/cancel
Permission: manage_tasks

Request:

{
  "cancellation_reason": "Customer cancelled order"
}

9. Delete Task

DELETE /api/v1/tasks/{task_id}
Permission: manage_tasks

No request body. Returns 204 No Content.


10. Get Task Stats

GET /api/v1/tasks/stats?project_id={id}
Permission: view_tasks

Query Params:

  • project_id - Filter by project (optional)
  • project_region_id - Filter by region (optional)

Response:

{
  "total_tasks": 150,
  "pending_tasks": 20,
  "assigned_tasks": 15,
  "in_progress_tasks": 30,
  "completed_tasks": 80,
  "cancelled_tasks": 3,
  "blocked_tasks": 2,
  "urgent_tasks": 5,
  "high_priority_tasks": 25,
  "normal_priority_tasks": 100,
  "low_priority_tasks": 20,
  "overdue_tasks": 8,
  "scheduled_today": 5,
  "scheduled_this_week": 15,
  "avg_completion_time_hours": 48.5,
  "completion_rate": 53.33,
  "by_task_type": {
    "installation": 50,
    "delivery": 25,
    "maintenance": 30,
    "survey": 20
  }
}

Task Status Flow

pending β†’ assigned β†’ in_progress β†’ completed
   ↓                                    ↑
cancelled ←---------------------------blocked

Status Values:

  • pending - Not yet started
  • assigned - Assigned but not started
  • in_progress - Currently being worked on
  • completed - Finished
  • cancelled - Cancelled
  • blocked - Blocked by dependencies

Priority Values

  • urgent - Needs immediate attention
  • high - High priority
  • normal - Normal priority (default)
  • low - Low priority

Common Task Types

  • installation - Install equipment/infrastructure
  • delivery - Deliver equipment (auto-created from inventory distributions)
  • maintenance - Maintenance work
  • survey - Site surveys
  • pickup - Pick up equipment
  • testing - Testing/quality checks
  • customer_visit - Customer visits
  • training - Training sessions

Note: task_type is flexible - you can use any string value


Permissions

Role View Tasks Manage Tasks
platform_admin βœ… All βœ… All
project_manager βœ… Their projects βœ… Their projects
sales_manager βœ… Their org βœ… Their org
contractor_admin βœ… Their org βœ… Their org
dispatcher βœ… Their org βœ… Their org
field_agent βœ… Assigned to them ❌
sales_agent βœ… Related to sales ❌

Auto-Created Tasks

Tasks are automatically created when:

  1. Inventory Distribution - When distributing inventory to regional hub
    • Task type: delivery
    • Task title: "Deliver {equipment} to {region}"
    • Includes metadata linking back to distribution

Check for auto-created tasks:

// Task has metadata.auto_created = true
if (task.additional_metadata?.auto_created) {
  // Show "Auto-created from distribution" badge
  // Link to distribution: task.additional_metadata.distribution_id
}

Converting Task to Ticket

Single Task Promotion

POST /api/v1/tickets/from-task
{
  "task_id": "uuid",
  "priority": "high",
  "scheduled_date": "2025-12-01",
  "scheduled_time_slot": "morning",
  "work_description": "Install fiber cable",
  "notes": "Customer prefers morning"
}

Response: Ticket object (see tickets API docs)

Bulk Task Promotion

POST /api/v1/tickets/bulk-from-tasks
{
  "task_ids": ["uuid1", "uuid2", "uuid3"],
  "priority": "normal",
  "notes": "Batch promotion - December deliveries"
}

Response:

{
  "total": 3,
  "successful": 2,
  "failed": 1,
  "errors": ["Task uuid3: Already has ticket"],
  "created_ticket_ids": ["ticket-uuid1", "ticket-uuid2"]
}

Note: Bulk promotion continues on error - creates as many tickets as possible


Example UI Components

Task List

const { data } = useQuery(['tasks', projectId], () => 
  api.get('/tasks', { params: { project_id: projectId } })
);

<TaskList>
  {data.items.map(task => (
    <TaskCard
      key={task.id}
      title={task.task_title}
      status={task.status}
      priority={task.priority}
      isOverdue={task.is_overdue}
      scheduledDate={task.scheduled_date}
      region={task.region_name}
    />
  ))}
</TaskList>

Task Stats Dashboard

const { data: stats } = useQuery(['task-stats', projectId], () =>
  api.get('/tasks/stats', { params: { project_id: projectId } })
);

<StatsGrid>
  <StatCard label="Total" value={stats.total_tasks} />
  <StatCard label="In Progress" value={stats.in_progress_tasks} />
  <StatCard label="Completed" value={stats.completed_tasks} />
  <StatCard label="Overdue" value={stats.overdue_tasks} variant="danger" />
  <StatCard label="Completion Rate" value={`${stats.completion_rate}%`} />
</StatsGrid>

Create Task Form

const createTask = useMutation(
  (data) => api.post('/tasks', data),
  {
    onSuccess: () => {
      queryClient.invalidateQueries(['tasks']);
      toast.success('Task created');
    }
  }
);

<Form onSubmit={handleSubmit}>
  <Input name="task_title" required />
  <Textarea name="task_description" />
  <Select name="task_type" options={taskTypes} />
  <Select name="priority" options={priorities} />
  <DatePicker name="scheduled_date" />
  <RegionSelect name="project_region_id" />
  <LocationPicker 
    onLocationSelect={(lat, lng) => {
      setFieldValue('task_latitude', lat);
      setFieldValue('task_longitude', lng);
    }}
  />
  <Button type="submit">Create Task</Button>
</Form>

Task Actions

// Start task
const startTask = useMutation((taskId) =>
  api.post(`/tasks/${taskId}/start`, { notes: 'Starting work' })
);

// Complete task
const completeTask = useMutation((taskId) =>
  api.post(`/tasks/${taskId}/complete`, { 
    completion_notes: 'Work done' 
  })
);

// Cancel task
const cancelTask = useMutation((taskId) =>
  api.post(`/tasks/${taskId}/cancel`, { 
    cancellation_reason: 'Customer cancelled' 
  })
);

// Convert single task to ticket
const convertToTicket = useMutation((data) =>
  api.post('/tickets/from-task', data)
);

// Bulk convert tasks to tickets
const bulkConvertToTickets = useMutation((data) =>
  api.post('/tickets/bulk-from-tasks', data),
  {
    onSuccess: (result) => {
      toast.success(`${result.successful} tickets created`);
      if (result.failed > 0) {
        toast.warning(`${result.failed} tasks failed to convert`);
      }
      queryClient.invalidateQueries(['tasks']);
      queryClient.invalidateQueries(['tickets']);
    }
  }
);

Bulk Task Promotion UI

// Task list with bulk selection
const [selectedTasks, setSelectedTasks] = useState([]);

const handleBulkPromote = () => {
  bulkConvertToTickets.mutate({
    task_ids: selectedTasks,
    priority: 'normal',
    notes: 'Batch promotion'
  });
};

<TaskList>
  <BulkActions>
    <Checkbox
      checked={selectedTasks.length === tasks.length}
      onChange={handleSelectAll}
      label="Select All"
    />
    <Button
      onClick={handleBulkPromote}
      disabled={selectedTasks.length === 0}
    >
      Promote {selectedTasks.length} to Tickets
    </Button>
  </BulkActions>
  
  {tasks.map(task => (
    <TaskCard
      key={task.id}
      task={task}
      selected={selectedTasks.includes(task.id)}
      onSelect={(id) => toggleSelection(id)}
    />
  ))}
</TaskList>

// Show bulk result
{bulkResult && (
  <Alert>
    <p>βœ… {bulkResult.successful} tickets created</p>
    {bulkResult.failed > 0 && (
      <>
        <p>❌ {bulkResult.failed} failed</p>
        <ul>
          {bulkResult.errors.map((error, i) => (
            <li key={i}>{error}</li>
          ))}
        </ul>
      </>
    )}
  </Alert>
)}

Error Handling

Common Errors:

  • 403 - Insufficient permissions
  • 404 - Task not found
  • 400 - Validation error (check response.detail)
  • 500 - Server error

Example:

try {
  await api.post('/tasks', taskData);
} catch (error) {
  if (error.response?.status === 403) {
    toast.error('You don\'t have permission to create tasks');
  } else if (error.response?.status === 400) {
    toast.error(error.response.data.detail);
  } else {
    toast.error('Failed to create task');
  }
}

Quick Reference

Base URL: /api/v1/tasks

Endpoints:

  • POST /tasks - Create
  • GET /tasks - List
  • GET /tasks/stats - Stats (BEFORE /{task_id})
  • GET /tasks/{id} - Get one
  • PUT /tasks/{id} - Update
  • PATCH /tasks/{id}/status - Update status
  • POST /tasks/{id}/start - Start
  • POST /tasks/{id}/complete - Complete
  • POST /tasks/{id}/cancel - Cancel
  • DELETE /tasks/{id} - Delete

Task β†’ Ticket Promotion:

  • POST /tickets/from-task - Convert single task
  • POST /tickets/bulk-from-tasks - Convert multiple tasks (NEW!)

Key Fields:

  • task_title (required)
  • project_id (required)
  • task_type (optional, flexible string)
  • status (pending/assigned/in_progress/completed/cancelled/blocked)
  • priority (urgent/high/normal/low)
  • scheduled_date (optional)
  • location_name, task_latitude, task_longitude (optional)

Computed Fields:

  • is_completed - Boolean
  • is_overdue - Boolean
  • has_location - Boolean
  • duration_days - Number (if completed)

That's it! πŸš€