since3-portal/lib/supabase/migrations/20260929150000_invoice_creation.sql
2026-09-29 13:05:38 +02:00

73 lines
2.8 KiB
PL/PgSQL

-- Zeigt die nächste Rechnungsnummer an, ohne den Zähler zu erhöhen (für Vorbelegung im Dialog).
-- Die echte Nummer vergibt weiterhin next_invoice_number() beim Insert.
create or replace function public.peek_next_invoice_number()
returns text language plpgsql stable security definer set search_path = '' as $$
declare
y int := extract(year from current_date)::int;
n int;
begin
if not public.is_admin() then
raise exception 'forbidden' using errcode = '42501';
end if;
select coalesce(last_number, 0) + 1 into n from public.invoice_counters where year = y;
return format('RE-%s-%s', y, lpad(coalesce(n, 1)::text, 4, '0'));
end $$;
-- Rechnung + Positionen in einer Transaktion anlegen, damit bei Fehlern
-- keine Rechnung ohne Positionen (und keine verbrauchte Nummer) übrig bleibt.
-- security invoker: RLS (admin_insert / admin_all) greift.
create or replace function public.create_invoice(
p_client_id uuid,
p_invoice_date date,
p_due_date date,
p_company_name text,
p_billing_street text,
p_billing_postal_code text,
p_billing_city text,
p_billing_country char(2),
p_items jsonb
) returns uuid
language plpgsql
security invoker
set search_path = ''
as $$
declare
v_invoice_id uuid;
begin
if not public.is_admin() then
raise exception 'forbidden' using errcode = '42501';
end if;
if p_items is null or jsonb_array_length(p_items) = 0 then
raise exception 'invoice needs at least one item' using errcode = '22023';
end if;
insert into public.invoices (
client_id, invoice_date, due_date,
company_name, billing_street, billing_postal_code, billing_city, billing_country
) values (
p_client_id, p_invoice_date, p_due_date,
p_company_name, p_billing_street, p_billing_postal_code, p_billing_city, p_billing_country
)
returning id into v_invoice_id;
insert into public.invoice_items (
invoice_id, position, product_template_id, description, quantity, unit_price, tax_rate
)
select
v_invoice_id,
item.ordinality::int,
nullif(item.value->>'product_template_id', '')::uuid,
item.value->>'description',
(item.value->>'quantity')::numeric,
(item.value->>'unit_price')::numeric,
(item.value->>'tax_rate')::numeric
from jsonb_array_elements(p_items) with ordinality as item(value, ordinality);
return v_invoice_id;
end $$;
revoke execute on function public.peek_next_invoice_number() from public, anon;
revoke execute on function public.create_invoice(uuid, date, date, text, text, text, text, char(2), jsonb) from public, anon;
grant execute on function public.peek_next_invoice_number() to authenticated;
grant execute on function public.create_invoice(uuid, date, date, text, text, text, text, char(2), jsonb) to authenticated;