diff --git a/README.md b/README.md index 6119bc8..50ce2fe 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Demo password for all accounts: `demo123` - **Cross-app master:** [`docs/GISHEN-MASTER-SPEC.md`](docs/GISHEN-MASTER-SPEC.md) — Admin / Mob / Ecom / B2B surface map, shared domain objects, auth providers, divergence notes. **Refresh before `git push`** (Cursor rule + optional `.githooks/pre-push`). - **Admin backend contract:** [`docs/admin-backend-spec.md`](docs/admin-backend-spec.md) — updated whenever Admin modules are finalized. -- **Entity schemas:** [`docs/schemas.md`](docs/schemas.md) — TypeScript + field tables for main domain elements (Money, Org, Rx, Order, Item, Stock, …). +- **Entity schemas:** [`docs/schemas.md`](docs/schemas.md) — platform schema pack (shared + Admin + B2B + Ecom/backend). ```bash # One-time: enable in-repo pre-push reminder for this clone diff --git a/docs/GISHEN-MASTER-SPEC.md b/docs/GISHEN-MASTER-SPEC.md index b88b983..4018742 100644 --- a/docs/GISHEN-MASTER-SPEC.md +++ b/docs/GISHEN-MASTER-SPEC.md @@ -15,6 +15,7 @@ | Date | Change | | --- | --- | +| 2026-08-08 | **Entity schemas (platform pack):** Expanded [`docs/schemas.md`](./schemas.md) to cover shared primitives, Admin ops backend DTOs, B2B portal entities, Ecom/retail DTOs, event catalog, and path-prefix ownership. §2.9 index updated. | | 2026-08-08 | **Entity schemas:** Added shared schema summary (§2.9) + link to Admin [`docs/schemas.md`](./schemas.md) for full field tables on Money, Staff, Org/Commercial, Doctor, Rx, Order, Item/UOM, Stock, Customer, Settlement, Dispatch. | | 2026-08-08 | **Org/Money alignment:** Canonical org status `pending_activation`; Admin activate path + `CommercialTerms`/`Money`; B2B register `POST /v1/org/register` + `org.activated`/`org.suspended`. Resolved Money default (decimal major ETB). Refreshed divergence table + Sources stamp after sibling B2B/Ecom skim. | | 2026-08-07 | **Seed batch:** Created master spec from Admin living contract + sibling Mob/Ecom/B2B/Dispatch sheets. Documented product surface map, shared domain objects (auth, orgs, doctors, orders/guest, Rx dosing, catalogue multi-UOM + PDP, stock, loyalty), auth provider matrix, divergence & sync notes, deeper-doc links, Sources stamp, and pre-push update rule. Wired `.cursor/rules` + optional `.githooks/pre-push` reminder. Linked from Admin `README.md` and `admin-backend-spec.md`. | @@ -185,22 +186,22 @@ Admin helper: `src/lib/dosingSchedule.ts`. ### 2.9 Shared schema index (main elements) -Canonical **field-level schemas** live in Admin: [`docs/schemas.md`](./schemas.md). -Do not invent a third shape here — change the Admin schema pack + sibling entity docs together. +Canonical **field-level schemas** live in Admin: [`docs/schemas.md`](./schemas.md) (**platform pack** — shared + Admin + B2B + Ecom). +Do not invent a third shape here — change the schema pack + owning sibling sheet together. | Element | Canonical TypeScript (summary) | Full schema | | --- | --- | --- | -| **Money** | `{ amount: string; currency: 'ETB' }` | [schemas § Money](./schemas.md#money) | -| **CommercialTerms** | `credit_limit` / `credit_used` Money + payment terms + price list + contract dates | [§ Organisation](./schemas.md#organisation--commercialterms) | -| **OrgStatus** | `pending_activation \| active \| suspended \| closed \| rejected` | same | -| **StaffUser** | `id`, `email`, `role: StaffRole`, optional `branchId`, `preferredLocale`, `authProvider` | [§ StaffUser](./schemas.md#staffuser) | -| **Doctor** | Hospital/clinic-scoped clinician; not a `StaffRole` | [§ Doctor](./schemas.md#doctor) | -| **PrescriptionItem** | `name`, `qty`, `frequency?`, `intervalHours?`, `times?: HH:mm[]` | [§ Prescription](./schemas.md#prescription) | -| **Order** | `customerType?: registered\|guest`, `customerPhone?`, `fulfillment`, `paid`, optional `doctorId`/`orgId` | [§ Order](./schemas.md#order) | -| **MedicationItem** | Catalogue master + `uoms[]` with `conversionFactor` into `baseUnit` | [§ Item](./schemas.md#medicationitem--itemuom) | -| **StockRow** | Branch × SKU × batch qty (+ `itemId`, `erpQty`) | [§ StockRow](./schemas.md#stockrow) | -| **Customer** | Shared `customer_id`, loyalty tier/points | [§ Customer](./schemas.md#customer) | -| **DomainEvent** | `id`, `type`, `occurred_at`, `org_id?`, `actor_id?`, `payload`, `schema_version` | [§ Envelopes](./schemas.md#api-envelopes) | +| **Money** | `{ amount: string; currency: 'ETB' }` | [§1](./schemas.md#1-shared-primitives) | +| **DomainEvent** | `id`, `type`, `occurred_at`, `org_id?`, `actor_id?`, `payload`, `schema_version` | [§2](./schemas.md#2-api-envelopes--domain-events) | +| **StaffUser / Doctor / SessionUser / Customer** | Distinct principals | [§3](./schemas.md#3-principals--actors) | +| **CommercialTerms / OrgStatus** | Admin writes; B2B reads | [§4](./schemas.md#4-admin--organisations--doctors) | +| **PrescriptionItem** | `frequency?`, `times?: HH:mm[]` | [§5](./schemas.md#5-admin--prescriptions--orders) | +| **Order** | `customerType`, guest phone, doctor/org optional | [§5](./schemas.md#5-admin--prescriptions--orders) | +| **MedicationItem / StockRow** | Catalogue + branch inventory | [§6](./schemas.md#6-admin--catalogue-stock--procurement) | +| **POS / Loyalty / Campaign** | Counter + CRM growth | [§7](./schemas.md#7-admin--pos-crm-loyalty--marketing) | +| **Member / Package / Department** | B2B portal | [§9](./schemas.md#9-b2b-portal-schemas) | +| **SpendLine / Statement / EntitlementQuote** | B2B finance + Ecom checkout | [§10](./schemas.md#10-b2b-finance-migration--rx-views) · [§11](./schemas.md#11-ecom--retail-schemas) | +| **Backend path ownership** | `/admin/*` vs `/v1/*` vs retail | [§12](./schemas.md#12-backend-ownership--path-prefixes) | ```ts // Platform Money (Admin + B2B agreement) @@ -270,7 +271,7 @@ Accurate as of **2026-08-08** skim. Prefer fixing sheets over inventing a third | Doc | Purpose | | --- | --- | | [`docs/admin-backend-spec.md`](./admin-backend-spec.md) | Living Admin API/UI contract (primary depth) | -| [`docs/schemas.md`](./schemas.md) | **Entity schemas** — Money, Org, Rx, Order, Item, Stock, … | +| [`docs/schemas.md`](./schemas.md) | **Platform schema pack** — shared + Admin + B2B + Ecom/backend DTOs | | [`README.md`](../README.md) | Runbook, demo accounts, deploy | | `src/lib/dosingSchedule.ts` | Frequency → default times helpers | | `src/config/navigation.ts` | Page registry | diff --git a/docs/admin-backend-spec.md b/docs/admin-backend-spec.md index 9e06e01..d4d765e 100644 --- a/docs/admin-backend-spec.md +++ b/docs/admin-backend-spec.md @@ -14,7 +14,7 @@ | Date | Change | | --- | --- | -| 2026-08-08 | **Entity schemas pack:** [`schemas.md`](./schemas.md) for main domain elements; linked from Conventions + master | +| 2026-08-08 | **Entity schemas pack (platform):** [`schemas.md`](./schemas.md) covers shared + Admin + B2B + Ecom/backend DTOs; linked from Conventions + master | | 2026-08-08 | **B2B org alignment:** `pending_activation`, `POST .../activate` + `CommercialTerms`/`Money`, events `org.activated`/`org.suspended`; B2B register path `POST /v1/org/register` | | 2026-08-07 | Pointer to cross-app [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) + pre-push refresh rule | | 2026-08-06 | Scaffold: conventions, roles, auth, empty module sections | @@ -63,19 +63,21 @@ ### Entity schemas (setup) -Full schemas for the main domain objects live in **[`schemas.md`](./schemas.md)** (TypeScript interfaces + field tables + samples). Keep that file in sync with `src/types/index.ts`, `src/mocks/catalog.ts`, and `src/mocks/data.ts`. +Full **platform** schemas (shared primitives, Admin ops, B2B portal, Ecom/retail, events) live in **[`schemas.md`](./schemas.md)**. Keep that file in sync with Admin mocks/types, B2B `docs/backend/entities/`, and Ecom `docs/backend.md`. -| Element | Schema anchor | +| Area | Schema anchors | | --- | --- | -| Money / envelopes / events | [`schemas.md#money`](./schemas.md#money) · [envelopes](./schemas.md#api-envelopes) | -| StaffUser · Branch | [`#staffuser`](./schemas.md#staffuser) · [`#branch`](./schemas.md#branch) | -| Organisation · CommercialTerms | [`#organisation--commercialterms`](./schemas.md#organisation--commercialterms) | -| Doctor | [`#doctor`](./schemas.md#doctor) | -| Prescription · dosing lines | [`#prescription`](./schemas.md#prescription) | -| Order · guest/registered | [`#order`](./schemas.md#order) | -| MedicationItem · ItemUom | [`#medicationitem--itemuom`](./schemas.md#medicationitem--itemuom) | -| StockRow · Customer · Settlement | [`#stockrow`](./schemas.md#stockrow) · [`#customer`](./schemas.md#customer) · [`#settlement`](./schemas.md#settlement) | -| RiderTrip · MigrationJob | [`#rider--ridertrip`](./schemas.md#rider--ridertrip) · [`#migrationjob`](./schemas.md#migrationjob) | +| Shared Money / envelopes / events | [§1–2](./schemas.md#1-shared-primitives) | +| Principals (Staff, Doctor, B2B session, Customer) | [§3](./schemas.md#3-principals--actors) | +| Org · Commercial · Branch · Doctor | [§4](./schemas.md#4-admin--organisations--doctors) | +| Rx · Order | [§5](./schemas.md#5-admin--prescriptions--orders) | +| Catalogue · Stock · Procurement | [§6](./schemas.md#6-admin--catalogue-stock--procurement) | +| POS · Loyalty · Marketing · FAQ | [§7](./schemas.md#7-admin--pos-crm-loyalty--marketing) | +| Settlements · Dispatch · Admin imports · Audit | [§8](./schemas.md#8-admin--finance-dispatch-imports--audit) | +| B2B registration · Member · Package · Dept | [§9](./schemas.md#9-b2b-portal-schemas) | +| B2B Finance · tenant Migration · Rx views | [§10](./schemas.md#10-b2b-finance-migration--rx-views) | +| Ecom catalog · order create · entitlement · KYC · payments | [§11](./schemas.md#11-ecom--retail-schemas) | +| Path-prefix ownership map | [§12](./schemas.md#12-backend-ownership--path-prefixes) | #### Quick reference — Money & Org (canonical) diff --git a/docs/schemas.md b/docs/schemas.md index 0d020ce..d9b0111 100644 --- a/docs/schemas.md +++ b/docs/schemas.md @@ -1,75 +1,107 @@ -# Gishen Admin — Entity schemas +# Gishen — Platform schema pack -**Living schema pack** for the Admin backend contract and cross-app master. -**Spec sheet:** [`admin-backend-spec.md`](./admin-backend-spec.md) · **Master:** [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) +**Living schemas** for the shared pharmacy platform backend and Admin ops UI. +Deeper per-repo narrative lives in sibling sheets; **this file is the field-level / TypeScript source** for wire shapes. | Field | Value | | --- | --- | -| **Source of types** | `src/types/index.ts`, `src/mocks/catalog.ts`, `src/mocks/data.ts` | -| **Wire format** | JSON over REST (`application/json`) | -| **Timestamps** | ISO-8601 UTC (`2026-08-06T08:30:00Z`) unless noted | -| **IDs** | Prefer prefix + opaque id (`org_*`, `rx_*`, `ord_*`, `adm_*`) or UUID — pick one platform-wide | +| **Home** | `Gishen-Admin/docs/schemas.md` | +| **Admin API / modules** | [`admin-backend-spec.md`](./admin-backend-spec.md) | +| **Cross-app map** | [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) | +| **B2B entities** | `Gishen-B2B/docs/backend/entities/` | +| **Ecom API sheet** | `Gishen-Ecom/docs/backend.md` | +| **Wire format** | `application/json` (multipart for uploads) | +| **Timestamps** | ISO-8601 UTC | +| **Money** | `{ "amount": "1250.00", "currency": "ETB" }` decimal string | -When you change a field on a main element, update **this file**, the Admin sheet changelog, and the master schema summary in the same change. +**Sources:** Admin `src/types/`, `src/mocks/catalog.ts`, `src/mocks/data.ts` · B2B entity docs · Ecom backend sheet. + +**Rule:** Change field shapes here + the owning sheet (`admin-backend-spec` / B2B entity / Ecom `backend.md`) + master schema index in the same change. ## Changelog | Date | Change | | --- | --- | -| 2026-08-08 | Initial schema pack for Money, Staff, Org/Commercial, Doctor, Rx, Order, Catalogue Item/UOM, Stock, Customer, Settlement, Dispatch trip, envelopes | +| 2026-08-08 | **Platform pack:** Shared primitives; Admin ops (POS, procurement, marketing, loyalty, FAQ, stock txns); B2B portal entities; Ecom/retail DTOs; event catalog; backend ownership map | +| 2026-08-08 | Initial Admin-focused schema pack | --- -## Index +## 0. Index -| Schema | Kind | Cross-app | -| --- | --- | --- | -| [Money](#money) | Shared value object | B2B `Money` | -| [Envelopes](#api-envelopes) | List / error / event | B2B pagination preferred long-term | -| [StaffUser](#staffuser) | Admin principal | — | -| [Branch](#branch) | Location | Ecom `/branches` | -| [Organisation](#organisation--commercialterms) | B2B / hospital account | B2B `organisation.md` | -| [Doctor](#doctor) | External clinical actor | Open IdP | -| [Prescription](#prescription) | Rx queue | B2B Rx clinical withhold | -| [Order](#order) | Branch fulfilment | Ecom/Mob orders | -| [MedicationItem](#medicationitem--itemuom) | Catalogue master | Mob/Ecom PDP + catalog | -| [StockRow](#stockrow) | Branch inventory | ERP sync | -| [Customer](#customer) | CRM / loyalty | Shared `customer_id` | -| [Settlement](#settlement) | Finance batch | — | -| [RiderTrip](#rider--ridertrip) | Dispatch | Dispatch Bot | -| [MigrationJob](#migrationjob) | Admin imports | Distinct from B2B tenant migration | +### Shared (platform) + +| Schema | Section | +| --- | --- | +| Primitives (Money, Phone, Locale, IDs) | [§1](#1-shared-primitives) | +| API envelopes + DomainEvent | [§2](#2-api-envelopes--domain-events) | +| Principal actors (Staff / Doctor / B2B session / Customer) | [§3](#3-principals--actors) | + +### Admin / ops backend + +| Schema | Section | +| --- | --- | +| Branch · Organisation · CommercialTerms · Doctor | [§4](#4-admin--organisations--doctors) | +| Prescription · Order | [§5](#5-admin--prescriptions--orders) | +| MedicationItem · Stock · Procurement | [§6](#6-admin--catalogue-stock--procurement) | +| POS · Customer · Loyalty · Marketing · FAQ | [§7](#7-admin--pos-crm-loyalty--marketing) | +| Settlement · Dispatch · Migration (platform) · Audit | [§8](#8-admin--finance-dispatch-imports--audit) | + +### B2B portal backend + +| Schema | Section | +| --- | --- | +| SessionUser · Org registration · Department · Member · Package | [§9](#9-b2b-portal-schemas) | +| B2B Finance · B2B Migration · B2B Prescription view | [§10](#10-b2b-finance-migration--rx-views) | + +### Ecom / retail backend + +| Schema | Section | +| --- | --- | +| Catalog product · Cart/Order create · Entitlement · Payments · Identity | [§11](#11-ecom--retail-schemas) | + +### Ownership + +| Map | [§12](#12-backend-ownership--path-prefixes) | --- -## Money +## 1. Shared primitives ```ts +/** Canonical money — Admin API + B2B agree. Prefer over raw number ETB on the wire. */ interface Money { - amount: string // decimal major units, e.g. "1250.00" + amount: string // "1250.00" major units currency: 'ETB' } + +type Timestamp = string // ISO-8601 UTC +type DateOnly = string // YYYY-MM-DD +type Phone = string // E.164 e.g. +251911234567 +type Locale = 'en' | 'am' + +/** Opaque ids — prefer prefix for logs (B2B style); Admin demos may use short ids */ +// org_* mbr_* pkg_* dept_* rx_* usr_* adm_* ord_* cus_* mig_* evt_* ``` -| Field | Type | Required | Notes | -| --- | --- | --- | --- | -| `amount` | `string` | ✓ | Decimal string — never float JSON numbers for money | -| `currency` | `"ETB"` | ✓ | Only ETB in v1 | +| Type | Format | Notes | +| --- | --- | --- | +| `Money` | `{ amount, currency: "ETB" }` | Never JSON floats for money | +| `Phone` | E.164 | Required for guests | +| `Locale` | `en` \| `am` | Staff `preferredLocale` / B2B `locale` | -Admin UI may keep parallel numeric major-ETB fields (`creditLimitEtb`, `totalEtb`) for demos — **API wiring uses `Money`** (or syncs numbers from `commercial`). +Admin UI demos may keep `creditLimitEtb` / `totalEtb` numbers — **sync from `Money` / `commercial` when wiring the API**. --- -## API envelopes +## 2. API envelopes & domain events -### Success (single) +### Envelopes ```ts type DataEnvelope = { data: T } -``` -### Success (list) — target platform - -```ts +/** Target platform (B2B). Prefer when consolidating. */ type ListEnvelope = { data: T[] pagination: { @@ -79,13 +111,13 @@ type ListEnvelope = { total_pages: number } } -``` -Legacy Admin sketches: `?page=&limit=` → `{ data, meta: { page, limit, total } }`. Prefer `page_size` / `pagination` when consolidating with B2B. +/** Legacy Admin sketches — migrate to ListEnvelope */ +type LegacyListEnvelope = { + data: T[] + meta: { page: number; limit: number; total: number } +} -### Error - -```ts type ErrorEnvelope = { error: { code: string @@ -96,33 +128,44 @@ type ErrorEnvelope = { } ``` -### Domain event +### Domain event envelope ```ts type DomainEvent> = { - id: string - type: string // e.g. "org.activated", "prescription.review_updated" - occurred_at: string // ISO timestamp + id: string // evt_* + type: string + occurred_at: Timestamp org_id?: string | null actor_id?: string | null payload: T - schema_version: string // "1" + schema_version: string // "1" } ``` +### Event catalog (backend bus) + +| Event | Emitter | Consumers | Payload (key fields) | +| --- | --- | --- | --- | +| `org.registration_submitted` | B2B | Admin | `registration_id`, `org_id`, company, super_user email | +| `org.registration_requested` | B2B | Admin CRM | lead contact + channel | +| `org.activated` | Admin | B2B, Ecom | `org_id`, `commercial`, `activated_by`, `activated_at` | +| `org.suspended` | Admin | B2B | `org_id`, reason? | +| `prescription.submitted` | B2B / Ecom | Admin | `prescription_id`, `member_id?`, `customer_id`, `page_count` (**no** images) | +| `prescription.review_updated` | Admin | B2B, Ecom, Mob | `prescription_id`, `status`, `days_supply?`, `query_message?` | +| `migration.job_*` | B2B | Admin audit | `job_id`, dataset, commit_result | +| `order.completed` | Platform / Ecom | B2B finance, loyalty | `order_id`, `customer_id`, amounts | +| `trip.assigned` / `trip.status_updated` | Admin dispatch | Bot, tracking | `trip_id`, `riderId`, status | + --- -## StaffUser +## 3. Principals & actors + +### StaffUser (Admin pharmacy) ```ts type StaffRole = - | 'pharmacist' - | 'stock_manager' - | 'procurement' - | 'finance' - | 'marketing_manager' - | 'operations' - | 'super_admin' + | 'pharmacist' | 'stock_manager' | 'procurement' | 'finance' + | 'marketing_manager' | 'operations' | 'super_admin' type AuthProvider = 'email' | 'google' | 'phone' | 'telegram' @@ -133,47 +176,83 @@ interface StaffUser { role: StaffRole branchId?: string branchName?: string - preferredLocale?: 'en' | 'am' + preferredLocale?: Locale avatarUrl?: string authProvider?: AuthProvider phone?: string } ``` -| Field | Type | Required | Notes | -| --- | --- | --- | --- | -| `id` | `string` | ✓ | Staff principal id | -| `name` | `string` | ✓ | Display name | -| `email` | `string` | ✓ | Login / Google subject email | -| `role` | `StaffRole` | ✓ | Single primary role in Admin SPA | -| `branchId` | `string` | | Required for pharmacist (and optionally stock) | -| `preferredLocale` | `"en" \| "am"` | | UI language | -| `authProvider` | `AuthProvider` | | How this session was established | +Login response: `{ accessToken, refreshToken, user: StaffUser }`. -**Not** a StaffRole: Doctor, B2B `SUPER_USER` / `HR_ADMIN` / `FINANCE` / `MEMBER`, retail `customer`. +### Doctor (external clinical — not StaffRole) -### Login response sketch +```ts +interface Doctor { + id: string + name: string + specialty: string + licenseNumber: string + phone: string + email: string + status: 'active' | 'inactive' + orgId: string // hospital | clinic Organisation + orgName: string + branchId?: string + branchName?: string + hasLinkedAccount: boolean + createdAt: Timestamp + updatedAt: Timestamp + avatarUrl?: string +} +``` -```json -{ - "accessToken": "...", - "refreshToken": "...", - "user": { - "id": "adm_01H...", - "name": "Hana Pharmacist", - "email": "pharmacist@gishen.et", - "role": "pharmacist", - "branchId": "br-bole", - "branchName": "Bole Branch", - "preferredLocale": "en", - "authProvider": "email" - } +### B2B SessionUser + +```ts +type PortalRole = 'SUPER_USER' | 'HR_ADMIN' | 'FINANCE' | 'MEMBER' + +interface SessionUser { + id: string // usr_* + email: string + full_name: string + phone?: Phone + org_id: string + member_id?: string + customer_id?: string + roles: PortalRole[] + locale: Locale + avatar_url?: string + org_status: OrgStatus + permissions: string[] +} +``` + +Deep fields: B2B [`session-user.md`](../../Gishen-B2B/docs/backend/entities/session-user.md). + +### Retail Customer (CRM / loyalty) + +```ts +interface Customer { + id: string // cus_* / c* demos — shared customer_id + name: string + phone: Phone + tier: string + points: number + branch: string + joinedAt: Timestamp + orders: number + spendEtb: number // demo; prefer Money on API + lastOrderAt: Timestamp | null + avatarUrl?: string } ``` --- -## Branch +## 4. Admin — organisations & doctors + +### Branch ```ts interface Branch { @@ -187,12 +266,11 @@ interface Branch { } ``` ---- - -## Organisation & CommercialTerms +### Organisation & CommercialTerms ```ts type OrganisationType = 'corporate' | 'hospital' | 'clinic' | 'ngo' | 'other' +/** + rejected for denied Admin queue entries */ type OrgStatus = 'pending_activation' | 'active' | 'suspended' | 'closed' | 'rejected' interface CommercialTerms { @@ -200,10 +278,10 @@ interface CommercialTerms { credit_used: Money payment_terms_days: number price_list_id?: string - contract_start?: string // ISO date YYYY-MM-DD - contract_end?: string - activated_at?: string - activated_by?: string // admin user id + contract_start?: DateOnly + contract_end?: DateOnly + activated_at?: Timestamp + activated_by?: string } interface Organisation { @@ -216,90 +294,46 @@ interface Organisation { commercialRegistration?: string status: OrgStatus commercial: CommercialTerms | null - /** Demo UI only — keep in sync with commercial when set */ - creditLimitEtb: number + creditLimitEtb: number // demo mirror usedEtb: number billingContact: string source: 'admin' | 'self_register' - createdAt: string + createdAt: Timestamp reviewedBy?: string - reviewedAt?: string + reviewedAt?: Timestamp decisionNote?: string } -``` -| Field | Type | Required | Notes | -| --- | --- | --- | --- | -| `orgType` | `OrganisationType` | ✓ | `hospital` / `clinic` unlock Doctors roster | -| `status` | `OrgStatus` | ✓ | Canonical lifecycle (not bare `pending`) | -| `commercial` | `CommercialTerms \| null` | ✓ | `null` until activate / admin-create | -| `tin` | `string` | ✓ | 10-digit Ethiopian TIN | -| `source` | `admin \| self_register` | ✓ | Who created the org | - -### Activate request body - -```ts type OrgActivateBody = { - commercial: { + commercial: Omit & { credit_limit: Money - payment_terms_days: number - price_list_id?: string - contract_start?: string - contract_end?: string } admin_notes?: string } ``` -Emits `org.activated`. Sample commercial payload matches B2B `organisation.md`. +Align with B2B [`organisation.md`](../../Gishen-B2B/docs/backend/entities/organisation.md). Admin adds `orgType`, KYC ids, `source`, `rejected`. + +Endpoints: `POST /admin/organisations`, `.../activate`, `.../suspend`, `.../commercial` — see Admin sheet § Finance & organisations. --- -## Doctor +## 5. Admin — prescriptions & orders -```ts -interface Doctor { - id: string - name: string - specialty: string - licenseNumber: string - phone: string - email: string - status: 'active' | 'inactive' - orgId: string - orgName: string - branchId?: string - branchName?: string - hasLinkedAccount: boolean - createdAt: string - updatedAt: string - avatarUrl?: string -} -``` - -Affiliation org **must** be `orgType` `hospital` or `clinic`. Auth: `POST /auth/doctor/login` (separate JWT / `actor=doctor`). - ---- - -## Prescription +### Prescription ```ts type DosingFrequency = 'QD' | 'BID' | 'TID' | 'QID' | 'QXH' | 'custom' type PrescriptionStatus = - | 'draft' // B2B/Ecom may create drafts - | 'submitted' - | 'under_review' - | 'approved' - | 'queried' - | 'rejected' + | 'draft' | 'submitted' | 'under_review' | 'approved' | 'queried' | 'rejected' interface PrescriptionItem { name: string qty: number controlled?: boolean frequency?: DosingFrequency - intervalHours?: number // when frequency === 'QXH' - times?: string[] // HH:mm 24h — Mob reminder seed + intervalHours?: number + times?: string[] // HH:mm 24h sku?: string dosage?: string instructions?: string @@ -309,39 +343,27 @@ interface Prescription { id: string customerName: string branchId: string - status: Exclude | PrescriptionStatus + status: PrescriptionStatus items: PrescriptionItem[] - submittedAt: string + submittedAt: Timestamp prescriber?: string doctorId?: string orgId?: string - customerId?: string // shared platform identity when known - memberId?: string // B2B member when from institutional portal - reviewStartedAt?: string + customerId?: string + memberId?: string + reviewStartedAt?: Timestamp reviewedBy?: string - reviewedAt?: string + reviewedAt?: Timestamp decisionNote?: string query_message?: string rejection_reason?: string days_supply?: number - refill_due_at?: string + refill_due_at?: DateOnly } -``` -| Rule | Notes | -| --- | --- | -| Clinical withhold | HR/Finance B2B never receive `items`, images, diagnosis | -| Verify | `POST /admin/prescriptions/:id/verify` → `prescription.review_updated` | -| Dosing helper | `src/lib/dosingSchedule.ts` | - -### Verify body - -```ts type PrescriptionVerifyBody = { status: 'approved' | 'queried' | 'rejected' medicine_lines?: PrescriptionItem[] - qtyAdjustments?: unknown - substitute?: unknown notes?: string query_message?: string rejection_reason?: string @@ -349,25 +371,17 @@ type PrescriptionVerifyBody = { } ``` ---- +B2B member upload shape (pages, clinical withhold): [`prescription.md`](../../Gishen-B2B/docs/backend/entities/prescription.md). -## Order +### Order ```ts type OrderCustomerType = 'registered' | 'guest' type OrderFulfillment = 'delivery' | 'pickup' -/** Suggested shared status pipeline (align with Ecom) */ type OrderStatus = - | 'draft' - | 'requested' - | 'pending' // demo alias - | 'pending_approval' - | 'confirmed' - | 'picking' - | 'out_for_delivery' - | 'ready_for_pickup' - | 'completed' - | 'cancelled' + | 'draft' | 'requested' | 'pending' | 'pending_approval' + | 'confirmed' | 'picking' | 'out_for_delivery' | 'ready_for_pickup' + | 'completed' | 'cancelled' interface OrderLine { sku: string @@ -385,13 +399,13 @@ interface Order { notes?: string branchId: string fulfillment: OrderFulfillment - status: string // OrderStatus in production - totalEtb: number // demo major ETB; prefer Money later - channel: string // web | telegram | mobile | b2b | pos | doctor | … + status: string + totalEtb: number + channel: string // web | mobile | telegram | b2b | pos | doctor | … assignedRiderId?: string zone?: string address?: string - createdAt: string + createdAt: Timestamp paid: boolean rxApproved?: boolean doctorId?: string @@ -400,32 +414,21 @@ interface Order { } ``` -| Rule | Notes | -| --- | --- | -| Guest | Requires `customerPhone`; no `customerId`; loyalty gated | -| Registered | Prefer `customerId` + shared platform identity | -| Doctor-authored | Set `doctorId` + hospital `orgId` | - --- -## MedicationItem & ItemUom +## 6. Admin — catalogue, stock & procurement -Catalogue master (`/admin/catalog/items`, UI `/items`). Retail reads as `/catalog/products`. +### MedicationItem & ItemUom ```ts interface ItemUom { uom: string - conversionFactor: number // how many base/stock units = 1 of this UOM + conversionFactor: number // base units per 1 of this UOM isStockUom?: boolean isPurchaseUom?: boolean isSalesUom?: boolean } -interface ActiveIngredient { - name: string - strength: string -} - interface MedicationItem { id: string sku: string @@ -435,35 +438,30 @@ interface MedicationItem { countryOfOrigin: string efdaRegistrationNumber: string barcodes: { value: string; label?: string }[] - productType: string therapeuticClass: string - ingredients: ActiveIngredient[] + ingredients: { name: string; strength: string }[] dosageForm: string routeOfAdministration?: string controlledSchedule?: string - packSize: string baseUnit: string sellByUnit: string packUnit: string uoms: ItemUom[] parentItemId?: string - costPriceEtb: number sellingPriceEtb: number currency: 'ETB' - sellingPriceOwner: 'platform' | 'erp' | string + sellingPriceOwner: string vatApplicable: boolean discountEligible: boolean b2bPriceListId?: string - prescriptionRequired: boolean efdaStatus: string controlledSubstance: boolean advertisingRestricted: boolean ageRestriction: string - shortDescription: string dosageGuidance: string sideEffects: string @@ -478,37 +476,30 @@ interface MedicationItem { storefrontUnit?: string compareAtPriceEtb?: number alternativeItemIds: string[] - defaultReorderPoint: number defaultExpiryAlertDays: number - images: string[] thumbnailUrl?: string badges: string[] seoSlug: string metaTitle: string metaDescription: string - - ownership: Record // FieldGroupOwnership - lastErpSyncAt?: string + ownership: Record + lastErpSyncAt?: Timestamp erpSyncError?: string | null - b2bEligible: boolean loyaltyEligible: boolean - createdBy: string - createdAt: string + createdAt: Timestamp updatedBy: string - updatedAt: string + updatedAt: Timestamp status: 'active' | 'draft' | 'archived' } ``` -Stock qty is always in `baseUnit`. Use `toStockQty` / `fromStockQty` for sales/purchase UOM conversions. +API: `/admin/catalog/items` · UI `/items`. Retail maps to Ecom `/catalog/products`. ---- - -## StockRow +### StockRow & stock movements ```ts interface StockRow { @@ -516,57 +507,220 @@ interface StockRow { name: string category: string branchId: string - qty: number // baseUnit + qty: number // baseUnit batch: string - expiry: string // YYYY-MM + expiry: string // YYYY-MM erpQty: number unitEtb: number - itemId?: string // → MedicationItem.id + itemId?: string reserved?: number } -``` ---- +type StockTxnType = 'receipt' | 'delivery' | 'transfer' | 'adjustment' +type StockTxnStatus = 'draft' | 'submitted' | 'cancelled' -## Customer +interface StockTransaction { + id: string + type: StockTxnType + status: StockTxnStatus + sku: string + branchId: string + qty: number + note?: string + createdAt: Timestamp + createdBy?: string +} -```ts -interface Customer { - id: string // platform customer_id - name: string - phone: string - tier: string - points: number - branch: string - joinedAt: string - orders: number - spendEtb: number - lastOrderAt: string | null - avatarUrl?: string +interface StockMovement { + id: string + at: Timestamp + type: string // sale | receive | adjust | reserve | … + qty: number + note: string } ``` -Same `id` links B2B `member.customer_id` and retail sessions when enrolled. +Inventory receive body (Admin sheet): `{ itemId, branchId, batch, expiresAt, qty, uom?, reorderPoint? }`. + +### ProcurementRequest + +```ts +interface ProcurementRequest { + id: string + sku: string + name: string + qty: number + branchId: string + status: 'open' | 'ordered' | 'received' + priority: 'low' | 'medium' | 'high' + requestedBy: string + notes?: string + createdAt: Timestamp +} +``` --- -## Settlement +## 7. Admin — POS, CRM, loyalty & marketing + +### POS + +```ts +type PosResolveBody = { code: string } // gishen-customer:{id} | phone | bare id + +interface PosPurchaseBody { + customerId: string + branchId: string + lines: { sku: string; qty: number }[] + channel?: 'pos' +} + +interface PurchaseTicketLine { + sku: string + name: string + qty: number + unitEtb: number +} + +interface PurchaseTicket { + id: string + source: string + status: 'pending' | 'accepted' | 'rejected' + customerHint?: string + lines: PurchaseTicketLine[] + createdAt: Timestamp +} + +interface CounterScan { + id: string + at: Timestamp + source: 'camera' | 'manual' | 'simulated' | 'ticket' + raw: string + customerId?: string +} + +interface InvoiceAssociateBody { + lines: unknown[] + association: { type: 'procurement' | 'supplier' | 'branch'; id: string } + note?: string +} +``` + +### Loyalty + +```ts +interface LoyaltyLedgerEntry { + id: string + customerId: string + customerName: string + activity: string + points: number // signed: earn +, redeem − + orderId?: string + branch: string + at: Timestamp +} + +interface LoyaltyActivity { + id: string + code: string + label: string + points: number + active: boolean + description?: string +} + +interface LoyaltyConfig { + earnRules: LoyaltyActivity[] + tiers: { name: string; thresholdPoints: number; multiplier: number }[] + referralBonusPoints?: number +} +``` + +### Marketing + +```ts +type MarketingChannel = 'SMS' | 'Telegram' | 'Push' | 'Email' + +interface MarketingSegment { + id: string + name: string + criteria: string + size: number + channel: MarketingChannel + status: 'active' | 'paused' | 'draft' + engagementRate: number + lastUsedAt: Timestamp | null + owner: string + createdAt: Timestamp + refresh: 'live' | 'daily' | 'manual' +} + +interface Campaign { + id: string + name: string + segmentId: string + channel: MarketingChannel + status: string + scheduledAt?: Timestamp + sentAt?: Timestamp + body_en?: string + body_am?: string +} +``` + +### FAQ + +```ts +interface FaqArticle { + id: string + category: string + title_en: string + title_am: string + body_en: string + body_am: string + published: boolean + updatedAt: Timestamp + views: number +} +``` + +--- + +## 8. Admin — finance, dispatch, imports & audit + +### Settlement & gateways ```ts interface Settlement { id: string - channel: string // Chapa | Telebirr | COD | M-Pesa | … - date: string // YYYY-MM-DD + channel: string + date: DateOnly volumeEtb: number status: 'reconciled' | 'pending' | 'matched' | 'unmatched' | 'refunded' | string txnCount: number feesEtb: number } + +interface PaymentGatewayConfig { + id: string // chapa | arifpay | telebirr | mpesa + label: string + enabled: boolean +} ``` ---- +### Org invoice (Admin view → B2B statements) -## Rider & RiderTrip +```ts +interface OrgInvoice { + id: string + period: string + amountEtb: number + status: string + pdf_url?: string // required for B2B Finance download +} +``` + +### Rider & RiderTrip ```ts interface Rider { @@ -584,27 +738,25 @@ interface RiderTrip { orderId: string customerName: string zone: string - assignedAt: string - completedAt: string | null + assignedAt: Timestamp + completedAt: Timestamp | null status: 'delivered' | 'failed' | 'en_route' | 'returned' minutes: number onTime: boolean codEtb: number } + +type TripAssignBody = { riderId: string } ``` -Dispatch board assigns riders; Telegram bot consumes `trip.assigned` / status updates. +### Platform MigrationJob (Admin imports) ---- - -## MigrationJob - -Admin **platform** import (stock, catalog, riders…) — not the same as B2B HR `MigrationJob` tenant imports. +Distinct from [B2B tenant MigrationJob](#10-b2b-finance-migration--rx-views). ```ts -interface MigrationJob { +interface AdminMigrationJob { id: string - templateId: string // stock | catalog | hr | branches | riders | generic + templateId: 'stock' | 'catalog' | 'hr' | 'branches' | 'riders' | 'generic' | string templateLabel: string filename: string rowsTotal: number @@ -612,76 +764,465 @@ interface MigrationJob { rowsFailed: number status: 'completed' | 'partial' | 'failed' | 'running' runBy: string - runAt: string + runAt: Timestamp durationSec: number mapping: Record } ``` ---- +### AuditEvent -## Sample: Organisation (active) - -```json -{ - "id": "org-1", - "name": "Horizon Bank", - "orgType": "corporate", - "tin": "0001234567", - "vatNumber": "VAT-ET-0001234567", - "businessLicense": "BL-AA/48291/2014", - "commercialRegistration": "CR/015842/2014", - "status": "active", - "commercial": { - "credit_limit": { "amount": "500000.00", "currency": "ETB" }, - "credit_used": { "amount": "124000.00", "currency": "ETB" }, - "payment_terms_days": 30, - "price_list_id": "pl_corporate_2026", - "contract_start": "2026-01-01", - "contract_end": "2026-12-31", - "activated_at": "2026-05-02T09:40:00Z", - "activated_by": "adm_finance" - }, - "creditLimitEtb": 500000, - "usedEtb": 124000, - "billingContact": "finance@horizon.et", - "source": "admin", - "createdAt": "2026-05-01T00:00:00Z", - "reviewedBy": "Yonas Finance", - "reviewedAt": "2026-05-02T09:40:00Z" +```ts +interface AuditEvent { + id: string + at: Timestamp + actor: string + actorRole?: StaffRole + action: string + target?: string + category?: string + before?: Record + after?: Record } ``` -## Sample: Prescription (under review) +--- + +## 9. B2B portal schemas + +Canonical narrative + samples: `Gishen-B2B/docs/backend/entities/`. Prefixes: `org_` `dept_` `mbr_` `pkg_` `mig_` `rx_` `usr_` `oreg_` `orreq_`. + +### Org registration + +```ts +type RegistrationStatus = 'submitted' | 'under_review' | 'activated' | 'rejected' +type RequestChannel = 'email' | 'phone' +type RequestStatus = 'new' | 'contacted' | 'converted' | 'closed' + +interface OrgRegistration { + id: string // oreg_* + org_id: string + submitted_at: Timestamp + company: { + legal_name: string + tin: string + billing_contact: { name: string; email: string; phone: Phone } + approx_headcount?: number + } + super_user: { + full_name: string + email: string + phone: Phone + // password only on write — never returned + } + status: RegistrationStatus + admin_notes?: string // Admin-only +} + +interface OrgRegistrationRequest { + id: string // orreq_* + contact_name: string + company_name?: string + channel: RequestChannel + email?: string + phone?: Phone + message?: string + status: RequestStatus + created_at: Timestamp +} +``` + +Endpoints: `POST /v1/org/register`, `POST /v1/org/register/request`. + +### Department + +```ts +interface Department { + id: string // dept_* + org_id: string + name: string + code?: string + parent_id?: string | null + sub_limit?: Money | null + sub_limit_used?: Money + member_count?: number + is_active: boolean + created_at: Timestamp + updated_at: Timestamp + version: number +} +``` + +### Member + +```ts +type MemberStatus = 'invited' | 'active' | 'inactive' | 'offboarded' +type MemberType = 'primary' | 'dependant' + +interface AllowanceSummary { + period_start: DateOnly + period_end: DateOnly + allowance_total: Money + allowance_used: Money + allowance_remaining: Money +} + +interface Member { + id: string // mbr_* + org_id: string + customer_id?: string + employee_id?: string + full_name: string + email?: string + phone: Phone + department_id?: string + package_id?: string + role: PortalRole // portal access display + member_type: MemberType + primary_member_id?: string | null + status: MemberStatus + start_date?: DateOnly + end_date?: DateOnly | null + overrides?: { + allowance_cap?: Money | null + copay_percent?: number | null + excluded_categories?: string[] + included_perks?: string[] + } + verification_id?: string + invite_id?: string + allowance_summary?: AllowanceSummary + prescription_count?: number + created_at: Timestamp + updated_at: Timestamp + version: number +} +``` + +### Package + +```ts +type PackageStatus = 'draft' | 'active' | 'archived' + +interface BenefitPackage { + id: string // pkg_* + org_id: string + name: string + code: string + description?: string + status: PackageStatus + allowance: { + amount: Money + period: 'monthly' | 'quarterly' | 'annual' + rollover: boolean + } + copay_percent: number + categories: { category_code: string; coverage_percent: number; max_per_order?: Money | null }[] + perks?: { code: string; label: string; description?: string }[] + caps?: { + max_order_amount?: Money + max_orders_per_month?: number + max_rx_fills_per_month?: number + } + exclusions?: { type: 'sku' | 'category'; ref: string; reason?: string }[] + member_count?: number + created_at: Timestamp + updated_at: Timestamp + version: number +} +``` + +Pending orgs: packages may be `draft` only until `org.activated` (default). + +--- + +## 10. B2B finance, migration & Rx views + +### Finance + +```ts +type ApprovalStatus = 'none' | 'flagged' | 'approved' | 'rejected' + +interface SpendLine { + id: string // spd_* + org_id: string + member_id: string + order_id: string + occurred_at: Timestamp + amount_total: Money + amount_org_covered: Money + amount_member_paid: Money + category_code: string // NOT medicine name for HR/Finance + department_id?: string + description: string // redacted + approval_status: ApprovalStatus +} + +interface Statement { + id: string // stmt_* + org_id: string + period: string // e.g. 2026-02 + opening_balance: Money + charges: Money + payments: Money + closing_balance: Money + credit_limit: Money + pdf_url: string + issued_at: Timestamp +} + +interface CreditUsage { + credit_limit: Money + credit_used: Money + credit_available: Money + utilization_percent: number + period_end: DateOnly +} + +interface ApprovalFlag { + id: string + org_id: string + spend_line_id: string + member_id: string + reason: 'threshold_exceeded' | 'unusual_category' | 'first_time_high_value' | 'manual_flag' + threshold_rule?: string + amount: Money + status: ApprovalStatus + reviewed_by?: string + reviewed_at?: Timestamp + notes?: string +} +``` + +Deep: B2B [`finance.md`](../../Gishen-B2B/docs/backend/entities/finance.md). + +### B2B MigrationJob (tenant) + +```ts +type MigrationDatasetType = + | 'departments' | 'packages' | 'members' | 'overrides' + | 'dependants' | 'verification_ids' | 'full_onboarding_pack' + +type B2BMigrationJobStatus = + | 'uploaded' | 'mapping' | 'validating' | 'validated' | 'preview_ready' + | 'committing' | 'completed' | 'failed' | 'rolled_back' + +interface B2BMigrationJob { + id: string // mig_* + org_id: string + created_by: string + dataset_type: MigrationDatasetType + status: B2BMigrationJobStatus + file_name: string + file_storage_key?: string + column_mapping?: Record + mapping_profile_id?: string + validation?: { total_rows: number; error_count: number; warning_count: number } + commit_result?: { created: number; updated: number; skipped: number; failed: number } + error_report_url?: string + rollback_until?: Timestamp + started_at?: Timestamp + completed_at?: Timestamp + created_at: Timestamp + updated_at: Timestamp +} +``` + +### B2B Prescription (member portal) + +Same status enum as Admin. Extra clinical fields: + +```ts +interface B2BPrescriptionPage { + id: string + page_number: number + url: string // signed — clinical withhold + mime_type: string + uploaded_at: Timestamp +} + +// HR/Finance: only status + dates (+ prescription_count on Member) +// MEMBER / SUPER_USER: pages, medicine_lines, notes +``` + +--- + +## 11. Ecom / retail schemas + +From `Gishen-Ecom/docs/backend.md`. Align Money to platform `Money` when implementing. + +### Catalog (retail read model) + +```ts +interface CatalogProduct { + id: string + slug: string + name: string + sku?: string + category?: string + badge?: string + // Mapped from MedicationItem storefront / PDP fields when platform catalog is live + storefrontUnit?: string + useCase?: string + compareAtPriceEtb?: number + sellingPriceEtb?: number +} + +interface BranchStockAvailability { + productId: string + sku: string + branches: { + branchId: string + name: string + qty: number + distanceKm?: number + hours?: string + }[] +} +``` + +### Order create (B2C) + +```ts +interface EcomOrderCreateBody { + fulfillment: 'delivery' | 'pickup' + branchId?: string + location?: { label: string; lat: number | null; lng: number | null } + items: { productId: string; qty: number }[] + prescriptionFileKey?: string | null + notes?: string + paymentPreference?: 'cod' | 'telebirr' | 'bank' + referralCode?: string | null + credit?: { + organisationId: string | null + coveredAmount: number // migrate → Money + selfPayAmount: number + planId: string | null + } + splitHint?: 'auto' | 'single_branch' | 'allow_split' +} +``` + +### Entitlement / checkout quote + +```ts +/** POST /checkout/credit/quote or POST /v1/checkout/entitlement-preview */ +interface EntitlementQuote { + basket_total: Money + org_covered: Money + member_pays: Money + organisation_id?: string + plan_id?: string + category_blocks?: string[] + requires_approval?: boolean +} +``` + +### Identity document (KYC) + +```ts +type IdentityDocType = 'national_id' | 'passport' +type IdentityStatus = 'pending' | 'verified' | 'rejected' + +interface IdentityDocument { + id: string + customer_id: string + doc_type: IdentityDocType + doc_number: string + issuing_country: string + file_key?: string + status: IdentityStatus + reviewed_at?: Timestamp + notes?: string +} +``` + +Admin review: `GET /admin/identity/queue`, `POST /admin/identity/:id/review`. + +### Payments + +```ts +interface PaymentIntent { + id: string + order_id: string + provider: 'telebirr' | 'chapa' | 'bank' | string + amount: Money + status: string + checkout_url?: string +} +``` + +### Telegram Mini App + +```ts +// POST /telegram/auth — validate initData → session +// GET /telegram/bootstrap — catalog lite + referral + community directory +``` + +--- + +## 12. Backend ownership & path prefixes + +| Surface | Typical prefix | Owns schemas | +| --- | --- | --- | +| Shared platform API | `/v1` or `/` (TBD host) | This pack | +| Admin staff | `/auth/staff/*`, `/admin/*`, `/pharmacy/*`, `/dispatch/*`, `/pos/*` | §3–8 + Admin sheet | +| Doctor | `/auth/doctor/*` | Doctor + Rx/Order create | +| B2B portal | `/v1/org/*`, `/v1/members/*`, `/v1/finance/*`, `/v1/prescriptions/*` | §9–10 + B2B entities | +| Ecom / Mob retail | `/catalog/*`, `/orders`, `/loyalty/*`, `/prescriptions`, `/payments/*` | §11 + Ecom sheet | +| Integrations | `/integrations/erp/*`, `/dispatch/bot/webhook` | Stock sync, rider bot | + +### Clinical withhold (backend rule) + +| Audience | Prescription / clinical | +| --- | --- | +| Admin pharmacist | Full | +| B2B MEMBER (own), SUPER_USER | Full | +| B2B HR_ADMIN / FINANCE | Status/dates/aggregates only — never pages, medicine lines, diagnosis | +| Event bus `prescription.submitted` | No image URLs or clinical content | + +--- + +## Samples + +### Organisation activated (event payload) ```json { - "id": "rx-1001", - "customerName": "Abebe Kebede", - "branchId": "br-bole", - "status": "under_review", - "items": [ - { - "name": "Amoxicillin 500mg", - "qty": 21, - "frequency": "TID", - "times": ["08:00", "14:00", "20:00"] + "type": "org.activated", + "org_id": "org_01HNEW", + "payload": { + "status": "active", + "commercial": { + "credit_limit": { "amount": "2000000.00", "currency": "ETB" }, + "price_list_id": "pl_corporate_2026", + "contract_start": "2026-04-01", + "contract_end": "2027-03-31" } - ], - "submittedAt": "2026-08-06T08:10:00Z", - "prescriber": "Dr. Selam", - "doctorId": "doc-1", - "orgId": "org-3" + } +} +``` + +### Entitlement split (checkout) + +```json +{ + "basket_total": { "amount": "1200.00", "currency": "ETB" }, + "org_covered": { "amount": "900.00", "currency": "ETB" }, + "member_pays": { "amount": "300.00", "currency": "ETB" } } ``` --- -## Related +## Related docs | Doc | Role | | --- | --- | -| [`admin-backend-spec.md`](./admin-backend-spec.md) | Endpoints + modules using these schemas | -| [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) | Cross-app map + schema summary | -| B2B `docs/backend/entities/` | Portal-facing entities (clinical withhold, members, packages) | -| Ecom `docs/backend.md` | Retail order/catalog contract | +| [`admin-backend-spec.md`](./admin-backend-spec.md) | Admin endpoints / modules | +| [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) | Surfaces, divergences, schema index | +| B2B `docs/backend/OVERVIEW.md` | Tenancy, RBAC, errors | +| B2B `docs/backend/entities/*` | Portal entity depth | +| B2B `docs/backend/events/README.md` | Event samples | +| Ecom `docs/backend.md` | Retail API priority + stubs | +| Email templates design | Props for transactional emails (not domain entities) |