Files
accounted/lib/reports/__tests__/supplier-reconciliation.test.ts
T
Jakob WennbergandClaude Opus 4.6 ba94f60d06 feat: add review confirmation dialogs and BAS account number tooltips
Add modal review/confirmation dialogs before submitting invoices, supplier
invoices, and journal entries. Since journal entries are legally immutable
once posted, users now see a full summary with an amber warning before
confirming. Add AccountNumber component with rich tooltips showing account
name, class, type, and plain-language Swedish explanation for ~45 key BAS
accounts across all report views.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 15:27:45 +01:00

172 lines
4.8 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
// ============================================================
// Mock — sequential result queue
// ============================================================
let resultIdx: number
let results: Array<{ data?: unknown; error?: unknown }>
function makeBuilder() {
const b: Record<string, unknown> = {}
for (const m of ['select', 'eq', 'in']) {
b[m] = vi.fn().mockReturnValue(b)
}
b.single = vi.fn().mockImplementation(async () => results[resultIdx++] ?? { data: null, error: null })
b.then = (resolve: (v: unknown) => void) => resolve(results[resultIdx++] ?? { data: null, error: null })
return b
}
function makeClient() {
return {
from: vi.fn().mockImplementation(() => makeBuilder()),
}
}
vi.mock('@/lib/supabase/server', () => ({
createClient: vi.fn(async () => makeClient()),
}))
import { generateReconciliation } from '../supplier-reconciliation'
beforeEach(() => {
vi.clearAllMocks()
resultIdx = 0
results = []
})
describe('generateReconciliation', () => {
it('returns reconciled when supplier total matches account 2440 balance', async () => {
results = [
// 0: supplier_invoices
{
data: [
{ remaining_amount: 5000 },
{ remaining_amount: 3000 },
],
error: null,
},
// 1: journal_entry_lines for account 2440
{
data: [
{ debit_amount: 0, credit_amount: 10000, journal_entry_id: 'e1' },
{ debit_amount: 2000, credit_amount: 0, journal_entry_id: 'e2' },
],
error: null,
},
]
const result = await generateReconciliation('user-1', 'period-1')
// Supplier total: 5000 + 3000 = 8000
expect(result.supplier_ledger_total).toBe(8000)
// Account 2440 (credit-normal): credits - debits = 10000 - 2000 = 8000
expect(result.account_2440_balance).toBe(8000)
expect(result.difference).toBe(0)
expect(result.is_reconciled).toBe(true)
})
it('detects mismatch when difference != 0', async () => {
results = [
// 0: supplier_invoices — total 5000
{
data: [
{ remaining_amount: 5000 },
],
error: null,
},
// 1: journal_entry_lines — balance 7000
{
data: [
{ debit_amount: 0, credit_amount: 7000, journal_entry_id: 'e1' },
],
error: null,
},
]
const result = await generateReconciliation('user-1', 'period-1')
expect(result.supplier_ledger_total).toBe(5000)
expect(result.account_2440_balance).toBe(7000)
expect(result.difference).toBe(-2000)
expect(result.is_reconciled).toBe(false)
})
it('returns reconciled when both are zero/empty', async () => {
results = [
{ data: [], error: null },
{ data: [], error: null },
]
const result = await generateReconciliation('user-1', 'period-1')
expect(result.supplier_ledger_total).toBe(0)
expect(result.account_2440_balance).toBe(0)
expect(result.difference).toBe(0)
expect(result.is_reconciled).toBe(true)
})
it('handles null invoice data gracefully', async () => {
results = [
{ data: null, error: null },
{
data: [
{ debit_amount: 0, credit_amount: 3000, journal_entry_id: 'e1' },
],
error: null,
},
]
const result = await generateReconciliation('user-1', 'period-1')
expect(result.supplier_ledger_total).toBe(0)
expect(result.account_2440_balance).toBe(3000)
expect(result.difference).toBe(-3000)
expect(result.is_reconciled).toBe(false)
})
it('computes credit-normal balance for account 2440 (liability)', async () => {
results = [
{ data: [], error: null },
{
data: [
{ debit_amount: 0, credit_amount: 15000, journal_entry_id: 'e1' },
{ debit_amount: 5000, credit_amount: 0, journal_entry_id: 'e2' },
{ debit_amount: 3000, credit_amount: 0, journal_entry_id: 'e3' },
],
error: null,
},
]
const result = await generateReconciliation('user-1', 'period-1')
// Balance = credits - debits = 15000 - 5000 - 3000 = 7000
expect(result.account_2440_balance).toBe(7000)
})
it('uses Math.round for monetary precision', async () => {
results = [
{
data: [
{ remaining_amount: 33.33 },
{ remaining_amount: 33.34 },
],
error: null,
},
{
data: [
{ debit_amount: 0, credit_amount: 66.67, journal_entry_id: 'e1' },
],
error: null,
},
]
const result = await generateReconciliation('user-1', 'period-1')
expect(result.supplier_ledger_total).toBe(66.67)
expect(result.account_2440_balance).toBe(66.67)
expect(result.difference).toBe(0)
expect(result.is_reconciled).toBe(true)
})
})