Commit Graph
2 Commits
Author SHA1 Message Date
MattssonandClaude Fable 5.1 a84d2723e0 feat(import): keep the source system's #BTRANS/#RTRANS correction history at SIE import (#2458)
* feat(import): keep the source system's #BTRANS/#RTRANS correction history at SIE import

A verifikat migrated from Fortnox/Visma lost the trail of what had been
corrected in the source system: the parser skipped #BTRANS (struck lines)
and #RTRANS (lines added by a rättelse) and nothing else read them. The
final state is still built from #TRANS only, exactly as SIE 4B prescribes
(#RTRANS is always twinned by an identical #TRANS, so summing all three
double-counts, #63). The two history record types now ride along the
voucher as `corrections` and land, inside the same atomic import
transaction, as one journal_entry_rattelse_log row per corrected voucher
with source='sie_import', the file's sie_import_id and the SIE `sign`
(who corrected in the source system; SIE carries who, never when).

Why the problem occurred: the March fix for double-counting chose "skip"
over "keep aside" because nowhere existed to keep the history. The inline
rättelse log (July) created that place, and every reader of it (verifikat
page, "Rättad" marker, behandlingshistorik, full archive) already renders
struck/added snapshots, so the history now flows through one table.

What was removed or simplified instead: no new table, no per-import
toggle, no fifth RPC parameter (sie_import_id travels inside each payload
entry so the (uuid,uuid,uuid,jsonb) signature, grants and
statement_timeout stay put and PostgREST sees no overload). The parser's
three identical TRANS/RTRANS/BTRANS field parsers collapsed into one
helper; the TRANS-only ledger path is byte-for-byte the same.

Why this over the proposed shape: the reporter suggested an own table or
column. A separate store would need its own readers, RLS, archive
classification and behandlingshistorik wiring; the rättelselogg already
has all four. Storing history in sie_imports.migration_documentation was
rejected as aggregate JSON that no per-verifikat surface reads.
Import-sourced log rows survive undo/replace like every other log row
(no FK on purpose); a re-import writes fresh rows against fresh entry ids.

Parser also warns when an #RTRANS is not followed by its identical #TRANS
twin (a spec violation that would silently drop a line from the final
state) and the record-type comments now match the spec wording.

Migration 20260909132618: three nullable/defaulted columns + CHECKs on
journal_entry_rattelse_log, sie_correction_snapshots() helper,
import_sie_journal_entries body verbatim plus the history insert. No
backfill; existing imports and log rows untouched.

Fixes #2427

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2FcXNv8qRp4GaXCtzEdyn

* fix(import): verify the SIE import id before it becomes provenance, keep per-line signatures

Review findings on PR #2458, one pass:

- Superagent P2: import_sie_journal_entries stored the caller-supplied
  sieImportId as WORM audit provenance without checking it. The RPC now
  requires the id to be one of the importing company's own sie_imports
  rows and fails closed (42501, whole import rolled back) on a foreign
  or fabricated id. pg-real test added.
- Compliance review: the voucher-level external_signature collapsed
  distinct correctors per line. Each struck/added snapshot now carries
  its own SIE sign (importer + sie_correction_snapshots), the summary
  column stays as the first one.
- Compliance review: created_at on imported rows is the import moment.
  Behandlingshistoriken now says so in the event details instead of
  leaving it implicit (the verifikat page already avoided a date).

Migration file is unshipped (not on main); staging re-applied under the
same version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2FcXNv8qRp4GaXCtzEdyn

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 14:45:29 +02:00
2a1ec5ec2f fix: Make SIE imports atomic (#860)
* fix: Make SIE imports atomic

* fix(import): carry dimensions + harden the atomic SIE RPC

Rebased onto current main. The RPC now:
- carries the per-line dimensions jsonb through the payload + INSERT so
  imported SIE object-list codes are not dropped (dimensions PR5 #866);
- uses the NULL-safe caller_is_company_member guard (drops the banned
  NOT IN (SELECT user_company_ids()) pattern ratcheted since #881);
- verifies the fiscal period belongs to the company;
- enforces per-voucher balance (sum debit = sum credit > 0) since
  SECURITY DEFINER + the direct draft->posted UPDATE bypass the trigger path;
- ships REVOKE ALL FROM PUBLIC, anon / GRANT EXECUTE TO authenticated,
  service_role (house style).
Migration renamed to a current timestamp. Added pg-real coverage for the
dimensions round-trip, unbalanced rejection, and foreign-fiscal-period guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:00:01 +02:00