This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Admin/docs/GISHEN-MASTER-SPEC.md
Kirubel-Kibru-Yaltopia 065423efbf Expand schemas.md into a full platform backend schema pack
Cover shared envelopes/events, Admin ops DTOs, B2B portal entities, and Ecom retail shapes so one pack backs all Gishen backend sheets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 00:55:48 +03:00

355 lines
21 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.

# Gishen — Cross-App Master Spec
**Living collective contract** across Gishen client apps. Deeper API sketches live in each repo’s own backend sheet; this file is the **shared map** of roles, domain objects, auth providers, and known divergences.
| Field | Value |
| --- | --- |
| **Home repo (this file)** | `Gishen-Admin` |
| **Sibling root** | `/Users/kirukib/Desktop/Yaltopia Project/` |
| **Shared API base (TBD)** | Prefer `https://api.gishenpharmacy.org/v1` (B2B draft / Admin alignment). Legacy sheets still show `api.gishen.example` — unify TBD |
| **Last reconciled** | 2026-08-08 |
---
## Changelog
| 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`. |
---
## Update rule (before every push)
This file is the **cross-app master**. Before `git push` from Gishen-Admin (humans **and** agents):
1. Diff Admin `src/` / `docs/admin-backend-spec.md` against the last Sources stamp below.
2. Skim sibling Mob / Ecom / B2B / Dispatch specs if those folders exist on disk.
3. Refresh relevant sections here; append a **Changelog** row (date + why).
4. If Admin endpoints/fields changed, also update [`admin-backend-spec.md`](./admin-backend-spec.md) in the same change set.
5. Update the **Sources stamp** (`Last reconciled` + checklist dates).
Agents: the Cursor rule `.cursor/rules/update-master-spec-before-push.mdc` applies on push requests. Optional git hook: see [Pre-push tooling](#pre-push-tooling).
---
## Sources stamp
Update these dates whenever you reconcile against a sibling.
| Source | Path (sibling area) | Found | Spec / README | Last skimmed |
| --- | --- | --- | --- | --- |
| **Admin** | `Gishen-Admin/` | ✅ | [`docs/admin-backend-spec.md`](./admin-backend-spec.md) + [`docs/schemas.md`](./schemas.md) | 2026-08-08 |
| **Mob** | `Gishen-Mob/` | ✅ | [`docs/BACKEND_SPEC.md`](../../Gishen-Mob/docs/BACKEND_SPEC.md) (relative from sibling root) | 2026-08-07 |
| **Ecom** | `Gishen-Ecom/` | ✅ | [`docs/backend.md`](../../Gishen-Ecom/docs/backend.md) | 2026-08-08 |
| **B2B** | `Gishen-B2B/` | ✅ | [`docs/backend/OVERVIEW.md`](../../Gishen-B2B/docs/backend/OVERVIEW.md) + coordination | 2026-08-08 |
| **Dispatch Bot** | `Gishen-Dispatch-Bot/` | ✅ (related) | `docs/BACKEND_SPEC.md` | 2026-08-07 |
| **Telegram Mini App** | `Gishen-Telegram-MiniApp/` | ⚠️ empty scaffold | — | 2026-08-07 |
### Sources not found
_None for the four primary surfaces (Admin / Mob / Ecom / B2B)._ Related folders present but thin: `Gishen-Telegram-MiniApp` (empty), Dispatch Bot documented as rider surface.
### Pre-push checklist (human / agent)
- [ ] Changelog row added if domain contract changed
- [ ] Auth / orders / Rx / catalogue / org notes still match siblings (or divergences listed)
- [ ] `Last reconciled` date bumped
- [ ] `admin-backend-spec.md` updated if Admin modules changed
- [ ] Reminder script run (optional): `bash scripts/sync-master-spec-reminder.sh`
---
## 1. Product surface map
One shared pharmacy platform; **four primary clients** + rider bot.
| App | Repo | Who | Primary jobs |
| --- | --- | --- | --- |
| **Admin** | `Gishen-Admin` | Pharmacy staff: `pharmacist`, `stock_manager`, `procurement`, `finance`, `marketing_manager`, `operations`, `super_admin` | Branch Rx queue, orders/POS, stock, procurement, org commercial activation, CRM/loyalty config, dispatch board, team/audit, catalogue master, doctor roster |
| **Mob** | `Gishen-Mob` | Retail customer (Expo) | Catalog/PDP, cart/checkout, guest pharmacist codes, Rx Care (scan/reminders), loyalty, family, subscriptions, B2B join/contexts, tracking |
| **Ecom** | `Gishen-Ecom` | Retail customer (Next.js PWA) | Storefront catalog, Rx upload/check, cart → order request, profile/favorites/loyalty, branches; v1 confirmation often phone/WhatsApp |
| **B2B** | `Gishen-B2B` | Institutional: `SUPER_USER`, `HR_ADMIN`, `FINANCE`, `MEMBER` | Org self-register; HR members/packages/migration; Finance statements/approvals; Member allowance + own Rx (**clinical withhold** for HR/Finance) |
| **Dispatch Bot** | `Gishen-Dispatch-Bot` | Riders/drivers | Telegram-enrolled trip status + POD; no separate rider login app for v1 |
| **Telegram Mini App** | `Gishen-Telegram-MiniApp` | Customers (planned) | Placeholder — Ecom backend mentions Telegram auth/bootstrap; **TBD** |
### Who creates what
| Artefact | Created by | Verified / fulfilled by |
| --- | --- | --- |
| Retail order (registered) | Customer (Mob/Ecom) | Pharmacist / Ops (Admin) |
| Guest order / pharmacist code (`GSH-XXXX`) | Guest/customer Mob/Ecom; staff/doctor Admin | Pharmacist Admin (`by-code` / POS) |
| Prescription intake (customer scan) | Mob/Ecom/B2B member | Pharmacist Admin |
| Doctor-authored Rx / clinical order | Doctor (hospital org) | Pharmacist Admin |
| Catalogue Item master | Stock/super_admin Admin | Consumed by Mob/Ecom as `/catalog/products` |
| Org commercial terms | Admin Finance | B2B unlocks on `org.activated` |
| Trip assign | Ops Admin | Dispatch Bot rider |
### External actors (not Admin staff roles)
| Actor | Auth surface | Notes |
| --- | --- | --- |
| **Doctor** | Separate doctor JWT (Admin sheet); hospital/clinic org affiliation | Create Rx & guest/registered patient orders; **not** a `StaffRole` |
| **End customer (guest)** | None / Mob `POST /auth/guest` / Ecom guest order | Phone required for assisted/guest checkout; loyalty gated |
| **B2B roles** | B2B portal session | Distinct from pharmacy staff; shared `customer_id` linkage TBD |
---
## 2. Shared domain objects
Fields below are the **platform-level** vocabulary. Path prefixes and exact DTOs still differ per sheet — see [§4 Divergence](#4-divergence--sync-notes).
### 2.1 Users & auth providers
| Concept | Shared idea | App notes |
| --- | --- | --- |
| Platform person | UUID user / customer | Staff vs customer vs doctor vs B2B user are **actors**, not one role enum everywhere |
| Staff | Admin `StaffUser` + `StaffRole` + optional `branchId` | Bearer JWT |
| Customer | Retail identity; loyalty points/tier | Mob `GET /users/me`; Ecom same idea |
| Doctor | Hospital/clinic-affiliated clinician | Admin roster + `POST /auth/doctor/login` |
| B2B session user | `roles[]`, `org_id`, `active_role` | Clinical withhold for HR/Finance |
| `authProvider` | `email` \| `google` \| `phone` \| `telegram` (+ Mob `guest`) | Report on `/me` / login response |
| Locale | `en` \| `am` | Admin `preferredLocale`; Mob/Ecom/B2B `locale` / Accept-Language |
See [§3 Auth matrix](#3-auth-provider-matrix).
### 2.2 Organisations
| Type (Admin) | Used for |
| --- | --- |
| `corporate` \| `ngo` \| `other` | B2B benefits clients — credit, packages, members |
| `hospital` \| `clinic` | Doctor affiliation; clinical Rx/orders |
**Lifecycle (canonical):** `pending_activation` → `active` → `suspended` \| `closed` (+ `rejected` for denied self-reg). Admin writes `commercial` (`Money` credit_limit/used, `payment_terms_days`, `price_list_id`, contract dates) via create-active or `POST /admin/organisations/:id/activate` and emits `org.activated`. B2B self-register: `POST /v1/org/register` → `org.registration_submitted` → Admin queue.
### 2.3 Doctors
- Roster under hospital/clinic orgs in Admin (`/doctors`, org detail).
- Fields (Admin): `name`, `specialty`, `licenseNumber`, `phone`, `email`, `status`, `orgId`, …
- Can create prescriptions and orders for **guest** or registered patients, org-scoped.
- Cannot manage catalogue, RBAC, finance, other hospitals.
- Identity provider strategy vs B2B hospital users: **open** (Admin Q8 / Q17).
### 2.4 Orders (registered + guest)
| Field / idea | Meaning |
| --- | --- |
| `customerType` | Admin: `registered` \| `guest` on staff/doctor-created orders |
| Guest identity | `customerName` + **required** `customerPhone`; no `customerId` |
| Channels | Admin includes branch / POS / doctor / (ops); Mob/Ecom retail + guest codes |
| Pharmacist code | Mob/Ecom: `GSH-XXXX` via `POST /orders/guest-codes`; Admin fulfill `GET/POST .../by-code/:code` |
| Fulfillment | `pickup` \| `delivery` (+ branch / any-branch for guest codes) |
| Payments | Mob: Chapa / Telebirr / M-Pesa for delivery; `pay_at_branch` pickup-only. Ecom v1 often offline confirm |
**Guest limits (proposed):** valid phone; controlled SKUs still need approved Rx; no loyalty earn until claim/merge — **TBD** claim-by-OTP (Admin Q9–10).
### 2.5 Prescriptions + dosing schedule
| Layer | Contract |
| --- | --- |
| Admin line item | `frequency?`: `QD`\|`BID`\|`TID`\|`QID`\|`QXH`\|`custom`; `intervalHours?` (QXH); `times?`: `HH:mm`[] |
| Mob reminders | `POST /health/reminders` with `times[]`, grace, escalate; calendar UI; confirm/snooze |
| Sync intent | Persist pharmacist final `times` (+ interval) on approve/dispense so Mob can seed reminders without re-derive — **when** to push still open (Admin Q13) |
| Customer Rx Care | Mob/Ecom upload + `prescriptions/check` stock match statuses |
| B2B | Member submits Rx; HR/Finance never see clinical lines |
Admin helper: `src/lib/dosingSchedule.ts`.
### 2.6 Catalog / Items + multi-UOM + storefront PDP
| Concept | Admin (source of truth for ops) | Mob / Ecom (retail read model) |
| --- | --- | --- |
| Master product | `/admin/catalog/items` — Item with SKU, classification, media, EFDA, … | `/catalog/products` (+ slug) |
| Stock link | Inventory rows reference `itemId` | `/stock/product/:productId`, branches nearby |
| Multi-UOM | `uoms[]` + `conversionFactor` into stock `baseUnit`; `toStockQty` / `fromStockQty` | **Not yet** on Mob product type — sell unit is display `unit` string only |
| PDP fields | `storefrontUnit`, `useCase`, `compareAtPriceEtb`, side-effect lists, `directions[]`, `storageNotes[]`, `pharmacistTip` | Mob PDP: `product` + `getDrugFacts()` clinical overlay (`useCase`, form, dosages, mild/severe, storage, directions, advice) |
| Pricing lists | B2B `price_list_id` on org commercial | Ecom/Mob show retail (and credit quote at checkout) |
### 2.7 Stock
- Branch qty, batch/lot, expiry on **Stock**, not Item.
- ERP sync / field ownership / merge proposals: Admin Stock module.
- Mob/Ecom: availability APIs only (no warehouse write).
### 2.8 Loyalty
| Surface | Role |
| --- | --- |
| Admin Marketing | Config, ledger, activities, CRM points adjust, liability KPI; POS earn |
| Mob / Ecom | `GET /loyalty/me`, ledger, redeem; referrals (Ecom) |
| Guests | No earn by default until registered claim |
| Rx/Controlled | Catalogue auto-excludes loyalty eligibility (Admin) |
| B2B | Pointer/summary only; retail owns full wallet UI |
### 2.9 Shared schema index (main elements)
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' }` | [§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)
interface Money { amount: string; currency: 'ETB' }
// Org commercial (Admin writes; B2B reads)
interface CommercialTerms {
credit_limit: Money
credit_used: Money
payment_terms_days: number
price_list_id?: string
contract_start?: string
contract_end?: string
activated_at?: string
activated_by?: string
}
```
---
## 3. Auth provider matrix
| Provider | Admin (staff) | Mob (customer) | Ecom (customer) | B2B (portal) | Dispatch (rider) | Doctor |
| --- | --- | --- | --- | --- | --- | --- |
| **Google** | `POST /auth/staff/oauth/google` (ID token); mock UI | `POST /auth/oauth/google` | OTP-first sheet; Google **TBD** in older Ecom auth § | UI mock → demo session; `.../oauth/google/start` planned | — | TBD (Admin Q17) |
| **Email** | Password `POST /auth/staff/login` | `POST /auth/email/login` (+ optional register) | Magic-link sketched | Magic/OTP planned; demo persona login today | — | Email/invite primary (`/auth/doctor/login`) |
| **Phone OTP** | `phone/start` + `phone/verify` | `otp/request` + `otp/verify` (primary demo) | `otp/request` + `otp/verify` | SMS OTP planned | — | TBD |
| **Telegram** | Login Widget → `POST /auth/staff/telegram` | `POST /auth/oauth/telegram` | Telegram Mini App `POST /telegram/auth` sketched | Widget/Mini App callback planned | **Telegram user id enrollment** (identity) | Not primary |
| **Guest** | N/A (staff always auth); **guest customers** on orders | `POST /auth/guest` + guest-codes | Guest checkout by phone | N/A (org membership) | N/A | Creates guest **patients** |
| **Biometric** | — | Bind device key (Mob) | — | — | — | — |
Path naming **diverges** (`/auth/staff/*` vs `/auth/otp/*` vs `/v1/auth/*`) — consolidation TBD; shared `authProvider` enum values should stay aligned.
Env (Admin SPA): `VITE_GOOGLE_CLIENT_ID`, `VITE_TELEGRAM_BOT_USERNAME` (empty = mock UX). Server-only: bot token, OTP gateway, Google audience.
---
## 4. Divergence & sync notes
Accurate as of **2026-08-08** skim. Prefer fixing sheets over inventing a third truth here.
| Topic | Divergence | Sync action |
| --- | --- | --- |
| API base host | Some sheets still use `api.gishen.example`; B2B + Admin alignment prefer `api.gishenpharmacy.org` | Pick one production host |
| Money | **Resolved default:** `{ amount: "1250.00", currency: "ETB" }` decimal major units (B2B + Admin master). Ecom open Q should close to match; Admin UI may keep numeric helpers synced from `commercial` | Done for Admin/B2B; close Ecom |
| Auth path prefixes | Staff `/auth/staff/*`, Mob `/auth/otp/*`, B2B `/v1/auth/*` | Shared IdP façade TBD |
| Org status naming | Admin mocks/UI now use `pending_activation` (was bare `pending`); activate (was `/approve`) | Keep Admin + B2B sheets in sync |
| Guest orders | Admin: staff/doctor `customerType=guest`; Mob/Ecom: guest session + `GSH-XXXX` codes | Document both flows; Admin POS `by-code` must match Mob code shape |
| Guest Admin endpoints | Mob lists `GET /admin/orders/by-code/:code` | Ensure Admin sheet mirrors when POS wire-up lands |
| Rx dosing | Admin structured `frequency`/`times`; Mob reminders separate resource | On approve, map Admin times → `/health/reminders` seed |
| Catalogue ID | Admin `Item` / `itemId`; retail `productId` / slug | Mapping table or shared SKU key |
| Multi-UOM | Admin full `uoms[]`; Mob/Ecom display `unit` string only | Expose sales UOM on catalog DTO when POS/cart need conversion |
| PDP clinical | Admin stores PDP fields on Item; Mob still derives much from `clinical.ts` overrides | Prefer catalogue PDP fields once API live; keep Mob overrides as fallback |
| Loyalty paths | Aligned idea (`/loyalty/me`); Admin config under `/admin/loyalty/*` | OK |
| Org types | Admin hospital/clinic for doctors + B2B commercial orgs | Keep `OrganisationType` shared |
| Roles naming | Admin snake staff roles; B2B `HR_ADMIN` SCREAMING; Ecom older `pharmacy_staff` names | Map in backend claims; don't rename UI mid-flight without sheet sync |
| Rider auth | Dispatch: Telegram enrollment; Ecom older Flutter rider JWT sketch **superseded** for v1 | Prefer Dispatch sheet |
| Mini App | Ecom documents endpoints; Mini App folder empty | Implement or mark out of scope |
| Doctor in B2B | Not a B2B portal role today | Open: doctor realm vs hospital org user |
---
## 5. Links to deeper docs
### Admin (this repo)
| Doc | Purpose |
| --- | --- |
| [`docs/admin-backend-spec.md`](./admin-backend-spec.md) | Living Admin API/UI contract (primary depth) |
| [`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 |
### Mob
| Doc | Purpose |
| --- | --- |
| `Gishen-Mob/docs/BACKEND_SPEC.md` | Customer mobile API sheet |
| `Gishen-Mob/README.md` | Expo run + demo OTP |
| UI anchors | `app/(root)/auth/*`, `...(screens)/product/[id].tsx`, `prescription/*`, `checkout.tsx` |
### Ecom
| Doc | Purpose |
| --- | --- |
| `Gishen-Ecom/docs/backend.md` | Storefront + platform API proposal |
| `Gishen-Ecom/README.md` | Product proposal / demo |
| `Gishen-Ecom/docs/README.md` | Docs index |
### B2B
| Doc | Purpose |
| --- | --- |
| `Gishen-B2B/docs/README.md` | Standing rule + layout |
| `Gishen-B2B/docs/backend/OVERVIEW.md` | Tenancy, RBAC, clinical withhold |
| `Gishen-B2B/docs/backend/entities/` · `endpoints/` | Model + REST |
| `Gishen-B2B/docs/coordination/gishen-admin.md` | Admin ↔ B2B events |
| `Gishen-B2B/docs/coordination/gishen-ecom.md` | Checkout entitlement split |
| `Gishen-B2B/docs/coordination/open-items.md` | Cross-repo decisions |
| `Gishen-B2B/docs/features/INDEX.md` | Feature → pages map |
### Related
| Doc | Purpose |
| --- | --- |
| `Gishen-Dispatch-Bot/docs/BACKEND_SPEC.md` | Rider Telegram events / trips |
| `Gishen-Dispatch-Bot/docs/FEATURE_BRIEF.md` | Acceptance inventory |
---
## 6. Cross-cutting open items (rolled up)
Do not invent APIs here — track decisions:
1. Unify API host + money units (cents vs major ETB).
2. Shared auth façade for Google / Email / Phone / Telegram across actors.
3. Guest order claim by phone OTP + PII retention.
4. Checkout entitlement-split owner (B2B recommends Ecom + shared calc).
5. Doctor IdP (dedicated realm vs hospital B2B user).
6. Multi-UOM ownership ERP vs Platform.
7. When Rx `times` fan out to Mob reminders.
8. Telegram Mini App scope vs empty repo.
9. Admin ↔ Mob `by-code` / POS scan contract end-to-end.
Admin-local list: [`admin-backend-spec.md` § Open questions](./admin-backend-spec.md#open-questions).
B2B cross-repo: `Gishen-B2B/docs/coordination/open-items.md`.
---
## Pre-push tooling
Committed in **Gishen-Admin** only:
| Piece | Path | Behavior |
| --- | --- | --- |
| Cursor rule | `.cursor/rules/update-master-spec-before-push.mdc` | Agents must refresh this master (and Admin sheet if needed) before push |
| Reminder script | `scripts/sync-master-spec-reminder.sh` | Prints Sources checklist + whether master looks stale vs `src/` / Admin sheet |
| Git hook | `.githooks/pre-push` | **Warns loudly** if significant paths changed since last push while master was not updated; does **not** call LLMs. Soft warn by default (`GISHEN_MASTER_SPEC_STRICT=1` to exit 1) |
| Enable hooks | `scripts/setup-githooks.sh` | Sets **local** `core.hooksPath=.githooks` for this repo only |
```bash
# One-time per clone (optional but recommended)
bash scripts/setup-githooks.sh
# Manual check anytime
bash scripts/sync-master-spec-reminder.sh
```
Do **not** silently rewrite global git config. Local `core.hooksPath` for this repository is intentional and reversible (`git config --unset core.hooksPath`).