fix(salary): book lonevaxling pension provision from frozen run snapshot (#1382)

The book flow never populated pension_contribution/pension_slp, so a
gross_deduction_pension line item reduced the salary entry but the
7410/2740 pension provision and 7533/2514 SLP lines were never posted.
Derive both at the createSalaryRunEntries boundary from the run's frozen
calculation_params.slpRate snapshot so the dashboard, MCP and v1 booking
paths all emit the pension verifikat, and reuse the exact 1.058 factor
via calculateLoneVaxlingPensionProvision shared with the planning
calculator.

Fixes #317

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-03 17:26:49 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 6782da3e9e
commit c9625fa45c
8 changed files with 143 additions and 13 deletions
+7
View File
@@ -41,6 +41,7 @@ const makeRun = (overrides: Record<string, unknown> = {}) => ({
total_net: 23000,
total_avgifter: 9426,
total_vacation_accrual: 0,
calculation_params: { slpRate: 0.2426 },
...overrides,
})
@@ -194,5 +195,11 @@ describe('bookPaidSalaryRun', () => {
expect(result.ok).toBe(true)
if (result.ok) expect(result.data.entryIds).toEqual(['je-1', 'je-2'])
expect(createSalaryRunEntries).toHaveBeenCalledTimes(1)
expect(createSalaryRunEntries).toHaveBeenCalledWith(
expect.anything(),
'company-1',
'user-1',
expect.objectContaining({ calculation_params: { slpRate: 0.2426 } }),
)
})
})
@@ -83,6 +83,7 @@ function makeRun(employees: ReturnType<typeof makeEmployee>[]) {
total_net: employees.reduce((s, e) => s + e.net_salary, 0),
total_avgifter: employees.reduce((s, e) => s + e.avgifter_amount, 0),
total_vacation_accrual: employees.reduce((s, e) => s + e.vacation_accrual, 0),
calculation_params: { slpRate: 0.2426 },
employees,
}
}
@@ -276,6 +277,36 @@ describe('salary entries: dimensions propagation (PR8)', () => {
assertBalanced(pension)
})
it('derives pension + SLP from gross_deduction_pension line items', async () => {
const run = makeRun([
makeEmployee({
employee_id: 'a',
default_dimensions: { '1': 'KS01' },
line_items: [
{
item_type: 'gross_deduction_pension',
amount: -2000,
account_number: '7218',
is_net_deduction: false,
is_gross_deduction: true,
},
],
}),
])
const result = await createSalaryRunEntries(makeSupabase(), 'company-1', 'user-1', run)
const pension = entryByDescription('Pensionsavsättning')
expect(result.pensionEntry).not.toBeNull()
expect(linesOn(pension, '7410')).toEqual([
expect.objectContaining({ debit_amount: 2116, dimensions: { '1': 'KS01' } }),
])
expect(linesOn(pension, '2740')[0].credit_amount).toBe(2116)
expect(linesOn(pension, '7533')[0].debit_amount).toBe(513.34)
expect(linesOn(pension, '2514')[0].credit_amount).toBe(513.34)
assertBalanced(pension)
})
it('rejects an invalid bag (coerce gate) rather than booking junk keys', async () => {
const run = makeRun([
makeEmployee({ employee_id: 'a', default_dimensions: { '0': 'BAD' } as Record<string, string> }),