Add spec pack
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# API Specification
|
||||
## SOP Governance Tool — Stage 1 (Next.js Route Handlers)
|
||||
|
||||
Conventions: JSON in/out · session cookie auth (Supabase) verified server-side on every route · errors as `{ "error": { "code": "FORBIDDEN", "message": "…" } }` with proper HTTP status · every mutation writes `audit_log` in the same transaction.
|
||||
|
||||
Plain reads (lists, dashboard) may query Supabase directly from server components under RLS; the routes below are the **mutations and AI calls** — the state machine lives here and only here (`lib/workflow.ts`).
|
||||
|
||||
## 1. SOPs
|
||||
|
||||
### POST /api/sops — create draft (editor+)
|
||||
Req: `{ "code":"WH-PICK-001", "title":"Order Picking", "department_id":"…", "category":"Warehouse", "review_months":12 }`
|
||||
Res 201: `{ "id":"…", "status":"draft" }`
|
||||
409 if code exists.
|
||||
|
||||
### PATCH /api/sops/:id — update draft content/meta (editor+; only when status = draft)
|
||||
Req: `{ "draft_content": { …sections… }, "title?":"…" }` → Res 200 `{ "ok":true, "saved_at":"…" }`
|
||||
409 `NOT_EDITABLE` if not draft.
|
||||
|
||||
### POST /api/sops/:id/submit — draft → submitted (editor+)
|
||||
Validates mandatory sections (purpose, ≥1 step). Res 200 `{ "status":"submitted" }`
|
||||
422 `VALIDATION` listing missing sections.
|
||||
|
||||
### POST /api/sops/:id/decide — approve/reject (approver/admin)
|
||||
Req: `{ "decision":"approved" | "rejected", "comment":"…" }` (comment required on reject)
|
||||
approved → status `approved`; rejected → status `draft`. Writes `approvals` row.
|
||||
Res 200 `{ "status":"approved" }`.
|
||||
|
||||
### POST /api/sops/:id/publish — approved → published (admin)
|
||||
Req: `{ "change_note":"Initial release", "is_major":false }`
|
||||
Logic (one transaction): compute next label via `nextVersion(current, is_major)` (none→1.0, 1.0+minor→1.1, +major→2.0) → insert `sop_versions` snapshot of `draft_content` → set `sops.current_version_id`, `status='published'`, `published_at` → mark existing translations of prior version stale (no-op flag; new version simply has none) → audit.
|
||||
Res 200 `{ "version_label":"1.0", "version_id":"…" }`.
|
||||
|
||||
### POST /api/sops/:id/assign — set departments (admin)
|
||||
Req: `{ "department_ids": ["…","…"] }` (replaces the set)
|
||||
Res 200 `{ "assigned_departments": 2 }`.
|
||||
|
||||
## 2. Acknowledgement
|
||||
|
||||
### POST /api/ack (staff, self only)
|
||||
Req: `{ "sop_version_id":"…", "language_viewed":"ms", "typed_name":"Ahmad bin Ali" }`
|
||||
Checks: user's department is assigned to the SOP; version is the current one; typed_name non-empty.
|
||||
Res 201 `{ "acknowledged_at":"…" }` · 409 `ALREADY_ACKNOWLEDGED` · 403 if not an assignee.
|
||||
|
||||
### GET /api/sops/:id/ack-report (approver/admin)
|
||||
Res 200:
|
||||
```json
|
||||
{ "sop": { "code":"WH-PICK-001", "title":"…", "version":"1.2" },
|
||||
"summary": { "assignees": 24, "acknowledged": 21, "pct": 87.5 },
|
||||
"rows": [ { "name":"Ahmad", "department":"Outbound",
|
||||
"status":"acknowledged", "version":"1.2",
|
||||
"language":"ms", "at":"2026-07-01T02:11:00Z" },
|
||||
{ "name":"Ravi", "department":"Outbound", "status":"outstanding" } ] }
|
||||
```
|
||||
`?format=csv` streams CSV of the same.
|
||||
|
||||
## 3. AI
|
||||
|
||||
### POST /api/ai/draft (editor+, rate-limited 20/day)
|
||||
Req: `{ "description":"How we pick e-commerce orders…", "notes?":"pasted messy notes" }`
|
||||
Server: prompt template → Anthropic → parse strict JSON (retry once) → log `ai_log`.
|
||||
Res 200: `{ "draft": { "purpose":"…", "scope":"…", "roles":"…", "steps":[{"order":1,"text":"…"}], "exceptions":"…", "safety":"…", "records":"…" } }`
|
||||
Client fills the editor; **user must Save** — the route never writes to `sops`.
|
||||
429 `RATE_LIMIT` · 502 `AI_FAILED` (input preserved client-side).
|
||||
|
||||
### POST /api/ai/translate (editor+)
|
||||
Req: `{ "sop_version_id":"…", "languages":["ms","zh"] }`
|
||||
Res 200: `{ "created":[ {"language":"ms","translation_id":"…"} ] }` (upserts `sop_translations`, `machine=true`).
|
||||
|
||||
### POST /api/translations/:id/review (editor+) — human sign-off
|
||||
Res 200 `{ "reviewed": true }`. Staff see safety sections in a translation only when reviewed (FRD-6.2).
|
||||
|
||||
## 4. Users & Departments (admin)
|
||||
|
||||
- `POST /api/users` `{ email, full_name, role, department_id, preferred_language }` → creates auth user (invite email) + profile.
|
||||
- `PATCH /api/users/:id` `{ role?, department_id?, active?, preferred_language? }`
|
||||
- `POST /api/departments` `{ name }` · `PATCH /api/departments/:id` `{ name }`
|
||||
- `DELETE /api/departments/:id` → 409 `HAS_USERS` if occupied.
|
||||
|
||||
## 5. Incidents
|
||||
|
||||
### POST /api/incidents (any authenticated role)
|
||||
Req: `{ "description":"Pallet label mismatch on inbound", "severity":"high", "sop_id?":"…", "sop_version_id?":"…", "photo_path?":"sop-photos/…" }`
|
||||
Server sets reporter_id + department from the session profile. Res 201 `{ "id":"…", "status":"open" }` · 422 if description empty.
|
||||
|
||||
### PATCH /api/incidents/:id (approver/admin)
|
||||
Req: `{ "status":"reviewed" | "closed", "resolution_note?":"Racking relabeled; SOP WH-INB-002 updated" }`
|
||||
Transitions: open→reviewed→closed only; closed is immutable (409 `CLOSED`). Writes audit (`incident.reviewed` / `incident.closed`). Res 200 `{ "status":"closed" }`.
|
||||
|
||||
Reads (incident list, filters, per-SOP tab) go through RLS-scoped queries in server components — staff see only their own reports; approver/admin see all.
|
||||
|
||||
## 6. Status Codes Summary
|
||||
200/201 success · 400 malformed · 401 no session · 403 wrong role / not assignee · 404 not found in org · 409 state conflict (duplicate code, not editable, already acknowledged) · 422 validation with field list · 429 AI rate limit · 500/502 server or AI failure.
|
||||
Reference in New Issue
Block a user