Document portal auth, profile, and pharmacy ID contracts in backend specs.

Keep the living API sheet aligned with demo personas, avatars, ID-card QR scan, list filters, and Admin/Ecom coordination.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
kirukib 2026-08-06 22:10:07 +03:00
parent a522916ff1
commit 8fe6d58a09
11 changed files with 209 additions and 37 deletions

View File

@ -28,12 +28,14 @@ For single-org sessions, `orgId` is implicit from JWT/session. Multi-org users (
| Mode (this phase) | Description | | Mode (this phase) | Description |
|-------------------|-------------| |-------------------|-------------|
| Mock demo | `/login` role selector sets session cookie with role + org + user | | Mock demo | `/login` **test-user dropdown** (`demo_user_id`) sets session cookie with user + roles + org; legacy `demo_role` still accepted |
| Production (future) | OAuth2 / magic link / SSO — TBD with platform auth | | Production (future) | OAuth2 / magic link / SSO — TBD with platform auth |
Demo personas: `user_super`, `user_hr`, `user_fin`, `user_mem` — see [`endpoints/auth.md`](endpoints/auth.md) (`GET /v1/auth/demo-profiles`).
### Session shape ### Session shape
See [`entities/session-user.md`](entities/session-user.md). See [`entities/session-user.md`](entities/session-user.md). Includes `avatar_url`, `roles[]`, and `active_role` for multi-role demo users.
### JWT claims (future) ### JWT claims (future)
@ -42,6 +44,7 @@ See [`entities/session-user.md`](entities/session-user.md).
"sub": "usr_01H...", "sub": "usr_01H...",
"org_id": "org_01H...", "org_id": "org_01H...",
"roles": ["HR_ADMIN", "FINANCE"], "roles": ["HR_ADMIN", "FINANCE"],
"active_role": "HR_ADMIN",
"locale": "am", "locale": "am",
"iat": 1710000000, "iat": 1710000000,
"exp": 1710003600 "exp": 1710003600
@ -52,10 +55,12 @@ See [`entities/session-user.md`](entities/session-user.md).
| Resource / action | SUPER_USER | HR_ADMIN | FINANCE | MEMBER | | Resource / action | SUPER_USER | HR_ADMIN | FINANCE | MEMBER |
|-------------------|:----------:|:--------:|:-------:|:------:| |-------------------|:----------:|:--------:|:-------:|:------:|
| Own profile (`/v1/me/profile`) | ✓ | ✓ | ✓ | ✓ |
| Org profile (commercial read) | ✓ | ✓ (active only) | ✓ (active only) | — | | Org profile (commercial read) | ✓ | ✓ (active only) | ✓ (active only) | — |
| Org registration submit | ✓ (public path) | — | — | — | | Org registration submit | ✓ (public path) | — | — | — |
| Departments CRUD | ✓ | ✓ | read | — | | Departments CRUD | ✓ | ✓ | read | — |
| Members CRUD / import / invite | ✓ | ✓ | read metadata | own row | | Members CRUD / import / invite | ✓ | ✓ | read metadata | own row |
| Pharmacy ID card (print / QR payload) | ✓ | ✓ | — | own only |
| Packages CRUD | ✓ | ✓ | read | own assignment | | Packages CRUD | ✓ | ✓ | read | own assignment |
| Migration jobs | ✓ | ✓ | read history | — | | Migration jobs | ✓ | ✓ | read history | — |
| Finance spend / statements | ✓ | read aggregates | ✓ | — | | Finance spend / statements | ✓ | read aggregates | ✓ | — |
@ -138,7 +143,15 @@ Default `page_size`: 25. Max: 100.
## Filtering & date ranges ## Filtering & date ranges
Finance and spend endpoints accept: List endpoints support search + filters (portal toolbar / modal when >3 filters):
| Domain | Query params (common) |
|--------|------------------------|
| Members | `q`, `status`, `department_id`, `package_id`, `member_type`, `has_dependants` |
| Finance spend | `from`, `to`, `department_id`, `category`, `coverage`, `member_id`, `approval_status` |
| Statements | `year`, `status` |
| Migration | `status`, `dataset_type`, `has_errors`, `created_by` |
| Packages / departments | `q`, `status` / site |
``` ```
?from=2026-01-01&to=2026-01-31&department_id=dept_01H... ?from=2026-01-01&to=2026-01-31&department_id=dept_01H...
@ -146,6 +159,24 @@ Finance and spend endpoints accept:
ISO 8601 dates in org timezone (default `Africa/Addis_Ababa`). ISO 8601 dates in org timezone (default `Africa/Addis_Ababa`).
## Pharmacy ID card (platform contract)
Members carry a printable verification card used at Gishen pharmacy / Ecom checkout:
| Field | Purpose |
|-------|---------|
| `avatar_url` | Photo on card + portal UI |
| `card_id` | Human-readable card number (e.g. `GSH-1042`) |
| `verification_id` | Org verification token encoded in QR |
**QR payload (canonical):**
```
gishen://member/{member_id}?v={verification_id}
```
Admin / POS scans the QR to resolve the member for covered purchase. Clinical detail is **not** on the card. See [`endpoints/members.md`](endpoints/members.md#get-v1-membersidid-card) and [`../coordination/gishen-admin.md`](../coordination/gishen-admin.md).
## File uploads ## File uploads
Multipart for Rx images and migration files: Multipart for Rx images and migration files:

View File

@ -8,9 +8,9 @@ REST-style API contract for Gishen-B2B. All paths prefixed with `/v1` unless not
| Group | Doc | Description | | Group | Doc | Description |
|-------|-----|-------------| |-------|-----|-------------|
| Auth | [auth.md](auth.md) | Login, session, locale | | Auth | [auth.md](auth.md) | Demo login personas, session, locale, `/me/profile` |
| Organisation | [org.md](org.md) | Org profile, registration, departments, verification | | Organisation | [org.md](org.md) | Org profile, registration, departments, verification |
| Members | [members.md](members.md) | CRUD, import, invites, join | | Members | [members.md](members.md) | CRUD, import, invites, join, pharmacy ID card |
| Packages | [packages.md](packages.md) | Benefit plans | | Packages | [packages.md](packages.md) | Benefit plans |
| Migration | [migration.md](migration.md) | Bulk import jobs | | Migration | [migration.md](migration.md) | Bulk import jobs |
| Finance | [finance.md](finance.md) | Spend, statements, approvals | | Finance | [finance.md](finance.md) | Spend, statements, approvals |
@ -53,6 +53,7 @@ Endpoints returning prescription or order clinical detail check session roles. H
| Path pattern | Auth | | Path pattern | Auth |
|--------------|------| |--------------|------|
| `/v1/auth/login` | Public | | `/v1/auth/login` | Public |
| `/v1/auth/demo-profiles` | Public |
| `/v1/org/register*` | Public | | `/v1/org/register*` | Public |
| `/v1/invite/:token` (GET) | Public | | `/v1/invite/:token` (GET) | Public |
| `/v1/join` (POST) | Public or member session | | `/v1/join` (POST) | Public or member session |

View File

@ -38,12 +38,14 @@ Production (future): `{ "email", "password" }` or OAuth code exchange.
"data": { "data": {
"token": "mock_jwt_...", "token": "mock_jwt_...",
"user": { "user": {
"id": "usr_01HHR", "id": "user_hr",
"email": "hr.demo@acme.et", "email": "hr@yaltopia.com",
"full_name": "Demo HR Admin", "full_name": "Hanna HR",
"org_id": "org_01DEMO", "org_id": "org_yaltopia",
"roles": ["HR_ADMIN"], "roles": ["HR_ADMIN"],
"active_role": "HR_ADMIN",
"locale": "en", "locale": "en",
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_hr",
"org_status": "active", "org_status": "active",
"permissions": ["org:read", "org:write_hr", "members:write", "packages:write", "migration:write", "finance:read"] "permissions": ["org:read", "org:write_hr", "members:write", "packages:write", "migration:write", "finance:read"]
} }
@ -55,7 +57,8 @@ Production (future): `{ "email", "password" }` or OAuth code exchange.
| Code | HTTP | When | | Code | HTTP | When |
|------|------|------| |------|------|------|
| `VALIDATION_FAILED` | 400 | Invalid `demo_role` | | `VALIDATION_FAILED` | 400 | Invalid `demo_user_id` / `demo_role` |
| `NOT_FOUND` | 404 | Unknown demo persona |
| `UNAUTHENTICATED` | 401 | Production bad credentials | | `UNAUTHENTICATED` | 401 | Production bad credentials |
### Clinical withhold ### Clinical withhold
@ -166,24 +169,38 @@ List pre-seeded demo personas for `/login` test-user dropdown.
"full_name": "Selam Super", "full_name": "Selam Super",
"demo_role": "SUPER_USER", "demo_role": "SUPER_USER",
"roles": ["SUPER_USER", "HR_ADMIN", "FINANCE", "MEMBER"], "roles": ["SUPER_USER", "HR_ADMIN", "FINANCE", "MEMBER"],
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_super",
"label": "Super User", "label": "Super User",
"description": "Full portal access including clinical detail" "description": "Full portal access including clinical detail"
}, },
{ {
"id": "user_hr", "id": "user_hr",
"email": "hr@yaltopia.com",
"full_name": "Hanna HR",
"demo_role": "HR_ADMIN", "demo_role": "HR_ADMIN",
"roles": ["HR_ADMIN"],
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_hr",
"label": "HR Administrator", "label": "HR Administrator",
"description": "Members, departments, packages — no clinical detail" "description": "Members, departments, packages — no clinical detail"
}, },
{ {
"id": "user_fin", "id": "user_fin",
"email": "finance@yaltopia.com",
"full_name": "Fikru Finance",
"demo_role": "FINANCE", "demo_role": "FINANCE",
"roles": ["FINANCE"],
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_fin",
"label": "Finance Approver", "label": "Finance Approver",
"description": "Spend, statements, approvals — no clinical detail" "description": "Spend, statements, approvals — no clinical detail"
}, },
{ {
"id": "user_mem", "id": "user_mem",
"email": "member@yaltopia.com",
"full_name": "Abebe Member",
"demo_role": "MEMBER", "demo_role": "MEMBER",
"roles": ["MEMBER"],
"member_id": "mbr_01HMEM001",
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_mem",
"label": "Member", "label": "Member",
"description": "Own allowance, orders, prescriptions" "description": "Own allowance, orders, prescriptions"
} }
@ -217,7 +234,9 @@ Current user’s profile (account page).
"roles": ["HR_ADMIN"], "roles": ["HR_ADMIN"],
"active_role": "HR_ADMIN", "active_role": "HR_ADMIN",
"locale": "en", "locale": "en",
"avatar_initials": "HH" "avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_hr",
"avatar_initials": "HH",
"member_id": null
} }
} }
``` ```
@ -226,7 +245,7 @@ Current user’s profile (account page).
## PATCH /v1/me/profile ## PATCH /v1/me/profile
Update display preferences (locale / name stub). Update display preferences (locale / name stub / active role for multi-role users).
| | | | | |
|---|---| |---|---|
@ -236,22 +255,33 @@ Update display preferences (locale / name stub).
### Request ### Request
```json ```json
{ "locale": "am" } {
"locale": "am",
"active_role": "SUPER_USER"
}
``` ```
`active_role` must be one of the user’s `roles[]` (demo: `user_super` holds all four).
### Response 200 ### Response 200
Updated profile object. Updated profile object.
### Errors
| Code | HTTP | When |
|------|------|------|
| `VALIDATION_FAILED` | 400 | Invalid locale or role not in `roles[]` |
--- ---
## UI routes ## UI routes
| Route | Purpose | | Route | Purpose |
|-------|---------| |-------|---------|
| `/login` | Demo test-user dropdown + locale switcher | | `/login` | Yimaru-style login — test-user dropdown + locale switcher |
| `/profile` | Own profile — role switch, sign out | | `/profile` | Own profile — avatar, org, role switch, sign out |
## Clinical withhold notes ## Clinical withhold notes
Auth endpoints do not expose clinical data. Role list on session determines withhold behavior on downstream endpoints. Auth endpoints do not expose clinical data. Role list / `active_role` on session determines withhold behavior on downstream endpoints.

