* feat: add bike benefit handling and optional vacation accrual - Introduced bike benefit (cykelförmån) with calculations for annual market value and monthly taxable value. - Updated schemas to include new benefit types and validation rules. - Implemented API routes for creating, updating, and deleting employee benefits. - Enhanced salary calculation logic to accommodate new vacation rule options, including a 'none' option for no accrual. - Added UI components for managing employee benefits, including input for bike benefit specifics. - Created database migrations for employee benefits and updated salary line items to support new benefit types. * chore: remove Langfuse env var checks Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: enhance OAuth callback URL handling and update default scopes for Visma integration * feat: remove trade_name field and simplify company naming in invoices * refactor: destructure canWrite from useCanWrite for consistency across components * feat: enhance PATCH endpoint to validate existing benefits and handle bike benefit updates * feat: add missing label for bike benefit in salary line item types --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { renderToBuffer } from '@react-pdf/renderer'
|
|
import { FinancialStatementPDF } from '../financial-statement-pdf-template'
|
|
import type { CompanySettings } from '@/types'
|
|
|
|
function fakeCompany(): CompanySettings {
|
|
return {
|
|
company_name: 'Gnubok',
|
|
org_number: '5566778899',
|
|
vat_number: 'SE556677889901',
|
|
address_line1: 'Kungsgatan 1',
|
|
postal_code: '11143',
|
|
city: 'Stockholm',
|
|
country: 'SE',
|
|
entity_type: 'aktiebolag',
|
|
} as unknown as CompanySettings
|
|
}
|
|
|
|
describe('FinancialStatementPDF', () => {
|
|
it('renders a balance-sheet-shaped document to a PDF buffer', async () => {
|
|
const doc = FinancialStatementPDF({
|
|
title: 'Balansräkning',
|
|
groups: [
|
|
{
|
|
heading: 'Tillgångar',
|
|
sections: [
|
|
{
|
|
title: 'Kassa och bank',
|
|
rows: [
|
|
{ account_number: '1930', account_name: 'Företagskonto', amount: 125_432.5 },
|
|
],
|
|
subtotal: 125_432.5,
|
|
},
|
|
],
|
|
totalLabel: 'Summa tillgångar',
|
|
total: 125_432.5,
|
|
},
|
|
{
|
|
heading: 'Eget kapital och skulder',
|
|
sections: [
|
|
{
|
|
title: 'Eget kapital',
|
|
rows: [
|
|
{ account_number: '2010', account_name: 'Eget kapital', amount: 100_000 },
|
|
{ account_number: '2091', account_name: 'Balanserat resultat', amount: 25_432.5 },
|
|
],
|
|
subtotal: 125_432.5,
|
|
},
|
|
],
|
|
totalLabel: 'Summa eget kapital och skulder',
|
|
total: 125_432.5,
|
|
},
|
|
],
|
|
period: { start: '2026-01-01', end: '2026-12-31' },
|
|
company: fakeCompany(),
|
|
generatedAt: '2026-04-21T10:00:00Z',
|
|
})
|
|
|
|
const buffer = await renderToBuffer(doc)
|
|
expect(buffer).toBeInstanceOf(Buffer)
|
|
expect(buffer.length).toBeGreaterThan(1000)
|
|
// PDF files always start with "%PDF-"
|
|
expect(buffer.slice(0, 5).toString()).toBe('%PDF-')
|
|
})
|
|
|
|
it('renders an income-statement-shaped document with a summary block', async () => {
|
|
const doc = FinancialStatementPDF({
|
|
title: 'Resultaträkning',
|
|
groups: [
|
|
{
|
|
heading: 'Rörelseintäkter',
|
|
sections: [
|
|
{
|
|
title: 'Huvudintäkter',
|
|
rows: [
|
|
{ account_number: '3001', account_name: 'Försäljning 25%', amount: 500_000 },
|
|
],
|
|
subtotal: 500_000,
|
|
},
|
|
],
|
|
totalLabel: 'Summa rörelseintäkter',
|
|
total: 500_000,
|
|
},
|
|
{
|
|
heading: 'Rörelsekostnader',
|
|
sections: [
|
|
{
|
|
title: 'Lokalkostnader',
|
|
rows: [
|
|
{ account_number: '5010', account_name: 'Lokalhyra', amount: 120_000 },
|
|
],
|
|
subtotal: 120_000,
|
|
},
|
|
],
|
|
totalLabel: 'Summa rörelsekostnader',
|
|
total: 120_000,
|
|
negate: true,
|
|
},
|
|
],
|
|
summary: [
|
|
{ label: 'Rörelseresultat', amount: 380_000 },
|
|
{ label: 'Årets resultat', amount: 380_000, emphasis: true },
|
|
],
|
|
period: { start: '2026-01-01', end: '2026-12-31' },
|
|
company: fakeCompany(),
|
|
generatedAt: '2026-04-21T10:00:00Z',
|
|
})
|
|
|
|
const buffer = await renderToBuffer(doc)
|
|
expect(buffer).toBeInstanceOf(Buffer)
|
|
expect(buffer.slice(0, 5).toString()).toBe('%PDF-')
|
|
})
|
|
|
|
it('handles empty section groups gracefully', async () => {
|
|
const doc = FinancialStatementPDF({
|
|
title: 'Balansräkning',
|
|
groups: [
|
|
{
|
|
heading: 'Tillgångar',
|
|
sections: [],
|
|
totalLabel: 'Summa tillgångar',
|
|
total: 0,
|
|
},
|
|
{
|
|
heading: 'Eget kapital och skulder',
|
|
sections: [],
|
|
totalLabel: 'Summa eget kapital och skulder',
|
|
total: 0,
|
|
},
|
|
],
|
|
period: { start: '', end: '' },
|
|
company: fakeCompany(),
|
|
generatedAt: '2026-04-21T10:00:00Z',
|
|
})
|
|
|
|
const buffer = await renderToBuffer(doc)
|
|
expect(buffer.slice(0, 5).toString()).toBe('%PDF-')
|
|
})
|
|
})
|