Implement course enrolment initiation from within the app
epic-certification-management-core-logic-task-007 — Add initiateCourseEnrolment to CertificationManagementService. The method should look up the relevant renewal course for the certification type, construct a deep link or API call to the HLF course portal, and log the enrolment initiation event. If the portal is unreachable, queue the request locally and retry on next sync.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 6 - 158 tasks
Can start after Tier 5 completes
Implementation Notes
Use a CoursePortalResolver helper class that maps cert_type enum values to portal URLs, making the mapping easy to update without touching service logic. Implement a PendingEnrolmentRetryCoordinator (or extend the existing sync service) that queries pending_enrolment_queue on app foreground resume. Use Dart's http package with a custom timeout interceptor rather than Supabase client for the portal reachability check, since the portal is an external system. Sealed classes for EnrolmentInitiationResult will allow exhaustive handling in BLoC event listeners.
Avoid storing the portal URL in the certification table — keep it in a separate course_type_config table or a Dart constant to prevent stale deep-links. The log table should use a composite unique index on (mentor_id, cert_type, DATE(initiated_at)) to prevent accidental duplicate logs on rapid re-taps.
Testing Requirements
Unit tests (flutter_test): (1) Mock CertificationRepository and url_launcher; assert initiateCourseEnrolment returns success and calls repository.logEnrolmentInitiation with correct args when portal responds 200. (2) Simulate connection timeout; assert portalUnreachable is returned and repository.queueEnrolment is called with retry_count=0. (3) Seed enrolment_initiation_log mock with a record within 30 days; assert alreadyEnrolled returned without opening portal. (4) Assert that retry logic increments retry_count and marks expired after 5 attempts.
Integration tests: Run against local Supabase instance; verify RLS prevents one mentor reading another's enrolment log; verify queue record is persisted and retrieved correctly across separate service instantiations. Coverage target: 90% branch coverage on initiateCourseEnrolment method.
The auto-pause workflow requires CertificationManagementService to call PauseManagementService and HLFDynamicsSyncService in the same logical transaction. If PauseManagementService succeeds but the Dynamics webhook fails, the mentor is paused locally but remains visible on the HLF portal.
Mitigation & Contingency
Mitigation: Implement a saga pattern: write a pending sync event to the database before calling Dynamics, and have a background retry job consume pending events. This guarantees eventual consistency even if the webhook fails transiently.
Contingency: If the Dynamics sync fails after auto-pause, surface an explicit coordinator alert in the dashboard indicating 'Dynamics sync pending — mentor may still be visible on portal'. Allow manual retry from coordinator UI.
If the nightly cron job runs concurrently (e.g., due to infra retry), CertificationReminderService could dispatch duplicate notifications to mentors before the cert_notification_log insert is visible to the second invocation.
Mitigation & Contingency
Mitigation: Use Supabase's upsert with a unique constraint on (mentor_id, threshold_days, cert_id) in cert_notification_log. The second concurrent insert will fail gracefully and the duplicate dispatch will be skipped.
Contingency: If duplicate notifications do reach mentors, add a post-dispatch dedup check and include a 'you may receive this notification again' disclaimer until the constraint is deployed.