Files
baya-monorepo/dev/post-phase/refinement/refinement-phase-3-contract-batch.md
T
2026-07-10 20:59:47 +03:30

13 KiB
Raw Blame History

Refinement Phase 3 — Backend contract batch (close the 37 REQ gaps)

Mission: build the missing DTO fields and endpoints the frontend already codes against, so each client domain can flip from mock to real. The frontend filed 37 contract requests (REQ-001…037), all still Status: open — and 21 of its 22 domains are mock-primary because of them. This is the single change that unblocks the whole "no API calls to the backend" problem. Almost all of it is additive (new nullable fields, new read endpoints), so it's low-risk but broad.

Track: backend · Depends on: Phase 0 (nothing hard) · Unlocks: Phase 4 (each REQ delivered lets one domain flip) Before you start, read ../../phases/_shared/agent-operating-rules.md.

1. Context — where this sits

The backend chain is complete against its own specs, but the two lanes were built in parallel and the frontend consistently discovered small shape gaps: a DTO missing a field it needs to render, or a customer-facing read that only exists as an admin route. Rather than edit backend code, the frontend recorded each as a REQ in dev/shared-working-context/frontend/requests/for-backend.md and shipped a mock that fills the gap, wired so that delivering the REQ + flipping one USE_*_MOCK flag swaps to real with no hook/component change.

This phase delivers those REQs. It is large but coherent — think of it as one backend phase-sized batch of mostly-additive changes. It may be run in 23 sittings grouped by the tiers below; deliver a tier, regenerate swagger, let the frontend flip that tier's domains, repeat.

The already-done audit dev/post-phase/server/frontend-backend-gaps.md reconciled REQ-001…015 against shipped code (2 done, 1 doc-fix, 12 to build). REQ-016…037 were filed after that audit (for the f9f15 domains) and are added here. The consolidated table below is the authoritative worklist.