View File

@ -42,7 +42,17 @@ Paginated spend lines.
### Query ### Query
`?page=1&member_id=mbr_01H&department_id=dept_01H&approval_status=flagged` `?page=1&member_id=mbr_01H&department_id=dept_01H&category=chronic&coverage=partial&period=2026-03&approval_status=flagged`
| Param | Notes |
|-------|--------|
| `from` / `to` | ISO date range |
| `department_id` | Site filter |
| `category` | Category code (no medicine names) |
| `coverage` | e.g. `full` \| `partial` \| `member_paid` (mock) |
| `period` | `YYYY-MM` convenience filter |
| `member_id` | Single member |
| `approval_status` | `flagged` \| `approved` \| … |
### Response 200 ### Response 200

View File

@ -19,7 +19,18 @@ List members.
### Query ### Query
`?page=1&status=active&department_id=dept_01H&q=abel&member_type=primary` `?page=1&status=active&department_id=dept_01H&package_id=pkg_01HGOLD&q=abel&member_type=primary&has_dependants=true`
| Param | Notes |
|-------|--------|
| `q` | Search name, email, phone, employee_id |
| `status` | `invited` \| `active` \| `inactive` \| `offboarded` |
| `department_id` | Filter by department |
| `package_id` | Filter by assigned package |
| `member_type` | `primary` \| `dependant` |
| `has_dependants` | `true` \| `false` — primaries with/without dependants |
Portal UI: when more than three filters are shown, they open in an Apply modal (client-only); API still accepts all query params above.
### Response 200 ### Response 200
@ -91,6 +102,59 @@ Member detail.
HR/Finance: no linked prescriptions. SUPER_USER: may include `prescriptions_summary`. MEMBER own: full allowance, order pointers. HR/Finance: no linked prescriptions. SUPER_USER: may include `prescriptions_summary`. MEMBER own: full allowance, order pointers.
Detail payload includes `avatar_url`, `card_id`, `verification_id` for pharmacy ID card (safe for HR).
---
## GET /v1/members/:id/id-card
Printable pharmacy verification card payload (B2B print UI + Admin/POS scan).
| | |
|---|---|
| **Auth** | Required |
| **Roles** | SUPER_USER, HR_ADMIN; MEMBER (own `member_id` only) |
### Response 200
```json
{
"data": {
"member_id": "mbr_01HMEM001",
"full_name": "Abel Mekonnen",
"employee_id": "EMP-1042",
"card_id": "GSH-1042",
"verification_id": "EMP-1042",
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=mbr_01HMEM001",
"org_name": "Acme Bank",
"org_join_code": "ACME-2026",
"package_name": "Gold Executive",
"status": "active",
"qr_payload": "gishen://member/mbr_01HMEM001?v=EMP-1042"
}
}
```
### QR / scan contract
| | |
|---|---|
| **Format** | `gishen://member/{member_id}?v={verification_id}` |
| **Consumer** | Gishen-Admin POS / pharmacist checkout — resolve covered member |
| **Not included** | Prescription images, medicine lines, diagnosis |
### Errors
| Code | HTTP |
|------|------|
| `FORBIDDEN` | 403 |
| `NOT_FOUND` | 404 |
| `MEMBER_INACTIVE` | 422 — offboarded / inactive cards may still print with watermark (product TBD) |
### Clinical withhold
ID card is verification metadata only — allowed for HR. No clinical fields.
--- ---
## PATCH /v1/members/:id ## PATCH /v1/members/:id
@ -108,10 +172,13 @@ Update member.
{ {
"department_id": "dept_01HNEW", "department_id": "dept_01HNEW",
"package_id": "pkg_01HSILVER", "package_id": "pkg_01HSILVER",
"avatar_url": "https://cdn.gishen.../avatars/mbr_01H.png",
"overrides": { "copay_percent": 5 } "overrides": { "copay_percent": 5 }
} }
``` ```
`avatar_url` may also be set via future `POST /v1/members/:id/avatar` (multipart). Mock phase uses deterministic portrait URLs.
--- ---
## POST /v1/members/:id/offboard ## POST /v1/members/:id/offboard
@ -297,10 +364,10 @@ Add dependant.
| Route | Endpoint(s) | | Route | Endpoint(s) |
|-------|-------------| |-------|-------------|
| `/members` | GET `/v1/members` | | `/members` | GET `/v1/members` (search + filters; modal when >3) |
| `/members/new` | POST `/v1/members` | | `/members/new` | POST `/v1/members` |
| `/members/import` | POST `/v1/members/import` | | `/members/import` | POST `/v1/members/import` |
| `/members/[id]` | GET/PATCH + invite/offboard/status mock actions | | `/members/[id]` | GET/PATCH + invite/offboard; **ID card** tab → GET `/v1/members/:id/id-card` |
| `/invite/[token]` | GET/POST invite | | `/invite/[token]` | GET/POST invite |
| `/join` | POST `/v1/join` | | `/join` | POST `/v1/join` |

