Use pending_activation, commercial Money terms, and activate flows so Admin master/spec and mocks match the shared organisation lifecycle. Co-authored-by: Cursor <cursoragent@cursor.com>
316 lines
18 KiB
Markdown
316 lines
18 KiB
Markdown
# 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 | **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) | 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 |
|
||
|
||
---
|
||
|
||
## 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) |
|
||
| [`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`).
|