Files
baya-monorepo/archive/build-chain/working-context/backend/handoff/after-backend-phase-3.md
T
2026-08-02 18:48:32 +03:30

4.1 KiB

After backend-phase-3 — profiles, patients & nurse bank accounts are live

On top of the b2 auth spine, the people behind the accounts now exist. A nurse has a seller profile (that b6 will verify and b5 will hang service variants off), a customer has a payer profile and their patients, and a nurse has payout bank accounts with an automated IBAN-ownership inquiry. Contract: dev/contracts/domains/identity-profiles.md; machine schema: dev/contracts/openapi/swagger.v1.json (refreshed).

What the frontend (f2-b3) can now build

  • Nurse profile bootstrap: POST api/v1/nurse_profiles/upsert (bio, experience, education, specializationsJson), GET api/v1/nurse_profiles/me, and the pause/resume toggle POST api/v1/nurse_profiles/set_accepting_bookings. isVerified and the rating/booking aggregates are read-only (verification is b6) — render them, never send them.
  • Customer profile: POST api/v1/customer_profiles/upsert (emergency contact) + GET …/me.
  • "Who is care for" (patients): create / list (paginated) / get/{id} / update/{id} / archive/{id} under api/v1/patients. gender (male/female) is required. A customer only ever sees/edits their own patients — someone else's id returns 404.
  • Nurse bank-account settings: add (returns the account with matchedNationalId set by the شبا inquiry), list (IBAN masked, last-4), set_primary/{id}, and verify_ownership/{id} to re-run the inquiry.

Rules baked into the API (don't fight them client-side)

  • Refresh after select_role. These endpoints authorize on the role claim in the access token; the token minted before role selection lacks it. Login → select_rolerefresh (or re-login) → then call profile/patient/bank endpoints. Otherwise you get 403.
  • Guarded verification — no field/endpoint sets isVerified; a nurse is not bookable until b6.
  • Tenancy — patients and bank accounts are strictly owner-scoped; cross-tenant reads/writes are 404.
  • IBAN is masked on the wire (last-4 only); the full value is encrypted at rest.
  • matchedNationalId is the money-mule-prevention gate for the first payout (enforced in b13). It is null until the inquiry runs; add runs it automatically. The bank rail is mocked at MVP.
  • Duplicate IBAN → a clean 400 (via iban_hash uniqueness), not a server error.

What's mocked

  • IBAN ownership (IBankAccountOwnershipVerifier🟡). Deterministic fake استعلام شبا: every IBAN matches except the configured mismatch IBAN (Seams:BankOwnership:MismatchIban, default IR000000000000000000000000) which returns matchedNationalId=false. No real bank/KYC call.

Schema / migration

Migration 20260702042131_IdentityProfilesPatientsBankAccounts (applied to the dev DB on startup): usr.NurseProfiles, usr.CustomerProfiles, usr.Patients, usr.NurseBankAccounts — with the 1:1 uniques, UNIQUE(iban_hash), filtered single-primary index, guarded is_verified, encrypted PII columns (iban, account_holder_name, emergency contacts, initial_medical_notes), and soft-delete on NurseProfiles. None of the CUT columns (verification_status, response_rate, … , customer_profiles.national_id_verified_at) exist.

Deferred to later phases (do not build against these yet)

  • Addresses & nurse service areas → b4 (need province/city/district + geocoder).
  • is_verified flip → b6 (verification pipeline).
  • Payout gating on matched_national_id → b13.
  • Aggregate recompute (rating/reviews/completed) → b9/b14.
  • Customer national-ID KYC — intentionally not collected; never gate browsing/booking on it.

Note for the whole backend chain

FluentValidation was previously inert (no validators registered). b3 activates it in AddApplicationServices — every AbstractValidator<T> now runs via ValidateCommandBehavior and the ModelStateValidationAttribute controller filter. Consequence: for route-supplied ids, don't add a body validator rule on that id (e.g. patients/update/{id} validates the body, whose Id is 0).