swiftops-backend / docs /agent /frontend /TICKET_COMPLETION_API.md
kamau1's picture
Fix dashboard and global work queue by correcting ticket_name usage, and ensuring overview endpoint returns active assignments.
99ef9a1

Ticket Completion API Guide

Overview

Dynamic completion forms generated from project requirements. Each project defines its own photo and activation requirements.

Workflow

1. Agent arrives β†’ Record arrival
2. Get completion checklist β†’ See what's required
3. Upload photos β†’ Multiple times, different types
4. Submit activation data β†’ Equipment serials, etc.
5. Complete ticket β†’ PM reviews and invoices

1. Get Completion Checklist

Endpoint: GET /api/v1/tickets/{ticket_id}/completion-checklist

Response:

{
  "ticket_id": "uuid",
  "project_id": "uuid",
  "completion_percentage": 45.5,
  "is_photos_complete": false,
  "is_activation_complete": true,
  "is_complete": false,
  "photo_items": [
    {
      "id": "photo_Speedtest",
      "type": "photo",
      "photo_type": "Speedtest",
      "label": "Speed test collected",
      "required": true,
      "min_photos": 1,
      "max_photos": 1,
      "uploaded_count": 0,
      "uploaded_photos": [],
      "status": "pending"
    }
  ],
  "field_items": [
    {
      "id": "field_ont_serial",
      "type": "field",
      "field_name": "ont_serial",
      "label": "ONT Serial Number",
      "data_type": "text",
      "required": true,
      "value": "HW12345678",
      "status": "complete"
    }
  ]
}

2. Upload Photos

Endpoint: POST /api/v1/tickets/{ticket_id}/upload-photos

Content-Type: multipart/form-data

Form Data:

  • photo_type: string (matches photo_items[].photo_type from checklist)
  • files: file[] (image files)

Example:

const formData = new FormData();
formData.append('photo_type', 'Speedtest');
formData.append('files', file1);
formData.append('files', file2); // If multiple allowed

await api.post(`/tickets/${ticketId}/upload-photos`, formData);

Response:

{
  "success": true,
  "message": "Photos updated successfully. Activation data still required.",
  "ticket_id": "uuid",
  "checklist": { /* updated checklist */ }
}

Notes:

  • Can call multiple times for different photo types
  • Photos stored with image_type="completion"
  • Description includes photo type: [Speedtest] Completion photo for ticket

3. Submit Activation Data

Endpoint: POST /api/v1/tickets/{ticket_id}/activation-data

Payload:

{
  "ont_serial": "HW12345678",
  "router_serial": "RT98765432",
  "installation_notes": "Installed on second floor"
}

Field names must match field_items[].field_name from checklist.

Response:

{
  "success": true,
  "message": "Activation data updated successfully",
  "ticket_id": "uuid",
  "checklist": { /* updated checklist */ }
}

Notes:

  • Data stored in ticket.completion_data (JSONB)
  • Used to create subscription on invoicing
  • Equipment serials tracked in inventory

4. Complete Ticket

Endpoint: POST /api/v1/tickets/{ticket_id}/complete

Payload:

{
  "work_notes": "Installation completed successfully. Customer trained on WiFi setup.",
  "force_complete": false
}

Response:

{
  "success": true,
  "message": "Ticket completed successfully",
  "ticket_id": "uuid",
  "subscription_id": "uuid",  // If created
  "equipment_installed": ["HW12345678", "RT98765432"]
}

Validation:

  • All required photos uploaded
  • All required fields filled
  • Agent must have arrived at location
  • Set force_complete: true to bypass (admin only)

Notes:

  • Ticket status β†’ completed
  • Subscription NOT auto-created (PM does this on invoicing)
  • Equipment marked as installed in inventory

Error Responses

400 Bad Request:

{
  "detail": {
    "message": "Photo validation failed",
    "errors": [
      "Speedtest: Requires 1 photo, got 0",
      "ODU outdoor image: Requires 1 photo, got 0"
    ]
  }
}

404 Not Found:

{
  "detail": "Ticket not found"
}

Progressive Completion

Agent can complete in any order:

  1. Upload some photos β†’ Save
  2. Upload more photos later β†’ Save
  3. Fill activation data β†’ Save
  4. Upload remaining photos β†’ Save
  5. Complete ticket β†’ Final

Each step updates completion_percentage in checklist.


Example Flow

// 1. Get checklist
const checklist = await api.get(`/tickets/${ticketId}/completion-checklist`);

// 2. Render form from checklist
checklist.photo_items.forEach(item => {
  renderPhotoUpload(item.photo_type, item.label, item.required);
});

checklist.field_items.forEach(item => {
  renderField(item.field_name, item.label, item.data_type, item.required);
});

// 3. Upload photos as agent takes them
async function uploadPhoto(photoType, file) {
  const formData = new FormData();
  formData.append('photo_type', photoType);
  formData.append('files', file);
  
  await api.post(`/tickets/${ticketId}/upload-photos`, formData);
  
  // Refresh checklist to show progress
  const updated = await api.get(`/tickets/${ticketId}/completion-checklist`);
  updateProgress(updated.completion_percentage);
}

// 4. Submit activation data
async function submitActivationData(data) {
  await api.post(`/tickets/${ticketId}/activation-data`, data);
  
  // Refresh checklist
  const updated = await api.get(`/tickets/${ticketId}/completion-checklist`);
  updateProgress(updated.completion_percentage);
}

// 5. Complete ticket
async function completeTicket(workNotes) {
  const result = await api.post(`/tickets/${ticketId}/complete`, {
    work_notes: workNotes
  });
  
  showSuccess('Ticket completed!');
}

Project Requirements Format

Projects define requirements in JSONB fields:

photo_requirements:

[
  {
    "type": "Speedtest",
    "required": true,
    "min_photos": 1,
    "max_photos": 1,
    "description": "Speed test collected"
  }
]

activation_requirements:

[
  {
    "field": "ont_serial",
    "label": "ONT Serial Number",
    "type": "text",
    "required": true,
    "placeholder": "HW12345678"
  }
]

Different projects = different requirements. Forms adapt automatically.