Reactivating Mentees: Restoring Future Bookings in a Transaction
The Problem
In our landing project, we encountered a critical bug within the mentorship feature. When a mentee decided to unsubscribe, all their future scheduled sessions were correctly marked as 'Cancelled'. However, the reactivation process was incomplete. It would successfully reactivate the mentee's subscription status but crucially, it left all previously cancelled bookings in their 'Cancelled' state. This meant that even after reactivation, mentees would log into their dashboard and find their "Próximas sesiones" (Upcoming Sessions) empty, leading to confusion and a broken user experience. Furthermore, a unique database constraint on (mentor_service_id, mentee_user_id, date) prevented simply re-creating these bookings, as the 'cancelled' records still existed.
The Approach
To address this, we implemented a robust, transactional solution that ensures all related data is consistently updated during mentee reactivation.
Understanding the Root Cause
The core issue stemmed from the fact that while the subscription flag was cleared, the individual booking records, once cancelled, were not automatically restored. The existing unique constraint on booking details (mentor, mentee, date) meant that if we tried to re-add a booking that was merely 'cancelled' but still present, it would fail. The key was to update the existing cancelled bookings, not try to insert new ones.
The Transactional Solution
The fix centered on performing a series of atomic operations within a single database transaction. This ensures that either all changes are successfully committed, or none are, maintaining data integrity. Upon reactivation:
- Capture Unsubscribe Timestamp: The exact
unsubscribed_attimestamp of the mentee's previous subscription cancellation is captured. - Restore Bookings: Within the same transaction, the system identifies all bookings for that mentee that were cancelled at or after the
unsubscribed_attimestamp and are scheduled fortodayor any future date. These bookings are then updated from 'Cancelled' to a 'Confirmed' status.
This approach ensures that only bookings relevant to the specific unsubscribe period are targeted, leaving any unrelated cancellations (e.g., manual cancellations by the mentor/mentee) untouched.
Here's a conceptual look at the database interaction, often managed through an ORM in PHP:
use Illuminate\\Support\\Facades\\DB;
use Carbon\\Carbon;
DB::transaction(function () use ($mentee, $unsubscribedAt) {
// Step 1: Reactivate the primary mentee subscription record
$mentee->subscription->update([
'is_active' => true,
'unsubscribed_at' => null, // Clear the unsubscribe timestamp
]);
// Step 2: Restore future bookings cancelled due to this specific unsubscribe
$mentee->bookings()
->where('status', 'cancelled')
->where('cancelled_at', '>=', $unsubscribedAt)
->where('date', '>=', Carbon::today())
->update([
'status' => 'confirmed',
'cancelled_at' => null, // Clear cancellation metadata
'google_event_id' => null, // Prepare for re-sync
]);
// Step 3: Trigger calendar synchronization (as detailed below)
// (new GoogleCalendarService())->syncBookings($mentee->bookings);
});
Calendar Synchronization
Beyond database updates, the fix also accounts for external integrations. Specifically, it clears any stale google_event_id associated with the restored bookings. This is crucial because cancelled Google Calendar events would still exist unless explicitly updated or re-synced. By clearing the ID, we force a re-synchronization with Google Calendar, ensuring that the mentee's and mentor's calendars accurately reflect the restored future sessions.
This comprehensive solution was applied symmetrically to both the administrative Filament dashboard's mentee reactivation action and the public-facing HTTP endpoint, ensuring consistent behavior across the application.
Key Outcomes
- Accurate Dashboards: Mentees can now reliably view their upcoming sessions immediately after reactivation.
- Improved User Experience: Eliminates confusion and frustration caused by missing sessions on the dashboard.
- Data Integrity: Ensures booking and subscription statuses are always synchronized and consistent.
- Robustness: Comprehensive new tests validate the fix, covering both happy paths and edge cases.
Key Insight
When dealing with state transitions in complex systems, it's vital to consider all related entities and external integrations. Relying solely on a primary entity's status change is often insufficient. Employing database transactions for multi-step updates, coupled with careful timestamp logic, is crucial for maintaining data consistency and a seamless user experience, especially when dealing with cancellations and reactivations that have cascading effects.