Surface Google, email, phone, and Telegram on login (mocked), polish the brand panel and fixes for Button/Menu, and keep the backend spec sheet aligned. Co-authored-by: Cursor <cursoragent@cursor.com>
223 lines
7.1 KiB
Markdown
223 lines
7.1 KiB
Markdown
# Backend overview
|
|
|
|
Living spec for the **shared platform API** consumed by Gishen-B2B. This repo implements mocks typed to this contract; the backend service is consolidated later from B2B, Ecom, Mobile, and Admin spec sheets.
|
|
|
|
**Workspace:** `/Users/kirukib/Desktop/Yaltopia Project/Gishen-B2B`
|
|
|
|
## Base URL (future)
|
|
|
|
```
|
|
https://api.gishenpharmacy.org/v1
|
|
```
|
|
|
|
Mock adapters in this repo live under `src/lib/api/` and `src/mocks/`.
|
|
|
|
## Tenancy
|
|
|
|
- Every authenticated request (except public registration) is scoped to an **organisation** via `orgId` on the session or explicit path prefix.
|
|
- Org lifecycle: `pending_activation` → `active` → `suspended` | `closed`.
|
|
- Pending orgs: SUPER_USER may read status and submitted registration; **cannot** create members, packages, or migration commits until Admin activates.
|
|
|
|
```
|
|
X-Org-Id: org_01H...
|
|
```
|
|
|
|
For single-org sessions, `orgId` is implicit from JWT/session. Multi-org users (rare in B2B) pass `X-Org-Id`.
|
|
|
|
## Authentication
|
|
|
|
| Mode (this phase) | Description |
|
|
|-------------------|-------------|
|
|
| Mock demo | `/login` offers **Google, email, phone, Telegram** (UI mocked) plus a **test-user dropdown**; any path sets session via `demo_user_id` |
|
|
| Production (future) | Google OIDC, email magic-link/OTP, SMS OTP, Telegram Login Widget / Mini App — see [`endpoints/auth.md`](endpoints/auth.md) |
|
|
|
|
Demo personas: `user_super`, `user_hr`, `user_fin`, `user_mem` — see `GET /v1/auth/demo-profiles`.
|
|
|
|
### Session shape
|
|
|
|
See [`entities/session-user.md`](entities/session-user.md). Includes `avatar_url`, `roles[]`, and `active_role` for multi-role demo users.
|
|
|
|
### JWT claims (future)
|
|
|
|
```json
|
|
{
|
|
"sub": "usr_01H...",
|
|
"org_id": "org_01H...",
|
|
"roles": ["HR_ADMIN", "FINANCE"],
|
|
"active_role": "HR_ADMIN",
|
|
"locale": "am",
|
|
"iat": 1710000000,
|
|
"exp": 1710003600
|
|
}
|
|
```
|
|
|
|
## RBAC matrix
|
|
|
|
| Resource / action | SUPER_USER | HR_ADMIN | FINANCE | MEMBER |
|
|
|-------------------|:----------:|:--------:|:-------:|:------:|
|
|
| Own profile (`/v1/me/profile`) | ✓ | ✓ | ✓ | ✓ |
|
|
| Org profile (commercial read) | ✓ | ✓ (active only) | ✓ (active only) | — |
|
|
| Org registration submit | ✓ (public path) | — | — | — |
|
|
| Departments CRUD | ✓ | ✓ | read | — |
|
|
| Members CRUD / import / invite | ✓ | ✓ | read metadata | own row |
|
|
| Pharmacy ID card (print / QR payload) | ✓ | ✓ | — | own only |
|
|
| Packages CRUD | ✓ | ✓ | read | own assignment |
|
|
| Migration jobs | ✓ | ✓ | read history | — |
|
|
| Finance spend / statements | ✓ | read aggregates | ✓ | — |
|
|
| Finance approvals | ✓ | — | ✓ | — |
|
|
| Member allowance / orders | ✓ | — | — | ✓ |
|
|
| Prescriptions (clinical) | ✓ full | **withheld** | **withheld** | own only |
|
|
| Settings / verification | ✓ | ✓ | — | — |
|
|
|
|
**Clinical withhold:** HR_ADMIN and FINANCE never receive prescription images, medicine line items, dosages, prescriber notes, or diagnosis-related fields. Aggregates (e.g. “Rx order — ETB 450, category: chronic”) may appear in finance spend. SUPER_USER bypasses withhold for support/disputes.
|
|
|
|
## Error model
|
|
|
|
All errors return:
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": "VALIDATION_FAILED",
|
|
"message": "Human-readable summary",
|
|
"details": [
|
|
{ "field": "email", "code": "INVALID_FORMAT", "message": "..." }
|
|
],
|
|
"request_id": "req_01H..."
|
|
}
|
|
}
|
|
```
|
|
|
|
### Standard HTTP status codes
|
|
|
|
| Status | Usage |
|
|
|--------|-------|
|
|
| 400 | Validation failed, malformed JSON |
|
|
| 401 | Missing or expired session |
|
|
| 403 | Authenticated but role/org forbids action; clinical withhold |
|
|
| 404 | Resource not found or not visible in tenant |
|
|
| 409 | Conflict (duplicate member, job already committed) |
|
|
| 422 | Business rule violation (org pending, credit exceeded) |
|
|
| 429 | Rate limit |
|
|
| 500 | Internal error |
|
|
|
|
### Error codes (catalog)
|
|
|
|
| Code | HTTP | Description |
|
|
|------|------|-------------|
|
|
| `UNAUTHENTICATED` | 401 | No valid session |
|
|
| `FORBIDDEN` | 403 | Role cannot perform action |
|
|
| `CLINICAL_WITHHELD` | 403 | Clinical field requested by HR/Finance |
|
|
| `ORG_PENDING` | 422 | Org not yet activated by Admin |
|
|
| `ORG_SUSPENDED` | 422 | Org suspended |
|
|
| `VALIDATION_FAILED` | 400 | Field-level validation |
|
|
| `DUPLICATE_MEMBER` | 409 | Phone/email/employee_id conflict |
|
|
| `CREDIT_LIMIT_EXCEEDED` | 422 | Would exceed org credit |
|
|
| `MIGRATION_JOB_LOCKED` | 409 | Job already committed or rolled back |
|
|
| `INVITE_EXPIRED` | 422 | Invite token expired |
|
|
| `NOT_FOUND` | 404 | Generic not found |
|
|
|
|
## Pagination
|
|
|
|
List endpoints accept:
|
|
|
|
```
|
|
?page=1&page_size=25&sort=-created_at&q=search
|
|
```
|
|
|
|
Response envelope:
|
|
|
|
```json
|
|
{
|
|
"data": [ ... ],
|
|
"pagination": {
|
|
"page": 1,
|
|
"page_size": 25,
|
|
"total_items": 142,
|
|
"total_pages": 6
|
|
}
|
|
}
|
|
```
|
|
|
|
Default `page_size`: 25. Max: 100.
|
|
|
|
## Filtering & date ranges
|
|
|
|
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...
|
|
```
|
|
|
|
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
|
|
|
|
Multipart for Rx images and migration files:
|
|
|
|
```
|
|
POST /v1/...
|
|
Content-Type: multipart/form-data
|
|
```
|
|
|
|
Max file size (Rx page): 10 MB. Accepted: `image/jpeg`, `image/png`, `application/pdf`.
|
|
|
|
Migration uploads: 50 MB `.xlsx`, `.xls`, `.csv`.
|
|
|
|
## Idempotency
|
|
|
|
Mutating endpoints that create billing or migration commits accept:
|
|
|
|
```
|
|
Idempotency-Key: <uuid>
|
|
```
|
|
|
|
Duplicate keys within 24h return the original response.
|
|
|
|
## Webhooks / events
|
|
|
|
Domain events published to platform bus; see [`events/README.md`](events/README.md). B2B emits and consumes events for org registration, migration, and prescriptions.
|
|
|
|
## Environment (Vercel frontend)
|
|
|
|
| Variable | Purpose |
|
|
|----------|---------|
|
|
| `NEXT_PUBLIC_API_BASE_URL` | Platform API base (mock or real) |
|
|
| `NEXT_PUBLIC_MOCK_AUTH` | `true` for demo role selector |
|
|
| `NEXT_PUBLIC_DEFAULT_LOCALE` | `en` |
|
|
|
|
Document production URL in this file when first Vercel deploy is finalized.
|
|
|
|
## Related docs
|
|
|
|
- [Entities index](entities/README.md)
|
|
- [Endpoints index](endpoints/README.md)
|
|
- [Events](events/README.md)
|
|
- [Features INDEX](../features/INDEX.md)
|