View File

@ -11,7 +11,7 @@ Data model entities required by Gishen-B2B. Types below are canonical for mocks
| Organisation | [organisation.md](organisation.md) | All roles (scoped) | | Organisation | [organisation.md](organisation.md) | All roles (scoped) |
| Org registration | [org-registration.md](org-registration.md) | Public signup, Admin | | Org registration | [org-registration.md](org-registration.md) | Public signup, Admin |
| Department | [department.md](department.md) | HR, Finance (aggregates) | | Department | [department.md](department.md) | HR, Finance (aggregates) |
| Member | [member.md](member.md) | HR, Member; Finance (metadata only) | | Member | [member.md](member.md) | HR, Member; Finance (metadata); pharmacy ID card (`avatar_url`, `card_id`, `verification_id`) |
| Package | [package.md](package.md) | HR, Member | | Package | [package.md](package.md) | HR, Member |
| Migration job | [migration-job.md](migration-job.md) | HR, SUPER_USER | | Migration job | [migration-job.md](migration-job.md) | HR, SUPER_USER |
| Finance | [finance.md](finance.md) | Finance, HR (read), SUPER_USER | | Finance | [finance.md](finance.md) | Finance, HR (read), SUPER_USER |
@ -32,6 +32,8 @@ usr_01HXXXXXXXXXXXXXX
ULID-style prefixes for human readability in logs. ULID-style prefixes for human readability in logs.
Printed pharmacy card numbers use org-scoped display IDs (e.g. `GSH-1042`) on `Member.card_id` — not a separate entity.
## Common field types ## Common field types
| Type | Format | | Type | Format |

