create table if not exists public.tenant_classes ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, region_id uuid references public.regions(id) on delete set null, legacy_id text, code text, name text not null, description text, status text not null default 'active' check (status in ('active', 'disabled', 'archived')), sort_order integer not null default 0, metadata jsonb not null default '{}'::jsonb, created_by uuid references public.platform_users(id) on delete set null, updated_by uuid references public.platform_users(id) on delete set null, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (tenant_id, id), unique (tenant_id, legacy_id) ); comment on table public.tenant_classes is 'Tenant-scoped class/cohort records used for teacher, head-teacher, assistant and student data scopes.'; create unique index if not exists idx_tenant_classes_code_unique on public.tenant_classes(tenant_id, lower(code)) where code is not null and code <> ''; create index if not exists idx_tenant_classes_tenant_status on public.tenant_classes(tenant_id, status, sort_order, created_at desc); create index if not exists idx_tenant_classes_region on public.tenant_classes(tenant_id, region_id) where region_id is not null; create table if not exists public.tenant_class_members ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, class_id uuid not null, user_id uuid not null references public.platform_users(id) on delete cascade, member_type text not null check (member_type in ('student', 'teacher', 'assistant', 'head_teacher')), status text not null default 'active' check (status in ('active', 'disabled', 'removed')), joined_at timestamptz not null default now(), left_at timestamptz, metadata jsonb not null default '{}'::jsonb, created_by uuid references public.platform_users(id) on delete set null, updated_by uuid references public.platform_users(id) on delete set null, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (tenant_id, class_id, user_id, member_type), foreign key (tenant_id, class_id) references public.tenant_classes(tenant_id, id) on delete cascade ); comment on table public.tenant_class_members is 'Class membership assignments. Students can belong to multiple classes; teachers gain scoped visibility through active class memberships.'; create index if not exists idx_class_members_tenant_class on public.tenant_class_members(tenant_id, class_id, status, member_type); create index if not exists idx_class_members_tenant_user on public.tenant_class_members(tenant_id, user_id, status, member_type); alter table public.tenant_classes enable row level security; alter table public.tenant_class_members enable row level security; drop policy if exists tenant_isolation on public.tenant_classes; create policy tenant_isolation on public.tenant_classes for all using (tenant_id = app.current_tenant_id() or app.is_platform_admin()) with check (tenant_id = app.current_tenant_id() or app.is_platform_admin()); drop policy if exists tenant_isolation on public.tenant_class_members; create policy tenant_isolation on public.tenant_class_members for all using (tenant_id = app.current_tenant_id() or app.is_platform_admin()) with check (tenant_id = app.current_tenant_id() or app.is_platform_admin()); drop trigger if exists set_updated_at on public.tenant_classes; create trigger set_updated_at before update on public.tenant_classes for each row execute function app.touch_updated_at(); drop trigger if exists set_updated_at on public.tenant_class_members; create trigger set_updated_at before update on public.tenant_class_members for each row execute function app.touch_updated_at();