# 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 = { data: T } /** Target platform (B2B). Prefer when consolidating. */ type ListEnvelope = { data: T[] pagination: { page: number page_size: number total_items: number total_pages: number } } /** Legacy Admin sketches — migrate to ListEnvelope */ type LegacyListEnvelope = { 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 request_id?: string } } ``` ### Domain event envelope ```ts type DomainEvent> = { 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 & { 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 } /** Staff review markers — distinct from line `controlled` and workflow status. */ type PrescriptionFlagCode = | 'controlled_substance' | 'interaction_concern' | 'incomplete_rx' | 'fraud_suspicion' | 'stock_shortage' | 'needs_clarification' | 'allergy_concern' | 'dosing_concern' | 'other' type PrescriptionFlagSeverity = 'info' | 'warning' | 'critical' type PrescriptionFlagStatus = 'open' | 'resolved' interface PrescriptionFlag { id: string code: PrescriptionFlagCode severity: PrescriptionFlagSeverity note: string createdBy: string createdAt: Timestamp status: PrescriptionFlagStatus resolvedBy?: string resolvedAt?: Timestamp resolutionNote?: 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 flags?: PrescriptionFlag[] } 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 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 } ``` ### AuditEvent ```ts interface AuditEvent { id: string at: Timestamp actor: string actorRole?: StaffRole action: string target?: string category?: string before?: Record after?: Record } ``` --- ## 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 { "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) |