* feat(arcim-migration): Briox provider with SIE-over-API import - Briox auth via account ID + application token (no app-level credentials); both tokens rotate on refresh and are persisted - New sie-fetcher pulls the general ledger as SIE through the provider API for Fortnox, Briox and Bjorn Lunden - Wizard stops on a failed SIE import and surfaces the real errors instead of proceeding to the misleading migrate-guard message - PROVIDER_SIE_ONLY_FORTNOX renamed to PROVIDER_SIE_NOT_SUPPORTED; new PROVIDER_TOKEN_INVALID for rejected provider credentials Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bookkeeping): per-line accruals (periodisering) on invoices and supplier invoices Defer revenue/costs per invoice line to 29xx/17xx interim accounts with automatic monthly dissolution (nightly cron + catch-up at registration), schedule cancellation on credit, year-end auto-detect exclusion for already-scheduled invoices, invoice-inbox service-period extraction for prefill, and an MCP tool to list schedules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bokslut): iXBRL arsredovisning generation and Bolagsverket digital filing Generate the annual report as iXBRL from a generated taxonomy registry (K2 element lists, taxonomy:generate/check scripts + CI guard), expose it via the fiscal-period API, and add the bolagsverket extension for digital submission to eget utrymme with webhook-driven status tracking (submissions table + pg tests, lifecycle events, year-end wizard UI). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(mcp): raise origin-guard test timeout to 20s The dynamic import pulls in the full server module; the parse alone flirts with the 5s default under full-suite parallel load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add new scripts and documentation for K2 AB taxonomy generation and validation - Introduced `generate-taxonomy-registry.ts` to automate the generation of the iXBRL taxonomy concept registry from official element lists and tuple models. - Added `validate-ixbrl.mjs` for validating generated iXBRL reports against the official taxonomy package using Arelle. - Included new documentation files: - `k2-ab-arsredovisning-elementlista-2024-09-12_rev20250312_sv.xlsx` - `tuple-innehallsmodell-arsredovisning-k2-2024-09-12.xlsx` - `taxonomi-paket-2024-09-12_rev20250312.zip` * Add tests for bookkeeping accruals dissolution and supplier invoices - Implement tests for the POST /api/bookkeeping/accruals/[id]/dissolve route, covering success and error scenarios. - Add tests for the DELETE /api/supplier-invoices/[id] route, including authentication checks and validation of invoice deletion conditions. - Introduce tests for the Arcim migration provider client, ensuring token handling and error classification. - Create tests for the Bolagsverket extension, validating submission role enforcement and environment settings. - Add Zod schemas for Bolagsverket response payloads to ensure proper validation. - Implement tests for MCP server's list accrual schedules, confirming registration and scope mapping. - Add consistency tests for IXBRL document generation, ensuring duplicate facts and XML escaping are handled correctly. - Introduce typed domain errors for accrual schedules to improve error handling in the service. - Add tests for resolving consent with Briox token refresh concurrency, ensuring proper token management and error handling. * fix(tests): update payload size guard comments to reflect recent changes in tool descriptions and ceiling adjustments * fix(gitattributes): mark generated JSON files in bokslut taxonomy as linguist-generated * feat(migrations): add backfill for invoices.journal_entry_id and fallback for next_voucher_number user_id * feat(bokslut): enhance compliance and financial processing features with new submission details and security measures --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
133 lines
5.4 KiB
TypeScript
133 lines
5.4 KiB
TypeScript
/**
|
||
* Golden tests against the official Bolagsverket/taxonomier.se example
|
||
* documents in dev_docs/bokslut/exempel/. They pin our generator's
|
||
* conventions (context naming, entity scheme, fact attributes, hidden
|
||
* vallistor, fastställelseintyg structure) to what a known-accepted filing
|
||
* actually looks like.
|
||
*/
|
||
|
||
import { readFileSync } from 'node:fs'
|
||
import { resolve } from 'node:path'
|
||
import { describe, expect, it } from 'vitest'
|
||
import { getRegistry } from '../taxonomy/registry'
|
||
import { generateK2IxbrlDocument } from '../document/k2-document'
|
||
import { makeInput } from './fixtures'
|
||
|
||
const EXEMPEL_PATH = resolve(
|
||
__dirname,
|
||
'../../../../dev_docs/bokslut/exempel/k2/faststalld-arsredovisning-exempel-1-rev20240214.xhtml',
|
||
)
|
||
const official = readFileSync(EXEMPEL_PATH, 'utf8')
|
||
const generated = generateK2IxbrlDocument(makeInput()).xhtml
|
||
|
||
function factNames(xhtml: string): Set<string> {
|
||
const names = new Set<string>()
|
||
for (const match of xhtml.matchAll(/<ix:(?:nonFraction|nonNumeric|tuple) [^>]*name="([\w:-]+)"/g)) {
|
||
names.add(match[1])
|
||
}
|
||
return names
|
||
}
|
||
|
||
describe('golden: official K2 exempel-1', () => {
|
||
it('every concept the official example tags exists in our 2024-09-12 registry, modulo known removals', () => {
|
||
const registry = getRegistry('k2-ab-2024-09-12')
|
||
const missing: string[] = []
|
||
for (const qname of factNames(official)) {
|
||
const local = qname.split(':')[1]
|
||
if (!registry.concepts[local]) missing.push(qname)
|
||
}
|
||
// The official example targets the 2021-10-31 taxonomy. Exactly two of
|
||
// its concepts were removed in 2024-09-12 (verified against the element
|
||
// list): the equity-change total now reuses EgetKapital, and the
|
||
// underskrift-ort concept was dropped with BFN's updated signing rules.
|
||
// Our generator accounts for both. Anything beyond these two means the
|
||
// registry lost coverage — investigate before widening this list.
|
||
expect(missing.sort()).toEqual([
|
||
'se-gen-base:ForandringEgetKapitalTotalt',
|
||
'se-gen-base:UndertecknandeArsredovisningOrt',
|
||
])
|
||
})
|
||
|
||
it('our generated document follows the official context conventions', () => {
|
||
for (const fragment of [
|
||
'<xbrli:identifier scheme="http://www.bolagsverket.se">',
|
||
'<xbrli:context id="period0">',
|
||
'<xbrli:context id="balans0">',
|
||
'<xbrli:unit id="SEK">',
|
||
'<xbrli:measure>iso4217:SEK</xbrli:measure>',
|
||
]) {
|
||
expect(official).toContain(fragment)
|
||
expect(generated).toContain(fragment)
|
||
}
|
||
})
|
||
|
||
it('our hidden vallista set matches the official example for a standard AB', () => {
|
||
const officialHidden =
|
||
official.match(/<ix:hidden>([\s\S]*?)<\/ix:hidden>/)?.[1] ?? ''
|
||
for (const concept of [
|
||
'se-cd-base:SprakHandlingUpprattadList',
|
||
'se-cd-base:LandForetagetsSateList',
|
||
'se-cd-base:RedovisningsvalutaHandlingList',
|
||
'se-cd-base:BeloppsformatList',
|
||
'se-gen-base:FinansiellRapportList',
|
||
'se-cd-base:RakenskapsarForstaDag',
|
||
'se-cd-base:RakenskapsarSistaDag',
|
||
]) {
|
||
expect(officialHidden).toContain(concept)
|
||
expect(generated).toContain(concept)
|
||
}
|
||
})
|
||
|
||
it('our fastställelseintyg structure mirrors the official one', () => {
|
||
for (const fragment of [
|
||
'se-bol-base:ArsstammaIntygande',
|
||
'se-bol-base:FaststallelseResultatBalansrakning',
|
||
'se-bol-base:Arsstamma',
|
||
'se-bol-base:IntygandeOriginalInnehall',
|
||
'se-bol-base:UnderskriftFaststallelseintygElektroniskt',
|
||
'se-bol-base:UnderskriftFaststallelseintygForetradareTilltalsnamn',
|
||
'se-bol-base:UnderskriftFastallelseintygDatum',
|
||
'ID_DATUM_UNDERTECKNANDE_FASTSTALLELSEINTYG',
|
||
]) {
|
||
expect(official).toContain(fragment)
|
||
expect(generated).toContain(fragment)
|
||
}
|
||
// Same continuation pattern joining intygande + original-innehåll.
|
||
expect(official).toMatch(/continuedAt="intygande_forts"/)
|
||
expect(generated).toMatch(/continuedAt="intygande_forts"/)
|
||
})
|
||
|
||
it('shared monetary facts carry the same attribute conventions as the official file', () => {
|
||
// Official: <ix:nonFraction contextRef=… name=… unitRef="SEK" … format="ixt:numspacecomma">
|
||
const officialNetto = official.match(
|
||
/<ix:nonFraction[^>]*name="se-gen-base:Nettoomsattning"[^>]*>/,
|
||
)?.[0]
|
||
const generatedNetto = generated.match(
|
||
/<ix:nonFraction[^>]*name="se-gen-base:Nettoomsattning"[^>]*>/,
|
||
)?.[0]
|
||
expect(officialNetto).toBeDefined()
|
||
expect(generatedNetto).toBeDefined()
|
||
for (const attr of ['unitRef="SEK"', 'format="ixt:numspacecomma"']) {
|
||
expect(officialNetto).toContain(attr)
|
||
expect(generatedNetto).toContain(attr)
|
||
}
|
||
// The official RR shows costs with the minus OUTSIDE the fact element and
|
||
// no sign attribute — verify we do the same for Personalkostnader.
|
||
expect(official).toMatch(/-<ix:nonFraction[^>]*name="se-gen-base:Personalkostnader"/)
|
||
expect(generated).toMatch(/−<ix:nonFraction[^>]*name="se-gen-base:Personalkostnader"/)
|
||
expect(generated).not.toMatch(/name="se-gen-base:Personalkostnader"[^>]*sign=/)
|
||
})
|
||
|
||
it('underskrifter use the same tuple + member concepts as the official file', () => {
|
||
for (const fragment of [
|
||
'se-gaap-ext:UnderskriftArsredovisningForetradareTuple',
|
||
'se-gen-base:UnderskriftHandlingTilltalsnamn',
|
||
'se-gen-base:UnderskriftHandlingEfternamn',
|
||
'se-gen-base:UnderskriftHandlingRoll',
|
||
]) {
|
||
expect(official).toContain(fragment)
|
||
expect(generated).toContain(fragment)
|
||
}
|
||
})
|
||
})
|