Files
myeasycms-v2/apps/web/supabase/migrations/20260407000001_finance_sepa.sql
2026-03-29 19:44:57 +02:00

119 lines
7.0 KiB
SQL

/*
* -------------------------------------------------------
* Finance / SEPA Schema
* Phase 8: sepa_batches, sepa_items, invoices, invoice_items
* -------------------------------------------------------
*/
create type public.sepa_batch_type as enum('direct_debit', 'credit_transfer');
create type public.sepa_batch_status as enum('draft', 'ready', 'submitted', 'executed', 'failed', 'cancelled');
create type public.sepa_item_status as enum('pending', 'success', 'failed', 'rejected');
create type public.invoice_status as enum('draft', 'sent', 'paid', 'overdue', 'cancelled', 'credited');
-- SEPA batches
create table if not exists public.sepa_batches (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.accounts(id) on delete cascade,
batch_type public.sepa_batch_type not null,
status public.sepa_batch_status not null default 'draft',
description text,
execution_date date not null,
total_amount numeric(12,2) not null default 0,
item_count integer not null default 0,
xml_storage_path text,
pain_format text not null default 'pain.008.003.02',
created_by uuid references auth.users(id) on delete set null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index ix_sepa_batches_account on public.sepa_batches(account_id);
alter table public.sepa_batches enable row level security;
revoke all on public.sepa_batches from authenticated, service_role;
grant select, insert, update, delete on public.sepa_batches to authenticated;
grant all on public.sepa_batches to service_role;
create policy sepa_batches_select on public.sepa_batches for select to authenticated using (public.has_role_on_account(account_id));
create policy sepa_batches_insert on public.sepa_batches for insert to authenticated with check (public.has_permission(auth.uid(), account_id, 'finance.sepa'::public.app_permissions));
create policy sepa_batches_update on public.sepa_batches for update to authenticated using (public.has_permission(auth.uid(), account_id, 'finance.sepa'::public.app_permissions));
create policy sepa_batches_delete on public.sepa_batches for delete to authenticated using (public.has_permission(auth.uid(), account_id, 'finance.sepa'::public.app_permissions));
-- SEPA items (individual transactions)
create table if not exists public.sepa_items (
id uuid primary key default gen_random_uuid(),
batch_id uuid not null references public.sepa_batches(id) on delete cascade,
member_id uuid references public.members(id) on delete set null,
debtor_name text not null,
debtor_iban text not null,
debtor_bic text,
amount numeric(10,2) not null,
mandate_id text,
mandate_date date,
remittance_info text,
status public.sepa_item_status not null default 'pending',
error_message text,
created_at timestamptz not null default now()
);
create index ix_sepa_items_batch on public.sepa_items(batch_id);
create index ix_sepa_items_member on public.sepa_items(member_id);
alter table public.sepa_items enable row level security;
revoke all on public.sepa_items from authenticated, service_role;
grant select, insert, update, delete on public.sepa_items to authenticated;
grant all on public.sepa_items to service_role;
create policy sepa_items_select on public.sepa_items for select to authenticated using (exists (select 1 from public.sepa_batches b where b.id = sepa_items.batch_id and public.has_role_on_account(b.account_id)));
create policy sepa_items_mutate on public.sepa_items for all to authenticated using (exists (select 1 from public.sepa_batches b where b.id = sepa_items.batch_id and public.has_permission(auth.uid(), b.account_id, 'finance.sepa'::public.app_permissions)));
-- Invoices
create table if not exists public.invoices (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.accounts(id) on delete cascade,
invoice_number text not null,
member_id uuid references public.members(id) on delete set null,
recipient_name text not null,
recipient_address text,
issue_date date not null default current_date,
due_date date not null,
status public.invoice_status not null default 'draft',
subtotal numeric(10,2) not null default 0,
tax_rate numeric(5,2) not null default 0,
tax_amount numeric(10,2) not null default 0,
total_amount numeric(10,2) not null default 0,
paid_amount numeric(10,2) not null default 0,
paid_at timestamptz,
notes text,
pdf_storage_path text,
created_by uuid references auth.users(id) on delete set null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique(account_id, invoice_number)
);
create index ix_invoices_account on public.invoices(account_id);
create index ix_invoices_member on public.invoices(member_id);
create index ix_invoices_status on public.invoices(account_id, status);
alter table public.invoices enable row level security;
revoke all on public.invoices from authenticated, service_role;
grant select, insert, update, delete on public.invoices to authenticated;
grant all on public.invoices to service_role;
create policy invoices_select on public.invoices for select to authenticated using (public.has_role_on_account(account_id));
create policy invoices_insert on public.invoices for insert to authenticated with check (public.has_permission(auth.uid(), account_id, 'finance.write'::public.app_permissions));
create policy invoices_update on public.invoices for update to authenticated using (public.has_permission(auth.uid(), account_id, 'finance.write'::public.app_permissions));
create policy invoices_delete on public.invoices for delete to authenticated using (public.has_permission(auth.uid(), account_id, 'finance.write'::public.app_permissions));
create trigger trg_invoices_updated_at before update on public.invoices for each row execute function public.update_account_settings_timestamp();
-- Invoice line items
create table if not exists public.invoice_items (
id uuid primary key default gen_random_uuid(),
invoice_id uuid not null references public.invoices(id) on delete cascade,
description text not null,
quantity numeric(10,2) not null default 1,
unit_price numeric(10,2) not null,
total_price numeric(10,2) not null,
sort_order integer not null default 0,
created_at timestamptz not null default now()
);
create index ix_invoice_items_invoice on public.invoice_items(invoice_id);
alter table public.invoice_items enable row level security;
revoke all on public.invoice_items from authenticated, service_role;
grant select, insert, update, delete on public.invoice_items to authenticated;
grant all on public.invoice_items to service_role;
create policy invoice_items_select on public.invoice_items for select to authenticated using (exists (select 1 from public.invoices i where i.id = invoice_items.invoice_id and public.has_role_on_account(i.account_id)));
create policy invoice_items_mutate on public.invoice_items for all to authenticated using (exists (select 1 from public.invoices i where i.id = invoice_items.invoice_id and public.has_permission(auth.uid(), i.account_id, 'finance.write'::public.app_permissions)));