2. Required reading (do this first)

  • dev/shared-working-context/frontend/requests/for-backend.md — the full REQ-001…037 text (Need / Why / Proposed shape for each). This is the spec.
  • dev/post-phase/server/frontend-backend-gaps.md — the backend's own REQ-by-REQ reconciliation with file/line evidence and the priority ordering (REQ-012 first).
  • dev/contracts/domains/*.md + dev/contracts/openapi/swagger.v1.json — the current contract; you'll extend the DTOs/routes and regenerate the swagger snapshot after each tier.
  • dev/contracts/conventions/api-conventions.md + money-and-types.md — the envelope, snake_case URL segments, pageSize pagination, IRR-digit-string money rules every new surface must follow.
  • The relevant server/src/Core/Baya.Application/Features/<Area>/ and Models/ for each domain you touch.

3. Scope — build this

Deliver the REQs below. Zero-code items are doc/tracker updates. Everything else is an additive DTO field or a new read/command. Regenerate dev/contracts/openapi/swagger.v1.json and flip the REQ's Status: open → delivered in refinement-phase-3 as each lands.

Tier A — the discovery → request → checkout funnel (do first; highest leverage)

  • REQ-012 (M) — search enrichment + public profile. DO THIS FIRST. Add nurseName/avatarUrl (+ optional distanceKm) to NurseSearchResultDto (denormalize into nurse_search_index via the maintainer, or join in SqlNurseSearch), and add the aggregated GET api/v1/nurses/{id}/profile. Unblocks the entire discovery funnel (C2/C3) that everything downstream depends on.
  • REQ-005 (SM) — patient relation + conditions[] on PatientDto + create/update.
  • REQ-008 (S) — accept client map pin (latitude/longitude) on address create/update; store as user_pin, else geocode. Also protects EVV accuracy.
  • REQ-009 (S) — provinceId on CustomerAddressDto (join cities.province_id).
  • REQ-011 (M) — nurse credential_details command (INO number, specialties) + isRequired on VerificationStepDto (stops silent INO/specialty data loss on the real path).
  • REQ-013 (S) — variantPrice (IRR digit-string, + optional nurseAvatarUrl) on BookingRequestDto.
  • REQ-014 (S) — variantLabel (+ optional patientAge) on BookingRequestListItemDto.
  • REQ-006 (M) — avatar upload (POST …_profiles/avatar, multipart, via IObjectStorage) + avatarUrl on nurse/customer profile DTOs. The only item needing multipart; feeds REQ-012/013 avatars. (Pairs with the real object-storage swap in Phase 8, but the endpoint + local-disk seam work now.)
  • REQ-007 (S) — customer firstName/lastName/preferredLanguage update.
  • REQ-016 (SM) — checkout summary GET api/v1/booking_requests/checkout_summary/{id} (served service/commission/VAT/total decomposition that reconciles to the rial).
  • REQ-017 (S) — client-readable payment outcome: add bookingId to BookingRequestDto once converted (and/or a bookings/{requestId}/payments/latest read) so the confirmation can deep-link and distinguish declined vs slow callback.
  • REQ-018 (S) — invoice reachable post-capture: auto-issue the commission invoice on capture (idempotent), or let the owning customer trigger the idempotent issue on first GET invoices/{bookingId}.
  • Auth polish: REQ-002 (S) codeLength/expiresInSeconds on RequestOtpResult; REQ-003 (SM) optional machine code (+retryAfterSeconds) on the OTP-failure envelope (the only cross-cutting change — a small OperationResult/ApiResult extension).

Tier B — refunds, BNPL, payouts, reviews, records (post-booking)

  • REQ-019/020/021 — customer refunds: POST bookings/{id}/cancel (customer-initiated), GET bookings/{id}/cancellation_policy preview (+ define the canonical cancellation_policy_code set — the frontend currently invents free_24h/partial_under_24h/customer_no_show), GET refunds/by_booking/{id}
    • fee-leg decomposition on the customer status.
  • REQ-022/023/024 — BNPL: checkout_bnpl/options/{id} + schedule, eligibility accepting the D3 {nationalId, mobile, consent}, checkout_bnpl/wallet_installments + bookingId on settled order + by_request/{id}. Also decide the balinyaar in-house provider (add to the provider_code enum or document how it's modelled).
  • REQ-025 — nurse earnings: nurse_payouts/earnings_balance (four-bucket + signed net), nurse_payouts/earnings?state= list, nurse nurse_payouts/{id} detail, failureReason on the history DTO.
  • REQ-026 — reviews: bookings/{id}/review_eligibility + bookings/{id}/my_review reads (+ confirm the masked-author omission).
  • REQ-027 — family care record: patients/{id}/care_record GET/PUT + record_access + structured taskResults on a visit note. Product decision required first — is the family-owned record an MVP entity? (It's not in the current data model.) Confirm before building.

Tier C — messaging, notifications, admin, partner consoles

  • REQ-028 — tickets: unreadCount/lastMessageAt on TicketSummaryDto, author label, by-booking lookup, optional clientMessageId idempotency on post-message.
  • REQ-029/030/031 — admin: config updatedAt/updatedBy; audit filters actor_id/action/from/to; the RBAC admin_roles/list|grant|revoke endpoints (coordinate with Phase 2).
  • REQ-032/033 — partner center: centers/me + split portal reads + activate/suspend toggle + confirm the write-then-masked IBAN; center-scoped invoice list + totalIrr on Invoice. ⚠ Verify route casing: the frontend client calls kebab-case routes (admin/partner-centers, .../set-active, centers/me) that violate the snake_case URL convention — either the b15 controllers already diverge, or the frontend guessed wrong. Pin the real routes in swagger and reconcile before the frontend flips USE_PARTNER_MOCK.
  • REQ-034 — verification admin: nurse-level queue (server returns per-step today), on-demand signed document URL, whole-verification approve/reject.
  • REQ-035 — refund admin: refund preview + explicit approve/reject (the single POST currently creates+executes).
  • REQ-036 — payout admin: single preview (eligible + skipped + processingDate), holidayShifted flag, transfer_reference record route.
  • REQ-037 — reviews admin: tagCodes[] on ModerationQueueItemDto.

Zero-code (do alongside every tier)

  • REQ-010 — pagination doc sweep: the server binds camelCase pageSize; sweep the page_size occurrences in dev/contracts/domains/*.md to pageSize.
  • REQ-001, 004, 015 — written confirmations: envelope/casing/pagination (done), client-owned active role (Phase 2), status enum + checkInAddressMatch tri-state (verified). Write them into the tracker.
  • Reconcile the fee/VAT-rate drift the frontend mock carries (0.15 fee / 0.10 VAT vs the "12% / 10%" described elsewhere) — the served checkout_summary (REQ-016) is the authority; make the config values it reads canonical and note them so the frontend stops assuming.

4. Mocks & seams in this phase

  • REQ-006 introduces the first multipart endpoint over IObjectStorage — the local-disk mock is fine now; the presigned-URL/S3 swap is Phase 8. Keep the endpoint contract (multipart in, stored URL out) stable across the swap.
  • No new external seams otherwise — these are DB/DTO reads and commands over existing tables.

5. Critical rules you must not get wrong

  • Additive, not breaking. New fields are nullable/optional; don't change existing field names/casing/shape (the frontend types are already generated against the current swagger).
  • Follow the conventions exactly: snake_case URL segments, pageSize pagination, ApiResult envelope, IRR money as digit-strings, stable snake_case enum codes. A new route that breaks a convention is worse than a missing route — the frontend guessed the convention, not the exception.
  • Money-free stays money-free: REQ-013's variantPrice is a display rate, not an engagement total — the booking_request still stores no money.
  • Server owns the numbers: commission/VAT/refund %/eligibility/holiday-shift are computed server-side and served; the client renders, never recomputes. REQ-016/020/025/036 must serve reconciling decompositions.
  • Regenerate swagger.v1.json after each tier and flip the REQ statuses — a stale contract re-breaks the frontend types.

6. Definition of Done

On top of the shared definition-of-done.md:

  • Every REQ above is either delivered (endpoint/field shipped + tested) or explicitly answered (zero-code confirmation / deferred with a written reason) in for-backend.md; no REQ still reads Status: open without a note.
  • dotnet build Baya.sln zero new warnings; dotnet test Baya.sln green, including tests for the new reads/commands (tenancy 404s, reconciling decompositions, idempotency where relevant).
  • dev/contracts/openapi/swagger.v1.json regenerated; dev/contracts/domains/*.md updated (incl. the page_sizepageSize sweep and the canonical cancellation_policy_code/BNPL-provider enums).
  • The partner-center route casing is pinned in swagger and matches (or the frontend is told to adjust).

7. How to test (what a human can verify after this phase)

Per tier, via Swagger/curl against the Phase 1 demo data:

  • Tier A: POST search/nurses returns rows with nurseName/avatarUrl; GET nurses/{id}/profile aggregates profile+variants+badge+latest review; GET booking_requests/checkout_summary/{id} returns a breakdown where service + commission + vat = total.
  • Tier B: a customer POST bookings/{id}/cancel creates a refund; GET bookings/{id}/cancellation_policy previews the tier + per-session refundability; nurse_payouts/earnings_balance returns four buckets + a signed net.
  • Tier C: centers/me resolves the caller's own center; the admin queues/previews return their shapes; RBAC grant/revoke works.

Each success here is what lets the matching frontend domain flip in Phase 4.

8. Hand off & document (close the phase)

  • Update every touched dev/contracts/domains/*.md, regenerate swagger, and flip all REQ statuses.
  • Update the mocks-registry "Frontend client-side mocks" rows: each delivered REQ moves its domain toward flippable.
  • Report which REQs landed per tier so the frontend lane knows exactly which USE_*_MOCK flags it can flip. Save memory notes on any non-obvious decision (canonical cancellation codes, the balinyaar provider decision, the family-care-record product call).