swiftops-backend / docs /agent /implementation-notes /TICKET_COMPLETION_SYSTEM.md
kamau1's picture
feat(project): add complete project setup workflow with service methods and API endpoints for regions, roles, subcontractors, and finalization including validation and authorization
4835b24
# Ticket Completion System - Implementation Guide
## 🎯 Overview
**Lightweight** dynamic completion validation that generates checklists **at runtime** from project requirements. Supports **progressive completion** - agent can save photos separately from activation data.
**πŸ”‘ Key Architecture Decision:** NO extra database table! Checklist is ephemeral, generated on-the-fly from existing `project.photo_requirements` and `project.activation_requirements` (JSONB).
---
## πŸ“‹ Key Features
### βœ… Runtime Checklist Generation (NO DB Persistence)
- Checklist generated on-the-fly from `project.activation_requirements` and `project.photo_requirements` (JSONB)
- Different projects have different requirements
- **NO database storage** - checklist is ephemeral validation schema
### βœ… Progressive Completion (Two Independent Scopes)
1. **Photos Scope** - Upload all required photo types
2. **Activation Data Scope** - Fill all required fields
Agent can complete these scopes independently:
- Save photos first, fill activation data later
- OR fill activation data first, upload photos later
- Ticket only completes when BOTH scopes are validated
### βœ… Scoped Validation
- When updating photos β†’ Validates ONLY photo requirements
- When updating activation β†’ Validates ONLY field requirements
- When completing ticket β†’ Validates BOTH scopes
### βœ… Subscription Creation
- Installation tickets β†’ Creates subscription with activation data
- Support tickets β†’ Updates subscription equipment details
- Infrastructure tickets β†’ Just completes ticket
---
## πŸ—‚οΈ Database Schema
### βœ… Existing Tables Used (NO New Tables!)
**tickets** - Already has these fields:
```sql
-- Stores captured activation/equipment data
completion_data JSONB DEFAULT '{}',
-- Completion flags
completion_photos_verified BOOLEAN DEFAULT FALSE,
completion_data_verified BOOLEAN DEFAULT FALSE
```
**ticket_images** - Links tickets to photos:
```sql
ticket_id UUID β†’ tickets(id)
document_id UUID β†’ documents(id)
image_type VARCHAR(50) -- 'before_installation', 'after_installation', etc.
```
**documents** - Stores actual files:
```sql
file_url TEXT -- Cloudinary URL for images
storage_provider VARCHAR(50) -- 'cloudinary' for images
entity_type VARCHAR(50) -- 'ticket'
entity_id UUID -- ticket.id
```
**projects** - Source of truth for requirements:
```sql
-- Photo requirements (JSONB array)
photo_requirements JSONB DEFAULT '[]'
-- Example: [{"type": "before_installation", "required": true, "min_photos": 2, "max_photos": 5}]
-- Activation requirements (JSONB array)
activation_requirements JSONB DEFAULT '[]'
-- Example: [{"field": "ont_serial_number", "label": "ONT Serial", "type": "text", "required": true}]
```
**subscriptions** - Receives data on completion:
```sql
equipment_details JSONB DEFAULT '{}' -- Routes from ticket.completion_data
activation_details JSONB DEFAULT '{}' -- Routes from ticket.completion_data
```
---
## πŸ”„ User Experience Flow
### **Flow 1: Progressive Completion (Recommended)**
```
1. Agent clicks "Start Work" β†’ Ticket status: in_progress
↓
2. Checklist auto-generated from project requirements
↓
3. Agent uploads photos
- POST /tickets/{id}/update-photos
- Validates: All required photo types present?
- Saves: ticket_images records created
- Result: Photos scope βœ… complete
↓
4. Agent fills activation form (separate step)
- POST /tickets/{id}/update-activation
- Validates: All required fields filled?
- Saves: Data to ticket.completion_data
- Result: Activation scope βœ… complete
↓
5. Agent clicks "Complete Ticket"
- POST /tickets/{id}/complete
- Validates: Both scopes complete?
- Creates: Subscription (installations)
- Marks: Ticket complete
- Result: Ticket βœ… completed
```
### **Flow 2: All-at-Once Completion**
```
1. Agent clicks "Start Work" β†’ Ticket status: in_progress
↓
2. Agent uploads photos + fills activation data
↓
3. Agent clicks "Complete Ticket"
- POST /tickets/{id}/complete
- Include photos + activation_data in request
- Backend updates both scopes, then completes
- Result: Ticket βœ… completed in one call
```
---
## πŸ“‘ API Endpoints
### 1. Get Completion Checklist
```http
GET /api/v1/tickets/{ticket_id}/completion-checklist
```
**Response:**
```json
{
"id": "checklist-uuid",
"ticket_id": "ticket-uuid",
"checklist_items": [
{
"id": "photo_before_installation",
"type": "photo",
"photo_type": "before_installation",
"label": "Before Installation Photos",
"required": true,
"min_photos": 2,
"max_photos": 5,
"status": "pending",
"uploaded_document_ids": []
},
{
"id": "field_ont_serial_number",
"type": "field",
"field_name": "ont_serial_number",
"label": "ONT Serial Number",
"data_type": "text",
"required": true,
"value": null,
"status": "pending"
}
],
"is_photos_complete": false,
"is_activation_complete": false,
"is_complete": false,
"completion_percentage": 0.0
}
```
### 2. Update Photos (Scope Update)
```http
POST /api/v1/tickets/{ticket_id}/update-photos
Content-Type: application/json
{
"photos": {
"before_installation": ["doc-uuid-1", "doc-uuid-2"],
"after_installation": ["doc-uuid-3", "doc-uuid-4"],
"ont_label": ["doc-uuid-5"]
}
}
```
**Validation:**
- βœ… All required photo types included?
- βœ… Min/max photo counts satisfied?
**Success Response:**
```json
{
"success": true,
"message": "Photos updated successfully. Activation data still required.",
"ticket_id": "ticket-uuid",
"checklist": {
"is_photos_complete": true,
"is_activation_complete": false,
"is_complete": false,
"completion_percentage": 50.0
}
}
```
**Error Response:**
```json
{
"detail": {
"message": "Photo validation failed",
"errors": [
{
"item_id": "photo_before_installation",
"item_type": "photo",
"photo_type": "before_installation",
"error_message": "Insufficient photos for Before Installation Photos. Required: 2, Uploaded: 1"
}
]
}
}
```
### 3. Update Activation Data (Scope Update)
```http
POST /api/v1/tickets/{ticket_id}/update-activation
Content-Type: application/json
{
"activation_data": {
"ont_serial_number": "HW12345678",
"ont_mac_address": "00:11:22:33:44:55",
"signal_strength": -18.5,
"fiber_cable_id": "FC-001-234"
}
}
```
**Validation:**
- βœ… All required fields included?
- βœ… Data types correct?
- βœ… Regex validation passed?
**Success Response:**
```json
{
"success": true,
"message": "Activation data updated successfully. Photos still required.",
"ticket_id": "ticket-uuid",
"checklist": {
"is_photos_complete": false,
"is_activation_complete": true,
"is_complete": false,
"completion_percentage": 50.0
}
}
```
### 4. Complete Ticket (Final Step)
```http
POST /api/v1/tickets/{ticket_id}/complete
Content-Type: application/json
{
"work_notes": "Installation completed successfully. Customer very satisfied with service quality.",
"force_complete": false
}
```
**Optional:** Include photos/activation_data to update in final call:
```json
{
"photos": { ... },
"activation_data": { ... },
"work_notes": "...",
"force_complete": false
}
```
**Success Response:**
```json
{
"success": true,
"message": "Ticket completed successfully!",
"ticket_id": "ticket-uuid",
"subscription_id": "subscription-uuid",
"checklist": {
"is_photos_complete": true,
"is_activation_complete": true,
"is_complete": true,
"completion_percentage": 100.0
}
}
```
---
## 🎨 Frontend Implementation Guide
### **Screen 1: Completion Checklist Overview**
```typescript
// Get checklist
const checklist = await api.get(`/tickets/${ticketId}/completion-checklist`);
// Display progress
<ProgressBar
value={checklist.completion_percentage}
label={`${checklist.completion_percentage}% Complete`}
/>
// Show two sections
<Section title="Photos" complete={checklist.is_photos_complete}>
{checklist.checklist_items
.filter(item => item.type === 'photo')
.map(item => <PhotoUploadCard item={item} />)}
</Section>
<Section title="Activation Data" complete={checklist.is_activation_complete}>
{checklist.checklist_items
.filter(item => item.type === 'field')
.map(item => <DynamicFormField item={item} />)}
</Section>
// Enable complete button only when both scopes done
<Button
disabled={!checklist.is_complete}
onClick={completeTicket}
>
Complete Ticket
</Button>
```
### **Screen 2: Photo Upload**
```typescript
// Agent selects photos for each type
const [photos, setPhotos] = useState({});
// Save photos (scoped update)
const savePhotos = async () => {
try {
const response = await api.post(`/tickets/${ticketId}/update-photos`, {
photos: photos
});
toast.success(response.message);
// Navigate back to checklist overview
} catch (error) {
// Show validation errors
error.errors.forEach(err => {
toast.error(err.error_message);
});
}
};
```
### **Screen 3: Activation Form**
```typescript
// Dynamic form based on checklist items
const [formData, setFormData] = useState({});
// Render fields dynamically
{fieldItems.map(item => {
switch(item.data_type) {
case 'text':
return <TextInput
name={item.field_name}
label={item.label}
required={item.required}
placeholder={item.placeholder}
/>;
case 'number':
return <NumberInput ... />;
case 'select':
return <Select options={item.options} ... />;
}
})}
// Save activation data (scoped update)
const saveActivationData = async () => {
try {
const response = await api.post(`/tickets/${ticketId}/update-activation`, {
activation_data: formData
});
toast.success(response.message);
// Navigate back to checklist overview
} catch (error) {
// Show validation errors
}
};
```
### **Screen 4: Final Completion**
```typescript
// Final step - add work notes and complete
const completeTicket = async () => {
try {
const response = await api.post(`/tickets/${ticketId}/complete`, {
work_notes: workNotes,
force_complete: false
});
toast.success("Ticket completed! Subscription created.");
// Navigate to ticket details or home
} catch (error) {
// Show what's missing
toast.error(`Cannot complete: ${error.message}`);
}
};
```
---
## πŸ”§ Configuration Examples
### **Example 1: FTTH Installation Project**
```json
{
"photo_requirements": [
{
"type": "before_installation",
"description": "Before Installation Photos",
"required": true,
"min_photos": 2,
"max_photos": 5
},
{
"type": "after_installation",
"description": "After Installation Photos",
"required": true,
"min_photos": 2,
"max_photos": 5
},
{
"type": "ont_label",
"description": "ONT Device Label",
"required": true,
"min_photos": 1,
"max_photos": 2
},
{
"type": "customer_signature",
"description": "Customer Signature",
"required": false,
"min_photos": 1,
"max_photos": 1
}
],
"activation_requirements": [
{
"field": "ont_serial_number",
"label": "ONT Serial Number",
"type": "text",
"required": true,
"placeholder": "Enter ONT serial number",
"validation_regex": "^[A-Z0-9]{10,20}$"
},
{
"field": "ont_mac_address",
"label": "ONT MAC Address",
"type": "text",
"required": true,
"validation_regex": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"
},
{
"field": "signal_strength",
"label": "Signal Strength (dBm)",
"type": "number",
"required": false,
"placeholder": "e.g., -18.5"
},
{
"field": "fiber_cable_id",
"label": "Fiber Cable ID",
"type": "text",
"required": true
},
{
"field": "speed_test_result",
"label": "Speed Test Result (Mbps)",
"type": "number",
"required": false
}
]
}
```
### **Example 2: Fixed Wireless Project**
```json
{
"photo_requirements": [
{
"type": "antenna_installation",
"description": "Antenna Installation Photo",
"required": true,
"min_photos": 2,
"max_photos": 4
},
{
"type": "signal_strength_screen",
"description": "Signal Strength Screenshot",
"required": true,
"min_photos": 1,
"max_photos": 1
}
],
"activation_requirements": [
{
"field": "cpe_serial_number",
"label": "CPE Serial Number",
"type": "text",
"required": true
},
{
"field": "antenna_model",
"label": "Antenna Model",
"type": "select",
"required": true,
"options": ["Ubiquiti NanoStation", "Mikrotik SXT", "TP-Link CPE"]
},
{
"field": "signal_strength",
"label": "Signal Strength (dBm)",
"type": "number",
"required": true
},
{
"field": "frequency_band",
"label": "Frequency Band",
"type": "select",
"required": true,
"options": ["2.4GHz", "5GHz"]
}
]
}
```
---
## βœ… Benefits
### For Agents
- βœ… **Clear guidance** - Know exactly what's required
- βœ… **Flexible workflow** - Save photos and activation data separately
- βœ… **Progress tracking** - See completion percentage
- βœ… **No confusion** - Dynamic form adapts to project
### For Managers
- βœ… **Consistent data** - All installations have required information
- βœ… **Quality control** - Can't complete without photos/data
- βœ… **Flexibility** - Different projects have different requirements
- βœ… **Audit trail** - Know what was captured and when
### For System
- βœ… **Data integrity** - Subscriptions created with complete information
- βœ… **Type safety** - Validation at every step
- βœ… **Scalability** - New projects just configure requirements
- βœ… **Maintainability** - No code changes for new requirements
---
## πŸš€ Deployment Path
### Step 1: Configure Projects βš™οΈ
```python
# Update existing projects with requirements (JSONB)
project.photo_requirements = [
{"type": "before_installation", "required": True, "min_photos": 2, "max_photos": 5},
{"type": "after_installation", "required": True, "min_photos": 2, "max_photos": 5}
]
project.activation_requirements = [
{"field": "ont_serial_number", "label": "ONT Serial Number", "type": "text", "required": True},
{"field": "ont_mac_address", "label": "ONT MAC Address", "type": "text", "required": True}
]
```
### Step 2: Deploy Backend πŸš€
- Deploy updated API endpoints
- No database migrations needed! βœ…
- Existing tickets work as-is
### Step 3: Update Mobile App πŸ“±
- Implement completion checklist screen
- Add photo upload functionality (multipart/form-data)
- Add dynamic form for activation data
- Test progressive completion flow
---
## 🎯 Implementation Status
1. βœ… **Runtime checklist generation** - Reads from project JSONB
2. βœ… **Service layer** - Validates, uploads via media_service.py
3. βœ… **Schemas** - Request/response models
4. βœ… **API endpoints** - GET checklist, POST photos, POST activation, POST complete
5. βœ… **Documentation** - Complete guide
6. ⏳ **Configure projects** - Add photo_requirements and activation_requirements
7. ⏳ **Frontend implementation** - Mobile app UI
8. ⏳ **Testing** - End-to-end validation
---
## βœ… Benefits of This Approach
**vs Extra Database Table:**
- βœ… **No data duplication** - Requirements already in projects table
- βœ… **No migration needed** - Uses existing schema
- βœ… **Lightweight** - Checklist generated on-demand
- βœ… **Flexible** - PM can update requirements anytime (just edit JSONB)
- βœ… **Scalable** - No extra database records per ticket
- βœ… **Simple** - Less code, less complexity
**Data Flow:**
```
project.photo_requirements (JSONB) ──┐
project.activation_requirements (JSONB) ───
↓
Generate Checklist (runtime)
↓
Validate User Input
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
↓ ↓
Photos β†’ Cloudinary Activation Data β†’ ticket.completion_data
(via media_service.py) (JSONB)
↓ ↓
documents + ticket_images subscription.equipment_details
subscription.activation_details
```
---
**Ready to implement on frontend!** πŸš€