feat: add platform dunning notifications

This commit is contained in:
Codex
2026-06-30 07:28:32 +08:00
parent e66623158d
commit 51f8cbdec8
26 changed files with 1782 additions and 24 deletions

View File

@@ -0,0 +1,88 @@
create table if not exists public.platform_dunning_notification_channels (
id uuid primary key default gen_random_uuid(),
channel_code text not null unique,
name text not null,
description text,
enabled boolean not null default true,
provider text not null default 'generic'
check (provider in ('generic', 'dingtalk', 'feishu', 'wecom')),
webhook_url text not null,
secret_ref text,
reminder_types text[] not null default array['overdue', 'final_notice']::text[],
reminder_channels text[] not null default array['internal']::text[],
min_reminder_level integer not null default 1 check (min_reminder_level between 1 and 20),
tenant_ids uuid[] not null default '{}'::uuid[],
timeout_sec integer not null default 10 check (timeout_sec between 1 and 60),
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
constraint platform_dunning_notification_reminder_types_check
check (reminder_types <@ array['due_soon', 'overdue', 'final_notice', 'manual']::text[]),
constraint platform_dunning_notification_reminder_channels_check
check (reminder_channels <@ array['manual', 'internal', 'sms', 'email', 'wechat', 'crm']::text[])
);
create table if not exists public.platform_dunning_notification_events (
id uuid primary key default gen_random_uuid(),
channel_id uuid not null references public.platform_dunning_notification_channels(id) on delete cascade,
reminder_id uuid not null references public.tenant_invoice_reminders(id) on delete cascade,
invoice_id uuid not null references public.tenant_invoices(id) on delete cascade,
tenant_id uuid not null references public.tenants(id) on delete cascade,
provider text not null default 'generic'
check (provider in ('generic', 'dingtalk', 'feishu', 'wecom')),
status text not null default 'pending'
check (status in ('pending', 'processing', 'sent', 'retrying', 'failed', 'discarded')),
attempts integer not null default 0 check (attempts >= 0),
scheduled_at timestamptz not null default now(),
next_attempt_at timestamptz,
last_attempt_at timestamptz,
sent_at timestamptz,
last_error text,
last_http_code integer,
last_response_summary text,
request_payload jsonb not null default '{}'::jsonb,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique (channel_id, reminder_id)
);
create index if not exists idx_platform_dunning_notification_channels_enabled
on public.platform_dunning_notification_channels(enabled, min_reminder_level, channel_code);
create index if not exists idx_platform_dunning_notification_events_due
on public.platform_dunning_notification_events(status, coalesce(next_attempt_at, scheduled_at), created_at);
create index if not exists idx_platform_dunning_notification_events_reminder
on public.platform_dunning_notification_events(reminder_id, status, created_at desc);
create index if not exists idx_platform_dunning_notification_events_invoice
on public.platform_dunning_notification_events(invoice_id, status, created_at desc);
create index if not exists idx_platform_dunning_notification_events_tenant
on public.platform_dunning_notification_events(tenant_id, status, created_at desc);
alter table public.platform_dunning_notification_channels enable row level security;
alter table public.platform_dunning_notification_events enable row level security;
drop policy if exists platform_admin_dunning_notification_channels on public.platform_dunning_notification_channels;
create policy platform_admin_dunning_notification_channels on public.platform_dunning_notification_channels
for all
using (app.is_platform_admin())
with check (app.is_platform_admin());
drop policy if exists platform_admin_dunning_notification_events on public.platform_dunning_notification_events;
create policy platform_admin_dunning_notification_events on public.platform_dunning_notification_events
for all
using (app.is_platform_admin())
with check (app.is_platform_admin());
drop trigger if exists set_updated_at on public.platform_dunning_notification_channels;
create trigger set_updated_at
before update on public.platform_dunning_notification_channels
for each row execute function app.touch_updated_at();
drop trigger if exists set_updated_at on public.platform_dunning_notification_events;
create trigger set_updated_at
before update on public.platform_dunning_notification_events
for each row execute function app.touch_updated_at();