View File

@ -147,4 +147,5 @@ SUPER_USER and MEMBER (own record) receive full clinical linkage via prescriptio
## Related endpoints ## Related endpoints
- [`../endpoints/members.md`](../endpoints/members.md) - [`../endpoints/members.md`](../endpoints/members.md) — including `GET /v1/members/:id/id-card`
- Pharmacy QR scan consumed by Admin / POS — see [`../../coordination/gishen-admin.md`](../../coordination/gishen-admin.md)

View File

@ -8,7 +8,7 @@ Authenticated user context for B2B portal — demo mock now, JWT/session later.
| Field | Type | Required | Notes | | Field | Type | Required | Notes |
|-------|------|----------|-------| |-------|------|----------|-------|
| `id` | `string` | ✓ | `usr_*` | | `id` | `string` | ✓ | `usr_*` or demo `user_*` |
| `email` | `string` | ✓ | | | `email` | `string` | ✓ | |
| `full_name` | `string` | ✓ | | | `full_name` | `string` | ✓ | |
| `phone` | `Phone` | | | | `phone` | `Phone` | | |
@ -16,10 +16,11 @@ Authenticated user context for B2B portal — demo mock now, JWT/session later.
| `member_id` | `string` | | Link to Member row if enrolled | | `member_id` | `string` | | Link to Member row if enrolled |
| `customer_id` | `string` | | Shared Gishen customer (Ecom/Mob) | | `customer_id` | `string` | | Shared Gishen customer (Ecom/Mob) |
| `roles` | `PortalRole[]` | ✓ | One or more | | `roles` | `PortalRole[]` | ✓ | One or more |
| `active_role` | `PortalRole` | ✓ | Role used for nav + withhold (must ∈ `roles`) |
| `locale` | `Locale` | ✓ | `en` \| `am` | | `locale` | `Locale` | ✓ | `en` \| `am` |
| `avatar_url` | `string` | | | | `avatar_url` | `string` | | Portrait URL (demo: deterministic DiceBear) |
| `org_status` | `OrgStatus` | ✓ | Denormalized for gating | | `org_status` | `OrgStatus` | ✓ | Denormalized for gating |
| `permissions` | `string[]` | | Computed from roles | | `permissions` | `string[]` | | Computed from `active_role` / roles |
| `demo_profile` | `DemoProfile` | | Mock only | | `demo_profile` | `DemoProfile` | | Mock only |
### PortalRole ### PortalRole
@ -35,7 +36,7 @@ SUPER_USER | HR_ADMIN | FINANCE | MEMBER
| `label` | `string` | | `label` | `string` |
| `description` | `string` | | `description` | `string` |
Pre-seeded demo profiles per role against a shared mock org. Pre-seeded demo personas: `user_super`, `user_hr`, `user_fin`, `user_mem` against a shared mock org. Listed via `GET /v1/auth/demo-profiles`.
## Computed permissions ## Computed permissions
@ -50,30 +51,37 @@ Pre-seeded demo profiles per role against a shared mock org.
| `finance:approve` | SUPER_USER, FINANCE | | `finance:approve` | SUPER_USER, FINANCE |
| `clinical:read` | SUPER_USER, MEMBER (own) | | `clinical:read` | SUPER_USER, MEMBER (own) |
| `prescriptions:write` | MEMBER (own), SUPER_USER | | `prescriptions:write` | MEMBER (own), SUPER_USER |
| `id_card:print` | SUPER_USER, HR_ADMIN, MEMBER (own) |
## Locale preference ## Locale preference
`locale` is persisted on the user record and returned in session. Updated via `PATCH /v1/auth/me/locale`. Synced to client cookie/localStorage for next-intl. `locale` is persisted on the user record and returned in session. Updated via `PATCH /v1/auth/me/locale` or `PATCH /v1/me/profile`. Synced to client cookie/localStorage for next-intl.
## Active role
Multi-role users (demo Super User) can set `active_role` on `/profile`. Portal nav and clinical withhold follow `active_role` for the session.
## Clinical withhold ## Clinical withhold
Session drives RBAC. If `roles` includes HR_ADMIN or FINANCE and **not** SUPER_USER, all prescription and clinical serializers apply withhold filters automatically. Session drives RBAC. If effective role is HR_ADMIN or FINANCE and **not** SUPER_USER, all prescription and clinical serializers apply withhold filters automatically.
## Sample session (mock demo — HR) ## Sample session (mock demo — HR)
```json ```json
{ {
"id": "usr_01HHR", "id": "user_hr",
"email": "hr.demo@acme.et", "email": "hr@yaltopia.com",
"full_name": "Demo HR Admin", "full_name": "Hanna HR",
"phone": "+251900000001", "phone": "+251900000001",
"org_id": "org_01DEMO", "org_id": "org_yaltopia",
"member_id": null, "member_id": null,
"customer_id": null, "customer_id": null,
"roles": ["HR_ADMIN"], "roles": ["HR_ADMIN"],
"active_role": "HR_ADMIN",
"locale": "en", "locale": "en",
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_hr",
"org_status": "active", "org_status": "active",
"permissions": ["org:read", "org:write_hr", "members:write", "packages:write", "migration:write", "finance:read"], "permissions": ["org:read", "org:write_hr", "members:write", "packages:write", "migration:write", "finance:read", "id_card:print"],
"demo_profile": { "demo_profile": {
"label": "HR Administrator", "label": "HR Administrator",
"description": "Manage members, departments, and packages" "description": "Manage members, departments, and packages"
@ -85,9 +93,13 @@ Session drives RBAC. If `roles` includes HR_ADMIN or FINANCE and **not** SUPER_U
```json ```json
{ {
"id": "usr_01HSUPER", "id": "user_super",
"roles": ["SUPER_USER"], "email": "super@yaltopia.com",
"full_name": "Selam Super",
"roles": ["SUPER_USER", "HR_ADMIN", "FINANCE", "MEMBER"],
"active_role": "SUPER_USER",
"locale": "am", "locale": "am",
"avatar_url": "https://api.dicebear.com/9.x/lorelei/svg?seed=user_super",
"permissions": ["*"], "permissions": ["*"],
"demo_profile": { "demo_profile": {
"label": "Super User", "label": "Super User",
@ -98,4 +110,4 @@ Session drives RBAC. If `roles` includes HR_ADMIN or FINANCE and **not** SUPER_U
## Related endpoints ## Related endpoints
- [`../endpoints/auth.md`](../endpoints/auth.md) - [`../endpoints/auth.md`](../endpoints/auth.md) — login, demo-profiles, `/me/profile`

View File

@ -12,6 +12,7 @@ How Gishen-B2B interacts with **Gishen-Admin** (pharmacy ops, commercial activat
| Org creation (legacy) | Offline sales creates org + invites SUPER_USER | B2B receives invite link | | Org creation (legacy) | Offline sales creates org + invites SUPER_USER | B2B receives invite link |
| Registration queue | Review `org.registration_submitted` / `org.registration_requested` | Admin contacts client, completes setup, activates | | Registration queue | Review `org.registration_submitted` / `org.registration_requested` | Admin contacts client, completes setup, activates |
| Pharmacist Rx queue | Review submitted prescriptions | B2B member sees status updates via `prescription.review_updated` | | Pharmacist Rx queue | Review submitted prescriptions | B2B member sees status updates via `prescription.review_updated` |
| **Pharmacy ID scan** | POS / pharmacist scans member QR at purchase | Resolve `member_id` + `verification_id` from `gishen://member/{id}?v={verification_id}`; apply org package entitlement at checkout |
| Credit / invoice oversight | Monthly billing, suspensions | B2B Finance sees statements; `org.suspended` blocks new orders | | Credit / invoice oversight | Monthly billing, suspensions | B2B Finance sees statements; `org.suspended` blocks new orders |
| Price lists | Corporate pricing | Referenced by `organisation.commercial.price_list_id` — read-only in B2B | | Price lists | Corporate pricing | Referenced by `organisation.commercial.price_list_id` — read-only in B2B |
@ -49,8 +50,22 @@ How Gishen-B2B interacts with **Gishen-Admin** (pharmacy ops, commercial activat
| `Organisation.status` | Admin | All authenticated | | `Organisation.status` | Admin | All authenticated |
| `Prescription` review fields | Admin | Member, SUPER_USER | | `Prescription` review fields | Admin | Member, SUPER_USER |
| `Member`, `Department`, `Package` | B2B (HR) | B2B | | `Member`, `Department`, `Package` | B2B (HR) | B2B |
| `Member.verification_id` / `card_id` / `avatar_url` | B2B (HR) | B2B portal + Admin POS scan |
| `Statement` | Admin/billing engine | B2B Finance | | `Statement` | Admin/billing engine | B2B Finance |
## Pharmacy ID card (scan contract)
B2B prints employee pharmacy ID cards from `GET /v1/members/:id/id-card` (UI: `/members/[id]` → ID card tab).
| Item | Spec |
|------|------|
| QR payload | `gishen://member/{member_id}?v={verification_id}` |
| Card fields | Photo, name, employee ID, `card_id`, package, status, org |
| Admin responsibility | Implement scanner → resolve member → covered checkout (with Ecom entitlement) |
| Clinical | Card has **no** Rx or medicine data |
See [`../backend/endpoints/members.md`](../backend/endpoints/members.md) and [`../backend/OVERVIEW.md`](../backend/OVERVIEW.md#pharmacy-id-card-platform-contract).
## Clinical withhold alignment ## Clinical withhold alignment
Admin pharmacists see full Rx detail. B2B HR/Finance never receive clinical payloads — enforced in B2B API layer and documented in entity/endpoint specs. Admin pharmacists see full Rx detail. B2B HR/Finance never receive clinical payloads — enforced in B2B API layer and documented in entity/endpoint specs.
@ -64,6 +79,7 @@ SUPER_USER in B2B may view member Rx for support; does not replace Admin review
- [ ] Pharmacist queue consumes `prescription.submitted` - [ ] Pharmacist queue consumes `prescription.submitted`
- [ ] Admin review emits `prescription.review_updated` - [ ] Admin review emits `prescription.review_updated`
- [ ] Statement PDF generation exposes URLs consumed by B2B `/statements` - [ ] Statement PDF generation exposes URLs consumed by B2B `/statements`
- [ ] POS / Admin scanner accepts `gishen://member/{id}?v={verification_id}` and resolves covered member
## Contact / escalation ## Contact / escalation

View File

@ -80,6 +80,7 @@ Mock phase: static link + shared demo customer.
| `GET /v1/customers/:id/allowance` | Ecom checkout, B2B `/me` | | `GET /v1/customers/:id/allowance` | Ecom checkout, B2B `/me` |
| `POST /v1/checkout/entitlement-preview` | Ecom cart | | `POST /v1/checkout/entitlement-preview` | Ecom cart |
| `GET /v1/customers/:id/orders` | B2B `/me/orders` (member view) | | `GET /v1/customers/:id/orders` | B2B `/me/orders` (member view) |
| Resolve pharmacy QR `gishen://member/{id}?v={verification_id}` | Admin POS / Ecom staff checkout → same entitlement preview |
Document exact shapes in Ecom spec sheet; B2B references via this coordination doc. Document exact shapes in Ecom spec sheet; B2B references via this coordination doc.
@ -94,3 +95,4 @@ Telegram Mini App rides **Ecom's backend spec**, not B2B. No B2B action required
- [ ] Checkout split UI implemented once in Ecom - [ ] Checkout split UI implemented once in Ecom
- [ ] Order events feed B2B finance aggregates (redacted) - [ ] Order events feed B2B finance aggregates (redacted)
- [ ] Confirm SSO/deep-link from B2B member portal to Ecom - [ ] Confirm SSO/deep-link from B2B member portal to Ecom
- [ ] Staff / pharmacist flow accepts B2B pharmacy ID QR at checkout

View File

@ -99,7 +99,7 @@ All primary tables include in-card **search + filter** toolbar (`TableToolbar`):
## Detail UX ## Detail UX
Yimaru-style `DetailHero` + `DetailSection` layouts with demo mutations (invite/offboard, package activate, migration commit/rollback, statement issue/pay, approval approve/flag). Dashboard uses Recharts placeholders from statements/spend/departments/packages. Yimaru-style `DetailHero` + `DetailSection` layouts with demo mutations (invite/offboard, package activate, migration commit/rollback, statement issue/pay, approval approve/flag). Dashboard uses Recharts placeholders from statements/spend/departments/packages. Member detail includes printable pharmacy ID card (`GET /v1/members/:id/id-card`).
## Spec file paths ## Spec file paths
``` ```