44082ff845
* fix: prevent P&L accumulation when importing multi-year SIE files The opening-balance fallback summed all prior journal lines without distinguishing balance sheet (class 1-2) from P&L (class 3-8). When users imported one SIE file per year without running year-end closing between them, resultatkonton accumulated across years instead of resetting at each räkenskapsårsskifte. Reported by a customer. Skip class 3-8 in the fallback path. P&L accounts must reset to zero each fiscal year (årets resultat → 2099 → equity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: only import unpaid supplier invoices from Fortnox Fortnox's /supplierinvoices list endpoint doesn't reliably expose FullyPaid, which caused historic paid invoices to be imported as unpaid. Switch to the ?filter=unpaid query and surface that scope in the migration options UI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: add processing_history table for behandlingshistorik Append-only event log per BFNAR 2013:2 kap 8. Includes: - processing_history table with seq, correlation/causation chaining, aggregate (Document/BankTransaction/MatchProposal/Verifikation/etc.), open event_type validated against processing_event_types registry. - Immutability via audit_log_immutable trigger (no UPDATE/DELETE). - RLS scoped to user_company_ids; writes via service role only. - appendProcessingHistory() helper with PII guard rejecting payloads containing personnummer/orgnr patterns. - Shared TS types in types/index.ts. No consumers wired yet — this is the persistence layer only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: add swedish-project-accounting skill Reference skill covering projektredovisning: dimensional tagging, WIP accounting, K2/K3 revenue recognition (successiv vinstavräkning, färdigställandemetoden), entreprenadavtal, BAS patterns (1470, 1620, 2420, 2450, 4970), and SIE4 #DIM 6 encoding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: rename processing_history migration to avoid timestamp collision Main already has 20260418120000_allow_retroactive_first_fiscal_year.sql from #265. Bumping this migration's timestamp to 20260418130000 to keep schema_migrations.version unique. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address Greptile review on processing_history - Add BEFORE DELETE immutability trigger so the service role can't silently remove rows. Mirrors the pattern from migration 014 (audit_log_no_update + audit_log_no_delete) and satisfies the immutability claim in BFNAR 2013:2 kap 8. Delivered as a follow-up migration since the original was already applied in some envs. - Tighten PII patterns with \b word boundaries to avoid false positives on Bankgiro numbers (123456-7890) and invoice references like 202312-1234. - Extend PII validation to actor.label, which previously bypassed the payload guard despite the docblock explicitly forbidding names/emails/personnummer there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
138 lines
5.2 KiB
SQL
138 lines
5.2 KiB
SQL
-- Processing history (behandlingshistorik)
|
|
-- Append-only event log recording every legally significant act per BFNAR 2013:2 kap 8.
|
|
-- 7-year retention enforced at application layer (not DB trigger) to allow GDPR redaction.
|
|
-- Immutable: UPDATE blocked via audit_log_immutable() trigger.
|
|
--
|
|
-- seq ordering: BIGSERIAL assigns at statement time, not commit time.
|
|
-- Two concurrent transactions may produce seq values out of commit order.
|
|
-- For legally meaningful ordering, use occurred_at. seq is a cursor for polling.
|
|
|
|
-- =============================================================
|
|
-- 1. Event type reference table
|
|
-- =============================================================
|
|
-- Adding a new event type = INSERT, no ALTER TABLE required.
|
|
|
|
CREATE TABLE public.processing_event_types (
|
|
event_type TEXT PRIMARY KEY
|
|
);
|
|
|
|
ALTER TABLE public.processing_event_types ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Readable by all authenticated users (reference data)
|
|
CREATE POLICY "processing_event_types_select" ON public.processing_event_types
|
|
FOR SELECT USING (true);
|
|
|
|
-- Seed the v0.2 event catalog (28 types)
|
|
INSERT INTO public.processing_event_types (event_type) VALUES
|
|
-- Document stream
|
|
('DocumentIngested'),
|
|
('DocumentExtractionAttempted'),
|
|
('DocumentClassified'),
|
|
('DocumentRejected'),
|
|
('DocumentArchived'),
|
|
('DocumentSupersededByDuplicate'),
|
|
-- BankTransaction stream
|
|
('BankTransactionIngested'),
|
|
('BankTransactionEnriched'),
|
|
-- MatchProposal stream
|
|
('MatchAttemptedDeterministic'),
|
|
('MatchAttemptedLlm'),
|
|
('MatchConfirmed'),
|
|
('MatchRejected'),
|
|
-- Verifikation stream
|
|
('ForslagCreated'),
|
|
('ForslagMutated'),
|
|
('VerifikationCommitted'),
|
|
('RättelseverifikationIssued'),
|
|
('ForslagAbandoned'),
|
|
-- CounterpartyTemplate stream
|
|
('CounterpartyObserved'),
|
|
('TemplateStrengthened'),
|
|
('TemplateBroken'),
|
|
-- Period stream
|
|
('PeriodCloseInitiated'),
|
|
('PeriodClosed'),
|
|
('PeriodReopened'),
|
|
-- Migration stream
|
|
('MigrationInitiated'),
|
|
('MigrationValidated'),
|
|
('MigrationCommitted'),
|
|
-- System stream
|
|
('TimingCeilingTriggered'),
|
|
('RubricVersionPublished');
|
|
|
|
-- =============================================================
|
|
-- 2. Processing history table
|
|
-- =============================================================
|
|
|
|
CREATE TABLE public.processing_history (
|
|
event_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
seq BIGSERIAL UNIQUE NOT NULL,
|
|
company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE RESTRICT,
|
|
correlation_id UUID NOT NULL,
|
|
causation_id UUID REFERENCES public.processing_history(event_id),
|
|
aggregate_type TEXT NOT NULL CHECK (aggregate_type IN (
|
|
'Document',
|
|
'BankTransaction',
|
|
'MatchProposal',
|
|
'Verifikation',
|
|
'CounterpartyTemplate',
|
|
'Period',
|
|
'Migration',
|
|
'System'
|
|
)),
|
|
aggregate_id UUID NOT NULL,
|
|
event_type TEXT NOT NULL REFERENCES public.processing_event_types(event_type),
|
|
payload JSONB NOT NULL DEFAULT '{}',
|
|
payload_schema_version SMALLINT NOT NULL DEFAULT 1,
|
|
actor JSONB NOT NULL,
|
|
rubric_version TEXT,
|
|
occurred_at TIMESTAMPTZ NOT NULL,
|
|
appended_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
-- No updated_at: append-only table
|
|
);
|
|
|
|
-- =============================================================
|
|
-- 3. Row-level security
|
|
-- =============================================================
|
|
|
|
ALTER TABLE public.processing_history ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- SELECT: users can read their companies' processing history
|
|
CREATE POLICY "processing_history_select" ON public.processing_history
|
|
FOR SELECT USING (company_id IN (SELECT public.user_company_ids()));
|
|
|
|
-- No INSERT/UPDATE/DELETE policies for authenticated users.
|
|
-- Writes via service role client only.
|
|
|
|
-- =============================================================
|
|
-- 4. Indexes
|
|
-- =============================================================
|
|
|
|
-- Aggregate history: "full history of this document/transaction/verifikation"
|
|
CREATE INDEX idx_ph_aggregate
|
|
ON public.processing_history (company_id, aggregate_type, aggregate_id, seq);
|
|
|
|
-- Event type filtering: "all VerifikationCommitted events for this company"
|
|
CREATE INDEX idx_ph_company_event_type
|
|
ON public.processing_history (company_id, event_type, seq);
|
|
|
|
-- Time range: "all events for this company in March 2026" + retention cleanup
|
|
CREATE INDEX idx_ph_company_occurred
|
|
ON public.processing_history (company_id, occurred_at);
|
|
|
|
-- =============================================================
|
|
-- 5. Immutability trigger
|
|
-- =============================================================
|
|
-- Reuses audit_log_immutable() which unconditionally blocks all UPDATE operations.
|
|
|
|
CREATE TRIGGER processing_history_no_update
|
|
BEFORE UPDATE ON public.processing_history
|
|
FOR EACH ROW EXECUTE FUNCTION public.audit_log_immutable();
|
|
|
|
-- =============================================================
|
|
-- 6. Schema reload
|
|
-- =============================================================
|
|
|
|
NOTIFY pgrst, 'reload schema';
|