Copy voucher, MRU booking templates, and PDF export for reports (#303)
* feat: copy voucher, MRU booking templates, and PDF export for reports - Add "Kopiera verifikat" action on the journal-entry detail page that prefills a new draft with the source entry's lines, description, and notes. Date defaults to today so locked-period posts can't happen by accident; source_type resets to manual. - Track per-company MRU for booking_template_library rows via a new booking_template_usage table (fire-and-forget touch endpoint hooked into both pickers) and sort the list most-recently-used first for the active company. - Generate downloadable PDFs for balansräkning and resultaträkning using the existing @react-pdf/renderer toolchain. Adds a reusable parameterized template and two API routes, with download buttons on the matching report views. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address PR review feedback on copy-voucher + report PDFs Compliance review (Swedish accounting): - Balance-sheet PDF now refuses to render when tillgångar ≠ eget kapital och skulder; the stale "Differens" summary row is gone. The on-screen view still surfaces the existing "Balanserar ej" warning so users can diagnose the imbalance before downloading. ÅRL 3 kap / K2 / K3 require exact balance. - Both PDF routes now 400 when the requested fiscal period cannot be resolved — identifiable period is part of räkenskapsinformation under BFL 7 kap. - Income-statement PDF adds the mandatory "Resultat efter finansiella poster" subtotal when financial items are present, per K2/K3 uppställningsform (ÅRL bilaga 2). - Copy-voucher flow now shows a clear banner ("Kopia av verifikat X — nytt, fristående verifikat skapas") so users cannot mistake the copy for a rättelse/storno. Code review (Greptile): - New migration adds updated_at column + trigger to booking_template_usage (project convention; applied to the Supabase project). - Replace localeCompare on ISO timestamps with plain relational comparison to avoid any locale-dependent ordering. - UUID-format validation on the copy_from query param before it goes into the fetch URL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: second round of Swedish compliance fixes on report PDFs - Balance-sheet PDF imbalance check now compares rounded-to-whole-kronor totals (SFL 22:1 convention). The previous 0.5-öre tolerance could reject a legitimate balance sheet when accumulated floating-point noise across hundreds of ledger lines exceeded the threshold. The on-screen view still surfaces the öre-precise "Balanserar ej" badge for diagnostic visibility. - Both PDFs now carry a prominent "Arbetsutkast — ej undertecknat" notice per ÅRL 2 kap 7 §. Prevents a downloaded PDF from being mistaken for or filed as an approved årsredovisning. - Income-statement PDF now follows K2/K3 uppställningsform (ÅRL bilaga 2) by splitting class 8 into three blocks with named subtotals: Finansiella poster (80–84), Bokslutsdispositioner (88), Skatter (89). The summary now always shows a "Skatt på årets resultat" row so the reader can verify the tax calculation, and adds "Resultat efter finansiella poster" / "Bokslutsdispositioner" subtotals when each block is present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: harden report PDFs against out-of-band filing + future BAS growth - Append "-utkast" to downloaded PDF filenames. The filename survives the PDF's disclaimer context — a file named balansrakning-2026-01-01.pdf in a Downloads folder or forwarded attachment is ambiguous, whereas balansrakning-2026-01-01-utkast.pdf makes the draft status legible even without opening the document. - Add a catch-all "Övriga finansiella poster" bucket in the income-statement PDF for any class-8 section whose account prefix isn't one of the known K2/K3 blocks (80–84 / 88 / 89). Counted in the "Resultat efter finansiella poster" subtotal so arithmetic stays consistent. Future-proofs the PDF against a generator change that starts emitting 85–87 sections. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
24107338fa
commit
4cd0a55761
@@ -0,0 +1,53 @@
|
||||
-- =============================================================================
|
||||
-- Booking Template Usage (per-company MRU tracking)
|
||||
-- =============================================================================
|
||||
--
|
||||
-- Tracks when a booking template was last used *within a specific company*.
|
||||
-- Stored separately from booking_template_library because:
|
||||
-- 1. System templates are shared globally (is_system = TRUE, company_id NULL)
|
||||
-- so a per-row last_used_at would be useless — company A using a template
|
||||
-- would surface it for company B too.
|
||||
-- 2. Team templates are shared across a team's companies; each company should
|
||||
-- track its own usage independently.
|
||||
--
|
||||
-- One row per (template_id, company_id). Upsert on use.
|
||||
|
||||
CREATE TABLE public.booking_template_usage (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
template_id UUID NOT NULL REFERENCES public.booking_template_library(id) ON DELETE CASCADE,
|
||||
company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
|
||||
last_used_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
UNIQUE (template_id, company_id)
|
||||
);
|
||||
|
||||
-- RLS
|
||||
ALTER TABLE public.booking_template_usage ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "btu_select" ON public.booking_template_usage
|
||||
FOR SELECT USING (
|
||||
company_id IN (SELECT public.user_company_ids())
|
||||
);
|
||||
|
||||
CREATE POLICY "btu_insert" ON public.booking_template_usage
|
||||
FOR INSERT WITH CHECK (
|
||||
company_id IN (SELECT public.user_company_ids())
|
||||
);
|
||||
|
||||
CREATE POLICY "btu_update" ON public.booking_template_usage
|
||||
FOR UPDATE USING (
|
||||
company_id IN (SELECT public.user_company_ids())
|
||||
);
|
||||
|
||||
CREATE POLICY "btu_delete" ON public.booking_template_usage
|
||||
FOR DELETE USING (
|
||||
company_id IN (SELECT public.user_company_ids())
|
||||
);
|
||||
|
||||
-- Index for the sort query: fetch last_used_at for a given company.
|
||||
CREATE INDEX idx_btu_company_last_used
|
||||
ON public.booking_template_usage (company_id, last_used_at DESC);
|
||||
|
||||
-- Schema reload for PostgREST
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
@@ -0,0 +1,24 @@
|
||||
-- =============================================================================
|
||||
-- Booking Template Usage: add updated_at column + trigger
|
||||
-- =============================================================================
|
||||
--
|
||||
-- Follow-up to 20260421160000_booking_template_usage.sql. The project
|
||||
-- migration rules (CLAUDE.md) require every table to carry an updated_at
|
||||
-- column maintained by the shared update_updated_at_column() trigger. The
|
||||
-- initial migration omitted it because the row is touched via upsert
|
||||
-- (which bumps last_used_at) — but the audit convention applies regardless.
|
||||
|
||||
ALTER TABLE public.booking_template_usage
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
|
||||
|
||||
-- Seed updated_at for existing rows to match last_used_at so history is
|
||||
-- coherent from day one.
|
||||
UPDATE public.booking_template_usage
|
||||
SET updated_at = last_used_at
|
||||
WHERE updated_at < last_used_at;
|
||||
|
||||
CREATE TRIGGER btu_updated_at
|
||||
BEFORE UPDATE ON public.booking_template_usage
|
||||
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
||||
|
||||
NOTIFY pgrst, 'reload schema';
|
||||
Reference in New Issue
Block a user