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

12 KiB

Flow — nurse-verification

Last verified: 2026-08-02 against commit c841bde

Actor(s): nurse · admin · (public, read-only trust badge) · Status: mocked Client: mock · Server: partial (nurse half real and live; admin half real code, unreachable — 403) Business source: product/business/02-nurse-verification.md Integration: docs/integration/domains/verification.md

What it does

A nurse proves who she is and what she is licensed to do — identity, phone binding, professional credentials, bank ownership — and an admin reviews the manual parts. Until the pipeline says approved the nurse is not bookable and does not appear in search. This is the flow the whole marketplace's trust claim rests on.

This is the atlas's clearest "real UI on a mocked service in front of a live server" trap. Every nurse screen renders in-browser fixtures from verification/apis/mockApi.ts because USE_VERIFICATION_MOCK = true (constants.ts:9) — while the server's nurse half answers correctly, right now, to a curl. Nothing in the browser ever calls it. The one flag also holds the working nurse half hostage to the broken admin half.

Screens

Step Route Component / notes
B3 status hub /fa/nurse/verification page.tsx — the canonical view of the one cached status query (useVerificationStatus). not_started CTA → grouped checklist → terminal approved panel. Renders mock-only approve/reject buttons (:84-95)
checklist (same) VerificationChecklist.tsx + verificationSteps.ts — data-driven; groups server steps into هویت / مدارک حرفه‌ای / بانک. Prepends a synthetic mobile_verified step (MOBILE_STEP, id 0, always passed) that is not a server step — it is satisfied at phone-OTP login
B4 identity /fa/nurse/verification/identity national id (10-digit + checksum) + ID-card image + liveness selfie → identity_kyc run, chained shahkar_match. The two images are local captures, never uploaded
B5 credentials /fa/nurse/verification/credentials one DocumentUpload per manual step in the status (MANUAL_CREDENTIAL_CODES, page.tsx:32) + INO number, specialties, credential dates
B6 under review /fa/nurse/verification/review a second focused view of the same cached query — never a second fetch
admin queue /fa/admin/verification useVerificationQueue; per-step wire rows folded to one row per nurse
admin case /fa/admin/verification/[nurseId] per-step pass/reject + DocumentViewer; prev/next off the queue's cached page order
admin group root /fa/admin/trust nav only, no data
public /fa/search/nurse/:nurseId TrustBadge + VerificationPanel read getTrustBadge — see search-and-discovery.md

DocumentUpload resolves its resting state as state === 'success' || (state === 'idle' && existingDoc != null) (DocumentUpload.tsx:132) — a just-finished upload wins over stale server metadata, so a re-upload never snaps back to the old file name.

API

Shapes, enums and the two vocabularies live in docs/integration/domains/verification.md. All 20 paths below were confirmed present in the live OpenAPI doc (178 paths).

Call Endpoint Notes
status (B3/B5/B6) GET /api/v1/nurse_verification live, probed 200. clientApi.ts:157NurseVerificationController.cs:32
start POST …/submit wired
presign / confirm POST …/steps/{stepId}/upload_url · POST …/steps/{stepId}/documents wired; XHR PUT direct to storage + SHA-256 integrity hash (clientApi.ts:180-208)
automated runs POST …/steps/{identity_kyc|shahkar_match|bank_account_verification}/run wired; each is a separately switchable seam
credential details POST …/credential_details endpoint EXISTS (NurseVerificationController.cs:49-52) but clientApi.ts:212 is an empty no-op — see gaps
public badge GET /api/v1/nurses/{nurseId}/trust_badge live, anonymous, probed 200
admin queue GET /api/v1/admin_verifications 403 for every seeded admin
admin case GET /api/v1/admin_verifications/{nurseVerificationId} 403
admin decide POST /api/v1/admin_verifications/steps/{stepId}/decide 403. Per-step only — approval emerges when the last required step passes
suspend · scan_expiring POST …/{id}/suspend · POST …/scan_expiring exist server-side, no client caller
step-type catalog GET/POST /api/v1/admin_verification_step_types, DELETE …/{id} exist, no client caller; probed 403
approve / reject / document URL POST …/{id}/approve, …/reject, GET …/documents/{id}/url proposed, do not exist (REQ-034) — the admin case page wires CTAs to all three

Rules that must hold

Rule Source
status is the source of truth; nurse_profiles.is_verified is derived and flips only inside the transaction that confirms every required step passed (INV-18) business/02; AdminReviewStepCommand.Handler.cs:74-78
The step catalog is data, not codeverification_step_types rows. 6 seeded, all is_required. Adding a regulatory step is one INSERT business/02 §(a); VerificationStepTypeSeed.cs
The server computes isBookable and names blockingSteps; the client must never derive bookability from the step array integration/verification.md
Search visibility is one gate: is_verified AND is_accepting_bookings AND status != suspended AND variant.is_active (INV-17) SearchIndexMaintainer.cs:177,248
expired is a normal step state, not an error — the credential-expiry scan runs every 24 h (verification_expiry_scan_cadence_hours) and re-gates bookability business/02
The trust badge exposes credential types only; credential_number is encrypted and never serialized business/02 §(c-bis)
INO membership locks once submitted (it feeds the public badge) integration/verification.md

