@
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:
@@ -48,6 +48,8 @@
|
||||
<p><strong>Relations:</strong> 1:1 → <code>users</code>, <code>nurse_verifications</code>; 1:N → <code>nurse_service_variants</code>, <code>nurse_service_areas</code>, <code>nurse_bank_accounts</code>, <code>nurse_credentials</code>, <code>bookings</code>, <code>nurse_payouts</code>, <code>nurse_clawbacks</code>; N:1 → <code>partner_centers</code>.</p>
|
||||
<h3 id="customer_profiles-core"><code>customer_profiles</code> [CORE] <a class="anchor" href="#customer_profiles-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> Lightweight extension for customers. <strong>Why intentionally thin:</strong> most customer reality lives in their <code>patients</code>, <code>customer_addresses</code>, and <code>bookings</code>. KYC for customers is deferred. Unchanged: <code>id</code>, <code>user_id</code> (unique), <code>default_emergency_contact_name</code>/<code>_phone</code> (enc), <code>created_at</code>, <code>updated_at</code>. <strong>CUT for MVP:</strong> <code>national_id_verified_at</code> (anti-fraud customer KYC — add when actually built). <strong>Relations:</strong> 1:1 → <code>users</code>; 1:N → <code>patients</code>, <code>customer_addresses</code>, <code>booking_requests</code>, <code>bookings</code>.</p>
|
||||
<blockquote><p><strong>As-built (backend-phase-3):</strong> a thin <code>customer_profiles</code> row is <strong>auto-provisioned</strong> 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.</p>
|
||||
</blockquote>
|
||||
<h3 id="patients-core"><code>patients</code> [CORE] <a class="anchor" href="#patients-core" aria-hidden="true">#</a></h3>
|
||||
<p><strong>Role:</strong> The person receiving care, <strong>separate from the payer</strong>. <strong>Why:</strong> 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: <code>id</code>, <code>customer_id</code>, <code>display_name</code>, <code>first_name</code>, <code>last_name</code>, <code>birth_date</code>, <code>gender</code>, <code>blood_type</code>, <code>initial_medical_notes</code> (enc), <code>is_active</code>, timestamps. <strong>Relations:</strong> N:1 → <code>customer_profiles</code>; 1:N → <code>booking_requests</code>, <code>patient_care_records</code>. <strong>Tenancy invariant:</strong> a <code>booking_request.patient_id</code> must belong to the same <code>customer_id</code>.</p>
|
||||
<h3 id="customer_addresses-core"><code>customer_addresses</code> [CORE] <a class="anchor" href="#customer_addresses-core" aria-hidden="true">#</a></h3>
|
||||
@@ -62,6 +64,8 @@
|
||||
<tr><td><code>ownership_vendor_ref</code></td><td>NVARCHAR(200) NULL</td><td><strong>NEW</strong> — vendor transaction id for audit.</td></tr>
|
||||
</tbody></table></div>
|
||||
<p>Constraints: <strong>filtered <code>UNIQUE(nurse_id) WHERE is_primary=1</code></strong>; <code>UNIQUE(iban_hash)</code>. <strong>Relations:</strong> N:1 → <code>nurse_profiles</code>; 1:N → <code>nurse_payouts</code>.</p>
|
||||
<blockquote><p><strong>As-built (backend-phase-3):</strong> the IBAN is <strong>returned masked (last 4 only)</strong> on every read — the full value is encrypted at rest and never leaves the server. A nurse's <strong>first</strong> bank account becomes primary automatically; <code>set_primary</code> thereafter clears the prior primary and sets the new one in one transaction. <code>matched_national_id</code> starts NULL and is set only by the استعلام شبا ownership inquiry (mocked behind <code>IBankAccountOwnershipVerifier</code> at MVP), which is the money-mule-prevention gate for the first payout (b13).</p>
|
||||
</blockquote>
|
||||
<a class="back-to-top" href="#">↑ Back to top</a>
|
||||
</div></main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user