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:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
5a8dd21931
commit
e733ab7c43
+19
@@ -121,6 +121,25 @@ describe('POST /api/bookkeeping/fiscal-periods/[id]/arsredovisning/narrative', (
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('returns 400 for a personnummer-shaped parent company org number', async () => {
|
||||
setupSupabase()
|
||||
const res = await POST(postReq({ parent_company_org_number: '19850101-1234' }), idParams)
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
|
||||
it('accepts a foreign parent company registration identifier', async () => {
|
||||
const { enqueue } = setupSupabase()
|
||||
enqueue({ data: { id: 'period-1' } }) // fiscal_periods ownership check
|
||||
enqueue({ data: null }) // no registrerad submission
|
||||
enqueue({ data: { ...narrativeRow, parent_company_org_number: 'CHE-123.456.789' } }) // upsert
|
||||
enqueue({ data: null }) // clear narrative confirmation
|
||||
const { status, body } = await parseJsonResponse<{ data: typeof narrativeRow }>(
|
||||
await POST(postReq({ parent_company_org_number: 'CHE-123.456.789' }), idParams),
|
||||
)
|
||||
expect(status).toBe(200)
|
||||
expect(body.data.parent_company_org_number).toBe('CHE-123.456.789')
|
||||
})
|
||||
|
||||
it('returns 400 when the payload contains an unknown field', async () => {
|
||||
setupSupabase()
|
||||
const res = await POST(
|
||||
|
||||
@@ -7,6 +7,10 @@ import {
|
||||
getNarrative,
|
||||
upsertNarrative,
|
||||
} from '@/lib/bokslut/arsredovisning/narrative-service'
|
||||
import {
|
||||
isValidParentCompanyIdentifier,
|
||||
PARENT_COMPANY_IDENTIFIER_ERROR,
|
||||
} from '@/lib/bokslut/arsredovisning/parent-company-identifier'
|
||||
|
||||
// Strip non-printable control characters that would corrupt PDF output or
|
||||
// mislead a human reader of the årsredovisning. Whitelist printable ASCII
|
||||
@@ -69,16 +73,15 @@ const PostSchema = z.object({
|
||||
securities_pledged: sanitizedText(4000).nullable().optional(),
|
||||
contingent_liabilities: sanitizedText(4000).nullable().optional(),
|
||||
parent_company_name: sanitizedText(200).nullable().optional(),
|
||||
// Swedish organisationsnummer NNNNNN-NNNN. Third digit ≥ 2 distinguishes
|
||||
// legal-entity org numbers from personnummer (whose third digit forms part
|
||||
// of a month, 0-1). ÅRL 5:13-15 disclosure is about parent legal entities,
|
||||
// so personnummer-shaped values are out of scope and a GDPR Art.5(1)(c)
|
||||
// data-minimisation concern if persisted. Empty string clears the override.
|
||||
// Swedish organisationsnummer or a foreign parent's registration identifier
|
||||
// (a Swiss holding's CHE-number was refused as "ej personnummer" on
|
||||
// 2026-08-20). Rules live in parent-company-identifier.ts; personnummer-
|
||||
// shaped values stay rejected. Empty string clears the override.
|
||||
parent_company_org_number: z
|
||||
.union([
|
||||
z.literal(''),
|
||||
z.string().regex(/^\d{2}[2-9]\d{3}-\d{4}$/, {
|
||||
message: 'Ogiltigt organisationsnummer (NNNNNN-NNNN, ej personnummer)',
|
||||
z.string().max(40).refine(isValidParentCompanyIdentifier, {
|
||||
message: PARENT_COMPANY_IDENTIFIER_ERROR,
|
||||
}),
|
||||
])
|
||||
.nullable()
|
||||
|
||||
Reference in New Issue
Block a user