# 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](refinement-phase-0-bring-up.md) (nothing hard) ·
> **Unlocks:** [Phase 4](refinement-phase-4-frontend-de-mock.md) (each REQ delivered lets one domain flip)
> **Before you start, read [../../phases/_shared/agent-operating-rules.md](../../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 2–3 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 f9–f15 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 (S–M) — 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](refinement-phase-8-external-rails.md), but the endpoint + local-disk
  seam work now.)
- **REQ-007 (S) — customer `firstName`/`lastName`/`preferredLanguage`** update.
- **REQ-016 (S–M) — 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 (S–M)**
  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](refinement-phase-2-auth-and-role-nav.md)).
- **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](refinement-phase-8-external-rails.md). 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](../../phases/_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_size`→`pageSize` 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](refinement-phase-1-database-and-seed.md) 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](refinement-phase-4-frontend-de-mock.md).

## 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).
