Spaces:
Sleeping
Sleeping
| # 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!** π | |