Files
gongxue-base/supabase/migrations/202606210006_growth_referral_crm.sql
2026-06-21 21:54:43 +08:00

151 lines
6.7 KiB
SQL

create table if not exists public.referral_codes (
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,
code citext not null,
status text not null default 'active' check (status in ('active', 'disabled')),
channel text,
landing_path text,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, user_id),
unique (tenant_id, code)
);
comment on table public.referral_codes is
'Tenant-scoped referral codes for sales, agents, teachers, operators, and other permitted referrers.';
create table if not exists public.referral_leads (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
student_user_id uuid not null references public.platform_users(id) on delete cascade,
referrer_user_id uuid references public.platform_users(id) on delete set null,
ref_code citext,
source text,
first_track_id uuid references public.referral_tracks(id) on delete set null,
bind_type text not null default 'first_touch' check (bind_type in ('first_touch', 'manual', 'imported')),
status text not null default 'protected' check (status in ('protected', 'invalid', 'released')),
protected_until timestamptz,
metadata jsonb not null default '{}'::jsonb,
bound_at timestamptz not null default now(),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, student_user_id)
);
comment on table public.referral_leads is
'First-binding lead ownership. Once a student is bound to a referrer, normal scan/share events cannot rebind the lead.';
create table if not exists public.referral_team_edges (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
member_user_id uuid not null references public.platform_users(id) on delete cascade,
leader_user_id uuid references public.platform_users(id) on delete set null,
relation_type text not null default 'sales_team' check (relation_type in ('sales_team', 'agent_network', 'teacher_class')),
status text not null default 'active' check (status in ('active', 'disabled')),
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, member_user_id, relation_type)
);
create table if not exists public.referral_qrcodes (
id uuid primary key default gen_random_uuid(),
tenant_id uuid not null references public.tenants(id) on delete cascade,
user_id uuid references public.platform_users(id) on delete set null,
ref_code citext not null,
scene text not null,
page text not null default 'pages/index/index',
provider text not null default 'wechat-miniapp',
qrcode_url text,
status text not null default 'pending' check (status in ('pending', 'ready', 'failed', 'disabled')),
error_message text,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (tenant_id, provider, scene, page)
);
alter table public.referral_tracks
add column if not exists metadata jsonb not null default '{}'::jsonb,
add column if not exists lead_id uuid references public.referral_leads(id) on delete set null;
alter table public.crm_webhook_queue
add column if not exists source text,
add column if not exists payload jsonb not null default '{}'::jsonb,
add column if not exists idempotency_key text,
add column if not exists target_url text;
alter table public.crm_webhook_log
add column if not exists request_payload jsonb not null default '{}'::jsonb;
create unique index if not exists idx_crm_queue_tenant_record_id
on public.crm_webhook_queue(tenant_id, record_id)
where record_id is not null;
create unique index if not exists idx_crm_queue_tenant_idempotency
on public.crm_webhook_queue(tenant_id, idempotency_key)
where idempotency_key is not null;
create index if not exists idx_referral_codes_tenant_code
on public.referral_codes(tenant_id, code, status);
create index if not exists idx_referral_leads_referrer
on public.referral_leads(tenant_id, referrer_user_id, bound_at desc);
create index if not exists idx_referral_team_leader
on public.referral_team_edges(tenant_id, leader_user_id, status);
create index if not exists idx_referral_tracks_lead
on public.referral_tracks(tenant_id, lead_id, created_at desc);
alter table public.referral_codes enable row level security;
alter table public.referral_leads enable row level security;
alter table public.referral_team_edges enable row level security;
alter table public.referral_qrcodes enable row level security;
drop policy if exists tenant_isolation on public.referral_codes;
create policy tenant_isolation on public.referral_codes
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.referral_leads;
create policy tenant_isolation on public.referral_leads
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.referral_team_edges;
create policy tenant_isolation on public.referral_team_edges
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.referral_qrcodes;
create policy tenant_isolation on public.referral_qrcodes
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.referral_codes;
create trigger set_updated_at
before update on public.referral_codes
for each row execute function app.touch_updated_at();
drop trigger if exists set_updated_at on public.referral_leads;
create trigger set_updated_at
before update on public.referral_leads
for each row execute function app.touch_updated_at();
drop trigger if exists set_updated_at on public.referral_team_edges;
create trigger set_updated_at
before update on public.referral_team_edges
for each row execute function app.touch_updated_at();
drop trigger if exists set_updated_at on public.referral_qrcodes;
create trigger set_updated_at
before update on public.referral_qrcodes
for each row execute function app.touch_updated_at();