PR #145 merged: fix: cleaner onboarding completion safety net + admin diagnostics
Summary
Closes the gap that left Betty Jepkemoi (and any future cleaner in the same shape) stranded mid-onboarding — invisible in search forever despite having done all the work. Three layers of defence + admin tooling so the same class of bug can't silently bite us again.
Root cause (recap)
searchable() requires onboarding_completed_at IS NOT NULL. The only code path that wrote that column was OnboardingController::complete() — i.e. a single explicit "Finish" button click on Step 5. If a cleaner walked away pre-click (e.g. their docs were still pending admin approval), nothing else stamped them. Betty had services, rate, location, ID + RtW all approved, and was the only user in this state — but the bug class is structural and would catch others.
Fixes
1. MaybeCompleteOnboarding action (app/Actions/Cleaner/MaybeCompleteOnboarding.php) — checks every requirement (services, rate, location, verifications or company status) and stamps onboarding_completed_at if all met. Returns an OnboardingCompletionResult DTO listing any blockers so callers can show admins exactly what's missing. Idempotent.
2. Two auto-trigger points so cleaners self-heal:
Admin/VerificationController::approvecalls it after a doc is approved (catches cleaners who walked away while waiting for admin review).Cleaner/DashboardController::indexcalls it on every dashboard load (catches anything else, including legacy stranded users).
3. Save* actions are now advance-only on onboarding_step — replaces unconditional 'onboarding_step' => N writes with max(current, N). Editing earlier steps after reaching a later one no longer regresses the counter (Betty's onboarding_step was 2 despite being on Step 4+, exactly because of this).
Admin tooling (per the ask)
Admin → User detail (/admin/users/{user}):
- New diagnostics panel renders when a cleaner's onboarding is incomplete.
- Shows current step, lists missing requirements (e.g. "Base hourly rate set", "ID and Right to Work verification both approved") in a "what's missing" card.
- When eligible (no blockers), shows a green confirmation card with a "Force complete onboarding" button. Clicking POSTs to
/admin/users/{user}/force-complete-onboarding. - Refuses to force-complete when blockers remain — admins should fix the underlying data first, and the action will report the blockers as a flash error.
User-facing UX
Cleaner dashboard — any cleaner with is_onboarding_complete = false now sees a clear amber banner "Your profile is not visible in search yet — finish setting up your profile so clients can find you" with a direct link to Step 5. Auto-complete fires on the same load, so eligible cleaners self-resolve and the banner disappears.
What I deliberately did not do
- No middleware-level routing on
onboarding_step. That field was unreliable as a gate (regressing on every edit) and I didn't want to make it load-bearing in this PR. Now it's advance-only, so it's a sound foundation if you want to add step-aware routing later. - No widening of
searchable()to derive completeness from data. Single source of truth (onboarding_completed_at IS NOT NULL) is correct; we just made sure that field gets stamped reliably. - No bulk backfill command. Production count of stranded users was 1 (Betty, already manually patched via tinker before this PR). The auto-complete on dashboard load will catch any others if they exist or appear later.
Test plan
100 new + updated tests across 8 files, all passing. Coverage:
-
MaybeCompleteOnboardingTest— full matrix: complete-eligible (non-company + company), already-complete (idempotent), each blocker class, no-profile case. -
Admin/VerificationControllerTest— approving the last needed doc auto-completes; approving when other blockers exist does not. -
Admin/UserControllerTest— diagnostics fields appear on the show payload (eligible + ineligible cases); force-complete endpoint succeeds for eligible cleaners, refuses when blockers remain, idempotent on already-complete, and rejects non-cleaners. -
Cleaner/DashboardControllerTest— dashboard load auto-completes eligible stranded cleaner; doesn't auto-complete when blockers remain. -
Cleaner/OnboardingStep{One,Two,Three,Four}Test— re-saving any step after reaching a later one does not regressonboarding_step. - Full Cleaner + Admin suites green (301 passing) — no regressions in adjacent surfaces.
- Manual: as admin, view Betty's user page after deploy and confirm diagnostics panel renders correctly with her existing complete state.
- Manual: trigger an admin verification approval on a stranded cleaner in staging and confirm the dashboard auto-completion path.