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
+16 -1
View File
@@ -48,6 +48,8 @@ interface AnnualReportStudioProps {
hasUnsavedNarrative: boolean
narrativeRevision: string | null
onVersionsChanged?: (versions: AnnualReportVersionSummary[]) => void
/** Blocking-issue count once the compliance check has loaded, null while loading. */
onBlockingCountChanged?: (count: number | null) => void
}
type NullableBoolean = boolean | null
@@ -91,6 +93,7 @@ export function AnnualReportStudio({
hasUnsavedNarrative,
narrativeRevision,
onVersionsChanged,
onBlockingCountChanged,
}: AnnualReportStudioProps) {
const t = useTranslations('annualReportStudio')
const { toast } = useToast()
@@ -143,6 +146,11 @@ export function AnnualReportStudio({
() => compliance?.validation.issues.filter((issue) => issue.severity === 'error') ?? [],
[compliance],
)
// The signature section further down explains why "Låst version" is empty
// in terms of this count, so it must not read 0 while we are still loading.
useEffect(() => {
onBlockingCountChanged?.(compliance ? blockingIssues.length : null)
}, [compliance, blockingIssues.length, onBlockingCountChanged])
const digitalOnlyIssues = useMemo(() => {
const generalCodes = new Set(compliance?.validation.issues.map((issue) => issue.code) ?? [])
return (
@@ -496,7 +504,7 @@ export function AnnualReportStudio({
</div>
</section>
<section>
<section id="ar-fullstandighetskontroll" className="scroll-mt-24">
<div className="mb-1 flex items-center gap-2 px-1">
<h3 className="font-sans text-xs font-medium uppercase tracking-wider text-muted-foreground">{t('checks_title')}</h3>
<div className="h-px flex-1 bg-border/60" />
@@ -561,6 +569,13 @@ export function AnnualReportStudio({
{t('lock_version')}
</Button>
</div>
{(blockingIssues.length > 0 || hasUnsavedNarrative) && (
<p className="text-right text-xs leading-5 text-muted-foreground">
{hasUnsavedNarrative
? t('lock_hint_unsaved')
: t('lock_hint_blocked', { count: blockingIssues.length })}
</p>
)}
</div>
</section>