fix(bookkeeping): gnubok_categorize_transaction now handles incoming expense refunds correctly (#761)
Incoming expense refunds (positive amount on an expense category) previously booked with inverted debit/credit — crediting the bank and debiting the expense account — which both imbalanced the book and reported negative bank flow. getCategoryAccountMapping now detects amount > 0 on expense categories and returns the reversed mapping: debit 1930, credit expense account, with 2641 as vatCreditAccount so ingående moms is correctly reversed on the VAT line. The VAT line description is "Återföring ingående moms X%" rather than the income-side "Utgående moms" label. Signed-off-by: Jonas Flodén <jonas@floden.nu> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
bbc8063f53
commit
5279dfb494
@@ -381,6 +381,64 @@ describe('private transaction accounts by entity type and direction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('incoming expense refund (positive amount, expense category)', () => {
|
||||
it('getCategoryAccountMapping swaps accounts: bank debited, expense account credited', () => {
|
||||
const result = getCategoryAccountMapping('expense_software', 500, true)
|
||||
expect(result.debitAccount).toBe('1930')
|
||||
expect(result.creditAccount).toBe('5420')
|
||||
})
|
||||
|
||||
it('getCategoryAccountMapping sets vatCreditAccount 2641 and clears vatDebitAccount for refund', () => {
|
||||
const result = getCategoryAccountMapping('expense_software', 500, true)
|
||||
expect(result.vatDebitAccount).toBeNull()
|
||||
expect(result.vatCreditAccount).toBe('2641')
|
||||
})
|
||||
|
||||
it('VAT-exempt expense refund (bank_fees) has no VAT accounts', () => {
|
||||
const result = getCategoryAccountMapping('expense_bank_fees', 100, true)
|
||||
expect(result.debitAccount).toBe('1930')
|
||||
expect(result.creditAccount).toBe('6570')
|
||||
expect(result.vatDebitAccount).toBeNull()
|
||||
expect(result.vatCreditAccount).toBeNull()
|
||||
})
|
||||
|
||||
it('buildMappingResultFromCategory generates credit line on 2641 for expense refund', () => {
|
||||
const tx = makeTransaction({ amount: 1000 })
|
||||
const result = buildMappingResultFromCategory('expense_software', tx, true)
|
||||
expect(result.vat_lines).toHaveLength(1)
|
||||
expect(result.vat_lines[0].account_number).toBe('2641')
|
||||
expect(result.vat_lines[0].credit_amount).toBe(200)
|
||||
expect(result.vat_lines[0].debit_amount).toBe(0)
|
||||
})
|
||||
|
||||
it('buildMappingResultFromCategory uses återföring description for expense refund VAT', () => {
|
||||
const tx = makeTransaction({ amount: 1000 })
|
||||
const result = buildMappingResultFromCategory('expense_software', tx, true)
|
||||
expect(result.vat_lines[0].description).toBe('Återföring ingående moms 25%')
|
||||
})
|
||||
|
||||
it('buildMappingResultFromCategory generates no VAT line for VAT-exempt expense refund', () => {
|
||||
const tx = makeTransaction({ amount: 100 })
|
||||
const result = buildMappingResultFromCategory('expense_bank_fees', tx, true)
|
||||
expect(result.vat_lines).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('buildMappingResultFromCategory maps debit/credit correctly (bank debited, expense credited)', () => {
|
||||
const tx = makeTransaction({ amount: 1250 })
|
||||
const result = buildMappingResultFromCategory('expense_software', tx, true)
|
||||
expect(result.debit_account).toBe('1930')
|
||||
expect(result.credit_account).toBe('5420')
|
||||
})
|
||||
|
||||
it('vat_amount override on expense refund uses återföring description', () => {
|
||||
const tx = makeTransaction({ amount: 1250 })
|
||||
const result = buildMappingResultFromCategory('expense_software', tx, true, 'enskild_firma', 'standard_25', 200)
|
||||
expect(result.vat_lines).toHaveLength(1)
|
||||
expect(result.vat_lines[0].credit_amount).toBe(200)
|
||||
expect(result.vat_lines[0].description).toBe('Återföring ingående moms (enligt underlag)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('category default → leaf account guarantee', () => {
|
||||
// BAS encodes the parent/leaf distinction in account_name via the
|
||||
// "(gruppkonto)" suffix. Auditors and Skatteverket downstream reporting
|
||||
|
||||
@@ -141,6 +141,18 @@ export function getCategoryAccountMapping(
|
||||
// Note: income tax deduction was abolished 2017 (IL 16 kap 2 §), but VAT deduction remains.
|
||||
const resolvedVat = vatTreatment ?? (isVatExempt ? null : category === 'expense_representation' ? 'reduced_12' : 'standard_25')
|
||||
|
||||
if (amount > 0) {
|
||||
// Incoming refund: bank receives money, expense account is reduced (credited).
|
||||
// Ingående moms is reversed — credit 2641 instead of debit.
|
||||
return {
|
||||
debitAccount: BANK_ACCOUNT,
|
||||
creditAccount: expenseAccount,
|
||||
vatTreatment: resolvedVat,
|
||||
vatDebitAccount: null,
|
||||
vatCreditAccount: resolvedVat ? '2641' : null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
debitAccount: expenseAccount,
|
||||
creditAccount: BANK_ACCOUNT,
|
||||
@@ -291,12 +303,16 @@ export function buildMappingResultFromCategory(
|
||||
description: hasVatOverride ? 'Ingående moms (enligt underlag)' : `Ingående moms ${vatRate * 100}%`,
|
||||
})
|
||||
} else if (vatAmount > 0 && transaction.amount > 0 && mapping.vatCreditAccount) {
|
||||
// Income: Utgående moms (output VAT)
|
||||
// Income output VAT, or expense refund reversing ingående moms (both credit vatCreditAccount).
|
||||
// Distinguish by account: 2641 = reversed ingående moms, 2611/2621/2631 = utgående moms.
|
||||
const isExpenseRefund = mapping.vatCreditAccount === '2641'
|
||||
vatLines.push({
|
||||
account_number: mapping.vatCreditAccount,
|
||||
debit_amount: 0,
|
||||
credit_amount: vatAmount,
|
||||
description: hasVatOverride ? 'Utgående moms (enligt underlag)' : `Utgående moms ${vatRate * 100}%`,
|
||||
description: isExpenseRefund
|
||||
? (hasVatOverride ? 'Återföring ingående moms (enligt underlag)' : `Återföring ingående moms ${vatRate * 100}%`)
|
||||
: (hasVatOverride ? 'Utgående moms (enligt underlag)' : `Utgående moms ${vatRate * 100}%`),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user