create mvp path

This commit is contained in:
hamid
2026-08-02 20:01:31 +03:30
parent 72ab290da1
commit fb58ca54e1
203 changed files with 863 additions and 156 deletions
@@ -0,0 +1,104 @@
# booking-requests — the money-free pre-payment request
> Client seam `client/src/services/bookingRequests/` · `USE_BOOKING_REQUESTS_MOCK = false` (**real**) · 7 server ops
> Last verified: 2026-07-30 against commit `d3ec723` and swagger.v1.json (2026-07-29).
Stage one of the booking flow: the customer asks, the nurse answers, and **no money exists yet**. A
`booking_requests` row becomes a `bookings` row only on payment capture — see
[bookings.md](bookings.md) and [payment.md](payment.md).
> `Features/Booking` (singular, this domain) and `Features/Bookings` (plural, the post-payment engine) are
> **different server areas, not a rename.**
## Endpoints
| Method | Path | Caller | Verdict |
| --- | --- | --- | --- |
| POST | `/api/v1/booking_requests/create` | customer | wired |
| GET | `/api/v1/booking_requests/list` | both | wired · paginated · role-scoped |
| GET | `/api/v1/booking_requests/get/{id}` | both | wired |
| POST | `/api/v1/booking_requests/accept/{id}` | nurse | wired |
| POST | `/api/v1/booking_requests/reject/{id}` | nurse | wired |
| POST | `/api/v1/booking_requests/cancel/{id}` | customer | wired |
| GET | `/api/v1/booking_requests/checkout_summary/{id}` | customer | wired — but read by the **[payment](payment.md)** domain, not this one |
All `[Authorize]`. No phantoms.
`checkout_summary` is the C6 money read (gross / commission / VAT breakdown, REQ-016 delivered). It lives
on this controller because the request is what gets paid for, and is documented in
[payment.md](payment.md) where it is consumed.
## The lifecycle
```
pending_nurse_response ──accept──▸ accepted_awaiting_payment ──capture──▸ converted
│ │
├──reject──▸ rejected_by_nurse └──window lapses──▸ payment_deadline_expired
├──deadline──▸ expired_no_response
└──customer──▸ cancelled_by_customer
```
**Forward-only.** A backward or sideways transition is a clean `409`, never a 500. Two deadlines are
server-owned and config-driven (`booking_request_response_deadline_minutes`,
`payment_window_minutes` in [admin.md](admin.md)):
- the nurse's response window → `expired_no_response`
- the customer's payment window after acceptance → `payment_deadline_expired`
`POST /api/v1/admin_booking_requests/expire` is the ops one-shot for both; the in-process scheduler runs
it unattended. See [admin.md](admin.md).
## Shape rules the JSON does not express
- **Stage-one disclosure is deliberately partial.** Before payment the nurse sees only unencrypted
`customerNotes` and a **city/district-coarse masked address**. Encrypted care instructions and the full
address are unreadable until the booking is confirmed. This is a hard server rule, not a UI choice.
- **The countdown is server-frozen.** `CountdownTimer` renders a deadline the server sent; the client
never computes an expiry from a local clock.
- **`variantPrice` is on `BookingRequestDto`** (REQ-013, delivered) so the customer sees the price they are
committing to without a second variant fetch. It is a **digit string**.
- **The inbox list item carries `variantLabel` + `patientAge`** (REQ-014, delivered).
### The two list shapes, exactly
Confirmed field-by-field against the live swagger, because three in-repo comments disagree about this:
| | `BookingRequestListItemDto` (list) | `BookingRequestDto` (detail) |
| --- | --- | --- |
| `variantLabel` | **yes** | yes |
| `patientAge` | **yes** | — (`patientName` instead) |
| `variantPrice` · `variantPriceUnit` | **no** | yes |
| address / notes | `customerNotes` only | full masked address block |
| `nurseRejectionReason` | — | yes, **free text** |
Two consequences:
- **`client/src/services/bookingRequests/types.ts` is behind the wire.** It marks `variantLabel` as
"client-augmented … `undefined` on the real path", but the server serves it. Widening the client type is
safe and would let the real inbox card render its decision-first headline today.
- **REQ-050 is partly stale.** It states the list DTO carries neither field, "confirmed against
`services/bookingRequests/types.ts`" — i.e. confirmed against the client type, not the wire. What the
wire genuinely lacks is `variantPrice`/`variantPriceUnit` on the *list* row and the `status=answered`
group filter.
- **`requiredCaregiverGender` is never defaulted or dropped.** `any` is an explicit choice, distinct from
absent.
- The address the request references is snapshotted at create time; later edits to the saved address do
not rewrite it.
## Enums
| Vocabulary | Values |
| --- | --- |
| `BookingRequestStatus` | `pending_nurse_response` `accepted_awaiting_payment` `converted` `rejected_by_nurse` `expired_no_response` `payment_deadline_expired` `cancelled_by_customer` |
| `RequiredCaregiverGender` | `male` `female` `any` |
| `RequestRole` *(client-side list filter)* | `customer` `nurse` |
Verified identical to `Baya.Domain/Entities/Booking/BookingRequestStatus.cs`. REQ-015 confirmed these
serialise as the exact snake_case codes.
## Open REQs
| REQ | Status | Effect |
| --- | --- | --- |
| REQ-044 | open | No structured `nurseRejectionReasonCode` — the wire carries free-text `nurseRejectionReason`. The client runs a keyword heuristic over it, documented in-code as a known approximation |
| REQ-050 | open, **narrower than filed** | The list row already has `variantLabel`; it lacks `variantPrice`/`variantPriceUnit`. The `status=answered` group filter is genuinely absent, so the «پاسخ‌داده» tab fires three page-1 queries and concatenates — an unpaged workaround a nurse with many answered requests will hit |