fix: INK2 rounding tolerance and clearer warnings (#197)

* fix: add rounding tolerance to INK2 balance check and clarify warnings

INK2/SRU rounds each ruta independently to whole kronor, so with 11+
rutor the accumulated rounding can produce a 1-2 kr difference that
triggered a false "balance sheet not in balance" warning. Add a 2 kr
tolerance. Also clarify the unclosed fiscal year warning to indicate
that generation still works.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: also warn when equity/liabilities exist but assets are zero

Address Greptile review feedback — the balance check guard should
trigger when either side has a non-zero total, not only when assets > 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-04-08 17:04:48 +02:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 20818e3283
commit 6c79f21679
3 changed files with 60 additions and 8 deletions
+41 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { INK2_ACCOUNT_MAPPINGS, isAccountInMapping } from '../ink2-engine'
import { INK2_ACCOUNT_MAPPINGS, isAccountInMapping, checkBalanceWarning } from '../ink2-engine'
import type { INK2SRUCode } from '../types'
/**
@@ -255,3 +255,43 @@ describe('INK2 Account Mappings', () => {
})
})
})
describe('checkBalanceWarning', () => {
it('returns null when perfectly balanced', () => {
expect(checkBalanceWarning(100000, 100000)).toBeNull()
})
it('returns null for 1 kr difference (within rounding tolerance)', () => {
expect(checkBalanceWarning(100000, 100001)).toBeNull()
expect(checkBalanceWarning(100001, 100000)).toBeNull()
})
it('returns null for 2 kr difference (within rounding tolerance)', () => {
expect(checkBalanceWarning(100000, 100002)).toBeNull()
expect(checkBalanceWarning(100002, 100000)).toBeNull()
})
it('returns warning for 3 kr difference (exceeds tolerance)', () => {
expect(checkBalanceWarning(100000, 100003)).not.toBeNull()
expect(checkBalanceWarning(100003, 100000)).not.toBeNull()
})
it('returns null when totals are zero', () => {
expect(checkBalanceWarning(0, 0)).toBeNull()
})
it('returns null when both totals are zero (no data)', () => {
expect(checkBalanceWarning(0, 0)).toBeNull()
})
it('returns warning when assets are zero but equity/liabilities exist', () => {
expect(checkBalanceWarning(0, 5)).not.toBeNull()
})
it('includes amounts in warning message', () => {
const warning = checkBalanceWarning(100000, 100005)
expect(warning).toContain('100000')
expect(warning).toContain('100005')
expect(warning).toContain('5')
})
})
+18 -6
View File
@@ -194,6 +194,20 @@ function roundToKrona(value: number): number {
return Math.round(value)
}
/**
* Check if the balance sheet totals differ beyond the expected rounding tolerance.
* Each ruta is independently rounded to whole kronor for SRU output, so with 11+
* rutor the accumulated rounding can produce a 1-2 kr difference.
*/
export function checkBalanceWarning(totalAssets: number, totalEquityLiabilities: number): string | null {
const balanceDiff = Math.abs(totalAssets - totalEquityLiabilities)
const ROUNDING_TOLERANCE_KR = 2
if (balanceDiff > ROUNDING_TOLERANCE_KR && (totalAssets > 0 || totalEquityLiabilities > 0)) {
return `Balansräkningen är inte i balans. Tillgångar: ${totalAssets} kr, Eget kapital och skulder: ${totalEquityLiabilities} kr (differens: ${balanceDiff} kr).`
}
return null
}
/**
* Generate INK2 declaration for a fiscal period
*/
@@ -349,18 +363,16 @@ export async function generateINK2Declaration(
// Add warnings
if (!(period as FiscalPeriod).is_closed) {
warnings.push('Räkenskapsåret är inte stängt. Siffrorna kan ändras.')
warnings.push('Räkenskapsåret är inte stängt — deklarationen kan genereras, men siffrorna kan ändras om fler bokföringar görs.')
}
if (totalAssets === 0 && totalEquityLiabilities === 0 && rutor['7310'] === 0) {
warnings.push('Inga bokförda transaktioner hittades för perioden.')
}
const balanceDiff = Math.abs(totalAssets - totalEquityLiabilities)
if (balanceDiff > 0 && totalAssets > 0) {
warnings.push(
`Balansräkningen är inte i balans. Tillgångar: ${totalAssets} kr, Eget kapital och skulder: ${totalEquityLiabilities} kr (differens: ${balanceDiff} kr).`
)
const balanceWarning = checkBalanceWarning(totalAssets, totalEquityLiabilities)
if (balanceWarning) {
warnings.push(balanceWarning)
}
return {
+1 -1
View File
@@ -302,7 +302,7 @@ export async function generateNEDeclaration(
// Add warnings
if (!(period as FiscalPeriod).is_closed) {
warnings.push('Räkenskapsåret är inte stängt. Siffrorna kan ändras.')
warnings.push('Räkenskapsåret är inte stängt — deklarationen kan genereras, men siffrorna kan ändras om fler bokföringar görs.')
}
if (rutor.R11 === 0 && totalRevenue === 0) {