add refinement phases
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user