# Backend phase 3 report — Identity: profiles, patients & nurse bank accounts ## What was built - **Four domain entities** (`Baya.Domain/Entities/Identity/`): `NurseProfile`, `CustomerProfile`, `Patient`, `NurseBankAccount`. `NurseProfile.is_verified` is write-guarded (private setter + `MarkVerified()`/`MarkUnverified()` — only b6 calls it); `is_accepting_bookings` toggled via a domain method; the search aggregates are read-only. - **One EF migration** `IdentityProfilesPatientsBankAccounts` (schema `usr`): 1:1 uniques on `user_id`, `UNIQUE(iban_hash)`, filtered `UNIQUE(nurse_id) WHERE is_primary=1`, soft-delete on `NurseProfiles`, encrypted PII columns, audit fields. No CUT columns. - **15 CQRS slices** under `Features/Identity/{Commands|Queries}/` + 4 `sealed : BaseController` controllers (`NurseProfilesController`, `CustomerProfilesController`, `PatientsController`, `NurseBankAccountsController`). Reads project to DTOs; lists paginate; the ownership-inquiry endpoints are rate-limited (`sensitive` policy). - **New seam `IBankAccountOwnershipVerifier`** (Application `Contracts/Common`) + mock `MockBankAccountOwnershipVerifier` (CrossCutting), registered in `AddCrossCuttingSeams`, config-selected. - **Persistence:** four per-domain repositories on `IUnitOfWork` (`NurseProfileRepository`, `CustomerProfileRepository`, `PatientRepository`, `NurseBankAccountRepository`); encrypted-PII value converters for the new columns wired in `ApplicationDbContext.OnModelCreating`; an atomic `SetPrimaryAsync` (clear-then-set in one transaction) so the single-primary index never trips. - **Infra fix:** `AddApplicationServices` now registers every `AbstractValidator` as `IValidator` so the pre-existing `ValidateCommandBehavior` and `ModelStateValidationAttribute` filter actually validate (they had **no** validators registered before this phase — validation was silently inert). ## What is now testable, and exactly how (mirrors the phase §7) Log in as a nurse and a customer (b2 OTP flow), **refreshing the token after `select_role`** so the role claim is present. Then: 1. **Nurse profile** — `POST api/v1/nurse_profiles/upsert` → row created `is_verified=0`, `is_accepting_bookings=0`; `GET …/me` shows aggregates at 0. No path sets `is_verified`. 2. **Accepting-bookings** — `POST …/set_accepting_bookings` flips it; verified untouched. 3. **Customer profile** — `POST api/v1/customer_profiles/upsert` with emergency contact → `GET …/me` round-trips it through the encrypted column. 4. **Patient CRUD** — `create` (gender required) / `list` / `get/{id}` / `update/{id}` / `archive/{id}`. 5. **Tenancy** — customer B calling `get`/`update` on customer A's patient id → **404**. 6. **Bank account + inquiry** — `POST api/v1/nurse_bank_accounts/add` (normal IBAN) → `matched_national_id=true`, vendor ref recorded; `list` shows the IBAN **masked**. 7. **Mismatch** — add the mismatch IBAN → `matched_national_id=false`. 8. **Duplicate IBAN** — re-add the same IBAN → clean `400` via `iban_hash` uniqueness. 9. **Primary flip** — add a 2nd account, `set_primary/{id2}` → account 2 primary, account 1 not; never two primaries. Automated coverage: 15 handler unit tests (NSubstitute) covering profile upsert, role forbidden, patient CRUD + cross-customer 404, bank add match/mismatch/duplicate + set-primary flip/not-owned; 13 `WebApplicationFactory` integration tests (one+ per controller: happy path, 401, validation 400, tenancy 404, mask, duplicate, primary flip, mismatch). `dotnet build` clean (0 new code warnings); `dotnet test` green (75 pass). ## What is mocked / waiting on a real service - **`IBankAccountOwnershipVerifier` (🟡)** — deterministic fake استعلام شبا. Make-it-real steps in `reports/mocks-registry.md`. Reused seams: `IFieldEncryptor`, `ICurrentUser`, `IDateTimeProvider`. ## Contracts produced / consumed - **Produced:** `dev/contracts/domains/identity-profiles.md`; `dev/contracts/openapi/swagger.v1.json` refreshed (adds all nurse-profile / customer-profile / patient / bank-account paths). - **Consumed:** b2 auth (login/roles), b0 seams (`IFieldEncryptor`/`ICurrentUser`/`IDateTimeProvider`), b1 `IPlatformConfig` (available; not needed this phase). ## Follow-ups for later phases - **b4:** `customer_addresses` + `nurse_service_areas` (need geography + geocoder) — deferred here. - **b6:** the `is_verified` flip (verification-confirm transaction); Shahkar/KYC populate `national_id`; the `bank_account_verification` step couples to `NurseBankAccounts`. - **b13:** first-payout gate on `matched_national_id = true`. - **b9/b14:** recompute `average_rating`/`total_reviews`/`total_completed_bookings` (read-only here). - **Chain-wide:** validators are now active — new phases must ensure route-supplied ids aren't validated in the body command, and can rely on FluentValidation for input rejection. ## Decisions taken (flagged for confirmation) - A thin `customer_profiles` row is **auto-provisioned** on a customer's first patient (so patient registration needs no separate profile step). Recorded in `product/data-model/01-identity-and-access.md`. - IBAN is returned **masked (last-4)** on every read; first account added is primary by default.