fix(salary): book net deductions as settlement lines so salary entries balance (#1374)
* fix(salary): book net deductions as settlement lines so salary entries balance Net deduction line items were skipped entirely in createSalaryEntry, so the credit side (2710 tax + 1930 net) fell short of the gross debit by exactly the deducted amount and the balance trigger rejected the voucher. Net deductions now book on their mapped settlement account (1613 advance repayment, 2794 union fee, 7385 benefit co-payment, 2799 other; explicit account_number overrides), aggregated and undimensioned like the other settlement legs. The default mapping in account-mapping.ts moves off 7210 so payslip lines, the booking preview and the voucher all agree. Fixes #316 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(salary): address compliance review on net-deduction accounts Label 7385 with its BAS 2026 name (Kostnader för fri bil) instead of the benefit-generic Bilförmån, document why the single benefit-payment item type defaults to 7385 with per-line override for other benefit kinds, and add a repayment-direction test (positive net deduction books as debit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8443062b1f
commit
0ef3c03904
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
||||
import { ensureInitialized } from '@/lib/init'
|
||||
import { withRouteContext } from '@/lib/api/with-route-context'
|
||||
import { SALARY_ACCOUNTS, getLineItemAccount } from '@/lib/salary/account-mapping'
|
||||
import { roundOre } from '@/lib/money'
|
||||
import type { CreateJournalEntryLineInput } from '@/types'
|
||||
|
||||
ensureInitialized()
|
||||
@@ -43,10 +44,19 @@ export const GET = withRouteContext<{ params: Promise<{ id: string }> }>(
|
||||
// Build salary entry preview
|
||||
const salaryLines: CreateJournalEntryLineInput[] = []
|
||||
const expenseByAccount = new Map<string, number>()
|
||||
// Net deductions book as settlement lines on their mapped liability or
|
||||
// receivable account, mirroring createSalaryEntry; they must not merge
|
||||
// into the 7xxx expense buckets.
|
||||
const netDeductionByAccount = new Map<string, number>()
|
||||
|
||||
for (const sre of employees) {
|
||||
for (const li of sre.line_items || []) {
|
||||
if (li.is_net_deduction || li.is_gross_deduction) continue
|
||||
if (li.is_net_deduction) {
|
||||
const account = li.account_number || getLineItemAccount(li.item_type, sre.employee?.employment_type || 'employee')
|
||||
netDeductionByAccount.set(account, (netDeductionByAccount.get(account) || 0) + li.amount)
|
||||
continue
|
||||
}
|
||||
if (li.is_gross_deduction) continue
|
||||
const account = li.account_number || getLineItemAccount(li.item_type, sre.employee?.employment_type || 'employee')
|
||||
expenseByAccount.set(account, (expenseByAccount.get(account) || 0) + li.amount)
|
||||
}
|
||||
@@ -62,6 +72,17 @@ export const GET = withRouteContext<{ params: Promise<{ id: string }> }>(
|
||||
})
|
||||
}
|
||||
|
||||
for (const [account, amount] of netDeductionByAccount) {
|
||||
const rounded = roundOre(Math.abs(amount))
|
||||
if (rounded === 0) continue
|
||||
salaryLines.push({
|
||||
account_number: account,
|
||||
debit_amount: amount > 0 ? rounded : 0,
|
||||
credit_amount: amount < 0 ? rounded : 0,
|
||||
line_description: `${desc}`,
|
||||
})
|
||||
}
|
||||
|
||||
const totalTax = employees.reduce((sum, e) => sum + e.tax_withheld, 0)
|
||||
if (totalTax > 0) {
|
||||
salaryLines.push({
|
||||
|
||||
Reference in New Issue
Block a user