How to test

  1. Log in as 09120000003 (مریم احمدی — the deliberately unverified nurse) — see testing-setup.md. Boot the API with Seams__Sms__Provider=mock or request_otp 500s.
  2. Open /fa/nurse/verification. Expect: the B3 grouped checklist renders and a «شبیه‌سازی بررسی ادمین» block with approve/reject buttons appears at the bottom. That block is the mock tell — it only renders when USE_VERIFICATION_MOCK is true.
  3. Walk B4 → B5, then press the mock «تأیید» button. Expect: the hub flips to the green approved panel with a «انتشار خدمات» CTA. This changed nothing on the server — reload the page and the mock store resets to its seed.
  4. Now read the server truth for the same nurse: curl --noproxy '*' http://localhost:5002/api/v1/nurse_verification -H "Authorization: Bearer $T_09120000003". Expect (probed 2026-08-02, HTTP 200): status: "in_review", isBookable: false, blockingSteps: ["moh_competency_license","criminal_record"], and 4 steps — identity_kyc passed, shahkar_match passed, moh_competency_license in_review, criminal_record pending. Compare it with what step 3 showed you; they are unrelated.
  5. Public badge, no auth: curl --noproxy '*' http://localhost:5002/api/v1/nurses/1/trust_badge. Expect: {"nurseId":1,"isVerified":true,"approvedAt":"2026-07-26T10:52:18…","credentialTypes":["criminal_record","moh_competency_license"]}. Nurse 3 returns isVerified:false, credentialTypes:[].
  6. Admin half: open /fa/admin/verification as 09120000020. Expect: a populated queue — all of it in-browser fixtures. The live endpoint is 403: curl --noproxy '*' "http://localhost:5002/api/v1/admin_verifications?page=1&page_size=5" -H "Authorization: Bearer $T_09120000020"403 (probed). Same for /api/v1/admin_verification_step_types.

Seeded-world limits. No admin can approve anything on the real path (the RBAC gap). Nurse 1 is already approved but returns steps: [], so there is no approved-with-checklist case to look at. Nurse 3 carries only 4 of the 6 seeded step types — ino_membership and bank_account_verification have no step rows at all, so the bank branch of the journey cannot be walked against real data. Workaround for the whole flow: none today; the honest demo is the mock, and the honest server check is curl.

Known gaps

  • USE_VERIFICATION_MOCK = true (verification/constants.ts:9) suppresses a working real nurse half — 7 nurse-side ops map live, probed-200 routes. One flag covers nurse + public badge + 6 admin ops, so the nurse half cannot be flipped independently.
  • submitCredentialDetails is an empty no-op (verification/apis/clientApi.ts:212) even though POST /api/v1/nurse_verification/credential_details exists and SubmitCredentialDetailsCommand matches CredentialDetailsInput field-for-field. Flipping the flag today would silently drop every nurse's INO number and specialties. The client comment, useSubmitCredentials.ts and integration/verification.md all still say "no nurse-facing endpoint" — stale.
  • Every admin_verifications and admin_verification_step_types route returns 403 for super_admin/finance (DynamicPermissionService.CanAccess). The admin review half of this flow is untestable end to end; the mock hides it completely.
  • nurse/verification/page.tsx:12 imports __mockApproveAll/__mockRejectStep from apis/mockApi unconditionally. The render is gated but the module edge is not — the mock is bundled into the nurse verification route in every build. This is the one production seam breach in the client.
  • With the flag flipped there is no way for a nurse to observe the approved flip in a demo: the mock admin controls disappear and the real admin queue 403s.
  • foldQueueRows (clientApi.ts:74-96) folds a per-step wire page to per-nurse items and leaves stepsPassed: 0, stepsTotal: 0, hasExpiringCredential: false, counts undefined; total stays the per-step count, so the queue's pager is nominal. A nurse's steps can straddle a page boundary (REQ-034, REQ-062).
  • approveVerification, rejectVerification and getDocumentSignedUrl target routes that do not exist (REQ-034). The admin case page wires visible approve/reject CTAs to two of them — they would 404 on the real path.
  • AdminVerificationsController suspend and scan_expiring, and all three admin_verification_step_types routes, have no client caller — no suspension UI and no step-catalog editor, so the "data-driven catalog" rule has no admin surface.
  • The wire serves isRequired per step (confirmed live) but VerificationStep (verification/types.ts:62-71) omits it, and progressCounts assumes every step is required. Adding one optional step type would make the "X از Y" meter wrong.
  • No submittedAt on VerificationStatusDto (REQ-055) → B6 omits the submitted-at line. TrustBadgeDto carries no per-step detail (REQ-043) → the public VerificationPanel is a summary only. Both confirmed against the live responses.
  • The admin case route folder is [nurseId] but the value it passes is a nurseVerificationId ([nurseId]/page.tsx:74). Typing /fa/admin/verification/3 opens verification-case 3, not nurse 3.
  • Seeded data: nurse 1 (approved) returns steps: [], and nurse 3 has no ino_membership or bank_account_verification step rows — two of the six catalog steps are unexercisable in the demo world.