13 KiB
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 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,pageSizepagination, IRR-digit-string money rules every new surface must follow.- The relevant
server/src/Core/Baya.Application/Features/<Area>/andModels/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(+ optionaldistanceKm) toNurseSearchResultDto(denormalize intonurse_search_indexvia the maintainer, or join inSqlNurseSearch), and add the aggregatedGET api/v1/nurses/{id}/profile. Unblocks the entire discovery funnel (C2/C3) that everything downstream depends on. - REQ-005 (S–M) — patient
relation+conditions[]onPatientDto+ create/update. - REQ-008 (S) — accept client map pin (
latitude/longitude) on address create/update; store asuser_pin, else geocode. Also protects EVV accuracy. - REQ-009 (S) —
provinceIdonCustomerAddressDto(joincities.province_id). - REQ-011 (M) — nurse
credential_detailscommand (INO number, specialties) +isRequiredonVerificationStepDto(stops silent INO/specialty data loss on the real path). - REQ-013 (S) —
variantPrice(IRR digit-string, + optionalnurseAvatarUrl) onBookingRequestDto. - REQ-014 (S) —
variantLabel(+ optionalpatientAge) onBookingRequestListItemDto. - REQ-006 (M) — avatar upload (
POST …_profiles/avatar, multipart, viaIObjectStorage) +avatarUrlon 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/preferredLanguageupdate. - 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
bookingIdtoBookingRequestDtoonceconverted(and/or abookings/{requestId}/payments/latestread) 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/expiresInSecondsonRequestOtpResult; REQ-003 (S–M) optional machinecode(+retryAfterSeconds) on the OTP-failure envelope (the only cross-cutting change — a smallOperationResult/ApiResultextension).
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_policypreview (+ define the canonicalcancellation_policy_codeset — the frontend currently inventsfree_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+bookingIdon settled order +by_request/{id}. Also decide thebalinyaarin-house provider (add to theprovider_codeenum or document how it's modelled). - REQ-025 — nurse earnings:
nurse_payouts/earnings_balance(four-bucket + signed net),nurse_payouts/earnings?state=list, nursenurse_payouts/{id}detail,failureReasonon the history DTO. - REQ-026 — reviews:
bookings/{id}/review_eligibility+bookings/{id}/my_reviewreads (+ confirm the masked-author omission). - REQ-027 — family care record:
patients/{id}/care_recordGET/PUT +record_access+ structuredtaskResultson 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/lastMessageAtonTicketSummaryDto, author label, by-booking lookup, optionalclientMessageIdidempotency on post-message. - REQ-029/030/031 — admin: config
updatedAt/updatedBy; audit filtersactor_id/action/from/to; the RBACadmin_roles/list|grant|revokeendpoints (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 +totalIrronInvoice. ⚠ Verify route casing: the frontend client calls kebab-case routes (admin/partner-centers,.../set-active,centers/me) that violate thesnake_caseURL convention — either the b15 controllers already diverge, or the frontend guessed wrong. Pin the real routes in swagger and reconcile before the frontend flipsUSE_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),
holidayShiftedflag,transfer_referencerecord route. - REQ-037 — reviews admin:
tagCodes[]onModerationQueueItemDto.
Zero-code (do alongside every tier)
- REQ-010 — pagination doc sweep: the server binds camelCase
pageSize; sweep thepage_sizeoccurrences indev/contracts/domains/*.mdtopageSize. - REQ-001, 004, 015 — written confirmations: envelope/casing/pagination (done), client-owned active role
(Phase 2), status enum +
checkInAddressMatchtri-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_caseURL segments,pageSizepagination,ApiResultenvelope, 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
variantPriceis 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.jsonafter 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 readsStatus: openwithout a note. dotnet build Baya.slnzero new warnings;dotnet test Baya.slngreen, including tests for the new reads/commands (tenancy 404s, reconciling decompositions, idempotency where relevant).dev/contracts/openapi/swagger.v1.jsonregenerated;dev/contracts/domains/*.mdupdated (incl. thepage_size→pageSizesweep and the canonicalcancellation_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/nursesreturns rows withnurseName/avatarUrl;GET nurses/{id}/profileaggregates profile+variants+badge+latest review;GET booking_requests/checkout_summary/{id}returns a breakdown whereservice + commission + vat = total. - Tier B: a customer
POST bookings/{id}/cancelcreates a refund;GET bookings/{id}/cancellation_policypreviews the tier + per-session refundability;nurse_payouts/earnings_balancereturns four buckets + a signed net. - Tier C:
centers/meresolves 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_*_MOCKflags it can flip. Save memory notes on any non-obvious decision (canonical cancellation codes, thebalinyaarprovider decision, the family-care-record product call).