alter table public.tenant_memberships add column if not exists commission_rate numeric(6,4), add column if not exists commission_config jsonb not null default '{}'::jsonb; do $$ begin if not exists (select 1 from pg_constraint where conname = 'tenant_memberships_commission_rate_check') then alter table public.tenant_memberships add constraint tenant_memberships_commission_rate_check check (commission_rate is null or (commission_rate >= 0 and commission_rate <= 1)); end if; end $$; do $$ begin if not exists (select 1 from pg_constraint where conname = 'code_batches_commission_rate_check') then alter table public.code_batches add constraint code_batches_commission_rate_check check (commission_rate is null or (commission_rate >= 0 and commission_rate <= 1)); end if; end $$; create table if not exists public.tenant_commission_settings ( tenant_id uuid primary key references public.tenants(id) on delete cascade, default_rate numeric(6,4) not null default 0.2000 check (default_rate >= 0 and default_rate <= 1), min_settlement_cents integer not null default 0 check (min_settlement_cents >= 0), settlement_cycle text not null default 'monthly' check (settlement_cycle in ('manual', 'weekly', 'monthly')), config jsonb not null default '{}'::jsonb, 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() ); create table if not exists public.commission_settlements ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, settlement_no text not null, referrer_user_id uuid not null references public.platform_users(id) on delete cascade, status text not null default 'draft' check (status in ('draft', 'pending_review', 'approved', 'paid', 'rejected', 'cancelled')), period_start date not null, period_end date not null, source_count integer not null default 0 check (source_count >= 0), paid_user_count integer not null default 0 check (paid_user_count >= 0), gross_amount_cents integer not null default 0 check (gross_amount_cents >= 0), commission_amount_cents integer not null default 0 check (commission_amount_cents >= 0), default_rate numeric(6,4) not null default 0 check (default_rate >= 0 and default_rate <= 1), effective_rate numeric(6,4), generated_by uuid references public.platform_users(id) on delete set null, reviewed_by uuid references public.platform_users(id) on delete set null, paid_by uuid references public.platform_users(id) on delete set null, reviewed_at timestamptz, paid_at timestamptz, payment_method text, payment_account text, remark text, metadata jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (tenant_id, settlement_no), unique (tenant_id, referrer_user_id, period_start, period_end) ); create table if not exists public.commission_settlement_items ( id uuid primary key default gen_random_uuid(), tenant_id uuid not null references public.tenants(id) on delete cascade, settlement_id uuid references public.commission_settlements(id) on delete set null, referrer_user_id uuid not null references public.platform_users(id) on delete cascade, student_user_id uuid references public.platform_users(id) on delete set null, source_type text not null check (source_type in ('order', 'activation_code')), source_id uuid not null, source_no text, source_paid_at timestamptz, gross_amount_cents integer not null default 0 check (gross_amount_cents >= 0), commission_rate numeric(6,4) not null check (commission_rate >= 0 and commission_rate <= 1), commission_amount_cents integer not null default 0 check (commission_amount_cents >= 0), rate_source text not null default 'default' check (rate_source in ('batch', 'member', 'default')), attribution_type text not null default 'protected_lead', metadata jsonb not null default '{}'::jsonb, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (tenant_id, source_type, source_id) ); create index if not exists idx_commission_items_referrer on public.commission_settlement_items(tenant_id, referrer_user_id, source_paid_at desc); create index if not exists idx_commission_items_settlement on public.commission_settlement_items(tenant_id, settlement_id); create index if not exists idx_commission_settlements_referrer on public.commission_settlements(tenant_id, referrer_user_id, period_start desc); alter table public.tenant_commission_settings enable row level security; alter table public.commission_settlements enable row level security; alter table public.commission_settlement_items enable row level security; drop policy if exists tenant_isolation on public.tenant_commission_settings; create policy tenant_isolation on public.tenant_commission_settings 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.commission_settlements; create policy tenant_isolation on public.commission_settlements 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.commission_settlement_items; create policy tenant_isolation on public.commission_settlement_items 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_commission_settings; create trigger set_updated_at before update on public.tenant_commission_settings for each row execute function app.touch_updated_at(); drop trigger if exists set_updated_at on public.commission_settlements; create trigger set_updated_at before update on public.commission_settlements for each row execute function app.touch_updated_at(); drop trigger if exists set_updated_at on public.commission_settlement_items; create trigger set_updated_at before update on public.commission_settlement_items for each row execute function app.touch_updated_at();