Adds db/schema.sql (12 tables), db/policies.sql (RLS on all 12,
audit_log append-only), db/auth-hook.sql (role/org_id into JWT per
AD-2), db/seed.sql (org + 3 departments, part 2 deferred to M1 auth).
Wires lib/supabase/{client,server,service}.ts per AD-3 and adds
/db-check page confirming DB connectivity and RLS deny-by-default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FcktbLXSSXzx23GCue813e
158 lines
5.9 KiB
SQL
158 lines
5.9 KiB
SQL
create type user_role as enum ('admin','approver','editor','staff');
|
|
create type sop_status as enum ('draft','submitted','approved','published','archived');
|
|
create type lang_code as enum ('en','ms','zh');
|
|
|
|
create table orgs (
|
|
id uuid primary key default gen_random_uuid(),
|
|
name text not null,
|
|
created_at timestamptz default now()
|
|
);
|
|
|
|
create table departments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
name text not null,
|
|
unique (org_id, name)
|
|
);
|
|
|
|
create table profiles (
|
|
id uuid primary key references auth.users(id) on delete cascade,
|
|
org_id uuid not null references orgs(id),
|
|
full_name text not null,
|
|
role user_role not null default 'staff',
|
|
department_id uuid references departments(id),
|
|
preferred_language lang_code not null default 'en',
|
|
active boolean not null default true,
|
|
created_at timestamptz default now()
|
|
);
|
|
|
|
create table sops (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
code text not null, -- e.g. WH-PICK-001
|
|
title text not null,
|
|
department_id uuid references departments(id),
|
|
category text,
|
|
owner_id uuid references profiles(id),
|
|
status sop_status not null default 'draft',
|
|
review_months int not null default 12 check (review_months in (3,6,12,24)),
|
|
draft_content jsonb not null default '{}'::jsonb, -- working copy (sections, steps)
|
|
current_version_id uuid, -- FK added after sop_versions exists
|
|
published_at timestamptz,
|
|
created_by uuid references profiles(id),
|
|
created_at timestamptz default now(),
|
|
updated_at timestamptz default now(),
|
|
unique (org_id, code)
|
|
);
|
|
|
|
create table sop_versions (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
sop_id uuid not null references sops(id) on delete cascade,
|
|
version_label text not null, -- '1.0', '1.1', '2.0'
|
|
content jsonb not null, -- FROZEN snapshot of sections at publish
|
|
change_note text,
|
|
is_major boolean not null default false,
|
|
published_by uuid references profiles(id),
|
|
published_at timestamptz default now(),
|
|
unique (sop_id, version_label)
|
|
);
|
|
|
|
alter table sops
|
|
add constraint fk_current_version
|
|
foreign key (current_version_id) references sop_versions(id);
|
|
|
|
create table approvals (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
sop_id uuid not null references sops(id) on delete cascade,
|
|
decision text not null check (decision in ('approved','rejected')),
|
|
comment text,
|
|
decided_by uuid not null references profiles(id),
|
|
decided_at timestamptz default now()
|
|
);
|
|
|
|
create table sop_assignments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
sop_id uuid not null references sops(id) on delete cascade,
|
|
department_id uuid not null references departments(id),
|
|
assigned_by uuid references profiles(id),
|
|
assigned_at timestamptz default now(),
|
|
unique (sop_id, department_id)
|
|
);
|
|
|
|
create table acknowledgements (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
sop_version_id uuid not null references sop_versions(id),
|
|
user_id uuid not null references profiles(id),
|
|
language_viewed lang_code not null,
|
|
typed_name text not null, -- the "signature"
|
|
acknowledged_at timestamptz default now(),
|
|
unique (sop_version_id, user_id) -- one ack per user per version
|
|
);
|
|
|
|
create table sop_translations (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
sop_version_id uuid not null references sop_versions(id) on delete cascade,
|
|
language lang_code not null,
|
|
content jsonb not null,
|
|
machine boolean not null default true,
|
|
reviewed_by uuid references profiles(id),
|
|
reviewed_at timestamptz,
|
|
created_at timestamptz default now(),
|
|
unique (sop_version_id, language)
|
|
);
|
|
|
|
create table audit_log (
|
|
id bigint generated always as identity primary key,
|
|
org_id uuid not null references orgs(id),
|
|
actor_id uuid references profiles(id),
|
|
action text not null, -- 'sop.published', 'ack.recorded', ...
|
|
entity_type text not null, -- 'sop','user','acknowledgement'
|
|
entity_id uuid,
|
|
detail jsonb,
|
|
created_at timestamptz default now()
|
|
);
|
|
-- Append-only: grant INSERT/SELECT; never UPDATE/DELETE (enforced by grants + no policy).
|
|
|
|
create table ai_log (
|
|
id bigint generated always as identity primary key,
|
|
org_id uuid not null references orgs(id),
|
|
user_id uuid references profiles(id),
|
|
kind text not null check (kind in ('draft','translate')),
|
|
input_chars int, output_chars int,
|
|
input_tokens int, output_tokens int,
|
|
model text, success boolean, error text,
|
|
created_at timestamptz default now()
|
|
);
|
|
|
|
create type incident_severity as enum ('low','medium','high');
|
|
create type incident_status as enum ('open','reviewed','closed');
|
|
|
|
create table incidents (
|
|
id uuid primary key default gen_random_uuid(),
|
|
org_id uuid not null references orgs(id),
|
|
reporter_id uuid not null references profiles(id),
|
|
department_id uuid references departments(id), -- copied from reporter at insert
|
|
sop_id uuid references sops(id), -- optional link
|
|
sop_version_id uuid references sop_versions(id), -- optional, set when reported from viewer
|
|
description text not null,
|
|
severity incident_severity not null default 'medium',
|
|
photo_path text, -- Storage path, optional
|
|
status incident_status not null default 'open',
|
|
resolution_note text,
|
|
reviewed_by uuid references profiles(id),
|
|
closed_at timestamptz,
|
|
created_at timestamptz default now()
|
|
);
|
|
|
|
create index idx_incidents_status on incidents(org_id, status, severity);
|
|
create index idx_incidents_sop on incidents(sop_id);
|
|
|
|
create index idx_sops_org_status on sops(org_id, status);
|
|
create index idx_ack_version on acknowledgements(sop_version_id);
|
|
create index idx_audit_entity on audit_log(entity_type, entity_id);
|