# 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:** ```json { "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:** ```javascript 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:** ```json { "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:** ```json { "ont_serial": "HW12345678", "router_serial": "RT98765432", "installation_notes": "Installed on second floor" } ``` **Field names must match `field_items[].field_name` from checklist.** **Response:** ```json { "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:** ```json { "work_notes": "Installation completed successfully. Customer trained on WiFi setup.", "force_complete": false } ``` **Response:** ```json { "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:** ```json { "detail": { "message": "Photo validation failed", "errors": [ "Speedtest: Requires 1 photo, got 0", "ODU outdoor image: Requires 1 photo, got 0" ] } } ``` **404 Not Found:** ```json { "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 ```javascript // 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:** ```json [ { "type": "Speedtest", "required": true, "min_photos": 1, "max_photos": 1, "description": "Speed test collected" } ] ``` **activation_requirements:** ```json [ { "field": "ont_serial", "label": "ONT Serial Number", "type": "text", "required": true, "placeholder": "HW12345678" } ] ``` Different projects = different requirements. Forms adapt automatically.