backend phase 5: service catalog & nurse pricing variants
Two-tier service model the marketplace is priced and searched on. Admin catalog skeleton (categories + EAV option groups/values, addable as data not migrations; NULL category = cross-category) and the nurse pricing layer (nurse_service_variants — the atomic bookable unit: category + one value per required dimension at the nurse's own IRR price and price unit). - New `catalog` schema via one additive migration; Price BIGINT (no floats), on the wire as a string of digits; total = price + unit + session_count. - Duplicate-listing guard: deterministic option_set_hash + filtered UNIQUE(nurse_id, service_category_id, option_set_hash) WHERE deleted_at IS NULL + friendly 409 pre-check. One value per dimension; required groups (incl. cross-category) enforced; deactivate, never delete. - Public catalog browse cached behind a CatalogCache generation token, invalidated on any admin write. IVariantSnapshotSerializer shipped for b8. - Contract (catalog.md) + handoff + report published; swagger refreshed. 122 tests green; zero new build warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,31 @@ One block per completed backend phase. Newest at the top. Backend lane writes he
|
||||
- **Notes for frontend:** <anything load-bearing>
|
||||
-->
|
||||
|
||||
## backend-phase-5 — Service catalog & nurse pricing variants — 2026-07-02
|
||||
- **Shipped:** five tables via one additive migration (`ServiceCatalogAndNurseVariants`) — new **`catalog`**
|
||||
schema `ServiceCategories` / `ServiceOptionGroups` (nullable `service_category_id` = cross-category) /
|
||||
`ServiceOptionValues` / `NurseServiceVariants` (`Price` **BIGINT IRR**, `PriceUnit`, `SessionCount?`,
|
||||
`DisplayName`, `OptionSetHash`) / `NurseServiceVariantOptions` (`UNIQUE(variant_id, option_group_id)`);
|
||||
seed of **5 categories** (`nameFa`+`nameEn`) via `HasData`. 16 CQRS slices across 3 controllers
|
||||
(`catalog` public browse, `admin_catalog` CRUD + set-active, `nurse_variants` create/update/set-active/
|
||||
list/get). Duplicate-listing guard = `OptionSetHash` + filtered `UNIQUE(nurse_id, service_category_id,
|
||||
option_set_hash) WHERE deleted_at IS NULL` + 409 pre-check. Public catalog reads cached behind a
|
||||
`CatalogCache` generation token (invalidate on any admin write). Ships **`IVariantSnapshotSerializer`**
|
||||
(pure, for b8). **No new seam.**
|
||||
- **Contracts:** dev/contracts/domains/catalog.md + openapi snapshot refreshed (yes — 14 new
|
||||
catalog/admin_catalog/nurse_variants paths; 71 total).
|
||||
- **Mocked:** none — this phase mocks nothing and adds **no** `reports/mocks-registry.md` row.
|
||||
- **Gate:** build clean (0 new code warnings) / tests green (122 pass: +10 handler/serializer unit,
|
||||
+11 `Baya.Test.Api` integration). Migration verified to apply on a real SQL Server; swagger exposes all
|
||||
b5 paths. Adversarial 4-dimension review: 0 confirmed findings.
|
||||
- **Handoff:** backend/handoff/after-backend-phase-5.md
|
||||
- **Notes for frontend:** the **variant is the bookable unit** (not the nurse). `price` is a **string of IRR
|
||||
digits**; the total is `price` + `priceUnit` + `sessionCount`, never price alone. A **NULL-category option
|
||||
group is cross-category** (render it under every category; required ones must be answered). Duplicate
|
||||
identical listing → **409**; missing required dimension → **400**. `displayName` auto-generates (editable).
|
||||
Deactivate, never delete. Routes are action-style POST (`admin_catalog/create_category`,
|
||||
`nurse_variants/create`, …); groups/values are admin-authored (only categories are seeded).
|
||||
|
||||
## backend-phase-4 — Geography, addresses & nurse service areas — 2026-07-02
|
||||
- **Shipped:** five tables via one migration (`GeographyAddressesServiceAreas`) — new **`geo`** schema
|
||||
`Provinces` 1:N `Cities` 1:N `Districts` (+ `NurseServiceAreas`) and `usr.CustomerAddresses`; seed
|
||||
|
||||
Reference in New Issue
Block a user