Files
accounted/lib/bokslut/arsredovisning/__tests__/version-service.test.ts
T
Mattsson 466e55a015 Fix/invoice delivery and payment accounts (#1116)
* fix: reconcile annual reports with final closing entries

* test: cover annual report depreciation and VAT balances

* Merge remote-tracking branch 'origin/main' into fix/usr-fdbck-ch

* fix: show exact invoice delivery details

* fix: use currency account in invoice emails

* fix: address invoice delivery review feedback

* fix: harden invoice delivery and payment accounts

* test: assert RLS-denied zero-row updates

* fix: close remaining invoice compliance gaps

* fix: harden invoice archive authorization

* fix: close invoice delivery review findings

* fix: verify delivery finalization results

* fix: cap combined invoice email recipients

* fix: close final invoice compliance findings

* fix: prevent stale payment account saves

* test: prove invoice delivery isolation

* fix: close invoice privacy review findings

* test: normalize delivery retention dates
2026-07-23 09:54:02 +02:00

153 lines
5.4 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import type { CanonicalAnnualReport } from '../compliance-types'
import {
annualReportContentHash,
createAnnualReportVersion,
} from '../version-service'
import { mapTrialBalancesToK2 } from '../../ixbrl/k2-mapper'
import { buildBrRows, buildRrRows } from '../statement-rows'
function model(signedAt: string | null): CanonicalAnnualReport {
const full = [
{ account_number: '1930', account_name: 'Bank', closing_debit: 100, closing_credit: 0 },
{ account_number: '2081', account_name: 'Share capital', closing_debit: 0, closing_credit: 80 },
{ account_number: '2099', account_name: 'Current result', closing_debit: 0, closing_credit: 20 },
{ account_number: '3010', account_name: 'Revenue', closing_debit: 20, closing_credit: 20 },
]
const preClosing = [
{ account_number: '1930', account_name: 'Bank', closing_debit: 100, closing_credit: 0 },
{ account_number: '2081', account_name: 'Share capital', closing_debit: 0, closing_credit: 80 },
{ account_number: '3010', account_name: 'Revenue', closing_debit: 0, closing_credit: 20 },
]
const mapping = mapTrialBalancesToK2({ full, preClosing }, null)
const balanceRows = buildBrRows(mapping)
return {
schema_version: '1.0',
generated_at: '2026-07-21T10:00:00Z',
company_id: 'company-1',
fiscal_period_id: 'period-1',
entity_type: 'aktiebolag',
report: {
accounting_framework: 'k2',
signatures: [{ role: 'Styrelseledamot', name: 'Anna Andersson', signed_at: signedAt }],
resultatrakning: buildRrRows(mapping),
balansrakning: {
assets: balanceRows.assets,
equity_liabilities: balanceRows.equityLiabilities,
total_assets: 100,
total_equity_liabilities: 100,
},
forvaltningsberattelse: {
resultatdisposition_amounts: { current_year_result: 20 },
},
},
profile: { reporting_currency: 'SEK' },
disclosures: {},
eligibility: {
digital_filing_eligible: true,
digital_issues: [],
},
validation: { ok: true },
ixbrl: {
entryPointId: 'k2-ab-risbs-2024-09-12',
rr: mapping.rr,
br: mapping.br,
totals: mapping.totals,
underskrifter: {
dateringsdatum: signedAt,
signers: [
{
firstName: 'Anna',
lastName: 'Andersson',
role: 'Styrelseledamot',
signedDate: signedAt,
},
],
},
faststallelseintyg: {
signerFirstName: 'Anna',
signerLastName: 'Andersson',
signerRole: 'Styrelseledamot',
genereratDatum: '2026-07-21',
},
},
} as unknown as CanonicalAnnualReport
}
describe('annualReportContentHash', () => {
it('does not change when evidence dates are overlaid after locking', () => {
expect(annualReportContentHash(model(null))).toBe(
annualReportContentHash(model('2026-03-01T10:00:00Z')),
)
})
it('changes when signed content changes', () => {
const original = model(null)
const changed = model(null)
changed.report.signatures[0].name = 'Bertil Andersson'
expect(annualReportContentHash(changed)).not.toBe(annualReportContentHash(original))
})
it('locks the digital eligibility decision into the validation snapshot', async () => {
const rpc = vi.fn().mockResolvedValue({
data: [{ id: 'version-1', version_number: 1, status: 'draft' }],
error: null,
})
await createAnnualReportVersion({ rpc } as never, 'user-1', model(null), false)
expect(rpc).toHaveBeenCalledWith(
'create_annual_report_version',
expect.objectContaining({
p_validation_summary: expect.objectContaining({
digital_filing_eligible: true,
digital_issues: [],
profile: expect.objectContaining({ reporting_currency: 'SEK' }),
disclosures: {},
eligibility: expect.objectContaining({ digital_filing_eligible: true }),
}),
}),
)
})
it('refuses to snapshot inconsistent financial statements', async () => {
const rpc = vi.fn()
const inconsistent = model(null)
inconsistent.report.resultatrakning.find(
(row) => row.label === 'Årets resultat',
)!.current = 21
inconsistent.validation = { ...inconsistent.validation, ok: true, issues: [] }
await expect(
createAnnualReportVersion({ rpc } as never, 'user-1', inconsistent, false),
).rejects.toThrow('inconsistent financial statements')
expect(rpc).not.toHaveBeenCalled()
})
it('refuses a PDF and iXBRL line reclassification with unchanged totals', async () => {
const rpc = vi.fn()
const inconsistent = model(null)
inconsistent.ixbrl!.rr = {
...inconsistent.ixbrl!.rr,
Nettoomsattning: { current: 0, previous: null },
OvrigaRorelseintakter: { current: 20, previous: null },
}
await expect(
createAnnualReportVersion({ rpc } as never, 'user-1', inconsistent, false),
).rejects.toThrow('inconsistent financial statements')
expect(rpc).not.toHaveBeenCalled()
})
it('refuses a mutated visible balance-sheet result row', async () => {
const rpc = vi.fn()
const inconsistent = model(null)
inconsistent.report.balansrakning.equity_liabilities.find(
(row) => row.label === 'Årets resultat',
)!.current = 19
await expect(
createAnnualReportVersion({ rpc } as never, 'user-1', inconsistent, false),
).rejects.toThrow('inconsistent financial statements')
expect(rpc).not.toHaveBeenCalled()
})
})