cleanup phases 6

This commit is contained in:
hamid
2026-08-02 18:48:32 +03:30
parent e2db97392a
commit 51e86a1e5f
239 changed files with 118 additions and 70 deletions
@@ -0,0 +1,57 @@
# 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`](../../../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_role`**refresh** (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).