forked from wangziqi/gongxue-base
182 lines
7.4 KiB
SQL
182 lines
7.4 KiB
SQL
create table if not exists public.platform_saas_plans (
|
|
id uuid primary key default gen_random_uuid(),
|
|
code text not null unique,
|
|
name text not null,
|
|
description text,
|
|
billing_cycle text not null default 'yearly' check (billing_cycle in ('monthly', 'quarterly', 'yearly', 'one_time')),
|
|
base_amount_cents integer not null default 0,
|
|
currency text not null default 'CNY',
|
|
included_quotas jsonb not null default '{}'::jsonb,
|
|
overage_prices jsonb not null default '{}'::jsonb,
|
|
feature_flags jsonb not null default '{}'::jsonb,
|
|
status text not null default 'active' check (status in ('active', 'archived')),
|
|
sort_order integer not null default 0,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists public.tenant_billing_profiles (
|
|
tenant_id uuid primary key references public.tenants(id) on delete cascade,
|
|
billing_name text,
|
|
tax_id text,
|
|
contact_name text,
|
|
contact_phone text,
|
|
contact_email extensions.citext,
|
|
billing_address text,
|
|
invoice_title text,
|
|
invoice_type text check (invoice_type in ('none', 'normal_vat', 'special_vat')),
|
|
bank_name text,
|
|
bank_account_masked text,
|
|
metadata jsonb not null default '{}'::jsonb,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists public.tenant_invoices (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
invoice_no text not null unique,
|
|
invoice_type text not null default 'subscription' check (invoice_type in ('subscription', 'service_fee', 'usage_overage', 'manual_adjustment')),
|
|
status text not null default 'draft' check (status in ('draft', 'issued', 'paid', 'void', 'overdue')),
|
|
currency text not null default 'CNY',
|
|
subtotal_cents integer not null default 0,
|
|
discount_cents integer not null default 0,
|
|
tax_cents integer not null default 0,
|
|
total_cents integer not null default 0,
|
|
paid_cents integer not null default 0,
|
|
balance_cents integer not null default 0,
|
|
billing_period_start date,
|
|
billing_period_end date,
|
|
due_date date,
|
|
issued_at timestamptz,
|
|
paid_at timestamptz,
|
|
note text,
|
|
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()
|
|
);
|
|
|
|
create table if not exists public.tenant_invoice_items (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
invoice_id uuid not null references public.tenant_invoices(id) on delete cascade,
|
|
item_type text not null default 'subscription',
|
|
item_ref_id uuid,
|
|
description text not null,
|
|
quantity numeric(12,2) not null default 1,
|
|
unit_amount_cents integer not null default 0,
|
|
amount_cents integer not null default 0,
|
|
metadata jsonb not null default '{}'::jsonb,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists public.tenant_invoice_payments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
tenant_id uuid not null references public.tenants(id) on delete cascade,
|
|
invoice_id uuid not null references public.tenant_invoices(id) on delete cascade,
|
|
payment_no text not null unique,
|
|
provider text not null default 'manual',
|
|
method text,
|
|
status text not null default 'paid' check (status in ('pending', 'paid', 'failed', 'refunded')),
|
|
amount_cents integer not null default 0,
|
|
paid_at timestamptz,
|
|
provider_trade_no text,
|
|
received_by uuid references public.platform_users(id) on delete set null,
|
|
raw_payload jsonb not null default '{}'::jsonb,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_saas_plans_status on public.platform_saas_plans(status, sort_order);
|
|
create index if not exists idx_tenant_subscriptions_tenant_status on public.tenant_subscriptions(tenant_id, status, expires_at desc);
|
|
create index if not exists idx_tenant_usage_records_lookup on public.tenant_usage_records(tenant_id, metric_key, period_start, period_end);
|
|
create index if not exists idx_tenant_invoices_tenant_status on public.tenant_invoices(tenant_id, status, due_date desc);
|
|
create index if not exists idx_tenant_invoice_items_invoice on public.tenant_invoice_items(invoice_id);
|
|
create index if not exists idx_tenant_invoice_payments_invoice on public.tenant_invoice_payments(invoice_id, status);
|
|
|
|
alter table public.platform_saas_plans enable row level security;
|
|
alter table public.tenant_billing_profiles enable row level security;
|
|
alter table public.tenant_invoices enable row level security;
|
|
alter table public.tenant_invoice_items enable row level security;
|
|
alter table public.tenant_invoice_payments enable row level security;
|
|
|
|
drop policy if exists platform_admin_saas_plans on public.platform_saas_plans;
|
|
create policy platform_admin_saas_plans on public.platform_saas_plans
|
|
for all
|
|
using (app.is_platform_admin())
|
|
with check (app.is_platform_admin());
|
|
|
|
do $$
|
|
declare
|
|
table_name text;
|
|
begin
|
|
foreach table_name in array array[
|
|
'tenant_billing_profiles',
|
|
'tenant_invoices',
|
|
'tenant_invoice_items',
|
|
'tenant_invoice_payments'
|
|
]
|
|
loop
|
|
execute format('drop policy if exists tenant_isolation on public.%I', table_name);
|
|
execute format(
|
|
'create policy tenant_isolation on public.%I 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())',
|
|
table_name
|
|
);
|
|
end loop;
|
|
end $$;
|
|
|
|
do $$
|
|
declare
|
|
table_name text;
|
|
begin
|
|
foreach table_name in array array[
|
|
'platform_saas_plans',
|
|
'tenant_billing_profiles',
|
|
'tenant_invoices',
|
|
'tenant_invoice_payments'
|
|
]
|
|
loop
|
|
execute format('drop trigger if exists set_updated_at on public.%I', table_name);
|
|
execute format('create trigger set_updated_at before update on public.%I for each row execute function app.touch_updated_at()', table_name);
|
|
end loop;
|
|
end $$;
|
|
|
|
insert into public.platform_saas_plans (
|
|
code, name, description, billing_cycle, base_amount_cents,
|
|
included_quotas, overage_prices, feature_flags, sort_order
|
|
)
|
|
values
|
|
(
|
|
'starter_yearly',
|
|
'合作商基础版',
|
|
'适合单地区题库合作商,含基础品牌与域名能力。',
|
|
'yearly',
|
|
980000,
|
|
'{"students":1000,"questions":5000,"storageGb":20}'::jsonb,
|
|
'{"studentsExtraPerYearCents":500,"storageGbPerYearCents":12000}'::jsonb,
|
|
'{"customDomain":true,"tenantBranding":true,"paymentTenantCollect":false}'::jsonb,
|
|
10
|
|
),
|
|
(
|
|
'pro_yearly',
|
|
'合作商专业版',
|
|
'适合多地区、多课程运营,支持更多运营工具。',
|
|
'yearly',
|
|
1980000,
|
|
'{"students":5000,"questions":30000,"storageGb":100}'::jsonb,
|
|
'{"studentsExtraPerYearCents":300,"storageGbPerYearCents":9000}'::jsonb,
|
|
'{"customDomain":true,"tenantBranding":true,"paymentTenantCollect":true,"crmWebhook":true}'::jsonb,
|
|
20
|
|
)
|
|
on conflict (code)
|
|
do update set name = excluded.name,
|
|
description = excluded.description,
|
|
billing_cycle = excluded.billing_cycle,
|
|
base_amount_cents = excluded.base_amount_cents,
|
|
included_quotas = excluded.included_quotas,
|
|
overage_prices = excluded.overage_prices,
|
|
feature_flags = excluded.feature_flags,
|
|
sort_order = excluded.sort_order,
|
|
updated_at = now();
|