diff --git a/DECISIONS.md b/DECISIONS.md index c8e224c4..fccf737c 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -753,3 +753,5 @@ One line per decision: `[YYYY-MM-DD] : `. Appended by agents and [2026-08-03] CI gained a pg-upgrade job: apply the merge-base schema, seed real rows, apply ONLY the PR migrations, assert the data survived. Rationale: pg-real applies all 548 migrations to an EMPTY database, so a NOT NULL / CHECK / unique index / backfill passes against zero rows and can still break prod. Proven locally against supabase/postgres:15.8.1.060 with three bad migrations: a CHECK violating an ore-level row and a NOT NULL on a populated column both exit 0 on empty and exit 3 on seeded. Base migrations are read from the merge-base git tree, not the working tree, so a PR that edits a shipped migration still surfaces here. [2026-08-03] Issue #323 automatic excess depreciation is limited to reconciled IL 18 machinery and equipment with linear book depreciation and posts 8853/2153: buildings, intangible assets, and the 25 percent rest-value method follow separate rules, so calculation fails closed on an incomplete register or unposted planned depreciation. + +[2026-08-03] Issue #314 zeroes the F-skatt avgifter basis at the calculation boundary as well as the rate: a rate-only exemption would stop the 7510/2731 charge but leave a false contribution basis in salary reports and AGI totals; the separate FK011/FK131 XML rendering defect remains scoped to issue #315. diff --git a/lib/salary/__tests__/calculation-engine.test.ts b/lib/salary/__tests__/calculation-engine.test.ts index 78f8cb47..4d239792 100644 --- a/lib/salary/__tests__/calculation-engine.test.ts +++ b/lib/salary/__tests__/calculation-engine.test.ts @@ -152,7 +152,7 @@ describe('calculateSalary', () => { expect(result.taxWithheld).toBe(12000) // 30% of 40000 }) - it('applies f-skatt with 0% withholding', () => { + it('exempts F-skatt compensation from withholding and employer contributions', () => { const result = calculateSalary( makeBasicInput({ fSkattStatus: 'f_skatt' }), config2026, @@ -161,6 +161,12 @@ describe('calculateSalary', () => { expect(result.taxWithheld).toBe(0) expect(result.netSalary).toBe(40000) + expect(result.avgifterRate).toBe(0) + expect(result.avgifterAmount).toBe(0) + expect(result.avgifterBasis).toBe(0) + expect(result.avgifterCategory).toBe('exempt') + expect(result.vacationAccrualAvgifter).toBe(0) + expect(result.totalEmployerCost).toBe(result.grossSalary + result.vacationAccrual) }) it('applies unverified flat 30%', () => { @@ -1050,6 +1056,25 @@ describe('calculateSjuklon', () => { }) describe('calculateAvgifterRate', () => { + it('returns the exempt rate for F-skatt before age-based rules', () => { + const result = calculateAvgifterRate( + makeBasicInput({ fSkattStatus: 'f_skatt', personnummer: 'mock_senior_person' }), + config2026, + 2026 + ) + + expect(result.rate).toBe(0) + expect(result.amount).toBe(0) + expect(result.basis).toBe(0) + expect(result.category).toBe('exempt') + expect(result.steps).toEqual([ + expect.objectContaining({ + label: 'Avgiftskategori', + output: null, + }), + ]) + }) + it('returns standard rate for normal employee', () => { const result = calculateAvgifterRate( makeBasicInput(), diff --git a/lib/salary/calculation-engine.ts b/lib/salary/calculation-engine.ts index e4a7d082..9d23d9e5 100644 --- a/lib/salary/calculation-engine.ts +++ b/lib/salary/calculation-engine.ts @@ -475,7 +475,7 @@ export function calculateSalary( // ─── Step 8: Employer contributions (avgifter) ─── const avgifterCalc = calculateAvgifterRate(input, config, paymentYear) - const avgifterBasis = r(grossSalary + totalBenefits) + const avgifterBasis = input.fSkattStatus === 'f_skatt' ? 0 : r(grossSalary + totalBenefits) // Handle salary caps for youth and växa-stöd: // Reduced rate applies only up to the cap, standard rate on the rest @@ -632,6 +632,16 @@ export function calculateAvgifterRate( ): AvgifterCalculation { const steps: CalculationStep[] = [] + if (input.fSkattStatus === 'f_skatt') { + steps.push({ + label: 'Avgiftskategori', + formula: 'F-skatt: inga arbetsgivaravgifter', + input: {}, + output: null, + }) + return { rate: 0, amount: 0, basis: 0, category: 'exempt', steps } + } + // Decrypt personnummer to calculate age let pnr: string try {