backend phase 15 & frontend phase 8
This commit is contained in:
@@ -455,6 +455,47 @@ review_tags) / `PatientCareRecordsController`. Load-bearing rules:
|
||||
config `Seams:ReviewModeration`) is the AI pre-screen; clean text stays pending by default (publish gate),
|
||||
banned-word → auto-hidden. Decision authority stays with `ModerateReviewCommand` (human override).
|
||||
|
||||
**Messaging, partner centers & admin backoffice (backend-phase-15).** The final backend phase adds two schemas
|
||||
and consolidates the admin surface. A new **`messaging` schema** holds `Tickets` / `TicketParticipants` /
|
||||
`TicketMessages` (entities in `Domain/Entities/Messaging/` + `TicketStatus`/`TicketCategory`/`TicketParticipantRole`
|
||||
codes) — the only sanctioned post-booking channel. A new **`partner` schema** holds `PartnerCenters` (entity in
|
||||
`Domain/Entities/PartnerCenters/`, `IAuditable`; the licensed sponsor / merchant-of-record). Configs in
|
||||
`Persistence/Configuration/{MessagingConfig|PartnerCentersConfig}/`; per-domain repos `ITicketRepository` +
|
||||
`IPartnerCenterRepository` on `IUnitOfWork`; features under `Baya.Application/Features/{Messaging|PartnerCenters}/`;
|
||||
controllers `TicketsController` / `AdminTicketsController` / `AdminPartnerCentersController` / `CentersController`
|
||||
/ `InternalCentersController`; one migration (`MessagingAndPartnerCenters`, which also adds the
|
||||
`nurse_profiles.partner_center_id` FK in place). Load-bearing rules:
|
||||
- **`is_internal` is a HARD visibility boundary enforced at the QUERY layer.** `GetTicketThreadQuery` takes an
|
||||
`AsAdmin` flag; the user view (`false`) strips every `is_internal` message in the repository projection
|
||||
(`GetMessagesAsync(includeInternal:false)`), the admin view (`true`, staff only) returns them. A non-staff
|
||||
caller can never *set* `is_internal` on `PostMessage` nor *read* one. Never enforced only in the UI.
|
||||
- **No direct nurse↔customer channel.** All post-booking comms are ticket-mediated + admin-readable; participation
|
||||
(via `TicketParticipant`, `UNIQUE(ticket_id, user_id)`, soft-remove via `removed_at`) plus staff is the auth
|
||||
boundary. `reference_code` is minted once (collision-checked, UNIQUE) and stable. Both `booking_id`/`refund_id`
|
||||
links are nullable — handle a ticket with neither. A coordination ticket is auto-created (idempotent, one per
|
||||
booking) on confirmation via `AutoCreateCoordinationTicketCommand`, dispatched from the card confirm + BNPL
|
||||
settle handlers. `LogEmergencyTicket` records the aftermath of an out-of-platform emergency call (+ optional
|
||||
`support_alert`) — it exposes no phone number.
|
||||
- **Merchant-of-record resolution follows `partner_centers`, not a hardcoded platform.**
|
||||
`PartnerCenterRepository.ResolveCenterForBookingAsync` (surfaced by `GetCenterForBookingQuery`, endpoint
|
||||
`GET /internal/bookings/{id}/center`) resolves booking → nurse → `partner_center_id`; the issuer/settlement
|
||||
target is `partner_center` **only** when that center `is_merchant_of_record`, else `platform`. This is the
|
||||
single resolver **b11's `IssueInvoice` now calls** to set `invoices.issuing_entity_type` + `partner_center_id`.
|
||||
- **`partner_centers` ≠ `organizations`.** The launch licensing *sponsor* (`partner_centers`) is distinct from
|
||||
the future *employer* (`organizations`, DEFERRED). `settlement_iban` is encrypted at rest (converter in
|
||||
`ApplicationDbContext`, `[AuditRedacted]`) and **masked** (last 4) in every read; `commission_rate` (the
|
||||
center's cut) is separate from `platform_fee_rate`. The four DEFERRED tables (`organizations`,
|
||||
`organization_nurses`, `fraud_flags`, `recurring_booking_schedules`) are **not** created.
|
||||
- **Refund↔ticket link wired.** `CreateRefundCommand` (b11) now auto-opens a `category=refund` ticket via
|
||||
`OpenTicketCommand` when the caller supplies none, so `refunds.ticket_id` is always non-null.
|
||||
- **Backoffice consolidation surfaces, doesn't rebuild.** The support-alert worklist (`ISupportAlertService`
|
||||
List/Assign/Resolve — `SupportAlertsController`) and the audit viewer (`GetAuditTrail` — `AuditController`)
|
||||
already existed since b1 and are reused as-is; verification/refund/payout/moderation surfaces are their own
|
||||
phases'. New seam **`ILicenseVerificationService`** (`Contracts/Common`; mock `MockLicenseVerificationService`
|
||||
in CrossCutting, config `Seams:LicenseVerification`, `AutoApprove` toggle) is the eNamad / MoH permit check —
|
||||
manual-approve at MVP; `VerifyPartnerCenter` records the human decision. There is **no** telephony/VoIP seam
|
||||
(the emergency call is an out-of-platform `tel:` link by design). This is the last backend phase.
|
||||
|
||||
**Keeping the Project map current.** When a change touches the architecture — adds, removes, or
|
||||
renames a project/assembly, a Clean-Architecture layer, or a major folder, or changes a cross-layer
|
||||
dependency — you **must** update this Project map (and the dependency rule above, if affected) in the
|
||||
|
||||
Reference in New Issue
Block a user