fix(migration): complete the rows of migrated sales invoices the hydration budget did not reach (#2291)

* fix(migration): complete the rows of migrated sales invoices the hydration budget did not reach

The migration maps sales invoices from the provider's list payload and
hydrates the detail form (rows, net, VAT) inside a fixed 90 s budget, open
invoices first. Fortnox, Briox and Björn Lundén ship no rows in a list
response, so every invoice the budget did not reach was imported as a header
with a total and no invoice_items, and nothing ever came back for it: the
wizard never showed the hydration report, so the user found out on the
invoice page. Measured on prod today: Profilio 384 of 384 (migrated before
hydration existed), Loftux 311 of 672, Damac 182 of 542, Clearstoq 1 125 of
1 125.

- lib/providers: hydrateSalesInvoices() hydrates a caller-chosen subset of
  an already-listed register, so a follow-up can spend its budget on the
  invoices still incomplete on our side instead of re-walking the register
  open-first and never reaching the rest.
- arcim-migration: completeMigratedInvoiceLines() starts from OUR row-less
  non-draft invoices, joins them to the provider register on number + date
  (unique on both sides), hydrates only that subset and writes each
  invoice's rows once the detail total matches the stored total to the öre.
  The header VAT split is rewritten only when the stored one holds no
  evidence (null rate, or a non-zero rate label beside 0 kr VAT and
  subtotal = total). Never the total, status, payments or a journal entry.
- Hourly cron (/api/extensions/arcim-migration/complete-invoice-lines/cron,
  vercel.json + Docker crontabs) drives the pass over consents accepted in
  the last 60 days, newest first, with a per-company share of the run.
- The wizard's result screen now shows "x av y fakturor hämtade med rader"
  and that the rest are fetched in the background within the hour.

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

* fix(migration): write the header VAT fill as a literal, raise the schema-guard ceiling for the row inserts

The phantom-column scanner resolves only object-literal payloads. The header
update is now a literal (so its six columns are checked); the two
invoice_items inserts are runtime row arrays from mapSalesInvoiceLine, the
same shape the orchestrator already inserts, so the ceiling moves 399 to
401 with the reason recorded beside the earlier ones.

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

* fix(migration): gate the completion cron on token freshness, not consent age, and visit every usable consent

Two review findings held. Prod holds 57 accepted consents from the last 60
days, so a fixed page of the newest 25 would leave older companies with
row-less invoices waiting behind companies that are already done: the cap
is gone (a company with nothing left costs one query and no provider call).
And the consent's created_at said nothing about whether its credentials
still work: Fortnox refresh tokens live 45 days and rotate on every
refresh, so eligibility is now read off the token row (access token expired
within the last 45 days, or no expiry at all), which also stops a dead
consent from being retried every hour.

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

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-09-05 10:20:10 +02:00
committed by GitHub
co-authored by Claude Fable 5.1 Jakob Wennberg
parent 0bd3c27fba
commit 8e1f9d5201
14 changed files with 1291 additions and 2 deletions
+3
View File
@@ -5601,6 +5601,9 @@
"ext_arcim_registration_links_already_linked": "{count, plural, one {# was already linked earlier} other {# were already linked earlier}}",
"ext_arcim_credit_notes_label": "Credit notes",
"ext_arcim_credit_notes_unlinked_detail": "{count, plural, one {# credit note was imported without a link to the invoice it credits: your old system sends no reference to the original invoice. Its amounts are reversed and the record is complete, but the link to the original invoice is missing.} other {# credit notes were imported without a link to the invoices they credit: your old system sends no reference to the original invoice. Their amounts are reversed and the records are complete, but the link to the original invoice is missing.}}",
"ext_arcim_invoice_lines_label": "Invoice lines",
"ext_arcim_invoice_lines_value": "{hydrated} of {needed} invoices fetched with their lines",
"ext_arcim_invoice_lines_pending_detail": "{count, plural, one {# invoice was imported with its total but without its lines: your old system only returns the lines in a separate fetch per invoice, and there was not time for all of them. The lines are fetched automatically in the background within the hour.} other {# invoices were imported with their totals but without their lines: your old system only returns the lines in a separate fetch per invoice, and there was not time for all of them. The lines are fetched automatically in the background within the hour.}}",
"ext_arcim_migration_description": "Migrate bookkeeping from Fortnox, Visma, Bokio, Björn Lundén or Briox",
"ext_arcim_migration_long_description": "Move all bookkeeping data from your old system to accounted. Imports chart of accounts, vouchers, customers, suppliers and open invoices automatically via a secure API integration directly with the provider.",
"ext_arcim_bokio_token_description": "Enter the integration token and company ID from Bokio to let {appName} read your bookkeeping data.",
+3
View File
@@ -5601,6 +5601,9 @@
"ext_arcim_registration_links_already_linked": "{count, plural, one {# var redan länkad sedan tidigare} other {# var redan länkade sedan tidigare}}",
"ext_arcim_credit_notes_label": "Kreditfakturor",
"ext_arcim_credit_notes_unlinked_detail": "{count, plural, one {# kreditfaktura importerades utan koppling till fakturan den krediterar: ditt gamla system skickar ingen referens till ursprungsfakturan. Beloppen är omvända och posten är komplett, men kopplingen till ursprungsfakturan saknas.} other {# kreditfakturor importerades utan koppling till fakturorna de krediterar: ditt gamla system skickar ingen referens till ursprungsfakturan. Beloppen är omvända och posterna är kompletta, men kopplingen till ursprungsfakturan saknas.}}",
"ext_arcim_invoice_lines_label": "Fakturarader",
"ext_arcim_invoice_lines_value": "{hydrated} av {needed} fakturor hämtade med rader",
"ext_arcim_invoice_lines_pending_detail": "{count, plural, one {# faktura importerades med totalbelopp men utan fakturarader: ditt gamla system lämnar ut raderna först i en separat hämtning per faktura, och tiden räckte inte till alla. Raderna hämtas automatiskt i bakgrunden inom en timme.} other {# fakturor importerades med totalbelopp men utan fakturarader: ditt gamla system lämnar ut raderna först i en separat hämtning per faktura, och tiden räckte inte till alla. Raderna hämtas automatiskt i bakgrunden inom en timme.}}",
"ext_arcim_migration_description": "Migrera bokföring från Fortnox, Visma, Bokio, Björn Lundén eller Briox",
"ext_arcim_migration_long_description": "Flytta all bokföringsdata från ditt gamla system till accounted. Importerar kontoplan, verifikationer, kunder, leverantörer och öppna fakturor automatiskt via säker API-integration direkt med leverantören.",
"ext_arcim_bokio_token_description": "Ange integrationstoken och företags-ID från Bokio för att ge {appName} tillgång att läsa din bokföringsdata.",