Fix/footer UI (#296)

* feat: enhance journal entry handling with follow-up entries and related RPC

* fix: improve validation for journal entry lines to ensure proper submission criteria

* feat: add commit_method and rubric_version columns to journal_entries for enhanced tracking

* fix: ensure conditional addition of commit_method and rubric_version columns in journal_entries

* Update supabase/migrations/20260421120000_journal_entries_with_related_rpc.sql

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
Mattsson
2026-04-21 12:55:43 +02:00
committed by GitHub
co-authored by greptile-apps[bot]
parent dd920355b3
commit 64cd6a0989
10 changed files with 289 additions and 11 deletions
@@ -8,10 +8,10 @@
-- 1. Add columns (nullable — existing rows get NULL)
ALTER TABLE public.journal_entries
ADD COLUMN commit_method TEXT CHECK (commit_method IS NULL OR commit_method IN (
ADD COLUMN IF NOT EXISTS commit_method TEXT CHECK (commit_method IS NULL OR commit_method IN (
'user_accept', 'bulk_accept', 'timing_ceiling', 'migration', 'legacy'
)),
ADD COLUMN rubric_version TEXT;
ADD COLUMN IF NOT EXISTS rubric_version TEXT;
-- 2. Update commit RPC to accept and set the new columns atomically
CREATE OR REPLACE FUNCTION public.commit_journal_entry(
@@ -0,0 +1,120 @@
-- RPC: list journal entries for a fiscal period, optionally including
-- follow-up entries booked in a later period that relate to aggregates
-- that originated in the selected period.
--
-- Why this exists: journal_entries.fiscal_period_id is strictly bound to
-- entry_date (validated in lib/bookkeeping/engine.ts). So a customer
-- invoice created in FY2025 and paid in FY2026 produces two entries in
-- two different periods, and filtering the /bookkeeping view by
-- fiscal_period_id hides the tail of the story. Users reviewing a past
-- fiscal year expect to see the full processing history for that year's
-- aggregates (behandlingshistorik per BFL/BFNAR).
--
-- Expansion rules (when p_include_related = true):
-- - Entries with source_type in invoice follow-ups whose invoice was
-- dated inside the selected period.
-- - Same for supplier invoice follow-ups.
--
-- Storno and correction entries inherit fiscal_period_id from their
-- original (see lib/bookkeeping/engine.ts reverseEntry and
-- lib/core/bookkeeping/storno-service.ts), so they are already captured
-- by the primary fiscal_period_id filter — no extra rule needed.
--
-- Currency revaluation is booked within the period it revalues, also
-- captured by the primary filter.
--
-- Returns jsonb rows shaped like the PostgREST response used by
-- GET /api/bookkeeping/journal-entries (entry + nested lines), plus an
-- out_of_period boolean the UI uses to badge tail entries.
CREATE OR REPLACE FUNCTION public.list_fiscal_period_entries_with_related(
p_company_id uuid,
p_period_id uuid,
p_include_related boolean DEFAULT true,
p_status text DEFAULT NULL,
p_date_from date DEFAULT NULL,
p_date_to date DEFAULT NULL,
p_sort_date text DEFAULT 'desc',
p_limit int DEFAULT 50,
p_offset int DEFAULT 0
)
RETURNS TABLE (
entry jsonb,
total_count bigint
)
LANGUAGE sql
STABLE
SECURITY INVOKER
SET search_path = public, pg_temp
AS $$
WITH period AS (
SELECT period_start, period_end
FROM public.fiscal_periods
WHERE id = p_period_id AND company_id = p_company_id
),
matching AS (
SELECT je.*
FROM public.journal_entries je
CROSS JOIN period p
WHERE je.company_id = p_company_id
AND (
je.fiscal_period_id = p_period_id
OR (
p_include_related
AND je.source_type IN ('invoice_paid','invoice_cash_payment','credit_note')
AND EXISTS (
SELECT 1 FROM public.invoices i
WHERE i.id = je.source_id
AND i.company_id = p_company_id
AND i.invoice_date BETWEEN p.period_start AND p.period_end
)
)
OR (
p_include_related
AND je.source_type IN ('supplier_invoice_paid','supplier_invoice_cash_payment','supplier_credit_note')
AND EXISTS (
SELECT 1 FROM public.supplier_invoices si
WHERE si.id = je.source_id
AND si.company_id = p_company_id
AND si.invoice_date BETWEEN p.period_start AND p.period_end
)
)
)
AND (p_status IS NULL OR je.status = p_status)
AND (p_date_from IS NULL OR je.entry_date >= p_date_from)
AND (p_date_to IS NULL OR je.entry_date <= p_date_to)
),
matching_with_total AS (
SELECT m.*, COUNT(*) OVER () AS total
FROM matching m
),
paged AS (
SELECT *
FROM matching_with_total
ORDER BY
CASE WHEN p_sort_date = 'asc' THEN entry_date END ASC NULLS LAST,
CASE WHEN p_sort_date = 'desc' THEN entry_date END DESC NULLS LAST,
voucher_series,
voucher_number
LIMIT p_limit OFFSET p_offset
)
SELECT
(to_jsonb(p.*) - 'total')
|| jsonb_build_object(
'lines', COALESCE(
(SELECT jsonb_agg(to_jsonb(l.*) ORDER BY l.sort_order)
FROM public.journal_entry_lines l
WHERE l.journal_entry_id = p.id),
'[]'::jsonb
),
'out_of_period', (p.fiscal_period_id IS DISTINCT FROM p_period_id)
) AS entry,
p.total AS total_count
FROM paged p;
$$;
GRANT EXECUTE ON FUNCTION public.list_fiscal_period_entries_with_related(
uuid, uuid, boolean, text, date, date, text, int, int
) TO authenticated;
NOTIFY pgrst, 'reload schema';
@@ -0,0 +1,28 @@
-- Drop the legacy user_id-scoped unique constraints on supplier_invoices that
-- the multi-tenant refactor (20260330130000) missed.
--
-- That refactor tried to drop constraints named
-- supplier_invoices_user_id_arrival_number_key
-- supplier_invoices_user_id_supplier_id_supplier_invoice_numbe_key
-- (the auto-generated names Postgres would have picked had the original
-- CREATE TABLE used inline UNIQUE constraints). But the 20240101000025
-- migration named them explicitly — uq_supplier_invoices_arrival and
-- uq_supplier_invoices_ref — so the IF EXISTS drops were no-ops and the
-- user_id-scoped uniqueness remained in place.
--
-- Meanwhile get_next_arrival_number() was rewritten to scope by company_id.
-- The mismatch blows up the moment a single user has supplier invoices in
-- two companies: the second company's arrival_number restarts at 1 and
-- collides with the first company's row under the user_id-scoped constraint.
--
-- The correct composite unique indexes — (company_id, arrival_number) and
-- (company_id, supplier_id, supplier_invoice_number) — were added as
-- CREATE UNIQUE INDEX IF NOT EXISTS in the 2026-03-30 refactor and are
-- already in place, so dropping the legacy constraints is all that's
-- needed.
ALTER TABLE public.supplier_invoices
DROP CONSTRAINT IF EXISTS uq_supplier_invoices_arrival,
DROP CONSTRAINT IF EXISTS uq_supplier_invoices_ref;
NOTIFY pgrst, 'reload schema';