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

180 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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](refinement-phase-8-external-rails.md), 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](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).