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:
co-authored by
greptile-apps[bot]
parent
dd920355b3
commit
64cd6a0989
+2
-2
@@ -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';
|
||||
+28
@@ -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';
|
||||
Reference in New Issue
Block a user