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/schemas.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

1229 lines
28 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 — Platform schema pack
**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 |
| --- | --- |
| **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 |
**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 | **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 |
---
## 0. Index
### 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) |
---
## 1. Shared primitives
```ts
/** Canonical money — Admin API + B2B agree. Prefer over raw number ETB on the wire. */
interface Money {
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_*
```
| 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 demos may keep `creditLimitEtb` / `totalEtb` numbers — **sync from `Money` / `commercial` when wiring the API**.
---
## 2. API envelopes & domain events
### Envelopes
```ts
type DataEnvelope<T> = { data: T }
/** Target platform (B2B). Prefer when consolidating. */
type ListEnvelope<T> = {
data: T[]
pagination: {
page: number
page_size: number
total_items: number
total_pages: number
}
}
/** Legacy Admin sketches — migrate to ListEnvelope */
type LegacyListEnvelope<T> = {
data: T[]
meta: { page: number; limit: number; total: number }
}
type ErrorEnvelope = {
error: {
code: string
message: string
details?: Array<{ field?: string; code?: string; message: string }> | Record<string, unknown>
request_id?: string
}
}
```
### Domain event envelope
```ts
type DomainEvent<T = Record<string, unknown>> = {
id: string // evt_*
type: string
occurred_at: Timestamp
org_id?: string | null
actor_id?: string | null
payload: T
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 |
---
## 3. Principals & actors
### StaffUser (Admin pharmacy)
```ts
type StaffRole =
| 'pharmacist' | 'stock_manager' | 'procurement' | 'finance'
| 'marketing_manager' | 'operations' | 'super_admin'
type AuthProvider = 'email' | 'google' | 'phone' | 'telegram'
interface StaffUser {
id: string
name: string
email: string
role: StaffRole
branchId?: string
branchName?: string
preferredLocale?: Locale
avatarUrl?: string
authProvider?: AuthProvider
phone?: string
}
```
Login response: `{ accessToken, refreshToken, user: StaffUser }`.
### Doctor (external clinical — not StaffRole)
```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
}
```
### 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
}
```
---
## 4. Admin — organisations & doctors
### Branch
```ts
interface Branch {
id: string
name: string
zone: string
phone: string
lat?: number
lng?: number
hours?: string
}
```
### 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 {
credit_limit: Money
credit_used: Money
payment_terms_days: number
price_list_id?: string
contract_start?: DateOnly
contract_end?: DateOnly
activated_at?: Timestamp
activated_by?: string
}
interface Organisation {
id: string
name: string
orgType: OrganisationType
tin: string
vatNumber?: string
businessLicense?: string
commercialRegistration?: string
status: OrgStatus
commercial: CommercialTerms | null
creditLimitEtb: number // demo mirror
usedEtb: number
billingContact: string
source: 'admin' | 'self_register'
createdAt: Timestamp
reviewedBy?: string
reviewedAt?: Timestamp
decisionNote?: string
}
type OrgActivateBody = {
commercial: Omit<CommercialTerms, 'credit_used' | 'activated_at' | 'activated_by'> & {
credit_limit: Money
}
admin_notes?: string
}
```
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.
---
## 5. Admin — prescriptions & orders
### Prescription
```ts
type DosingFrequency = 'QD' | 'BID' | 'TID' | 'QID' | 'QXH' | 'custom'
type PrescriptionStatus =
| 'draft' | 'submitted' | 'under_review' | 'approved' | 'queried' | 'rejected'
interface PrescriptionItem {
name: string
qty: number
controlled?: boolean
frequency?: DosingFrequency
intervalHours?: number
times?: string[] // HH:mm 24h
sku?: string
dosage?: string
instructions?: string
}
interface Prescription {
id: string
customerName: string
branchId: string
status: PrescriptionStatus
items: PrescriptionItem[]
submittedAt: Timestamp
prescriber?: string
doctorId?: string
orgId?: string
customerId?: string
memberId?: string
reviewStartedAt?: Timestamp
reviewedBy?: string
reviewedAt?: Timestamp
decisionNote?: string
query_message?: string
rejection_reason?: string
days_supply?: number
refill_due_at?: DateOnly
}
type PrescriptionVerifyBody = {
status: 'approved' | 'queried' | 'rejected'
medicine_lines?: PrescriptionItem[]
notes?: string
query_message?: string
rejection_reason?: string
days_supply?: number
}
```
B2B member upload shape (pages, clinical withhold): [`prescription.md`](../../Gishen-B2B/docs/backend/entities/prescription.md).
### Order
```ts
type OrderCustomerType = 'registered' | 'guest'
type OrderFulfillment = 'delivery' | 'pickup'
type OrderStatus =
| 'draft' | 'requested' | 'pending' | 'pending_approval'
| 'confirmed' | 'picking' | 'out_for_delivery' | 'ready_for_pickup'
| 'completed' | 'cancelled'
interface OrderLine {
sku: string
name: string
qty: number
unitEtb: number
}
interface Order {
id: string
customerName: string
customerType?: OrderCustomerType
customerId?: string
customerPhone?: string
notes?: string
branchId: string
fulfillment: OrderFulfillment
status: string
totalEtb: number
channel: string // web | mobile | telegram | b2b | pos | doctor | …
assignedRiderId?: string
zone?: string
address?: string
createdAt: Timestamp
paid: boolean
rxApproved?: boolean
doctorId?: string
orgId?: string
lines?: OrderLine[]
}
```
---
## 6. Admin — catalogue, stock & procurement
### MedicationItem & ItemUom
```ts
interface ItemUom {
uom: string
conversionFactor: number // base units per 1 of this UOM
isStockUom?: boolean
isPurchaseUom?: boolean
isSalesUom?: boolean
}
interface MedicationItem {
id: string
sku: string
genericName: string
brandName: string
manufacturer: string
countryOfOrigin: string
efdaRegistrationNumber: string
barcodes: { value: string; label?: string }[]
productType: string
therapeuticClass: string
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: string
vatApplicable: boolean
discountEligible: boolean
b2bPriceListId?: string
prescriptionRequired: boolean
efdaStatus: string
controlledSubstance: boolean
advertisingRestricted: boolean
ageRestriction: string
shortDescription: string
dosageGuidance: string
sideEffects: string
sideEffectsMild?: string[]
sideEffectsSevere?: string[]
warnings: string
storageInstructions: string
storageNotes?: { title: string; body: string }[]
directions?: { title: string; body: string }[]
pharmacistTip?: string
useCase?: string
storefrontUnit?: string
compareAtPriceEtb?: number
alternativeItemIds: string[]
defaultReorderPoint: number
defaultExpiryAlertDays: number
images: string[]
thumbnailUrl?: string
badges: string[]
seoSlug: string
metaTitle: string
metaDescription: string
ownership: Record<string, string>
lastErpSyncAt?: Timestamp
erpSyncError?: string | null
b2bEligible: boolean
loyaltyEligible: boolean
createdBy: string
createdAt: Timestamp
updatedBy: string
updatedAt: Timestamp
status: 'active' | 'draft' | 'archived'
}
```
API: `/admin/catalog/items` · UI `/items`. Retail maps to Ecom `/catalog/products`.
### StockRow & stock movements
```ts
interface StockRow {
sku: string
name: string
category: string
branchId: string
qty: number // baseUnit
batch: string
expiry: string // YYYY-MM
erpQty: number
unitEtb: number
itemId?: string
reserved?: number
}
type StockTxnType = 'receipt' | 'delivery' | 'transfer' | 'adjustment'
type StockTxnStatus = 'draft' | 'submitted' | 'cancelled'
interface StockTransaction {
id: string
type: StockTxnType
status: StockTxnStatus
sku: string
branchId: string
qty: number
note?: string
createdAt: Timestamp
createdBy?: string
}
interface StockMovement {
id: string
at: Timestamp
type: string // sale | receive | adjust | reserve | …
qty: number
note: string
}
```
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
}
```
---
## 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
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)
```ts
interface OrgInvoice {
id: string
period: string
amountEtb: number
status: string
pdf_url?: string // required for B2B Finance download
}
```
### Rider & RiderTrip
```ts
interface Rider {
id: string
name: string
phone: string
telegramId?: string
branchId: string
zone: string
avatarUrl?: string
}
interface RiderTrip {
id: string
orderId: string
customerName: string
zone: string
assignedAt: Timestamp
completedAt: Timestamp | null
status: 'delivered' | 'failed' | 'en_route' | 'returned'
minutes: number
onTime: boolean
codEtb: number
}
type TripAssignBody = { riderId: string }
```
### Platform MigrationJob (Admin imports)
Distinct from [B2B tenant MigrationJob](#10-b2b-finance-migration--rx-views).
```ts
interface AdminMigrationJob {
id: string
templateId: 'stock' | 'catalog' | 'hr' | 'branches' | 'riders' | 'generic' | string
templateLabel: string
filename: string
rowsTotal: number
rowsImported: number
rowsFailed: number
status: 'completed' | 'partial' | 'failed' | 'running'
runBy: string
runAt: Timestamp
durationSec: number
mapping: Record<string, string>
}
```
### AuditEvent
```ts
interface AuditEvent {
id: string
at: Timestamp
actor: string
actorRole?: StaffRole
action: string
target?: string
category?: string
before?: Record<string, unknown>
after?: Record<string, unknown>
}
```
---
## 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<string, string>
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
{
"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"
}
}
}
```
### 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 docs
| Doc | Role |
| --- | --- |
| [`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) |