/** * K2 vasentlighetsgrans for periodisering, extracted as a pure helper so it * can be unit-tested (this repo's Vitest runs in a node environment and does * not render components). * * K2 (BFNAR 2016:10) lets a smaller company skip accruing individual * recurring costs below 5 000 kr that do not fluctuate more than 20 % year * over year; personnel costs must always be accrued. It is a simplification * the company MAY use, never an obligation, so the UI copy it drives is * advisory only. That is exactly why it must not fire on an amount we cannot * express in kronor: an advisory nudge based on the wrong unit is worse than * no nudge at all. * * The invoice editors hold line amounts in the invoice's currency. A 500 EUR * line is roughly 5 750 kr and therefore ABOVE the threshold, but a naive * `amount < 5000` reads it as below (and a 400 USD line reads the reverse * way once the rate moves). Converting needs an FX rate. The supplier-invoice * form has one (Riksbanken-fetched or hand-typed); the customer-invoice * editor has none. When no rate is available we return null and the caller * shows nothing, rather than guessing a branch. */ /** BFNAR 2016:10: the K2 accrual simplification ceiling, in SEK. */ export const K2_ACCRUAL_THRESHOLD_SEK = 5000 export interface AccrualAmountInput { /** Net line amount, expressed in `currency`. */ amount: number /** ISO currency code of `amount`. Missing/empty is treated as SEK. */ currency?: string | null /** SEK per one unit of `currency`. Required to convert anything non-SEK. */ exchangeRate?: number | null } /** * The line amount in SEK, or null when it cannot be determined. * * Deliberately NOT `resolveSekAmount()` from `@/lib/bookkeeping/currency-utils`: * that helper falls back to returning the foreign amount untouched, which is * the silent wrong-unit branch this helper exists to avoid. */ export function resolveAccrualAmountSek({ amount, currency, exchangeRate, }: AccrualAmountInput): number | null { if (typeof amount !== 'number' || !Number.isFinite(amount)) return null const code = (currency ?? '').trim().toUpperCase() if (code === '' || code === 'SEK') { return Math.round(amount * 100) / 100 } if ( typeof exchangeRate !== 'number' || !Number.isFinite(exchangeRate) || exchangeRate <= 0 ) { return null } return Math.round(amount * exchangeRate * 100) / 100 } /** * Whether to show the "below 5 000 kr need not be deferred (K2)" hint. * * False whenever the SEK value is unknown: no hint beats a hint measured in * the wrong currency. */ export function shouldShowK2AccrualHint(input: AccrualAmountInput): boolean { const amountSek = resolveAccrualAmountSek(input) if (amountSek === null) return false return amountSek > 0 && amountSek < K2_ACCRUAL_THRESHOLD_SEK }