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
@@ -1069,6 +1069,7 @@ function OptionsStep({
onStart: () => void
onBack: () => void
}) {
const t = useTranslations('extensions')
const [showConfirm, setShowConfirm] = useState(false)
const toggleOption = (key: keyof MigrationOptions) => {
@@ -1155,7 +1156,7 @@ function OptionsStep({
<div className="flex items-center gap-3 border-t border-border py-3">
<div className="min-w-0 flex-1">
<p className="text-sm font-medium">Verifikationsserie</p>
<p className="mt-0.5 text-xs text-muted-foreground">Serie för importerade verifikationer</p>
<p className="mt-0.5 text-xs text-muted-foreground">{t('ext_arcim_option_series_help')}</p>
</div>
<Input
className="w-16 text-center"
@@ -1597,6 +1598,13 @@ function DocumentImportFollowUp({
? t('ext_arcim_documents_discovery_error')
: t('ext_arcim_documents_import_error')}
</p>
{state.problem?.providerMessage && (
<p className="text-xs text-muted-foreground">
{t('ext_arcim_documents_provider_message', {
message: state.problem.providerMessage,
})}
</p>
)}
{state.problem?.requestId && (
<p className="text-xs text-muted-foreground">
{t('ext_arcim_documents_error_reference', {
@@ -63,6 +63,8 @@ export interface ArcimDocumentImportProblem {
requestId: string | null
reconnectRequired: boolean
message?: string
/** What the source system (Fortnox/Bokio) itself answered, when known. */
providerMessage?: string
}
export function documentOAuthProblemFromReason(
@@ -211,16 +213,25 @@ function problemFromPayload(payload: unknown): ArcimDocumentImportProblem {
const error = (payload as { error?: unknown } | null)?.error
const structured =
error && typeof error === 'object'
? (error as { code?: unknown; requestId?: unknown })
? (error as { code?: unknown; requestId?: unknown; details?: unknown })
: null
const code = typeof structured?.code === 'string' ? structured.code : null
const requestId =
typeof structured?.requestId === 'string' ? structured.requestId : null
const details =
structured?.details && typeof structured.details === 'object'
? (structured.details as { providerMessage?: unknown })
: null
const providerMessage =
typeof details?.providerMessage === 'string' && details.providerMessage.trim()
? details.providerMessage.trim()
: null
return {
code,
requestId,
reconnectRequired: code === PROVIDER_DOCUMENT_SCOPES_REQUIRED,
...(providerMessage ? { providerMessage } : {}),
}
}