Files
baya-monorepo/archive/build-chain/working-context/backend/handoff/after-backend-phase-5.md
T
2026-08-02 18:48:32 +03:30

6.4 KiB
Raw Blame History

After backend-phase-5 — the service catalog & nurse pricing variants are live

The two-tier service model the whole marketplace is priced and searched on now exists. Admins curate a catalog skeleton (categories → configurable option groups → option values) that ships as data, not migrations, and each nurse turns that skeleton into variants — the atomic bookable unit: a category + one chosen value per dimension, at the nurse's own price and price unit. Contract: dev/contracts/domains/catalog.md; machine schema: dev/contracts/openapi/swagger.v1.json (refreshed for b5).

What the frontend (f4-b5) can now build

  • Category grid / browseGET api/v1/catalog/categories (active, ordered, paginated, cached). Five categories are seeded (Elderly, Post-Surgery, Infant, Chronic, Companionship) with nameFa+nameEn.
  • The nurse service-builder — for a chosen category, GET api/v1/catalog/option_groups?category_id= returns the applicable dimensions (the category's own groups plus every cross-category group), each with isRequired and its active values. The nurse then POST api/v1/nurse_variants/create { serviceCategoryId, options: [{ optionGroupId, optionValueId }], price, priceUnit, sessionCount?, displayName? }. Manage with update/{id}, set_active/{id}, list, get/{id}. Requires a nurse profile first (b3 nurse_profiles/upsert).
  • Admin catalog consoleadmin_catalog/create_category|update_category/{id}|set_category_active/{id}, create_option_group|update_option_group/{id}, create_option_value|update_option_value/{id} (admin token / dynamic-permission).
  • Public variant viewGET api/v1/nurse_variants/get/{id} returns the public (active-only) projection for anyone, backing a nurse-profile offerings list.

Rules baked into the API (don't fight them client-side)

  • The bookable unit is the variant, not the nurse. Price/book against a specific variant.
  • price is a string of IRR-Rial digits (e.g. "8000000") — integer money, no floats, no Toman. The total is price + priceUnit + sessionCount; never compute it from price alone. Parse with a BigInt-safe helper, format for display, map priceUnit to an i18n label (never off the code).
  • A NULL-category option group is cross-category — it applies to every category. Render the cross- category groups in the builder for every category; the required ones must be answered.
  • All required dimensions must be answered, one value per dimension, a value must belong to its group. A missing required dimension → 400 (names it); a duplicate identical listing → 409; surface both cleanly.
  • displayName auto-generates (category + chosen value labels) — show it, let the nurse override it.
  • Deactivate, never delete. A deactivated variant stays in the nurse's own list (flagged isActive:false) but is unbookable and 404s on the public get.
  • Every catalog row has nameFa (primary) + nameEn — pick by locale, never label off a code.
  • Tenancy — variants are strictly owner-scoped; another nurse's id on write → 404 (existence not leaked). Catalog skeleton writes are admin-only.
  • Refresh after select_role still applies (nurse scoping reads the role claim in the token).
  • Routes are action-style (admin_catalog/create_category, nurse_variants/create, …), POST for mutations, camelCase bodies — see the contract for the full list.

What b7 (search & matching) must read from the variant

The variant is a clean, projection-friendly source. b7 owns the denormalized nurse_search_index and the INurseSearch seam (not built here). Its fan-out reads, per active variant: service_category_id, price, price_unit, is_active, nurse_id — joined to the nurse's nurse_service_areas (b4) to emit one index row per covered city/district, and to nurse_profiles for is_verified/accepting/gender/rating. The single write trigger points to maintain that index are CreateVariantCommand, SetVariantActiveCommand (and later option/price edits) — hook the index maintenance there.

What b8 (booking) consumes

IVariantSnapshotSerializer (Application/Contracts/Common, single real impl in Application/Common) is ready: string Serialize(VariantSnapshot) emits the canonical variant_snapshot_json (category id + labels, each (group label, value label), price as a digit string, price_unit, session_count, display_name, variant_id). b8 owns the booking_requests.variant_snapshot_json column and calls the serializer at booking time so later variant edits/deactivation never mutate past bookings. The snapshot column is not added here.

Schema / migration

Migration 20260702132758_ServiceCatalogAndNurseVariants (applies on startup), new catalog schema: ServiceCategories, ServiceOptionGroups (nullable ServiceCategoryId = cross-category), ServiceOptionValues, NurseServiceVariants (Price BIGINT, PriceUnit code, SessionCount?, DisplayName, OptionSetHash), NurseServiceVariantOptions. Constraints: UNIQUE(VariantId, OptionGroupId) (one value per dimension); filtered UNIQUE(NurseId, ServiceCategoryId, OptionSetHash) WHERE DeletedAt IS NULL (duplicate-listing backstop); soft-delete query filters. Seed: five categories (nameFa+nameEn), ids 15. Option groups/values are admin-authored data (not seeded).

Deferred to later phases (do not build against these yet)

  • nurse_search_index, INurseSearch, the search query & index fan-outb7 (variant writes are the clean trigger point; the variant shape is projection-ready).
  • variant_snapshot_json persistenceb8 (the serializer is shipped and unit-tested here).
  • nurse_availability_slots / nurse_availability_exceptionsdeferred (soft scheduling guidance, not on the money/safety path) — not built.
  • Holiday/surge pricing, a Companionship tier pricing model, tiered per-category commissiondeferred. Companionship ships only as a seeded category (data), not a special pricing path.

What's mocked

Nothing. Catalog and variant data are fully owned by Balinyaar's DB — this phase introduces no cross-cutting seam and adds no row to reports/mocks-registry.md. It reuses ICacheService (b0) for the public catalog reads with invalidate-on-mutation.