Files
accounted/lib/bokslut/arsredovisning/__tests__/completeness.test.ts
T
Mattsson e11f70b347 Bug/gh issues fiz (#1103)
* refactor: optimize page loading and data fetching

* fix: resolve recurring production runtime errors

* feat: add MCP company and customer updates

* fix: handle year-end tax adjustments

* feat: harden annual report compliance

* fix: expand invoice logo and font support

* fix: sanitize API route error responses

* fix: sanitize user-facing error messages

* feat: persist onboarding and tax assessment notices

* fix: reduce cloud backup audit churn

* feat: refine invoice editor layout

* fix: show saved tax adjustments in INK2

* fix: complete annual report API mappings

* docs: record operational safeguards and decisions

* fix: harden annual report review findings

* fix: adjust column span for description based on VAT registration

* New css class name
2026-07-21 23:00:15 +02:00

183 lines
6.7 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { ArsredovisningData } from '../types'
import {
emptyAnnualReportProfile,
type AnnualReportEligibilityResult,
type AnnualReportProfile,
} from '../compliance-types'
import { validateAnnualReportCompleteness } from '../completeness'
const eligibility: AnnualReportEligibilityResult = {
k2_eligible: true,
digital_filing_eligible: true,
size_classification: 'smaller',
k2_relief_rule: 'eligible',
issues: [],
digital_issues: [],
}
function report(): ArsredovisningData {
return {
accounting_framework: 'k2',
company: { name: 'Test AB', org_number: '556012-5790', city: 'Stockholm' },
fiscal_period: {
id: 'period-1',
name: '2025',
period_start: '2025-01-01',
period_end: '2025-12-31',
},
previous_period: null,
forvaltningsberattelse: {
description: 'Bolaget bedriver konsultverksamhet.',
important_events: 'Inga väsentliga händelser.',
resultatdisposition: 'Resultatet balanseras i ny räkning.',
proposed_dividend: 0,
resultatdisposition_amounts: {
retained_earnings: 80,
share_premium_reserve: 0,
current_year_result: 20,
total: 100,
proposed_dividend: 0,
carried_forward: 100,
},
agm_date: '2026-03-15',
},
balansrakning: {
total_assets: 100,
total_equity_liabilities: 100,
total_assets_previous: null,
total_equity_liabilities_previous: null,
assets: [{ label: 'Bank', amount: 100 }],
},
resultatrakning: [{ label: 'Nettoomsättning', amount: 100 }],
noter: [{ number: 1, title: 'Principer', body: 'K2' }],
signatures: [{ role: 'Styrelseledamot', name: 'Anna Andersson', signed_at: '2026-03-01' }],
warnings: [],
} as unknown as ArsredovisningData
}
function input(stage: 'draft' | 'signing' | 'filing') {
const profile: AnnualReportProfile = {
...emptyAnnualReportProfile('company-1', 'period-1'),
k2_assessment_confirmed_at: '2026-02-01T10:00:00Z',
narrative_confirmed_at: '2026-02-01T10:00:00Z',
signer_roster_confirmed_at: '2026-02-01T10:00:00Z',
is_in_liquidation: false,
auditor_report_required: false,
}
return {
report: report(),
profile,
eligibility,
stage,
todayIso: '2026-03-20',
disclosures: {
long_term_debt_over_five_years_confirmed: true,
securities_pledged_confirmed: true,
contingent_liabilities_confirmed: true,
parent_company_confirmed: true,
agm_disposition_outcome: 'proposal_approved' as
| 'proposal_approved'
| 'alternative_decision'
| null,
agm_disposition_decision: null,
},
}
}
describe('validateAnnualReportCompleteness', () => {
it('accepts a complete filing model', () => {
const result = validateAnnualReportCompleteness(input('filing'))
expect(result.ok).toBe(true)
expect(result.error_count).toBe(0)
})
it('does not infer an unanswered disclosure confirmation', () => {
const value = input('draft')
value.disclosures.securities_pledged_confirmed = false
const result = validateAnnualReportCompleteness(value)
expect(result.ok).toBe(false)
expect(result.issues.some((issue) => issue.code === 'AR-NOTE-SECURITIES-UNCONFIRMED')).toBe(
true,
)
})
it('requires signers before a version can be locked', () => {
const value = input('signing')
value.report.signatures = []
const result = validateAnnualReportCompleteness(value)
expect(result.issues.some((issue) => issue.code === 'AR-SIGNERS-MISSING')).toBe(true)
})
it('validates the organisation number check digit', () => {
const value = input('draft')
value.report.company.org_number = '556012-5791'
const result = validateAnnualReportCompleteness(value)
expect(result.issues.some((issue) => issue.code === 'AR-COMPANY-ORGNR')).toBe(true)
})
it('requires confirmation that the signer roster matches Bolagsverket', () => {
const value = input('signing')
value.profile.signer_roster_confirmed_at = null
const result = validateAnnualReportCompleteness(value)
expect(result.issues.some((issue) => issue.code === 'AR-SIGNER-ROSTER-UNCONFIRMED')).toBe(true)
})
it('blocks impossible signature and AGM chronology', () => {
const value = input('filing')
value.report.signatures[0].signed_at = '2025-12-30'
value.report.forvaltningsberattelse.agm_date = '2025-12-29'
const result = validateAnnualReportCompleteness(value)
expect(result.issues.map((issue) => issue.code)).toEqual(
expect.arrayContaining(['AR-SIGNATURE-BEFORE-PERIOD-END', 'AR-AGM-BEFORE-PERIOD-END']),
)
})
it('keeps K3 output draft-only until the complete K3 disclosure matrix exists', () => {
const value = input('signing')
value.report.accounting_framework = 'k3'
const result = validateAnnualReportCompleteness(value)
expect(result.issues.some((issue) => issue.code === 'AR-K3-DRAFT-ONLY')).toBe(true)
})
it('requires the AGM decision and evidence dates at filing stage', () => {
const value = input('filing')
value.report.signatures[0].signed_at = null
value.disclosures.agm_disposition_outcome = null
const result = validateAnnualReportCompleteness(value)
expect(result.issues.map((issue) => issue.code)).toEqual(
expect.arrayContaining(['AR-SIGNATURES-INCOMPLETE', 'AR-AGM-DISPOSITION-OUTCOME']),
)
})
it('blocks a proposed dividend above distributable equity', () => {
const value = input('draft')
value.report.forvaltningsberattelse.resultatdisposition_amounts.proposed_dividend = 101
const result = validateAnnualReportCompleteness(value)
expect(result.issues.some((issue) => issue.code === 'AR-DIVIDEND-EXCEEDS-EQUITY')).toBe(true)
})
it('requires a documented prudence assessment for a positive dividend', () => {
const value = input('draft')
value.report.forvaltningsberattelse.resultatdisposition_amounts.proposed_dividend = 50
const result = validateAnnualReportCompleteness(value)
expect(
result.issues.some((issue) => issue.code === 'AR-DIVIDEND-PRUDENCE-UNCONFIRMED'),
).toBe(true)
value.profile.dividend_prudence_confirmed = true
const confirmed = validateAnnualReportCompleteness(value)
expect(
confirmed.issues.some((issue) => issue.code === 'AR-DIVIDEND-PRUDENCE-UNCONFIRMED'),
).toBe(false)
})
it('does not report a dividend error for zero dividend and negative equity', () => {
const value = input('draft')
value.report.forvaltningsberattelse.resultatdisposition_amounts.total = -100
value.report.forvaltningsberattelse.resultatdisposition_amounts.carried_forward = -100
const result = validateAnnualReportCompleteness(value)
expect(result.issues.some((issue) => issue.code.startsWith('AR-DIVIDEND-'))).toBe(false)
})
})