Files
baya-monorepo/mvp/fix-plan.md
T
2026-08-02 23:24:28 +03:30

106 lines
9.3 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.
# Fix plan — how to close every item in blockers.md
An index into [`blocker-phases/`](blocker-phases/), an engineering-facing companion to
[blockers.md](blockers.md): one numbered file per fix, in a suggested working order. Each file has the exact
root cause (file:line), the proposed fix, and anywhere the fix depends on a business-rule decision that
shouldn't be guessed. Written from a full read of the current code (commit `fb58ca5`, 2026-08-02). Phases 01
and 02 are applied (see Status column); the rest are unapplied — work through them at your own pace, in
whatever order you prefer.
> **Two blockers already got a scope decision, recorded so it isn't re-litigated:** §B.4 (مودیان
> e-invoicing) gets a note in [forgotten-features.md](forgotten-features.md) instead of a build — no chosen
> provider to integrate against. §B.6 (bank-transfer payout rail) stays mocked — the seam already exists, see
> phase 11.
## Phases
| # | File | Blocker | Depends on | Status |
| --- | --- | --- | --- | --- |
| 01 | [admin-rbac](blocker-phases/01-admin-rbac.md) | Admin can't do anything (the #1 leverage item) | — | ✅ Done |
| 02 | [reviews-moderation](blocker-phases/02-reviews-moderation.md) | Reviews can never go live | 01 | ✅ Done (no separate fix needed — unblocked by 01) |
| 03 | [address-edit-bug](blocker-phases/03-address-edit-bug.md) | Editing an address wipes recipient name/phone | — | ✅ Done |
| 04 | [payment-timezone](blocker-phases/04-payment-timezone.md) | The 30-minute payment countdown can lie | — | ✅ Done (follow-up filed below) |
| 05 | [bnpl-setup](blocker-phases/05-bnpl-setup.md) | Installments (BNPL) don't work at all | — | — |
| 06 | [catalog-admin-page](blocker-phases/06-catalog-admin-page.md) | No admin page for service categories/pricing | — | — |
| 07 | [card-payment-redirect](blocker-phases/07-card-payment-redirect.md) | Card payment can never complete | — | ✅ Done (follow-up filed below) |
| 08 | [refunds-demock](blocker-phases/08-refunds-demock.md) | Refunds are demo-only, off by 100× | — | ✅ Done (customer surface only, follow-up filed below) |
| 09 | [nurse-verification-badge](blocker-phases/09-nurse-verification-badge.md) | Verification badge doesn't reflect reality | pairs with 10 | ✅ Done |
| 10 | [search-dedup-and-trust](blocker-phases/10-search-dedup-and-trust.md) | Search isn't de-duplicated; trust info hardcoded | pairs with 09 | ✅ Done |
| 11 | [nurse-payouts](blocker-phases/11-nurse-payouts.md) | Nurse pay/payouts are fake, no "process" action | benefits from 01 | — |
| 12 | [patient-records](blocker-phases/12-patient-records.md) | Patient records & visit notes are fake demo data | needs a product decision first | 🟡 Partial (see follow-up below) |
| 13 | [booking-lifecycle](blocker-phases/13-booking-lifecycle.md) | Stuck bookings; "today's visits" unfiltered | pairs with 04 | — |
| 14 | [partner-center](blocker-phases/14-partner-center.md) | Partner/business-center accounts are fake | benefits from 01 | — |
| 15 | [debug-mode-production](blocker-phases/15-debug-mode-production.md) | Turn off dev mode on the live site (§B.2) | do last, deliberately | — |
## Follow-ups filed (not yet phases of their own)
- **Phase 08 closed the customer-facing refund surface only — the admin refund console stays mocked.**
`AdminRefundsController` only implements create-and-execute (`POST admin_refunds`, matching
`initiateRefund`); there is no real read-only preview, no retry/approve, and no reject route. The phase
doc's own read ("real server side is live and correct") only checked the customer half
(`RefundsController`, `CreateRefundCommand`, `GetCancellationPolicyPreviewQuery`) — the admin gaps weren't
called out and would have 404'd the console had the single mock flag been flipped wholesale. Split into
two independently-selected flags instead (`client/src/services/refunds/constants.ts`):
`USE_CUSTOMER_REFUNDS_MOCK = false` (real — closes the actual ×100 money-safety bug) and
`USE_ADMIN_REFUNDS_MOCK = true` (stays mocked; the four admin methods are kept together rather than mixed,
since a real `initiateRefund` executing against a mocked preview's numbers would be actively dangerous).
Building the missing preview/retry/reject endpoints needs real design (retry semantics re-executing a
channel call, what "reject" reverses) that isn't specified anywhere — filed as its own future phase, not
guessed here.
- **Same timezone bug as 04, lower severity, not fixed.** Phase 04 fixed `BookingRequest.PaymentDeadlineAt`/
`NurseResponseDeadlineAt` — a `DateTime` (not `DateTimeOffset`) read back from SQL Server's `datetime2`
loses its `Kind` tag (comes back `Unspecified`), so JSON serialization drops the trailing `Z` and a client
`Date.parse()` misreads it as local time. A grep for every other bare `DateTime`/`DateTime?` entity property
found four more real instances, all on `Booking` (`server/src/Core/Baya.Domain/Entities/Booking/Booking.cs`):
`DisputeWindowEndsAt` (:95, rendered via `formatShamsiDate` in
`client/src/components/booking/BookingDetailView/BookingDetailView.tsx:444`), and `ConfirmedAt`/
`CancelledAt`/`CompletedAt` (:73,74,91), booking timeline timestamps shown to the client. Lower severity than
04 — nothing here drives a live countdown, so the failure mode is "can show the wrong calendar day near a
Tehran (UTC+3:30) midnight boundary," not "actively expires while still showing time left." (Everything
else with a bare `DateTime` — payout batches, webhook events, ASP.NET Identity tables — is internal/audit-only
and never reaches a client, so it's excluded.) Fix is the same pattern phase 04 used: apply the existing
`UtcDateTimeConverter` (`server/src/Infrastructure/Baya.Infrastructure.Persistence/ValueConversion/
UtcDateTimeConverter.cs`) to these four properties in `BookingConfig.cs`. No migration needed. Deliberately
left undone — pick up as its own small phase, or fold into whatever eventually addresses 13b's timezone
decision (booking-lifecycle's "today's visits" fix), since both are the same missing
`Asia/Tehran`/UTC-boundary discipline.
- **Phase 12 closed everything except the medication/routine schema decision, which stays deliberately
unresolved.** Fixed: the silent data-wipe (`patients/[id]/record/page.tsx`'s `EditableTabs.save` now always
resends the full `medications`/`routine`/`tasks` triple, not just the tab being edited — the real
`UpsertCarePlanCommand` fully replaces all three from the request, no server-side merge); the
`RecordAccess.deniedReason` enum (client now matches the server's real `'not_authorized' | 'not_found'`,
was `'no_access' | 'not_found'`); and the visit-note `taskResults` mapper bug (`clientApi.ts` was hardcoding
`taskResults: []` on read and folding the checklist into free text on write, even though `CareRecordDto`
already serves a structured `TaskResults` array and `WriteCareRecordBody` already accepts one — both sides
now pass the array through directly). Also corrected the stale "no backend at all" comments in
`constants.ts`/`types.ts`/`clientApi.ts`/`mockApi.ts``PatientCareRecordsController` implements every
route this domain needs.
**Still open, on purpose:** the client's medication/routine shape (structured dose amount/unit, frequency
preset codes, a multi-select `timeOfDay`) doesn't match the server's (one free-text dose, one required
frequency string, no `timeOfDay` on either medications or routine items) — see phase 12's items #3/#4. Asked
which side should change; the answer was to leave both as they are for now and just keep this note so it
isn't lost. `USE_PATIENT_RECORDS_MOCK` therefore stays `true`, and the id-type mismatch (client `string` ids
vs. the wire's `long`, phase 12 item #2) is left unfixed too — it can't be finished without first knowing
the target field shape, since `getFamilyRecord`/`updateFamilyRecord` would need a real mapping function
between the two, not just an id conversion.
- **ZarinPal-callback translator, latent until a real gateway is switched on.** Phase 07 restored the
mock-gateway harness and pointed `MockPaymentProvider.InitPaymentAsync`'s `redirectUrl` at it (relative
URL, no more dead `mock-psp.local` host) — the card-payment dead end (blockers.md § "Payments") is closed
for the mock path, which is the only path reachable pre-e-namad (§B.5). Once a real acquirer is ever
switched on, ZarinPal's callback redirects the browser with its **own** query params (`Authority`,
`Status`) — nothing under `Controllers/V1` today translates that into what `checkout/return/page.tsx`
expects (`request_id`/`transaction_id`/`outcome`). Needs a translating endpoint/route before the real path
can work; deliberately out of scope of the mock fix, and moot until §B.5 clears.
## Suggested order
1. **01 (admin RBAC)** — unlocks 02, and makes 09/10/11/14 independently testable via the seeded
`super_admin`/`finance` accounts.
2. **Small, contained, no dependencies:** 03, 04, 05, 06.
3. **De-mock passes, each roughly self-contained:** 08, 09+10 together, 11.
4. **Needs a product decision before coding:** 12 (medication/routine schema), 13a (all-missed edge case).
5. **14 (partner center)** — the largest single phase, mostly new server surface.
6. **15 (debug mode)** — on its own, right before any real user is let near the site.