Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma strängvärde byggs med konkatenering i stället för att matcha mönstret.
607 lines
23 KiB
TypeScript
607 lines
23 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
|
vi.mock('../trial-balance', () => ({
|
|
generateTrialBalance: vi.fn(),
|
|
}))
|
|
|
|
// Mocked rather than fed through the queued Supabase stub: the queue resolves
|
|
// in strict call order, so an extra real query inside the engine would shift
|
|
// every enqueued response in this file.
|
|
vi.mock('../latest-vouchers', () => ({
|
|
getLatestPostedVouchers: vi.fn(),
|
|
}))
|
|
|
|
import { generateResultatrapport, shiftDateOneYearBack } from '../resultatrapport'
|
|
import { generateTrialBalance } from '../trial-balance'
|
|
import { getLatestPostedVouchers } from '../latest-vouchers'
|
|
import { createQueuedMockSupabase } from '@/tests/helpers'
|
|
import type { TrialBalanceRow } from '@/types'
|
|
|
|
const mockTrialBalance = vi.mocked(generateTrialBalance)
|
|
const mockLatestVouchers = vi.mocked(getLatestPostedVouchers)
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockLatestVouchers.mockResolvedValue([])
|
|
})
|
|
|
|
function makeRow(overrides: Partial<TrialBalanceRow>): TrialBalanceRow {
|
|
const row: TrialBalanceRow = {
|
|
account_number: '3001',
|
|
account_name: 'Test',
|
|
account_class: 3,
|
|
opening_debit: 0,
|
|
opening_credit: 0,
|
|
period_debit: 0,
|
|
period_credit: 0,
|
|
closing_debit: 0,
|
|
closing_credit: 0,
|
|
...overrides,
|
|
}
|
|
// Full-period P&L reality: no opening balance, so window activity equals
|
|
// closing. Tests specify closing_*; mirror into period_* unless the test
|
|
// sets period activity explicitly.
|
|
if (row.period_debit === 0 && row.period_credit === 0) {
|
|
row.period_debit = row.closing_debit
|
|
row.period_credit = row.closing_credit
|
|
}
|
|
return row
|
|
}
|
|
|
|
function tb(rows: TrialBalanceRow[]) {
|
|
const totalDebit = rows.reduce((s, r) => s + r.closing_debit, 0)
|
|
const totalCredit = rows.reduce((s, r) => s + r.closing_credit, 0)
|
|
return {
|
|
rows,
|
|
totalDebit: Math.round(totalDebit * 100) / 100,
|
|
totalCredit: Math.round(totalCredit * 100) / 100,
|
|
isBalanced: Math.abs(totalDebit - totalCredit) < 0.01,
|
|
}
|
|
}
|
|
|
|
describe('generateResultatrapport', () => {
|
|
it('groups P&L accounts by class with current and prior period values', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Försäljning 25%', account_class: 3, closing_credit: 100000 }),
|
|
makeRow({ account_number: '5010', account_name: 'Lokalhyra', account_class: 5, closing_debit: 30000 }),
|
|
makeRow({ account_number: '7210', account_name: 'Löner', account_class: 7, closing_debit: 50000 }),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.groups).toHaveLength(3)
|
|
expect(report.groups.map((g) => g.class)).toEqual([3, 5, 7])
|
|
expect(report.groups[0].rows[0]).toEqual({
|
|
account_number: '3001',
|
|
account_name: 'Försäljning 25%',
|
|
current_period: 100000,
|
|
prior_period: 0,
|
|
})
|
|
// Expense rows shown as negative (credit - debit)
|
|
expect(report.groups[1].rows[0].current_period).toBe(-30000)
|
|
expect(report.groups[2].rows[0].current_period).toBe(-50000)
|
|
|
|
// Net result = revenue - expenses = 100000 - 30000 - 50000 = 20000
|
|
expect(report.net_result_current).toBe(20000)
|
|
expect(report.net_result_prior).toBe(0)
|
|
expect(report.prior_period).toBeNull()
|
|
})
|
|
|
|
it('joins prior-period values onto current accounts', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: 'period-0' },
|
|
error: null,
|
|
})
|
|
q.enqueue({
|
|
data: { period_start: '2025-01-01', period_end: '2025-12-31' },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance
|
|
.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Försäljning', account_class: 3, closing_credit: 200000 }),
|
|
makeRow({ account_number: '5010', account_name: 'Lokalhyra', account_class: 5, closing_debit: 60000 }),
|
|
])
|
|
)
|
|
.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Försäljning', account_class: 3, closing_credit: 150000 }),
|
|
makeRow({ account_number: '5010', account_name: 'Lokalhyra', account_class: 5, closing_debit: 45000 }),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
const revenueRow = report.groups[0].rows[0]
|
|
expect(revenueRow.current_period).toBe(200000)
|
|
expect(revenueRow.prior_period).toBe(150000)
|
|
|
|
const expenseRow = report.groups[1].rows[0]
|
|
expect(expenseRow.current_period).toBe(-60000)
|
|
expect(expenseRow.prior_period).toBe(-45000)
|
|
|
|
expect(report.net_result_current).toBe(140000)
|
|
expect(report.net_result_prior).toBe(105000)
|
|
expect(report.prior_period).toEqual({ start: '2025-01-01', end: '2025-12-31' })
|
|
})
|
|
|
|
it('includes accounts that exist only in prior period (with current=0)', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: 'period-0' },
|
|
error: null,
|
|
})
|
|
q.enqueue({
|
|
data: { period_start: '2025-01-01', period_end: '2025-12-31' },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance
|
|
.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Försäljning', account_class: 3, closing_credit: 100000 }),
|
|
])
|
|
)
|
|
.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Försäljning', account_class: 3, closing_credit: 80000 }),
|
|
// Account discontinued this year
|
|
makeRow({ account_number: '3002', account_name: 'Gammal intäkt', account_class: 3, closing_credit: 5000 }),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
const class3 = report.groups.find((g) => g.class === 3)!
|
|
expect(class3.rows).toHaveLength(2)
|
|
const discontinued = class3.rows.find((r) => r.account_number === '3002')!
|
|
expect(discontinued.current_period).toBe(0)
|
|
expect(discontinued.prior_period).toBe(5000)
|
|
})
|
|
|
|
it('lists a booked 8999 as a class-8 row and nets it into beräknat resultat (#2455)', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Revenue', account_class: 3, closing_credit: 100000 }),
|
|
makeRow({ account_number: '8999', account_name: 'Årets resultat', account_class: 8, closing_debit: 100000 }),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
const class8 = report.groups.find((g) => g.class === 8)
|
|
const row8999 = class8?.rows.find((r) => r.account_number === '8999')
|
|
expect(row8999?.current_period).toBe(-100000)
|
|
// The omföring of årets resultat zeroes the operational result, exactly
|
|
// as a Fortnox/Visma resultatrapport reads after bokslut.
|
|
expect(report.net_result_current).toBe(0)
|
|
})
|
|
|
|
it('ignores balance accounts (class 1-2)', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '1930', account_name: 'Bank', account_class: 1, closing_debit: 50000 }),
|
|
makeRow({ account_number: '2440', account_name: 'Lev.skuld', account_class: 2, closing_credit: 10000 }),
|
|
makeRow({ account_number: '3001', account_name: 'Revenue', account_class: 3, closing_credit: 40000 }),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.groups).toHaveLength(1)
|
|
expect(report.groups[0].class).toBe(3)
|
|
})
|
|
|
|
it('drops rows where both current and prior are zero', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({ account_number: '3001', account_name: 'Revenue', account_class: 3, closing_credit: 50000 }),
|
|
makeRow({ account_number: '3002', account_name: 'Tom rad', account_class: 3, closing_credit: 0 }),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.groups[0].rows).toHaveLength(1)
|
|
expect(report.groups[0].rows[0].account_number).toBe('3001')
|
|
})
|
|
|
|
it('falls back to the date-adjacent prior period when previous_period_id is null', async () => {
|
|
// Reproduces the multi-year-SIE bug: the continuity chain was never linked,
|
|
// so the comparison must resolve the prior year by date instead.
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
// Date-range fallback finds the immediately-preceding period.
|
|
q.enqueue({ data: [{ id: 'period-0' }], error: null })
|
|
// Prior-period dates.
|
|
q.enqueue({ data: { period_start: '2025-01-01', period_end: '2025-12-31' }, error: null })
|
|
|
|
mockTrialBalance
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 200000 })])
|
|
)
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 150000 })])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.groups[0].rows[0].current_period).toBe(200000)
|
|
expect(report.groups[0].rows[0].prior_period).toBe(150000)
|
|
expect(report.prior_period).toEqual({ start: '2025-01-01', end: '2025-12-31' })
|
|
// The fallback resolved 'period-0' and the prior TB was fetched for it.
|
|
expect(mockTrialBalance).toHaveBeenNthCalledWith(2, expect.anything(), 'company-1', 'period-0', {
|
|
closingEntry: 'exclude-all-year-end',
|
|
})
|
|
})
|
|
|
|
it('leaves the prior column empty when there is no earlier period at all', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
q.enqueue({ data: [], error: null }) // no date-adjacent predecessor
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 100000 })])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.prior_period).toBeNull()
|
|
expect(report.net_result_prior).toBe(0)
|
|
expect(mockTrialBalance).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('throws when fiscal period not found', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({ data: null, error: null })
|
|
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
generateResultatrapport(q.supabase as any, 'company-1', 'missing')
|
|
).rejects.toThrow('Fiscal period not found')
|
|
})
|
|
|
|
it('compares a date range against the same window shifted one year back', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: 'period-0' },
|
|
error: null,
|
|
})
|
|
// Fiscal periods covering the shifted window 2025-01-01..2025-03-31.
|
|
q.enqueue({
|
|
data: [{ id: 'period-0', period_start: '2025-01-01', period_end: '2025-12-31' }],
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 90000 })])
|
|
)
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 60000 })])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1', {
|
|
fromDate: '2026-01-01',
|
|
toDate: '2026-03-31',
|
|
})
|
|
|
|
expect(report.groups[0].rows[0].current_period).toBe(90000)
|
|
expect(report.groups[0].rows[0].prior_period).toBe(60000)
|
|
expect(report.prior_period).toEqual({ start: '2025-01-01', end: '2025-03-31' })
|
|
expect(mockTrialBalance).toHaveBeenNthCalledWith(2, expect.anything(), 'company-1', 'period-0', {
|
|
closingEntry: 'exclude-all-year-end',
|
|
fromDate: '2025-01-01',
|
|
toDate: '2025-03-31',
|
|
})
|
|
})
|
|
|
|
it('merges the shifted window across two fiscal periods (brutet räkenskapsår)', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
// Current period: brutet räkenskapsår Jul 2025 - Jun 2026; window is the
|
|
// calendar Q1 2026, so the shifted window Jan-Mar 2025 spans FY 24/25
|
|
// only. Use a window that straddles instead: Jun-Jul 2026 shifted to
|
|
// Jun-Jul 2025, split across FY 24/25 (ends Jun 30) and FY 25/26.
|
|
q.enqueue({
|
|
data: { period_start: '2025-07-01', period_end: '2026-06-30', previous_period_id: 'period-0' },
|
|
error: null,
|
|
})
|
|
q.enqueue({
|
|
data: [
|
|
{ id: 'period-old', period_start: '2024-07-01', period_end: '2025-06-30' },
|
|
{ id: 'period-1', period_start: '2025-07-01', period_end: '2026-06-30' },
|
|
],
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance
|
|
// Current window
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 50000 })])
|
|
)
|
|
// Prior part 1: 2025-06-01..2025-06-30 in period-old
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 10000 })])
|
|
)
|
|
// Prior part 2: 2025-07-01..2025-07-31 in period-1 (current period is a
|
|
// legitimate source when the shifted window reaches into it)
|
|
.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 15000 })])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1', {
|
|
fromDate: '2026-06-01',
|
|
toDate: '2026-07-31',
|
|
})
|
|
|
|
expect(report.groups[0].rows[0].prior_period).toBe(25000)
|
|
expect(report.prior_period).toEqual({ start: '2025-06-01', end: '2025-07-31' })
|
|
expect(mockTrialBalance).toHaveBeenNthCalledWith(2, expect.anything(), 'company-1', 'period-old', {
|
|
closingEntry: 'exclude-all-year-end',
|
|
fromDate: '2025-06-01',
|
|
toDate: '2025-06-30',
|
|
})
|
|
expect(mockTrialBalance).toHaveBeenNthCalledWith(3, expect.anything(), 'company-1', 'period-1', {
|
|
closingEntry: 'exclude-all-year-end',
|
|
fromDate: '2025-07-01',
|
|
toDate: '2025-07-31',
|
|
})
|
|
})
|
|
|
|
it('drops the comparison when the shifted window would overlap the current one', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
// 18-month fiscal period with a 14-month window: shifting back one year
|
|
// overlaps the window itself, so no comparison is possible.
|
|
q.enqueue({
|
|
data: { period_start: '2025-01-01', period_end: '2026-06-30', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 100000 })])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1', {
|
|
fromDate: '2025-01-01',
|
|
toDate: '2026-02-28',
|
|
})
|
|
|
|
expect(report.prior_period).toBeNull()
|
|
expect(mockTrialBalance).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('still drops the comparison for dimension-filtered ranges', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: 'period-0' },
|
|
error: null,
|
|
})
|
|
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([makeRow({ account_number: '3001', account_class: 3, closing_credit: 100000 })])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1', {
|
|
fromDate: '2026-01-01',
|
|
toDate: '2026-03-31',
|
|
dimensions: { '6': 'P001' },
|
|
})
|
|
|
|
expect(report.prior_period).toBeNull()
|
|
expect(mockTrialBalance).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('reports window activity, not rolled-forward YTD closing', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
q.enqueue({ data: [], error: null }) // no fiscal period covers the shifted window
|
|
|
|
// A June window: trial balance rolls Jan-May (60 000) into opening, so
|
|
// closing shows 100 000 YTD while the window's own activity is 40 000.
|
|
mockTrialBalance.mockResolvedValueOnce(
|
|
tb([
|
|
makeRow({
|
|
account_number: '3001',
|
|
account_class: 3,
|
|
opening_credit: 60000,
|
|
period_credit: 40000,
|
|
closing_credit: 100000,
|
|
}),
|
|
])
|
|
)
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1', {
|
|
fromDate: '2026-06-01',
|
|
toDate: '2026-06-30',
|
|
})
|
|
|
|
expect(report.groups[0].rows[0].current_period).toBe(40000)
|
|
expect(report.net_result_current).toBe(40000)
|
|
})
|
|
|
|
describe('latest_vouchers header line', () => {
|
|
function enqueuePeriod(q: ReturnType<typeof createQueuedMockSupabase>) {
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: null },
|
|
error: null,
|
|
})
|
|
mockTrialBalance.mockResolvedValueOnce(tb([]))
|
|
}
|
|
|
|
it('carries the last posted voucher per series', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
enqueuePeriod(q)
|
|
mockLatestVouchers.mockResolvedValueOnce([
|
|
{ series: 'A', last_number: 214 },
|
|
{ series: 'B', last_number: 37 },
|
|
])
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.latest_vouchers).toEqual([
|
|
{ series: 'A', last_number: 214 },
|
|
{ series: 'B', last_number: 37 },
|
|
])
|
|
})
|
|
|
|
it('omits the field entirely when the period has no vouchers', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
enqueuePeriod(q)
|
|
mockLatestVouchers.mockResolvedValueOnce([])
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect('latest_vouchers' in report).toBe(false)
|
|
})
|
|
|
|
it('still returns the report when the lookup fails', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
enqueuePeriod(q)
|
|
mockLatestVouchers.mockRejectedValueOnce(new Error('boom'))
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
expect(report.latest_vouchers).toBeUndefined()
|
|
expect(report.net_result_current).toBe(0)
|
|
})
|
|
|
|
it('scopes the window to the reported date range', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
enqueuePeriod(q)
|
|
|
|
await generateResultatrapport(
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
q.supabase as any,
|
|
'company-1',
|
|
'period-1',
|
|
{ fromDate: '2026-01-01', toDate: '2026-03-31' }
|
|
)
|
|
|
|
expect(mockLatestVouchers).toHaveBeenCalledWith(
|
|
expect.anything(),
|
|
'company-1',
|
|
'period-1',
|
|
{ fromDate: '2026-01-01', toDate: '2026-03-31' }
|
|
)
|
|
})
|
|
|
|
it('skips the lookup entirely on a dimension-filtered report', async () => {
|
|
const q = createQueuedMockSupabase()
|
|
enqueuePeriod(q)
|
|
|
|
const report = await generateResultatrapport(
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
q.supabase as any,
|
|
'company-1',
|
|
'period-1',
|
|
{ dimensions: { '6': 'P001' } }
|
|
)
|
|
|
|
// The report already discloses that it is partial; an unfiltered voucher
|
|
// range next to a filtered result would invite the wrong conclusion.
|
|
expect(mockLatestVouchers).not.toHaveBeenCalled()
|
|
expect(report.latest_vouchers).toBeUndefined()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('shiftDateOneYearBack', () => {
|
|
it('shifts a plain date one year back', () => {
|
|
expect(shiftDateOneYearBack('2026-03-15')).toBe('2025-03-15')
|
|
expect(shiftDateOneYearBack('2026-01-01')).toBe('2025-01-01')
|
|
expect(shiftDateOneYearBack('2026-12-31')).toBe('2025-12-31')
|
|
})
|
|
|
|
it('clamps leap day to the last day of February', () => {
|
|
expect(shiftDateOneYearBack('2028-02-29')).toBe('2027-02-28')
|
|
})
|
|
|
|
it('keeps Feb 29 when the target year is also a leap year divisible correctly', () => {
|
|
// 2001 -> 2000 is a leap year (divisible by 400)
|
|
expect(shiftDateOneYearBack('2001-02-28')).toBe('2000-02-28')
|
|
})
|
|
})
|
|
|
|
describe('closed fiscal year', () => {
|
|
it('excludes year-end closing entries on every trial-balance pass', async () => {
|
|
// Regression: the resultatavslut posts the mirror image of each P&L
|
|
// account into 2099 inside the same period, so without this exclusion the
|
|
// period movements this report sums cancel out and a closed year reads 0
|
|
// on every line. Reported against INK2R on the same ledger 2026-07-29.
|
|
const q = createQueuedMockSupabase()
|
|
q.enqueue({
|
|
data: { period_start: '2026-01-01', period_end: '2026-12-31', previous_period_id: 'period-0' },
|
|
})
|
|
q.enqueue({ data: { period_start: '2025-01-01', period_end: '2025-12-31' } })
|
|
mockTrialBalance.mockResolvedValue({
|
|
rows: [makeRow({ account_number: '3001', period_credit: 500000 })],
|
|
totalDebit: 0,
|
|
totalCredit: 0,
|
|
isBalanced: true,
|
|
})
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const report = await generateResultatrapport(q.supabase as any, 'company-1', 'period-1')
|
|
|
|
// Both the current pass and the prior-year comparison pass must exclude:
|
|
// a prior year is almost always closed.
|
|
for (const call of mockTrialBalance.mock.calls) {
|
|
expect(call[3]).toMatchObject({ closingEntry: 'exclude-all-year-end' })
|
|
}
|
|
expect(report.groups[0].rows[0].current_period).toBe(500000)
|
|
})
|
|
})
|