alter table public.exam_dates add column if not exists description text, add column if not exists metadata jsonb not null default '{}'::jsonb; alter table public.reports add column if not exists title text, add column if not exists category text, add column if not exists priority text not null default 'normal' check (priority in ('low', 'normal', 'high', 'urgent')), add column if not exists handled_by uuid references public.platform_users(id) on delete set null, add column if not exists handled_at timestamptz, add column if not exists resolution text, add column if not exists contact text, add column if not exists attachments jsonb not null default '[]'::jsonb, add column if not exists metadata jsonb not null default '{}'::jsonb; do $$ begin if not exists (select 1 from pg_constraint where conname = 'reports_status_check') then alter table public.reports add constraint reports_status_check check (status in ('pending', 'accepted', 'rejected', 'resolved', 'closed')); end if; if not exists (select 1 from pg_constraint where conname = 'reports_type_check') then alter table public.reports add constraint reports_type_check check (type is null or type in ('question_error', 'content_error', 'video_error', 'asset_error', 'system_bug', 'suggestion', 'other')); end if; end $$; create table if not exists public.report_status_events ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, report_id uuid not null references public.reports(id) on delete cascade, from_status text, to_status text not null, note text, actor_user_id uuid references public.platform_users(id) on delete set null, metadata jsonb not null default '{}'::jsonb, created_at timestamptz not null default now() ); comment on table public.report_status_events is 'Audit timeline for student feedback/question correction status changes.'; create table if not exists public.user_score_events ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, user_id uuid not null references public.platform_users(id) on delete cascade, event_type text not null check (event_type in ('check_in', 'manual_adjust', 'feedback_reward', 'activity_reward', 'redeem_cost')), points integer not null, balance_after integer not null, source_type text, source_id uuid, idempotency_key text, metadata jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), unique (tenant_id, idempotency_key) ); comment on table public.user_score_events is 'Tenant-scoped student score ledger. Positive points add score, negative points consume score.'; create index if not exists idx_reports_tenant_status on public.reports(tenant_id, status, created_at desc); create index if not exists idx_reports_tenant_question on public.reports(tenant_id, question_id, created_at desc) where question_id is not null; create index if not exists idx_report_status_events_report on public.report_status_events(tenant_id, report_id, created_at desc); create index if not exists idx_score_events_user on public.user_score_events(tenant_id, user_id, created_at desc); create index if not exists idx_exam_dates_tenant_school on public.exam_dates(tenant_id, school_id, exam_date); alter table public.report_status_events enable row level security; alter table public.user_score_events enable row level security; drop policy if exists tenant_isolation on public.report_status_events; create policy tenant_isolation on public.report_status_events 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.user_score_events; create policy tenant_isolation on public.user_score_events 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());