Files
baya-monorepo/archive/docs/flows/onboarding-nurse.md
T
2026-08-02 20:01:31 +03:30

10 KiB
Raw Blame History

Flow — onboarding-nurse

Last verified: 2026-08-02 against commit c841bde

Actor(s): nurse · Status: partial Client: partial · Server: real Business source: product/business/01-actors-and-onboarding.md Integration: nurse.md · profiles.md

What it does

A phone-verified account becomes a listable nurse: pick the nurse role, write the profile families will read, register the IBAN that earnings land in, and watch one checklist say what is still missing before the listing goes live. Verification itself is a separate journey — nurse-verification.md; this flow is everything around it.

Screens

Step Route Component / notes
0 /fa/select-role SelectRole — only customer/nurse offered. Reached programmatically (resolveRoleDestination on an empty roles), never linked
1 /fa/nurse thin RSC → NurseDashboardScreenDashboardActivationSlot → the shared ActivationChecklist
2 /fa/nurse/profile client page.tsx; react-hook-form, three FormSections (معرفی / تجربه و تحصیلات / تخصص‌ها) + avatar upload + a beforeunload guard on a staged-but-unsaved avatar
3 /fa/nurse/profile/preview «نمایهٔ عمومی من» — the C3 dossier composed from the nurse's own cached queries, so it renders pre-publish with no search-index dependency
4 /fa/nurse/bank an accounts section: submit شبا + holder, watch pending → verified/mismatch, «افزودن حساب دیگر», make-primary
/fa/nurse/practice, /fa/nurse/services the checklist is also mounted above the offerings list (MyServicesList.tsx:92) next to PublishGate

API

Call Endpoint Notes
read profile GET /api/v1/nurse_profiles/me 404 → null (empty form) via orNull (profiles/apis/clientApi.ts:19-26)
save profile POST /api/v1/nurse_profiles/upsert body carries 5 fields only — see the avatarUrl gap
avatar POST /api/v1/nurse_profiles/avatar multipart/form-data; clientFetch must not set Content-Type
go live POST /api/v1/nurse_profiles/set_accepting_bookings the real switch behind PublishGate; reindexes in-transaction
bank list / add / primary GET nurse_bank_accounts/list, POST .../add, POST .../set_primary/{id} add is sensitive 20/min
role POST /api/v1/me/select_role RoleNames.SelfAssignable = [customer, nurse] (RoleNames.cs:19)

Shapes live in profiles.md and nurse.md. POST nurse_bank_accounts/verify_ownership/{id} is wired server-side and implemented at nurse/apis/clientApi.ts:30 but no hook or component ever calls it.

Rules that must hold

Rule Where enforced
is_verified is never client-settable. UpsertNurseProfileCommand has no such field; the handler's Apply() writes only bio/years/education×2/specializations. Only the verification finalize transaction flips it (INV-18) UpsertNurseProfileCommand.cs, .Handler.cs
iban_hash is UNIQUE platform-wide — deterministic hash checked in the handler, UNIQUE(iban_hash) as the DB backstop AddNurseBankAccountCommand.Handler.cs:36-39
Write-then-masked IBAN. Every read model returns ibanMasked (last-4); the full IBAN is never re-served nurse.md
A verified primary IBAN with matchedNationalId == true is the first-payout gate — not a search gate. No account ⇒ the nurse accrues a balance and is skipped with a recorded reason payouts.md; product/business/10 §(b)
is_searchable = is_verified AND is_accepting_bookings AND status != suspended AND variant.is_active (INV-17). Bio and avatar are not in it SearchIndexMaintainer.cs:248-249
A profile row does not exist until the first upsert; select_role only grants the role SelectRoleCommand.Handler.cs
Bank add requires a nurse profile to already exist ("Create your profile first.") — profile precedes bank AddNurseBankAccountCommand.Handler.cs:32-34

How to test

