feat: import Fortnox voucher attachments (#1541)

* feat: import Fortnox voucher attachments

* fix: show Fortnox document import follow-up

* fix: harden optional Fortnox document import

* test: pin optional Fortnox import flow

* fix: use browser timer handle type

* fix: avoid serializing OAuth resume state
This commit is contained in:
Mattsson
2026-08-13 00:33:10 +02:00
committed by GitHub
parent aff29b2b05
commit a97b0023d4
20 changed files with 2409 additions and 93 deletions
+5
View File
@@ -876,6 +876,11 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
[2026-08-11] Anthropic, Vercel and Supabase removed from the portal directory: all three email their invoices to European customers, so listing them told the user to go and log in for a document already in their inbox. The directory's bar is "does not send the invoice", not "also has a portal", and the poll it was seeded from asked which portals people log into, which people answered with where an invoice can ALSO be found. The same objection may reach further down the list; an entry is a claim that the invoice cannot be had any other way and is worth checking per vendor.
[2026-08-11] Portal URLs are swept by scripts/check-portal-urls.mts rather than trusted: the directory shipped with 18 hand-written paths, none opened, the file said so and shipped anyway, and a founder then hit a 404 on Google Workspace (/ac/billing/history). A sweep found GitHub's /settings/billing 404 too. Rule now is the shallowest URL that certainly resolves: landing one click short of the invoice costs little, landing on an error page spends the trust the feature runs on. Google, OpenAI and Hetzner refuse automated requests, so they cannot be swept and are kept shallow deliberately; only a genuine 404 fails the script, since failing on an unreachable host would train people to ignore it. Trygg Hansa removed: neither candidate URL could be reached at all.
[2026-08-11] Credit-note deduction fields (deduction_total, per-item deduction_amount) stay POSITIVE magnitudes, unlike every other amount on a credit note: both columns carry CHECK (>= 0) in the DB, and negating them made every ROT/RUT credit fail at insert (prod support case 2026-08-11). Verified inert: the reversing verifikat recomputes the ROT/RUT split from quantity/unit_price (generateRotRutLines), the PDF hides the deduction section for credit notes, getAmountToPay skips deductions when credited_invoice_id is set, and ROT payout candidates require status='paid', which invoices_credit_note_not_paid makes impossible for credit notes. Any future reader summing these fields across invoice + credit note must special-case credit notes.
[2026-08-12] Fortnox and Bokio receipt imports share one private attachment adapter: keeping period resolution, WORM upload, per-verifikat hash deduplication, content sniffing, and best-effort counters in one loop prevents provider branches from drifting on compliance and idempotency.
[2026-08-12] Provider receipt imports derive a document UUID from company, verifikat, and content hash: the existing document primary key becomes an atomic cross-request claim without a migration, while each attempt uses a unique storage object and a losing insert verifies the retained winner before removing only its own unreferenced object.
[2026-08-12] The Fortnox migration completion prompt reports dry-run scanned attachments as "found", not would-link counts: provider availability is known before download, while content duplicates and final importability are only honest after the user starts the idempotent import.
[2026-08-12] The Fortnox document follow-up resolves the provider from the completed preview and stays visible after a zero-result scan: preview consent data is authoritative across wizard state transitions, and a visible empty state prevents a successful migration from silently losing the promised document step.
[2026-08-12] Fortnox document import stays opt-in after the SIE migration: the automatic follow-up performs only a dry-run count, and document downloads and WORM writes begin only when the user clicks the separate import action.
[2026-08-12] Skattekonto booking claim race: the loser leaves its just-created draft unlinked instead of deleting it. lib/bookkeeping/engine.ts exposes no draft-discard function and journal tables are never raw-deleted; the delete_last_voucher RPC exists but is owner/admin-gated and lives outside the engine, so best-effort cleanup could fail on role and add phantom-delete audit noise. An orphan draft is legally deletable by the user in /bookkeeping; the request that lost the claim returns ALREADY_BOOKED and never commits.
[2026-08-12] SLP on supplier invoices mirrors the reverse-charge zero-net pair mechanism (apply_slp item flag) instead of free-form credit rows: keeps the 2440 balance guarantee and the item model intact; year-end calculator nets off posted 7533 to avoid double provision.
[2026-08-12] Enable Banking auth-method pin narrowed to hidden_method=true + psu_types match: PR #854's blanket decoupled pin broke Lunar-class banks; hidden-only restores its stated intent.