forked from wangziqi/gongxue-base
179 lines
8.2 KiB
SQL
179 lines
8.2 KiB
SQL
create table if not exists public.point_activity_tasks (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
legacy_id text,
|
|
code extensions.citext not null,
|
|
title text not null,
|
|
description text,
|
|
task_type text not null default 'manual'
|
|
check (task_type in (
|
|
'daily_check_in',
|
|
'feedback_submit',
|
|
'feedback_resolved',
|
|
'practice_complete',
|
|
'vocabulary_review',
|
|
'mock_exam_submit',
|
|
'manual'
|
|
)),
|
|
reward_points integer not null default 0 check (reward_points > 0 and reward_points <= 100000),
|
|
claim_limit_per_user integer not null default 1 check (claim_limit_per_user > 0 and claim_limit_per_user <= 100000),
|
|
period_type text not null default 'once'
|
|
check (period_type in ('once', 'daily', 'weekly', 'monthly', 'unlimited')),
|
|
valid_from timestamptz,
|
|
valid_to timestamptz,
|
|
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,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (tenant_id, code),
|
|
unique (tenant_id, legacy_id)
|
|
);
|
|
|
|
comment on table public.point_activity_tasks is
|
|
'Tenant-scoped score activity tasks configured by marketing/operations teams.';
|
|
|
|
create table if not exists public.user_point_activity_claims (
|
|
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,
|
|
task_id uuid not null references public.point_activity_tasks(id) on delete cascade,
|
|
period_key text not null,
|
|
score_event_id uuid references public.user_score_events(id) on delete set null,
|
|
source_type text,
|
|
source_id uuid,
|
|
status text not null default 'claimed'
|
|
check (status in ('claimed', 'cancelled')),
|
|
metadata jsonb not null default '{}'::jsonb,
|
|
claimed_at timestamptz not null default now(),
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (tenant_id, user_id, task_id, period_key)
|
|
);
|
|
|
|
comment on table public.user_point_activity_claims is
|
|
'Idempotent student claim records for activity rewards.';
|
|
|
|
create table if not exists public.point_exchange_items (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
legacy_id text,
|
|
code extensions.citext not null,
|
|
title text not null,
|
|
description text,
|
|
cost_points integer not null check (cost_points > 0 and cost_points <= 10000000),
|
|
item_type text not null default 'manual'
|
|
check (item_type in ('coupon', 'manual', 'asset', 'custom')),
|
|
coupon_id uuid references public.coupons(id) on delete set null,
|
|
asset_id uuid references public.content_assets(id) on delete set null,
|
|
stock_total integer check (stock_total is null or stock_total >= 0),
|
|
stock_used integer not null default 0 check (stock_used >= 0),
|
|
per_user_limit integer not null default 1 check (per_user_limit > 0 and per_user_limit <= 100000),
|
|
valid_from timestamptz,
|
|
valid_to timestamptz,
|
|
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,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (tenant_id, code),
|
|
unique (tenant_id, legacy_id),
|
|
check (stock_total is null or stock_used <= stock_total)
|
|
);
|
|
|
|
comment on table public.point_exchange_items is
|
|
'Tenant-scoped score exchange catalog. Coupon fulfillment is supported, activation-code fulfillment is intentionally kept out of the first contract.';
|
|
|
|
create table if not exists public.user_point_exchange_orders (
|
|
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,
|
|
item_id uuid not null references public.point_exchange_items(id) on delete restrict,
|
|
status text not null default 'completed'
|
|
check (status in ('completed', 'pending_fulfillment', 'cancelled')),
|
|
cost_points integer not null check (cost_points > 0),
|
|
score_event_id uuid references public.user_score_events(id) on delete set null,
|
|
coupon_redemption_id uuid references public.coupon_redemptions(id) on delete set null,
|
|
asset_id uuid references public.content_assets(id) on delete set null,
|
|
idempotency_key text,
|
|
metadata jsonb not null default '{}'::jsonb,
|
|
exchanged_at timestamptz not null default now(),
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (tenant_id, user_id, idempotency_key)
|
|
);
|
|
|
|
comment on table public.user_point_exchange_orders is
|
|
'Student score exchange orders. Each completed order has a negative redeem_cost ledger event.';
|
|
|
|
create index if not exists idx_point_activity_tasks_tenant_status
|
|
on public.point_activity_tasks(tenant_id, status, sort_order, created_at desc);
|
|
|
|
create index if not exists idx_point_activity_claims_user
|
|
on public.user_point_activity_claims(tenant_id, user_id, claimed_at desc);
|
|
|
|
create index if not exists idx_point_activity_claims_task
|
|
on public.user_point_activity_claims(tenant_id, task_id, claimed_at desc);
|
|
|
|
create index if not exists idx_point_exchange_items_tenant_status
|
|
on public.point_exchange_items(tenant_id, status, sort_order, created_at desc);
|
|
|
|
create index if not exists idx_point_exchange_orders_user
|
|
on public.user_point_exchange_orders(tenant_id, user_id, exchanged_at desc);
|
|
|
|
create index if not exists idx_point_exchange_orders_item
|
|
on public.user_point_exchange_orders(tenant_id, item_id, exchanged_at desc);
|
|
|
|
alter table public.point_activity_tasks enable row level security;
|
|
alter table public.user_point_activity_claims enable row level security;
|
|
alter table public.point_exchange_items enable row level security;
|
|
alter table public.user_point_exchange_orders enable row level security;
|
|
|
|
drop policy if exists tenant_isolation on public.point_activity_tasks;
|
|
create policy tenant_isolation on public.point_activity_tasks
|
|
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_point_activity_claims;
|
|
create policy tenant_isolation on public.user_point_activity_claims
|
|
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.point_exchange_items;
|
|
create policy tenant_isolation on public.point_exchange_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 policy if exists tenant_isolation on public.user_point_exchange_orders;
|
|
create policy tenant_isolation on public.user_point_exchange_orders
|
|
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.point_activity_tasks;
|
|
create trigger set_updated_at
|
|
before update on public.point_activity_tasks
|
|
for each row execute function app.touch_updated_at();
|
|
|
|
drop trigger if exists set_updated_at on public.user_point_activity_claims;
|
|
create trigger set_updated_at
|
|
before update on public.user_point_activity_claims
|
|
for each row execute function app.touch_updated_at();
|
|
|
|
drop trigger if exists set_updated_at on public.point_exchange_items;
|
|
create trigger set_updated_at
|
|
before update on public.point_exchange_items
|
|
for each row execute function app.touch_updated_at();
|
|
|
|
drop trigger if exists set_updated_at on public.user_point_exchange_orders;
|
|
create trigger set_updated_at
|
|
before update on public.user_point_exchange_orders
|
|
for each row execute function app.touch_updated_at();
|