fix(arsredovisning): unblock the signing flow, accept foreign parent org nr, explain Fortnox underlag failures (#1738)

Batch from a real migration walkthrough (Fortnox -> Accounted, 2026-08-20):

- Årsredovisning: the "Låst version" select was empty with no explanation
  because the only version was a draft and "Lås version för underskrift"
  is disabled while the four Lagstadgade upplysningar checkboxes and the
  content confirmation count as blockers. The select is now disabled with a
  hint that names the blocker count and links to Fullständighetskontroll,
  the four AR-NOTE-*-UNCONFIRMED issues carry remediation text, the lock
  button explains why it is grey, and "Markera som signerad" says what it
  still needs (locked version, bevisreferens, date).
- Moderföretagets org.nr accepts a foreign registration identifier
  (CHE-123.456.789, HRB 12345, 923 609 016); personnummer shapes stay out.
- Fortnox underlag discovery: log status, body and Fortnox's message on
  failure, show the message in the UI, treat a 400 with behörighet/scope
  text as scopes-required, and fall back to an unfiltered
  voucherfileconnections list when the financialyear filter answers 400.
- Kontomapping: the Momskod column had min-w only; table-fixed collapsed it
  and its selects overflowed into Konfidens. Real w-72 now.
- SIE import warnings pluralise correctly for one skipped voucher; the
  Verifikationsserie option says the source series is preserved.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-20 13:25:06 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 5a8dd21931
commit e733ab7c43
20 changed files with 418 additions and 45 deletions
+18 -2
View File
@@ -36,6 +36,7 @@ import { BAS_REFERENCE } from '@/lib/bookkeeping/bas-reference'
import type { ProviderName } from '@/lib/providers/types'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
import { classifyProviderError } from '@/lib/providers/with-provider-call'
import { FortnoxApiError, fortnoxErrorMessage } from '@/lib/providers/fortnox/client'
import { createLogger } from '@/lib/logger'
const moduleLog = createLogger('extensions/arcim-migration')
@@ -1371,7 +1372,18 @@ export const arcimMigrationExtension: Extension = {
})
return NextResponse.json({ success: true, dryRun, result })
} catch (error) {
log.error('arcim import-documents failed', error as Error)
// "400 Bad Request" alone told us nothing when a live Fortnox
// discovery failed (2026-08-20): keep status, Fortnox's own
// message and a body excerpt in the log, and hand the message
// to the UI so the user sees what the source system said.
const providerStatus = error instanceof FortnoxApiError ? error.statusCode : undefined
const providerMessage = fortnoxErrorMessage(error)
log.error('arcim import-documents failed', error as Error, {
providerStatus,
providerMessage,
providerBody:
error instanceof FortnoxApiError ? error.body?.slice(0, 500) : undefined,
})
if (error instanceof FortnoxDocumentScopesRequiredError) {
return errorResponseFromCode(
'PROVIDER_DOCUMENT_SCOPES_REQUIRED',
@@ -1380,7 +1392,11 @@ export const arcimMigrationExtension: Extension = {
)
}
return errorResponseFromCode('PROVIDER_IMPORT_DOCUMENTS_FAILED', moduleLog, {
details: { reason: error instanceof Error ? error.message : 'unknown' },
details: {
reason: error instanceof Error ? error.message : 'unknown',
...(providerStatus ? { providerStatus } : {}),
...(providerMessage ? { providerMessage } : {}),
},
})
}
},
@@ -35,7 +35,11 @@ import {
downloadBokioUpload,
type BokioUpload,
} from '@/lib/providers/bokio/attachments'
import { FortnoxApiError, FortnoxClient } from '@/lib/providers/fortnox/client'
import {
FortnoxApiError,
FortnoxClient,
isFortnoxPermissionError,
} from '@/lib/providers/fortnox/client'
import {
downloadFortnoxArchiveFile,
fetchFortnoxFileConnections,
@@ -213,7 +217,7 @@ function fortnoxSource(
}
})
} catch (error) {
if (error instanceof FortnoxApiError && error.statusCode === 403) {
if (isFortnoxPermissionError(error)) {
throw new FortnoxDocumentScopesRequiredError()
}
throw error
@@ -386,11 +390,7 @@ export async function importProviderDocuments(
}
}
if (
provider === 'fortnox' &&
finalError instanceof FortnoxApiError &&
finalError.statusCode === 403
) {
if (provider === 'fortnox' && isFortnoxPermissionError(finalError)) {
throw new FortnoxDocumentScopesRequiredError()
}