Spaces:
Sleeping
Sleeping
Ticket Completion - Quick Reference
β Architecture: Runtime Checklist (NO Extra Table)
You were 100% correct! No need for extra database table. Here's the final implementation:
π Data Storage
Existing Tables Used (No Migrations!)
| Table | Purpose | Data Stored |
|---|---|---|
| projects | Requirements source | photo_requirements (JSONB)activation_requirements (JSONB) |
| tickets | Completion tracking | completion_data (JSONB)completion_photos_verified (bool)completion_data_verified (bool) |
| ticket_images | Photo links | ticket_id, document_id, image_type |
| documents | File storage | Cloudinary URLs, metadata |
| subscriptions | Final data | equipment_details (JSONB)activation_details (JSONB) |
π Flow
1. Agent Clicks "Complete Ticket" or "Add Photo"
# Backend generates checklist on-the-fly (runtime)
checklist = TicketCompletionService.generate_checklist(ticket, db)
# Reads from:
project.photo_requirements # JSONB array
project.activation_requirements # JSONB array
# Returns ephemeral checklist (not stored)
2. Agent Uploads Photos
# POST /tickets/{id}/upload-photos
# multipart/form-data: photo_type, files
# Validates against project.photo_requirements
errors = _validate_photos(photos, project.photo_requirements)
# Routes to Cloudinary via media_service.py
document = await StorageService.upload_file(
file=file,
entity_type="ticket",
force_provider="cloudinary" # Images go to Cloudinary
)
# Links via ticket_images
ticket_image = TicketImage(
ticket_id=ticket.id,
document_id=document.id,
image_type=photo_type
)
3. Agent Fills Activation Form
# POST /tickets/{id}/update-activation
# JSON: {"activation_data": {...}}
# Validates against project.activation_requirements
errors = _validate_activation_data(data, project.activation_requirements)
# Stores in ticket.completion_data (JSONB)
ticket.completion_data = activation_data
ticket.completion_data_verified = True
4. Agent Completes Ticket
# POST /tickets/{id}/complete
# Generates checklist (runtime)
checklist = generate_checklist(ticket, db)
# Validates BOTH scopes
if not checklist['is_complete']:
raise HTTPException(400, "Requirements not satisfied")
# Creates subscription (installations)
if ticket.source == "sales_order":
subscription = create_subscription()
subscription.equipment_details = ticket.completion_data # JSONB β JSONB
subscription.activation_details = {
"photos": [img.document_id for img in ticket.images]
}
# Updates equipment (support tickets)
elif ticket.source == "incident":
subscription.equipment_details.update(ticket.completion_data)
π‘ API Endpoints
GET /tickets/{id}/completion-checklist
Generates checklist at runtime
Response:
{
"ticket_id": "uuid",
"project_id": "uuid",
"photo_items": [
{
"photo_type": "before_installation",
"required": true,
"min_photos": 2,
"uploaded_count": 0,
"status": "pending"
}
],
"field_items": [
{
"field_name": "ont_serial_number",
"label": "ONT Serial Number",
"required": true,
"value": null,
"status": "pending"
}
],
"is_photos_complete": false,
"is_activation_complete": false,
"completion_percentage": 0.0
}
POST /tickets/{id}/upload-photos
Uploads to Cloudinary, creates ticket_images
Request (multipart/form-data):
photo_type: "before_installation"
files: [file1.jpg, file2.jpg]
POST /tickets/{id}/update-activation
Stores in ticket.completion_data JSONB
Request:
{
"activation_data": {
"ont_serial_number": "HW12345678",
"ont_mac_address": "00:11:22:33:44:55"
}
}
POST /tickets/{id}/complete
Final validation + subscription creation
Request:
{
"work_notes": "Completed successfully",
"force_complete": false
}
π¨ Frontend Integration
Step 1: Get Checklist
const checklist = await api.get(`/tickets/${ticketId}/completion-checklist`);
// Show progress
<ProgressCircle value={checklist.completion_percentage} />
// Show what's needed
{checklist.photo_items.map(item => (
<PhotoUploadCard
type={item.photo_type}
label={item.label}
required={item.required}
uploaded={item.uploaded_count}
min={item.min_photos}
status={item.status}
/>
))}
Step 2: Upload Photos (Separate)
const uploadPhotos = async (photoType: string, files: File[]) => {
const formData = new FormData();
formData.append('photo_type', photoType);
files.forEach(file => formData.append('files', file));
await api.post(`/tickets/${ticketId}/upload-photos`, formData);
toast.success("Photos uploaded!");
};
Step 3: Fill Activation (Separate)
const saveActivation = async (data: Record<string, any>) => {
await api.post(`/tickets/${ticketId}/update-activation`, {
activation_data: data
});
toast.success("Activation data saved!");
};
Step 4: Complete Ticket
const completeTicket = async () => {
// Check if both scopes complete
const checklist = await api.get(`/tickets/${ticketId}/completion-checklist`);
if (!checklist.is_complete) {
toast.error("Please complete all requirements first");
return;
}
await api.post(`/tickets/${ticketId}/complete`, {
work_notes: notes
});
toast.success("Ticket completed! Subscription created.");
};
βοΈ Project Configuration
Example: FTTH Installation
project.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
}
]
project.activation_requirements = [
{
"field": "ont_serial_number",
"label": "ONT Serial Number",
"type": "text",
"required": True,
"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
}
]
π Benefits
β vs Extra Table Approach
| Aspect | Extra Table | Runtime (Current) |
|---|---|---|
| Data Duplication | β Duplicates project requirements | β Reads from projects |
| Database Records | β One record per ticket | β Zero extra records |
| Migrations | β New table, triggers, FKs | β No migrations needed |
| Flexibility | β Checklist locked once created | β Always reflects latest requirements |
| PM Changes | β Old checklists outdated | β Instant updates |
| Complexity | β More code, more models | β Simpler codebase |
| Performance | β Extra joins | β Direct JSONB reads |
π Deployment Checklist
- Service layer created (
ticket_completion_service.py) - API endpoints created (
ticket_completion.py) - Schemas created (
ticket_completion.py) - Router registered (
router.py) - Documentation updated
- Configure
photo_requirementson projects - Configure
activation_requirementson projects - Frontend implementation
- End-to-end testing
Ready for frontend implementation! π