Files
accounted/supabase/migrations/20260510130000_invoice_number_no_truncate.sql
Mattsson 0ee5219b6c feat(invoices): implement öresavrundning logic and next invoice numbe… (#429)
* feat(invoices): implement öresavrundning logic and next invoice number preview

- Added `getDisplayTotal` utility to handle rounding for SEK invoices based on company settings.
- Updated `InvoicesPage` to utilize the new rounding logic when displaying totals.
- Introduced `peek_next_invoice_number` function to allow previewing the next invoice number without consuming the sequence.
- Modified invoice number generation to remove the year prefix and prevent truncation of numbers exceeding three digits.
- Enhanced tests for invoice number generation and rounding functionality to ensure correctness.
- Updated PDF template to reflect new rounding logic for totals and display appropriate values.
- Adjusted company switcher to hide options in sandbox mode.
- Improved error handling and logging in sandbox seeding process.

* fix(skatteverket): remove unused scope labels from SCOPE_LABELS and DEFAULT_SCOPES
2026-05-10 14:21:26 +02:00

88 lines
2.5 KiB
PL/PgSQL

-- Fix LPAD truncation in invoice number generation.
--
-- Postgres LPAD(string, length [, fill]) TRUNCATES on the right when string
-- is longer than length. So LPAD('10159', 3, '0') returns '101' — not the
-- '10159' the user expected. The previous migration (20260510120000)
-- preserved this LPAD pattern from the original 20260427150100 function
-- on the assumption that LPAD never truncates; that was wrong.
--
-- The customer-visible symptom: setting next_invoice_number = 10159 with
-- no prefix produces invoice number '101' instead of '10159', and the
-- preview surfaced the same '101'.
--
-- Fix: pad to at LEAST three digits, but never shorter than the actual
-- number. GREATEST(3, length(...)) is the simplest way to express that.
CREATE OR REPLACE FUNCTION public.generate_invoice_number(
p_company_id uuid,
p_invoice_id uuid,
p_document_type text DEFAULT 'invoice'
)
RETURNS text
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
v_existing text;
v_prefix text;
v_number integer;
v_final text;
BEGIN
SELECT invoice_number INTO v_existing
FROM public.invoices
WHERE id = p_invoice_id AND company_id = p_company_id
FOR UPDATE;
IF NOT FOUND THEN
RAISE EXCEPTION 'Invoice % not found in company %', p_invoice_id, p_company_id;
END IF;
IF v_existing IS NOT NULL THEN
RETURN v_existing;
END IF;
UPDATE public.company_settings
SET next_invoice_number = next_invoice_number + 1,
updated_at = now()
WHERE company_id = p_company_id
RETURNING invoice_prefix, next_invoice_number - 1
INTO v_prefix, v_number;
IF v_number IS NULL THEN
RAISE EXCEPTION 'Company settings not found for company %', p_company_id;
END IF;
v_final := CASE
WHEN p_document_type = 'proforma' THEN 'PF-'
ELSE COALESCE(v_prefix, '')
END || LPAD(v_number::text, GREATEST(3, length(v_number::text)), '0');
UPDATE public.invoices
SET invoice_number = v_final
WHERE id = p_invoice_id AND company_id = p_company_id;
RETURN v_final;
END;
$function$;
CREATE OR REPLACE FUNCTION public.peek_next_invoice_number(
p_company_id uuid,
p_document_type text DEFAULT 'invoice'
)
RETURNS text
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
SELECT CASE
WHEN p_document_type = 'proforma' THEN 'PF-'
ELSE COALESCE(invoice_prefix, '')
END || LPAD(next_invoice_number::text, GREATEST(3, length(next_invoice_number::text)), '0')
FROM public.company_settings
WHERE company_id = p_company_id
$function$;
NOTIFY pgrst, 'reload schema';