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