@
backend phase 3: identity profiles, patients & nurse bank accounts Add the role-attached identity layer on top of the b2 auth spine: nurse seller profiles (guarded is_verified, read-only aggregates), thin customer payer profiles, first-class patients (tenancy-scoped), and nurse payout bank accounts hardened with an iban_hash uniqueness guard and an automated استعلام شبا IBAN-ownership inquiry. - Four usr tables via one migration (1:1 uniques, UNIQUE(iban_hash), filtered UNIQUE(nurse_id) WHERE is_primary=1, guarded is_verified, encrypted PII, soft-delete on nurse_profiles) - 15 CQRS slices + 4 role-scoped controllers; reads projected + paginated, IBAN masked (last-4); ownership-inquiry endpoints rate-limited - New IBankAccountOwnershipVerifier seam (mock deterministic شبا match) + per-domain repositories on IUnitOfWork + encrypted-PII value converters - Activate FluentValidation repo-wide (validators were never registered) - Handler unit tests + WebApplicationFactory integration tests (76 pass); contract identity-profiles.md + swagger snapshot; docs, handoff & report Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -45,6 +45,10 @@ Fields unchanged from baseline: `id`, `email` (enc, nullable), `phone` (enc, uni
|
||||
### `customer_profiles` [CORE]
|
||||
**Role:** Lightweight extension for customers. **Why intentionally thin:** most customer reality lives in their `patients`, `customer_addresses`, and `bookings`. KYC for customers is deferred. Unchanged: `id`, `user_id` (unique), `default_emergency_contact_name`/`_phone` (enc), `created_at`, `updated_at`. **CUT for MVP:** `national_id_verified_at` (anti-fraud customer KYC — add when actually built). **Relations:** 1:1 → `users`; 1:N → `patients`, `customer_addresses`, `booking_requests`, `bookings`.
|
||||
|
||||
> **As-built (backend-phase-3):** a thin `customer_profiles` row is **auto-provisioned** the first time a
|
||||
> customer registers a patient, so the customer/patient split works without a separate "create profile"
|
||||
> step. The emergency-contact fields are encrypted at rest and returned in full only to the owning customer.
|
||||
|
||||
### `patients` [CORE]
|
||||
**Role:** The person receiving care, **separate from the payer**. **Why:** the payer (adult child, spouse) is usually not the patient (elderly parent, newborn, post-surgical adult); one customer registers many patients, each with its own clinical baseline and longitudinal record. Unchanged: `id`, `customer_id`, `display_name`, `first_name`, `last_name`, `birth_date`, `gender`, `blood_type`, `initial_medical_notes` (enc), `is_active`, timestamps. **Relations:** N:1 → `customer_profiles`; 1:N → `booking_requests`, `patient_care_records`. **Tenancy invariant:** a `booking_request.patient_id` must belong to the same `customer_id`.
|
||||
|
||||
@@ -63,3 +67,9 @@ Fields unchanged from baseline: `id`, `email` (enc, nullable), `phone` (enc, uni
|
||||
| `ownership_vendor_ref` | NVARCHAR(200) NULL | **NEW** — vendor transaction id for audit. |
|
||||
|
||||
Constraints: **filtered `UNIQUE(nurse_id) WHERE is_primary=1`**; `UNIQUE(iban_hash)`. **Relations:** N:1 → `nurse_profiles`; 1:N → `nurse_payouts`.
|
||||
|
||||
> **As-built (backend-phase-3):** the IBAN is **returned masked (last 4 only)** on every read — the full
|
||||
> value is encrypted at rest and never leaves the server. A nurse's **first** bank account becomes primary
|
||||
> automatically; `set_primary` thereafter clears the prior primary and sets the new one in one transaction.
|
||||
> `matched_national_id` starts NULL and is set only by the استعلام شبا ownership inquiry (mocked behind
|
||||
> `IBankAccountOwnershipVerifier` at MVP), which is the money-mule-prevention gate for the first payout (b13).
|
||||
|
||||
Reference in New Issue
Block a user