Files
baya-monorepo/archive/build-chain/working-context/reports/backend-phase-15-report.md
T
2026-08-02 18:48:32 +03:30

6.5 KiB

Backend Phase 15 report — Messaging (tickets), partner centers & admin backoffice

The final backend phase. It closes the operational loop: the ticket system, the licensed partner centers (merchant-of-record), and the consolidated admin backoffice. The backend chain is now complete.

What was built

Messaging (tickets) — new messaging schema

  • Entities Domain/Entities/Messaging/: Ticket, TicketParticipant, TicketMessage + TicketStatus / TicketCategory / TicketParticipantRole code sets. Configs in Persistence/Configuration/MessagingConfig/.
  • ITicketRepository (+ TicketRepository) on IUnitOfWork.
  • Features Application/Features/Messaging/: OpenTicket, AutoCreateCoordinationTicket, PostMessage, AddParticipant, RemoveParticipant, CloseTicket, ReopenTicket, LogEmergencyTicket, GetTicketThread (role-aware user/admin view), ListMyTickets, ListTicketsForAdmin. Shared helpers TicketReferenceCode (collision-checked mint) + TicketRoleResolver + StaffRoles (Application/Common).
  • Controllers TicketsController (authenticated) + AdminTicketsController (support/admin).

Partner centers — new partner schema

  • Entity Domain/Entities/PartnerCenters/PartnerCenter (IAuditable; settlement_iban [AuditRedacted] + encrypted converter in ApplicationDbContext). Config in Persistence/Configuration/PartnerCentersConfig/ (also adds the nurse_profiles.partner_center_id FK in place). IPartnerCenterRepository (+ impl).
  • Features: CreatePartnerCenter, UpdatePartnerCenter, VerifyPartnerCenter, SponsorNurse, GetCenterForBooking (the merchant-of-record resolver), ListPartnerCenters, GetPartnerCenterById, GetCenterDashboard. Controllers AdminPartnerCentersController, CentersController (portal), InternalCentersController (resolver).

Seam

  • ILicenseVerificationService (Application/Contracts/Common) + MockLicenseVerificationService (CrossCutting/Seams/, registered in AddCrossCuttingSeams, config Seams:LicenseVerification:AutoApprove).

Cross-phase wiring

  • b11 IssueInvoiceCommand now sets invoices.issuing_entity_type + partner_center_id from ResolveCenterForBookingAsync (the single merchant-of-record resolver).
  • b11 CreateRefundCommand auto-opens a category=refund ticket via OpenTicketCommand when the caller passes none, so refunds.ticket_id is always non-null (replaces the old config-gated "ticket required" check).
  • The card ConfirmPaymentAndPostLedger and BNPL SettleBnplOrder handlers dispatch AutoCreateCoordinationTicketCommand after a booking is confirmed (idempotent, one per booking).

Reused, not rebuilt (admin backoffice consolidation)

  • Support-alert worklist (ISupportAlertService List/Assign/Resolve — SupportAlertsController) and the audit viewer (GetAuditTrailAuditController) already existed since b1; verified as the backoffice surface. Verification/refund/payout/moderation queues are their own phases' endpoints.

What is now testable and exactly how (the §7 steps)

  1. Open + message: POST /api/v1/tickets (no links) → 200 with a TKT-… referenceCode + opener as participant; POST /api/v1/tickets/{id}/messages → the message appears in the thread.
  2. Internal boundary (proven by a test): admin POST …/messages {isInternal:true} → 200; user GET /api/v1/tickets/{id} omits it; admin GET /api/v1/admin/tickets/{id} includes it; a non-staff isInternal:true → 403. (MessagingApiTests.InternalNote_IsHiddenInUserView_ShownInAdminView + NonAdmin_CannotSetInternal.)
  3. Participant uniqueness: add a user → 200; add again → 409 (not 500); delete → 200. (MessagingApiTests.AddParticipant_DuplicateIsConflict_NotServerError.)
  4. Partner center + masked IBAN: POST /api/v1/admin/partner-centers {isMerchantOfRecord:true, settlementIban} → 200 with settlementIbanMasked (last 4), never plaintext; GET …/{id} masks it too; created inactive. (PartnerCentersApiTests.CreateMerchantOfRecord_MasksSettlementIban, Verify_ActivatesTheCenter.)
  5. Merchant-of-record resolution: GET /api/v1/internal/bookings/{id}/centerpartner_center (+ id) for a nurse sponsored by a merchant-of-record center, platform otherwise. (CenterForBookingTests, 4 cases.)
  6. Refund anchors a ticket: CreateRefund yields a non-null refunds.ticket_id (foundation refund tests pass with the auto-open wired via TestSenders.WithTicketHooks()).
  7. Admin worklists / RBAC: support alerts + audit reachable under admin scope; a non-admin token on an admin route → 403 (PartnerCentersApiTests.NonAdmin_IsForbidden), unauthenticated → 401.
  8. Audit: admin state changes (e.g. VerifyPartnerCenter) append an audit_logs row (PartnerCenter is IAuditable; settlement_iban is redacted in the diff).

What is mocked / waiting on a real service

  • ILicenseVerificationService → manual-approve at MVP (no public eNamad/MoH B2B API). Make-it-real steps in reports/mocks-registry.md (🟡). No telephony seam — the emergency call is out-of-platform by design.

Contracts produced

  • dev/contracts/domains/messaging-notifications-admin.md; dev/contracts/openapi/swagger.v1.json refreshed (now includes tickets, partner centers, the center resolver).

Gate

  • dotnet build Baya.sln — 0 new code warnings. dotnet test Baya.sln — green: 4 identity + 240 foundation + 114 API (12 new tests this phase). Migration MessagingAndPartnerCenters scaffolds cleanly.

Decisions / notes for the future

  • Merchant-of-record = partner_center issuer only when the sponsoring center is_merchant_of_record; a non-MoR sponsor leaves the platform as issuer (so a sponsored-but-platform-billed nurse is representable).
  • Participant removal is a soft removed_at stamp (not a hard delete / not deleted_at), so the UNIQUE(ticket_id, user_id) row survives and a re-add resurrects it.
  • SQLite gotcha (again): messages are ordered by the monotonic Id (== send order), never ORDER BY sent_at (DateTimeOffset), which the SQLite test provider can't translate.
  • Follow-ups: the invoice-issuer wire sets the columns but the downstream settlement rail (paying a center's IBAN when it is MoR) is not exercised end-to-end here; the center dashboard caps the sponsored-nurse list at 50 (count is exact) — paginate it if a center grows large. Bookings/invoices partner_center_id columns exist without a DB FK (only nurse_profiles got the FK, per the DoD).