Log in as 09120000001 (زهرا عزیزی, verified nurse) — see testing-setup.md.

  1. Open /fa/nurse. Expect: the «راه‌اندازی حساب» card. Actual today: it renders incomplete with «احراز هویت و مدارک» un-passed, because the verification row reads a client mock (see gaps). It never collapses to the green «فعال در جستجو» state for any account.
  2. Open /fa/nurse/profile. Expect: bio «پرستار سالمند با هشت سال سابقه مراقبت در منزل.», years 8, level «کارشناسی», field «پرستاری». A warning card «پروفایل شما هنوز فعال نیست» is shown — also mock-driven, and wrong for this account (GET nurse_profiles/meisVerified: true).
  3. Edit the bio and save. Expect: a success toast and the value persisted on reload. Verified live: POST nurse_profiles/upsert200.
  4. Same call with "isVerified": false, "isAcceptingBookings": false injected into the body. Expect (verified live): 200 with isVerified: true, isAcceptingBookings: true — the guard holds.
  5. Open /fa/nurse/bank. Expect: one card, «تأییدشده», «بانک ملت», ••••9012, primary.
  6. Submit IR820170000000123456789012 (nurse 1's own seeded IBAN) via «افزودن حساب دیگر». Expect (verified live): 400 {"Iban":["This IBAN is already registered."]} and no new row. The UI shows only the generic «ثبت این حساب ممکن نشد…» toast.
  7. Open /fa/nurse/profile/preview. Expect: the C3 dossier rendered from own data. The avatar renders as a fallback icon, not the photo — see the file:// gap.

Seeded-world limits. Nothing exercises the first-run path: all three demo nurses already have a profile, and nurses 12 already have a verified primary IBAN. To see the empty profile → 404 → blank form, the "no nurse profile yet" bank rejection, or select-role, you need a new account, which the phone-OTP login cannot mint for an unseeded phone. 09120000003 (unverified, in_review) is the closest stand-in for a mid-onboarding nurse; its token in tokens.env returned 401 at this stamp, so its state was not re-probed live.

Known gaps

  • The activation checklist mixes real and mocked truth. useActivationChecklist.ts:43-47 folds four real domains (profiles, catalog, serviceAreas, nurse) with one mocked one (verification, USE_VERIFICATION_MOCK = true at verification/constants.ts:9). The «احراز هویت و مدارک» row is browser state, not server state.
  • Every nurse looks unverified on every page load. The verification mock's steps starts [] (verification/apis/mockApi.ts:40) and getStatus returns that aggregate, so isApproved() is false for the seeded, server-verified nurse 1 — and the module state resets on every reload/HMR. Consequences: the checklist never reaches «فعال در جستجو»; PublishGate renders the blocked branch and hides the real set_accepting_bookings CTA; /fa/nurse/profile shows a false «پروفایل شما هنوز فعال نیست» banner.
  • isSearchVisible is not the server's gate, despite the component saying it is. useActivationChecklist.ts:35-41 claims the four rows "are exactly the server's is_searchable gate". They are not: the profile row (bio.trim() !== '' && avatarUrl != null, :31-33) is client-invented and the server never reads it, while is_accepting_bookings — which is in the gate — is excluded from searchRows and tracked separately. A nurse can pass all four rows and still be invisible.
  • The avatar URL is unrenderable on the default seam. LocalDiskObjectStorage.GetUrl returns new Uri(localPath).AbsoluteUri (LocalDiskObjectStorage.cs:51), so nurse_profiles/me serves file:///C:/Users/.../avatars/nurse/1/….png (verified live). A browser cannot load file:// from an http:// page — the <Avatar> on /fa/nurse/profile and the preview both fall back to the placeholder icon, while the checklist's profile row counts the non-null URL as passed.
  • avatarUrl on the upsert input is dead on the real path. UpsertNurseProfileInput.avatarUrl (profiles/types.ts:40) is populated by the form (nurse/profile/page.tsx:154) but profilesClientApi.upsertNurseProfile (profiles/apis/clientApi.ts:85-95) never sends it. Harmless today (the multipart route already persisted it) but it is a mock-era field the real client silently drops.
  • Duplicate IBAN is 400, not 409. nurse.md says "the second add returns a 409"; the handler returns FailureResult400 with a field error (verified live). The integration doc is wrong.
  • The duplicate-IBAN message never reaches the nurse. nurse/bank/page.tsx:51 toasts the generic «ثبت این حساب ممکن نشد. شبا را بررسی کرده و دوباره تلاش کنید.» and discards the server's {"Iban":["This IBAN is already registered."]}, so a nurse re-entering their own IBAN is told to check it.
  • The ownership inquiry ignores the national id. MockBankAccountOwnershipVerifier decides from the IBAN alone (:21-26) and matches everything except Seams:BankOwnership:MismatchIban. A nurse who has not done identity KYC has users.national_id = NULL (NurseIdentityContext.cs:9-10) and still gets matchedNationalId = true — the payout gate opens on a claim nothing checked.
  • verifyOwnership is a dead seam op on the client. Declared (nurse/types.ts:41), implemented (nurse/apis/clientApi.ts:30), server route live — but no hook exists, so a mismatch account has no re-inquiry affordance; the UI only offers "re-enter the IBAN".
  • /fa/select-role cannot be reached with any seeded account — all 8 demo users already hold a role, so the role picker is untestable end-to-end today.
  • The bank page polls forever on a stuck inquiry. useNurseBankAccounts refetches every 2 s while any account is pending (nurse/constants.ts:14) with no ceiling and no timeout copy.