Files
accounted/lib/reports/ink2/__tests__/sru-generator.test.ts
T
Jakob WennbergandClaude Opus 4.6 29240738fa feat: add INK2 declaration, full archive export, AI consent gate, fix VAT declaration rutor
- Fix VAT declaration ruta mappings to match SKV 4700 form correctly
  (ruta 05 = total taxable sales, ruta 10/11/12 = output VAT per rate)
- Add INK2 declaration report for aktiebolag with SRU export
- Add full archive ZIP export for 7-year retention compliance
- Add AI consent gate requiring user approval before AI extension API calls
- Add DPA and privacy policy public pages
- Add audit trail API routes
- Update VAT registration threshold from 80k to 120k kr in onboarding
- Update CLAUDE.md documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:20:47 +01:00

175 lines
5.9 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { generateSRUFile, sruFileToString, validateSRUFile, getSRUFilename } from '../sru-generator'
import type { INK2Declaration } from '../types'
function makeDeclaration(overrides?: Partial<INK2Declaration>): INK2Declaration {
return {
fiscalYear: {
id: 'period-1',
name: 'Räkenskapsår 2025',
start: '2025-01-01',
end: '2025-12-31',
isClosed: true,
},
rutor: {
'7201': 0, '7202': 50000, '7203': 0,
'7210': 10000, '7211': 25000, '7212': 100000,
'7220': 50000, '7221': 20000, '7222': 15000,
'7230': 30000, '7231': 70000,
'7310': 500000, '7320': 200000, '7330': 100000,
'7340': 80000, '7350': 10000, '7360': 5000,
'7370': -3000, '7380': 0,
},
breakdown: {} as INK2Declaration['breakdown'],
totals: {
totalAssets: 185000,
totalEquityLiabilities: 185000,
operatingResult: 105000,
resultAfterFinancial: 102000,
},
companyInfo: {
companyName: 'Test AB',
orgNumber: '556677-8899',
},
warnings: [],
...overrides,
}
}
describe('INK2 SRU Generator', () => {
describe('generateSRUFile', () => {
it('produces valid SRU file structure', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const validation = validateSRUFile(sruFile)
expect(validation.isValid).toBe(true)
expect(validation.errors).toEqual([])
})
it('uses #BLANKETT INK2', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const blankettRecord = sruFile.records.find(r => r.fieldCode === 'BLANKETT')
expect(blankettRecord?.value).toBe('INK2')
})
it('includes only non-zero field values', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const uppgiftRecords = sruFile.records.filter(r => r.fieldCode === 'UPPGIFT')
// 7000 (fiscal year) + non-zero rutor
// Zero rutor: 7201, 7203, 7380 = 3 zero fields
// Non-zero: 16 fields
// Total UPPGIFT records: 1 (fiscal year) + 16 (non-zero values)
expect(uppgiftRecords).toHaveLength(17)
// Verify zero fields are excluded
const fieldCodes = uppgiftRecords.map(r => String(r.value).split(' ')[0])
expect(fieldCodes).not.toContain('7201')
expect(fieldCodes).not.toContain('7203')
expect(fieldCodes).not.toContain('7380')
})
it('includes fiscal year as field 7000', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const fiscalYearRecord = sruFile.records.find(
r => r.fieldCode === 'UPPGIFT' && String(r.value).startsWith('7000')
)
expect(fiscalYearRecord).toBeDefined()
expect(fiscalYearRecord?.value).toBe('7000 20250101-20251231')
})
it('handles negative values (financial items)', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const financialRecord = sruFile.records.find(
r => r.fieldCode === 'UPPGIFT' && String(r.value).startsWith('7370')
)
expect(financialRecord?.value).toBe('7370 -3000')
})
it('strips dashes from org number', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const identityRecord = sruFile.records.find(r => r.fieldCode === 'IDENTITET')
expect(identityRecord?.value).toBe('5566778899')
})
})
describe('sruFileToString', () => {
it('formats records as #FIELD value lines', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const content = sruFileToString(sruFile)
expect(content).toContain('#BLANKETT INK2')
expect(content).toContain('#IDENTITET 5566778899')
expect(content).toContain('#BLANKETTSLUT')
})
it('uses CRLF line endings', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const content = sruFileToString(sruFile)
expect(content).toContain('\r\n')
})
it('ends with newline', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const content = sruFileToString(sruFile)
expect(content.endsWith('\r\n')).toBe(true)
})
})
describe('getSRUFilename', () => {
it('returns correct filename format', () => {
const declaration = makeDeclaration()
expect(getSRUFilename(declaration)).toBe('INK2_5566778899_2025.sru')
})
it('handles missing org number', () => {
const declaration = makeDeclaration({
companyInfo: { companyName: 'Test AB', orgNumber: null },
})
expect(getSRUFilename(declaration)).toBe('INK2_unknown_2025.sru')
})
})
describe('validateSRUFile', () => {
it('validates a correct SRU file', () => {
const declaration = makeDeclaration()
const sruFile = generateSRUFile(declaration)
const result = validateSRUFile(sruFile)
expect(result.isValid).toBe(true)
})
it('detects missing PRODUKT header', () => {
const result = validateSRUFile({
records: [
{ fieldCode: 'BLANKETT', value: 'INK2' },
{ fieldCode: 'BLANKETTSLUT', value: '' },
],
generatedAt: new Date().toISOString(),
})
expect(result.isValid).toBe(false)
expect(result.errors).toContain('Missing PRODUKT header')
})
it('detects wrong blankett type', () => {
const result = validateSRUFile({
records: [
{ fieldCode: 'PRODUKT', value: 'KONTROLLUPPGIFTER' },
{ fieldCode: 'BLANKETT', value: 'NE' },
{ fieldCode: 'BLANKETTSLUT', value: '' },
],
generatedAt: new Date().toISOString(),
})
expect(result.isValid).toBe(false)
expect(result.errors).toContain('Expected BLANKETT INK2, got NE')
})
})
})