Spaces:
Sleeping
Sleeping
| # Ticket Reactivation Strategy - Final Design | |
| ## Overview | |
| The ticket system now supports **space-efficient reactivation** of cancelled/completed tickets, while still maintaining unique deduplication keys for audit trails. | |
| --- | |
| ## Business Problem Solved | |
| ### Scenario: Customer Service Reactivation | |
| ``` | |
| Customer orders WiFi β Cancels β Wants WiFi again | |
| ``` | |
| **Bad Design (Create New Ticket):** | |
| - β Duplicate rows in database | |
| - β Lose payment history if customer paid before cancelling | |
| - β Can't track chronic cancellers | |
| - β Wastes database space | |
| **Good Design (Reactivate Ticket):** | |
| - β Reuse existing ticket row (space efficient) | |
| - β Preserve payment history | |
| - β Track customer behavior (chronic cancellers) | |
| - β Complete audit trail | |
| --- | |
| ## Implementation Logic | |
| ### Create Ticket Flow (With Reactivation) | |
| ```python | |
| def create_ticket_from_sales_order(sales_order_id): | |
| """ | |
| Creates new ticket OR reactivates cancelled/completed ticket. | |
| """ | |
| # 1. Check for ACTIVE ticket (prevent duplicates) | |
| active_ticket = find_active_ticket(sales_order_id) | |
| if active_ticket: | |
| raise HTTPException(409, "Active ticket already exists") | |
| # 2. Check for CANCELLED/COMPLETED ticket (reactivation opportunity) | |
| cancelled_ticket = find_cancelled_ticket(sales_order_id) | |
| if cancelled_ticket: | |
| # REACTIVATE: Update existing ticket | |
| cancelled_ticket.status = "open" | |
| cancelled_ticket.scheduled_date = new_date | |
| cancelled_ticket.notes = f"[REACTIVATED {now}] {notes}\n{old_notes}" | |
| # dedup_key remains unchanged (permanent) | |
| return cancelled_ticket # β Reused row | |
| # 3. No existing ticket - Create new one | |
| dedup_key = MD5(source + source_id + ticket_type + created_at) | |
| new_ticket = Ticket( | |
| source_id=sales_order_id, | |
| status="open", | |
| dedup_key=dedup_key # Permanent unique key | |
| ) | |
| return new_ticket # β New row | |
| ``` | |
| ### Key Points | |
| 1. **Active Ticket Check**: Prevents duplicate active tickets | |
| 2. **Cancelled Ticket Check**: Enables reactivation (space efficient) | |
| 3. **Dedup Key**: Permanent (tracks creation instance, not lifecycle) | |
| 4. **Reactivation**: Updates status, dates, adds note, reuses row | |
| --- | |
| ## Business Rules | |
| ### Rule 1: One Source = One Active Ticket | |
| - Sales order can have ONE active ticket at a time | |
| - Prevents duplicate work orders | |
| - Check: `status NOT IN ('completed', 'cancelled')` | |
| ### Rule 2: Reactivation for Same Service | |
| ``` | |
| Customer cancels 10Mbps WiFi β Wants 10Mbps WiFi again | |
| β REACTIVATE cancelled ticket (reuse row) | |
| ``` | |
| **Implementation:** | |
| - Find cancelled ticket for same sales order | |
| - Update status: cancelled β open | |
| - Update dates and schedule | |
| - Add reactivation note with timestamp | |
| - **Dedup key remains unchanged** | |
| ### Rule 3: New Ticket for Different Service | |
| ``` | |
| Customer had 10Mbps (cancelled) β Now wants 50Mbps | |
| β Create NEW sales order β NEW ticket (new row) | |
| ``` | |
| **Implementation:** | |
| - Create new sales order (different package) | |
| - Create new ticket with new dedup_key | |
| - History shows multiple tickets for customer | |
| --- | |
| ## Database Space Efficiency | |
| ### Scenario: Chronic Canceller | |
| **Without Reactivation (Wasteful):** | |
| ```sql | |
| -- Customer orders/cancels 5 times | |
| INSERT INTO tickets (...) -- Ticket 1 | |
| INSERT INTO tickets (...) -- Ticket 2 | |
| INSERT INTO tickets (...) -- Ticket 3 | |
| INSERT INTO tickets (...) -- Ticket 4 | |
| INSERT INTO tickets (...) -- Ticket 5 | |
| -- Result: 5 rows for same sales order | |
| ``` | |
| **With Reactivation (Efficient):** | |
| ```sql | |
| -- Customer orders/cancels 5 times | |
| INSERT INTO tickets (...) -- Ticket 1 created | |
| UPDATE tickets SET status='cancelled' -- Cancelled | |
| UPDATE tickets SET status='open' -- Reactivated | |
| UPDATE tickets SET status='cancelled' -- Cancelled again | |
| UPDATE tickets SET status='open' -- Reactivated again | |
| -- Result: 1 row for sales order | |
| -- Notes field tracks all state changes | |
| ``` | |
| **Savings:** | |
| - 1 row instead of 5 | |
| - 80% space reduction | |
| - Payment history preserved | |
| - Chronic canceller easily identified | |
| --- | |
| ## Dedup Key Strategy | |
| ### Format | |
| ``` | |
| dedup_key = MD5(source + '::' + source_id + '::' + ticket_type + '::' + created_at) | |
| ``` | |
| ### Properties | |
| - **Permanent**: Never cleared (even on completion/cancellation) | |
| - **Unique**: Timestamp ensures uniqueness | |
| - **Tracks Creation**: Identifies when ticket was first created | |
| - **Not Lifecycle**: Doesn't change with status updates | |
| ### Why Timestamp? | |
| 1. **Prevents Collisions**: If same source tries multiple times | |
| 2. **Audit Trail**: Know when ticket was created | |
| 3. **Allows Multiple Tickets**: New creation = new timestamp = new dedup_key | |
| 4. **Reactivation Support**: Same dedup_key when reactivating | |
| --- | |
| ## API Behavior | |
| ### POST /api/v1/tickets/from-sales-order | |
| **Request:** | |
| ```json | |
| { | |
| "sales_order_id": "uuid-123", | |
| "priority": "normal", | |
| "scheduled_date": "2024-03-20", | |
| "notes": "Customer wants morning installation" | |
| } | |
| ``` | |
| **Response (New Ticket):** | |
| ```json | |
| { | |
| "id": "ticket-uuid", | |
| "source": "sales_order", | |
| "source_id": "uuid-123", | |
| "status": "open", | |
| "dedup_key": "abc123...", | |
| "created_at": "2024-03-15T10:00:00Z" | |
| } | |
| ``` | |
| **Response (Reactivated Ticket):** | |
| ```json | |
| { | |
| "id": "ticket-uuid", | |
| "source": "sales_order", | |
| "source_id": "uuid-123", | |
| "status": "open", // Changed from 'cancelled' | |
| "dedup_key": "abc123...", // Same as before | |
| "created_at": "2024-03-10T08:00:00Z", // Original creation date | |
| "updated_at": "2024-03-15T10:00:00Z", // Now | |
| "notes": "[REACTIVATED 2024-03-15T10:00:00Z] Customer wants morning installation\n[CANCELLED 2024-03-12] Customer changed mind\n..." | |
| } | |
| ``` | |
| --- | |
| ## Customer Behavior Tracking | |
| ### Identifying Chronic Cancellers | |
| **Query:** | |
| ```sql | |
| -- Find tickets that were reactivated multiple times | |
| SELECT | |
| ticket_name, | |
| status, | |
| notes, | |
| (LENGTH(notes) - LENGTH(REPLACE(notes, '[REACTIVATED', ''))) / LENGTH('[REACTIVATED') as reactivation_count | |
| FROM tickets | |
| WHERE notes LIKE '%[REACTIVATED%' | |
| ORDER BY reactivation_count DESC; | |
| ``` | |
| **Business Value:** | |
| - Identify customers who frequently cancel | |
| - Adjust payment terms (require upfront payment) | |
| - Risk assessment for resource allocation | |
| - Customer support insights | |
| --- | |
| ## Payment History Preservation | |
| ### Scenario: Customer Paid Before Cancelling | |
| **Without Reactivation:** | |
| ``` | |
| Ticket 1 (cancelled) β Has payment record | |
| Ticket 2 (new) β No link to previous payment | |
| β Lost payment history! β | |
| ``` | |
| **With Reactivation:** | |
| ``` | |
| Ticket 1 (cancelled) β Has payment record | |
| Ticket 1 (reactivated) β Still has payment record | |
| β Payment history preserved! β | |
| ``` | |
| **Additional Metadata Example:** | |
| ```json | |
| { | |
| "payments": [ | |
| { | |
| "amount": 5000, | |
| "date": "2024-03-01", | |
| "status": "refunded", | |
| "reason": "Customer cancelled before installation" | |
| } | |
| ], | |
| "cancellations": [ | |
| { | |
| "date": "2024-03-05", | |
| "reason": "Customer changed mind" | |
| } | |
| ], | |
| "reactivations": [ | |
| { | |
| "date": "2024-03-15", | |
| "reason": "Customer wants service again" | |
| } | |
| ] | |
| } | |
| ``` | |
| --- | |
| ## Summary | |
| ### Key Features | |
| 1. β **Space Efficient**: Reactivates existing tickets instead of creating duplicates | |
| 2. β **Payment History**: Preserves financial records when reactivating | |
| 3. β **Customer Behavior**: Tracks chronic cancellers via reactivation notes | |
| 4. β **Audit Trail**: Dedup key is permanent (tracks creation instance) | |
| 5. β **Flexibility**: New service = new ticket, same service = reactivate | |
| ### Database Impact | |
| - **Reduces rows**: 1 ticket per sales order (not 1 per order attempt) | |
| - **Preserves data**: Payment history, notes, metadata | |
| - **Clear history**: Notes field shows all state changes with timestamps | |
| ### Business Value | |
| - **Cost Savings**: Less database storage | |
| - **Better Analytics**: True customer behavior tracking | |
| - **Financial Accuracy**: Payment history never lost | |
| - **Operational Efficiency**: One ticket to manage per order | |
| --- | |
| ## Migration Notes | |
| **No database migration needed!** | |
| The existing schema already supports this: | |
| - `dedup_key` is already unique and permanent | |
| - `status` column supports state transitions | |
| - `notes` field stores reactivation history | |
| - Logic change only (service layer, not schema) | |
| **Code Changes:** | |
| - β Service: Check for cancelled tickets before creating new | |
| - β Service: Reactivate logic (update status, dates, notes) | |
| - β API: Document reactivation behavior | |
| - β Docs: Update business rules | |