Files
baya-monorepo/archive/post-phase/server/frontend-backend-gaps.md
T
2026-08-02 18:48:32 +03:30

251 lines
18 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.
# Frontend ↔ backend gaps — REQ reconciliation
**Audit date:** 2026-07-10 · **Sources:** `dev/shared-working-context/frontend/requests/for-backend.md`
(REQ-001…015), the frontend phase reports/STATUS (f0f8), the client code's mock flags, the published
contracts (`dev/contracts/domains/*.md` + `dev/contracts/openapi/swagger.v1.json`), and the server code.
Every verdict was checked against **both** the contract surface and the actual DTO/handler/controller code.
**Headline:** the backend chain is complete (b0b15), but of the 15 filed REQs only **REQ-001** and
**REQ-015** are effectively satisfied and **REQ-010** is a documentation fix — the other **12 are
undelivered**. Every REQ still reads `Status: open` in the tracker. As a direct consequence, **11 of the
client's 12 service domains still default to mock-primary** (only auth is real-default,
`client/src/services/auth/constants.ts:6`). Beyond the filed REQs, the unbuilt frontend phases f9f15 will
consume backend surfaces that mostly exist — with one data gap (catalog option groups) and one pre-flagged
shape gap (checkout VAT line).
## Verdict summary
| REQ | Ask (short) | Verdict | One-line evidence |
| --- | --- | --- | --- |
| REQ-001 | Envelope / casing / pagination shape | **Done** (confirm + caveat) | `ApiResult` + `PagedResult` match the typed shape; `statusCode` is an **integer** enum |
| REQ-002 | `codeLength`/`expiresInSeconds` on RequestOtpResult | **Missing** | `RequestOtpResult.cs:7` has only `OtpSent`, `ResendAvailableInSeconds` |
| REQ-003 | Machine error codes for verify_otp | **Missing** | envelope has no `code` slot; lockout differs only by message text |
| REQ-004 | `activeRole` on MeResult (confirmation) | **Missing** (answer: client owns it) | `MeResult.cs:9` — no ActiveRole anywhere in the contract |
| REQ-005 | Patient `relation` + `conditions` | **Missing** | `PatientDto.cs:7`, create/update commands unchanged |
| REQ-006 | Avatar upload route + `avatarUrl` | **Missing** | zero `IFormFile`/avatar matches in `server/src` |
| REQ-007 | Customer name + preferred language | **Missing** | upsert body is emergency-contact only |
| REQ-008 | Accept client map pin on address create/update | **Missing** | commands have no lat/lng; always geocodes |
| REQ-009 | `provinceId` on CustomerAddressDto | **Missing** | DTO ends at RecipientPhone |
| REQ-010 | pageSize vs page_size | **Partial** | server binds `pageSize` (verified); contract docs still say `page_size` |
| REQ-011 | Nurse credential_details endpoint + `isRequired` | **Missing** | no such route/command; step DTO lacks isRequired |
| REQ-012 | Search row name/avatar/distance + `GET nurses/{id}/profile` | **Missing** | DTO ids-only; no profile action on NursesController |
| REQ-013 | `variantPrice` on BookingRequestDto | **Missing** | DTO has unit without price |
| REQ-014 | `variantLabel`/`patientAge` on list item | **Missing** | list DTO omits both |
| REQ-015 | Status enum codes + `checkInAddressMatch` tri-state (confirmation) | **Done** (verified in code) | snake_case string constants on the wire; null-when-no-GPS confirmed |
---
## Per-REQ detail
### REQ-001 — envelope, casing, pagination — **Done, needs a written confirmation + one caveat**
- **Frontend expects:** payload always under `data` in
`{ isSuccess, statusCode, message, requestId, data }`; camelCase JSON; lists as
`{ items, total, page, pageSize }`.
- **Backend ships:** exactly that. `ApiResult` at
`server/src/Core/Baya.Application/Models/ApiResult/ApiResult.cs:8` (+ generic `Data` at `:28`);
`PagedResult<T>(Items, Total, Page, PageSize)` at
`server/src/Core/Baya.Application/Models/Common/PagedResult.cs:4`; camelCase is the System.Text.Json
default (no naming-policy override exists in `server/src/API`); swagger confirms
(`dev/contracts/openapi/swagger.v1.json:13448` envelope, `:14205` paged shape).
- **Caveat to communicate:** `statusCode` serializes as an **integer** (`ApiResultStatusCode` enum,
swagger `:13468-13469`) — matches the client's `number` typing, but worth stating so nobody expects an
HTTP-status string.
- **Fix:** zero code. Write the confirmation into the REQ and mark it delivered.
### REQ-002 — OTP length + expiry — **Missing**
- **Expected:** `RequestOtpResult { otpSent, resendAvailableInSeconds, codeLength, expiresInSeconds }`.
- **Actual:** `server/src/Core/Baya.Application/Models/Identity/RequestOtpResult.cs:7` — two fields only;
handler returns them at `Features/Identity/Commands/RequestOtp/RequestOtpCommand.Handler.cs:67`; swagger
agrees (`swagger.v1.json:16539`). The client hardcodes `OTP_CODE_LENGTH = 6`
(`client/src/services/auth/constants.ts:19`).
- **Fix:** add the two ints (code length is a constant today; TTL from the OTP provider options). S effort.
### REQ-003 — machine-readable verify_otp errors — **Missing**
- **Expected:** stable `code` (`otp_invalid` | `otp_expired` | `otp_locked`) + `retryAfterSeconds` on
lockout.
- **Actual:** the envelope has no `code` slot (`ApiResult.cs:8`; `OperationResult` carries only boolean
flags — `Models/Common/OperationResult.cs:14-31`). Wrong/expired share one anti-enumeration message
(`Features/Identity/Commands/VerifyOtp/VerifyOtpCommand.Handler.cs:24,30,34,50`); lockout is a different
English string only (`:38`). The client keys off the mock-only `otp_locked` code
(`client/src/services/auth/constants.ts:29`).
- **Fix:** add an optional `code` (+ optional `data`) to the failure envelope — a small
`OperationResult`/`ApiResult` extension — and emit `otp_locked` + `retryAfterSeconds` from the lockout
branch; keep wrong-vs-expired collapsed if enumeration-safety is preferred (state that in the REQ
answer). SM effort (the only REQ touching a cross-cutting type).
### REQ-004 — activeRole confirmation — **Missing (recommend: answer "client owns it")**
- **Actual:** no `activeRole` on `MeResult`
(`server/src/Core/Baya.Application/Models/Identity/MeResult.cs:9`) or anywhere in the contract (schema
scan). No endpoint persists a current-role choice.
- **Fix:** zero code — write the decision (client-owned `intended_role` stands) into the REQ so the router
behavior is contract-blessed. If the product later wants a persisted active role, it's a `me/select_role`
extension.
### REQ-005 — patient relation + conditions — **Missing**
- **Actual:** `PatientDto` ends at `InitialMedicalNotes`/`IsActive`
(`Models/Identity/PatientDto.cs:7,15-16`); create/update commands unchanged
(`Features/Identity/Commands/CreatePatient/CreatePatientCommand.cs:12-19`,
`UpdatePatient/UpdatePatientCommand.cs:8-16`); no `relation`/`conditions` in any schema.
- **Fix:** `relation` as a nullable code column; `conditions` as stable codes (JSON column or child table —
child table if search/filtering is ever wanted). Gate: flips `USE_PATIENTS_MOCK`
(`client/src/services/patients/constants.ts:8`). SM effort.
### REQ-006 — avatar upload + avatarUrl — **Missing**
- **Actual:** zero `IFormFile`/multipart/avatar usage in `server/src` (repo-wide grep); no `avatarUrl` on
`NurseProfileDto` (swagger `:18945`) or `CustomerProfileDto`
(`Models/Identity/CustomerProfileDto.cs:7`). The client's real path deliberately throws 501
(`client/src/services/profiles/apis/clientApi.ts:84`).
- **Fix:** `POST api/v1/{nurse|customer}_profiles/avatar` (multipart, size/type-validated) storing via
`IObjectStorage` + `avatar_url` column on both profiles. Note it also feeds REQ-012 (search card avatar)
and REQ-013 (nurse avatar on request detail) — deliver before or with those. M effort (first multipart
endpoint; pairs with the object-storage swap, plan §5.5).
### REQ-007 — customer name + preferred language — **Missing**
- **Actual:** upsert body is emergency-contact only
(`Features/Identity/Commands/UpsertCustomerProfile/UpsertCustomerProfileCommand.cs:11`); `MeResult`
exposes name read-only (`MeResult.cs:12`); no `preferredLanguage` anywhere (schema scan).
- **Fix:** decide the home (recommend: extend the upsert to write `Users.FirstName/LastName` +
`preferred_language` on the customer profile) and answer the REQ. S effort.
### REQ-008 — accept the client map pin — **Missing**
- **Actual:** create/update commands have no coordinates; the server always geocodes
(`Features/Addresses/Commands/CreateAddress/CreateAddressCommand.cs:9-12`,
`UpdateAddress/UpdateAddressCommand.cs:8-9`; swagger `:18051`). The user's pin is silently discarded on
the real path — exactly what the REQ warned. This also degrades **EVV accuracy** (b9 measures distance to
the stored coordinate; a mock/geocoded centroid is ±5 km off — `CrossCutting/Seams/MockGeocoder.cs:52`).
- **Fix:** optional `latitude`/`longitude` on both bodies; when present store as source `user_pin`, else
geocode as today. S effort; do before the real geocoder swap (plan §5.4).
### REQ-009 — provinceId on CustomerAddressDto — **Missing**
- **Actual:** DTO fields run `Id..RecipientPhone` (`Models/Addresses/CustomerAddressDto.cs:9-24`); no
`provinceId` (swagger `:17983` — the property exists only on `CityDto`).
- **Fix:** join `cities.province_id` into the address projections. S effort. Gate (with REQ-008):
`USE_ADDRESSES_MOCK` (`client/src/services/addresses/constants.ts:10`).
### REQ-010 — pageSize param name — **Partial (server verified; docs stale)**
- **Actual:** every list binds a `PageSize` record property via `[FromQuery]` — so the working wire name is
camelCase `pageSize` (case-insensitive), and `page_size` **silently does not bind**. Verified:
`Features/ServiceAreas/Queries/ListMyServiceAreas/ListMyServiceAreasQuery.cs:8`,
`Features/Variants/Queries/ListMyVariants/ListMyVariantsQuery.cs:8`,
`Controllers/V1/NurseServiceAreasController.cs:35`, `AdminPayoutsController.cs:54`; swagger names the
parameter `pageSize` (`swagger.v1.json:3104`). But the requested deliverable — fixing the docs — never
happened: `dev/contracts/domains/catalog.md:41` and `config-reference.md:11` (and others, e.g.
bookings-evv.md, verification.md) still write `page_size`.
- **Fix:** sweep the contract docs to `pageSize`, answer the REQ. Zero server code.
### REQ-011 — nurse credential_details + isRequired — **Missing**
- **Actual:** the nurse-facing controller exposes only submit/status/upload_url/documents/run
(`Controllers/V1/NurseVerificationController.cs:26-57`); repo-wide grep for
`credential_details|SubmitCredential` finds nothing. `VerificationStepDto` has no `IsRequired`
(`Models/Verification/VerificationDtos.cs:17`); the flag exists only on the admin step-type catalog.
Consequence on the real path: the INO number + specialties a nurse types are **silently dropped**
(`verificationClientApi.submitCredentialDetails` no-ops —
`dev/shared-working-context/reports/frontend-phase-5-report.md:101`).
- **Fix:** `POST api/v1/nurse_verification/credential_details` writing the structured
`nurse_credentials` fields (the registry table already stores number/authority/expiry), + project
`isRequired` onto the step DTO. M effort. Gate: `USE_VERIFICATION_MOCK`
(`client/src/services/verification/constants.ts:9`).
### REQ-012 — search enrichment + public nurse profile — **Missing (highest-leverage gap)**
- **Actual:** `NurseSearchResultDto` carries ids + price/rating/gender/geo only
(`Models/Search/NurseSearchResultDto.cs:8-19`); the public `NursesController` has trust_badge, reviews,
review_tags — **no `/profile`** (`Controllers/V1/NursesController.cs:24-36`); no
`avatarUrl`/`distanceKm` anywhere in the contract (schema scan).
- **Why it leads the priority list:** C2/C3 are the trust funnel — the family picks a *named, faced,
priced* nurse here; this single REQ keeps `services/search` mock-primary
(`client/src/services/search/constants.ts:9`) and blocks the whole discovery→request→booking real-path
chain (search feeds C4's nurse/variant ids).
- **Fix:** (a) denormalize `nurse_name`/`avatar_url` into `nurse_search_index` (the maintainer already
re-derives rows from source — `Persistence/Services/Search/SearchIndexMaintainer.cs:25`; add columns +
reindex-on-profile-change) or join at query time in `SqlNurseSearch`; `distanceKm` is optional — the
district model makes it derived-if-cheap. (b) an aggregated `GET nurses/{id}/profile` composing existing
reads (profile + variants + trust badge + latest published review). M effort; depends on REQ-006 for the
avatar itself.
### REQ-013 — variantPrice on BookingRequestDto — **Missing**
- **Actual:** the DTO has `VariantLabel` + `VariantPriceUnit` but no price and no nurse avatar
(`Models/Booking/BookingRequestDto.cs:20-21`, full list `:11-42`; swagger `:16674`).
- **Fix:** join the variant's `Price` (IRR digit-string, consistent with the money convention) into the
projection. The money-free rule stays intact — this is the display *rate*, not an engagement total (the
request row still stores no money). S effort.
### REQ-014 — variantLabel/patientAge on the inbox row — **Missing**
- **Actual:** `BookingRequestListItemDto` has neither (`Models/Booking/BookingRequestListItemDto.cs:10-21`;
swagger `:16903`) — the nurse inbox can't show *which service* was requested without opening the detail.
- **Fix:** add `variantLabel` (already on the detail DTO); `patientAge` as a coarse band if product wants
it. S effort. Gate (with REQ-013): `USE_BOOKING_REQUESTS_MOCK`
(`client/src/services/bookingRequests/constants.ts:14`) — though that flip also needs the upstream
domains real (see below).
### REQ-015 — enum codes + checkInAddressMatch tri-state — **Done (verified), needs a written confirmation**
- **Verified in code:** statuses are stored/projected as snake_case **string constants** — exactly the
client unions: `Domain/Entities/Booking/BookingStatus.cs:11-30`, `BookingSessionStatus.cs:10-19`,
`VisitVerificationStatus.cs:11-17`; DTOs copy them verbatim (`Models/Booking/BookingDtos.cs:97`), so no
PascalCase/int ever hits the wire. `checkInAddressMatch` is `bool?` (`BookingDtos.cs:104`) assigned only
inside the lat/lng-present branch
(`Features/Bookings/Commands/CheckInVisit/CheckInVisitCommand.Handler.cs:64-77`) → **null when GPS is
absent**; a `false` is advisory only (support alert + notification, no block — `:90-109`).
- **One nuance to include in the answer:** `null` also occurs when GPS *was* sent but the frozen booking
address has no resolvable coordinates — the client copy for «موقعیت ثبت نشد» should tolerate that.
- **Fix:** zero code; write the confirmation, mark delivered.
---
## Beyond the filed REQs — what f9f15 will hit
Frontend phases f0f8 are built (reports exist); **f9f15 are specs only**. Reconciling their declared
consumption against the shipped backend:
| Upcoming phase | Consumes | Backend reality | Verdict |
| --- | --- | --- | --- |
| f9 checkout/card | b10 `payments.md` + b11 invoice | endpoints exist (initiate/webhook/`GET invoices/{booking_id}`), but **no checkout-summary read with the VAT line** — f8 already flagged `BookingDetailDto` has no tax field (`reports/frontend-phase-8-report.md:113`); f9's spec expects `vat_irr`/`vat_rate`/`redirect_url` shapes (`dev/phases/frontend/frontend-phase-9-b10.md:117,330`) | **Partial — pre-file the checkout-summary REQ now** |
| f10 refund status | b11 `refunds-invoices.md` | `GET refunds/{id}/status` + `GET invoices/{booking_id}` shipped (`Controllers/V1/RefundsController`, `InvoicesController`) | Done (verify shapes when f10 runs) |
| f11 BNPL | b12 `bnpl.md` | full eligibility→initiate→status surface shipped (`CheckoutBnplController`) | Done (verify shapes) |
| f12 nurse earnings | b13 `payouts.md` | `nurse_payouts/history` + admin console shipped (`NursePayoutsController`, `AdminPayoutsController`) | Done (verify shapes) |
| f13 reviews/care records | b14 `reviews-records.md` | submit/list/tags/moderation + care records shipped (5 controllers) | Done (verify shapes) |
| f14 tickets + notifications | b15 + **b1 notifications** | tickets shipped; notifications **verified present**: `GET notifications/get_notifications`/`get_unread_count`, `POST mark_notification_read`/`mark_all_read` (`Controllers/V1/NotificationsController.cs:24-42`) — the f14 spec's worry about missing b1 endpoints is unfounded | Done |
| f15 admin/partner consoles | admin endpoints across b1/b6/b11/b13/b14/b15 | all shipped per the chain (verification queue, refunds, payouts, moderation, config/holidays/audit/support-alerts, partner centers) | Done (expect shape-polish REQs when f15 runs) |
**Data gap (not a contract gap):** flipping `USE_CATALOG_MOCK` against a fresh backend yields categories
with **no option groups** — only the 5 categories are seeded; groups/values are admin-authored and the
admin catalog UI is f15 (`reports/frontend-phase-4-report.md:92`). Until f15 (or a seed migration), the
variant builder's required-option step has nothing to render on the real path. Recommend: a small
representative option-group seed, or prioritize the f15 catalog manager.
**Tracker hygiene:** all 15 REQs read `Status: open` (`for-backend.md:32…216`) and the mocks-registry's
early block contradicts its own later rows (see plan §7.6). Whoever lands this batch should update both in
the same change.
---
## Frontend-unblock priority
1. **REQ-012** (search row enrichment + public profile) — unlocks the discovery funnel; everything
downstream needs C2/C3 real. Include the `nurse_search_index` columns + reindex.
2. **REQ-005, REQ-008, REQ-009** — the booking-request *inputs* (patients, addresses) go real; REQ-008
also protects EVV accuracy.
3. **REQ-013, REQ-014** — the request flow prices/labels itself; with (1)+(2) the whole
search→request→accept chain can flip to real.
4. **REQ-006, REQ-007** — profile/avatar polish; REQ-006 also feeds (1) and (3)'s avatar fields.
5. **REQ-011** — verification detail capture (stops silent INO/specialty data loss).
6. **REQ-002, REQ-003** — auth UX polish (real path already works without them).
7. **Zero-code batch: REQ-001, REQ-004, REQ-010, REQ-015** — written confirmations + contract-doc
`page_size` sweep + tracker statuses.
8. **Pre-file the f9 checkout-summary REQ** (VAT line, redirect_url, idempotency header echo) so b-side
work can precede the f9 build.