Files
accounted/lib/salary/account-mapping.ts
T
MattssonandClaude Fable 5 4e14182a00 fix(salary): declare, book and pay AGI in whole kronor (SKV per-sats computation) (#1611)
* fix(salary): declare, book and pay AGI in whole kronor (SKV per-sats computation)

A user's first lönekörning surfaced öre amounts in the AGI payable while
Skatteverket deals in whole kronor. Three connected defects:

- the AGI XML rounded amounts (Math.round); öretal bortfaller (SFF
  2011:1261 22 kap. 1 §) requires truncation, and FK487 must be
  Skatteverket's own per-sats computation on the whole-krona underlag sums
  (IK587, kontroll B_006), not a truncation of the öre-exact engine sum
- the salary booking credited 2731 with exact öre, leaving a residual
  after the whole-krona skattekonto draw; 2731 now carries the declared
  amount with the remainder on 3740 (Öres- och kronutjämning)
- the LB payment file and TaxPaymentPanel paid/showed öre; they now use
  the declared whole-krona totals stored on agi_declarations (which also
  lets skattekonto auto-settlement match the draw); legacy öre rows keep
  paying öre-exact so pre-deploy bookings still clear 2731

New lib/salary/declared-avgifter.ts implements the SKV computation (per-IU
whole-krona underlag, per-sats sums, youth/växa cap splits, exact integer
math) shared by the AGI generator, the booking split and the preview.
Review overrides route all legs through the same per-category truncation;
basis overrides are inert on money totals (they never reach the filed
IUs); the v1 book route gains override parity with book-run; F-skatt rows
ignore avgifter overrides on every surface. Booked runs show their posted
verifikat instead of a recomputed projection. tax_withheld_override
requires whole kronor. Adversarially verified over three /skeptic rounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: merge origin/main and re-ratchet the öre-round baseline

The merge brought #1609 (net-pay öresavrundning) whose two new
Math.round(x*100)/100 occurrences are counted against the baseline this
branch had tightened from 637 to 629; 631 keeps the net -6 improvement
without policing already-merged code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(salary): address PR review (hybrid override computation, legacy youth cap, robustness)

CodeRabbit round on #1611, all findings in one pass:

- computeDeclaredAvgifterWithOverrides: one shared hybrid for the AGI
  generator AND the booking split. Overridden rows contribute their manual
  amounts per category; colleagues keep the SKV-exact per-sats underlag
  computation (a FoU override on one employee no longer costs the rest of
  the roster kronor of declared accuracy)
- youth cap keys on the RESOLVED category so legacy null-category rows
  classified as youth by the rate heuristic still get the 25k split
- F-skatt rows zero their avgifter_basis on both booking surfaces and in
  the preview, matching the AGI's isFSkattRow invariant
- preview route: posted-voucher lookup errors return 500 instead of
  masquerading as a booked run with no vouchers; 400/500 tests added
- run page clears stale AGI totals when the tax-payment fetch fails
- SalaryOverridePanel truncates the tax override to whole kronor so the
  schema's .int() cannot bounce a decimal input with a 400
- v1 book route override parity pinned by a lifecycle test
- DECISIONS.md format fixes + superseded entry marked; exempt category
  mapped explicitly; unified truncation-drift band with rationale

Declined (recorded): dating the decision entries 2026-08-13 (bot assumed
UTC; the decisions were made after midnight local time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(salary): round-2 review nits (shared F-skatt helper, test hygiene)

- isFSkattStatus in declared-avgifter.ts: single source for the F-skatt
  exclusion, consumed by book-run, the v1 book route, the preview route and
  the AGI generator, per the Swedish review's drift-risk finding
- declared-avgifter test suite gets the standard beforeEach cleanup

Declined (recorded for the summary): auto-generated correction voucher for
regenerated legacy periods (data-repair follow-up needing Emil's go); SFF
22 kap. 1 par. citation doubt (verified against lagen.nu and already shipped
in tax-tables.ts); 3740 scope doubt (BAS generic utjamning account, Visma
praxis, matches the user's reference voucher).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 02:22:07 +02:00

128 lines
4.6 KiB
TypeScript

import type { SalaryLineItemType } from '@/types'
/**
* Salary account mapping: maps line item types and calculation results
* to BAS accounts per Swedish chart of accounts standards.
*/
/** Default BAS account for each salary line item type */
const LINE_ITEM_ACCOUNTS: Record<SalaryLineItemType, string> = {
// Salary components
monthly_salary: '7210',
hourly_salary: '7210',
overtime: '7210',
overtime_50: '7210',
overtime_100: '7210',
// OB-tillägg: bookat på samma lönekonto som grundlönen; differentieras via
// rad-text på verifikatet och lönespecifikationen.
ob_weekday_evening: '7210',
ob_weekend: '7210',
ob_night: '7210',
ob_holiday: '7210',
bonus: '7210',
commission: '7210',
// Gross deductions
gross_deduction_pension: '7218',
gross_deduction_other: '7210',
// Benefits (förmånsvärden: not a cash payment, just tax base)
benefit_car: '7385',
benefit_housing: '7381',
benefit_meals: '7382',
benefit_wellness: '7699',
benefit_bike: '7388',
benefit_other: '7389',
// Absence
sick_karens: '7281',
sick_day2_14: '7281',
sick_day15_plus: '7281',
vab: '7210',
parental_leave: '7210',
unpaid_leave: '7210',
vacation: '7285',
semesterersattning: '7285',
// Travel
traktamente_taxfree: '7321',
traktamente_taxable: '7322',
mileage_taxfree: '7331',
mileage_taxable: '7332',
// Net deductions (nettolöneavdrag): withheld from the payout and owed to a
// third party, so the default account is the credit-side settlement account,
// not a 7xxx salary expense. Advance repayments credit the receivable (1613),
// union fees credit 2794, everything unmapped lands on 2799 Övriga
// löneavdrag. There is a single benefit-payment item type, so its default
// credits 7385 Kostnader för fri bil, the dominant co-payment case;
// co-payments for other benefit kinds must set account_number on the line
// (7381/7382/7388/7389/7699).
net_deduction_advance: '1613',
net_deduction_union: '2794',
net_deduction_benefit_payment: '7385',
net_deduction_other: '2799',
// Öresavrundning: net payout rounded up to whole kronor; the 0-99 öre diff
// debits the standard rounding account (same account the invoice flows use).
oresavrundning: '3740',
// Other
correction: '7210',
other: '7210',
}
/**
* Get the BAS account number for a salary line item type.
* Can be overridden per line item via account_number field.
*/
export function getLineItemAccount(
itemType: SalaryLineItemType,
employmentType: string = 'employee'
): string {
// Company owner uses 7220 instead of 7210
if (employmentType === 'company_owner') {
const baseAccount = LINE_ITEM_ACCOUNTS[itemType]
if (baseAccount === '7210') return '7220'
if (baseAccount === '7281') return '7282'
if (baseAccount === '7285') return '7286'
}
// Board member uses 7240
if (employmentType === 'board_member') {
const baseAccount = LINE_ITEM_ACCOUNTS[itemType]
if (baseAccount === '7210') return '7240'
}
return LINE_ITEM_ACCOUNTS[itemType]
}
/** Journal entry accounts for salary booking */
export const SALARY_ACCOUNTS = {
// Salary expense (debit)
SALARY_EMPLOYEE: '7210', // Löner till tjänstemän
SALARY_OWNER: '7220', // Löner till företagsledare
SALARY_BOARD: '7240', // Styrelsearvoden
SICK_PAY: '7281', // Sjuklöner
VACATION_PAY: '7285', // Semesterlöner
// Tax withholding (credit)
TAX_WITHHELD: '2710', // Personalskatt
// Bank / payment (credit)
BANK: '1930', // Företagskonto
// Employer contributions
AVGIFTER_EXPENSE: '7510', // Lagstadgade sociala avgifter (debit)
AVGIFTER_LIABILITY: '2731', // Avräkning sociala avgifter (credit)
// Whole-krona remainder: 2731 holds what Skatteverket actually draws
// (hela kronor, öretal bortfaller), the öre difference lands here.
ORESUTJAMNING: '3740', // Öres- och kronutjämning (credit)
// Vacation accrual
VACATION_ACCRUAL_EXPENSE: '7290', // Förändring semesterlöneskuld (debit)
VACATION_ACCRUAL_LIABILITY: '2920', // Upplupna semesterlöner (credit)
// Vacation accrual avgifter
VACATION_AVGIFTER_EXPENSE: '7519', // Sociala avgifter semester (debit)
VACATION_AVGIFTER_LIABILITY: '2940', // Upplupna sociala avgifter (credit)
// Pension provisions (löneväxling)
PENSION_EXPENSE: '7410', // Pensionsförsäkringspremier (debit)
PENSION_LIABILITY: '2740', // Skuld pensionsförsäkringar (credit)
SLP_EXPENSE: '7533', // Särskild löneskatt på pensionskostnader (debit)
SLP_LIABILITY: '2514', // Beräknad särskild löneskatt (credit)
} as const