[IMP] l10n_at_hr_payroll_private: add settlement payments under § 67 Abs 8 lit a (AP15-C)

Complete the termination sequence with the settlement-sum path: comparison sums from court or out-of-court settlements, judgment/decision arrears and dismissal-objection payments are split between the statutory-severance and voluntary-severance rules and the settlement remainder. The remainder (VERGLEICH, bemessung neutral, sv_art laufend) reuses the lit-a/b fifth rule: after deducting the § 62 employee-rate contributions, one fifth stays tax-free capped at one fifth of nine times the monthly HBG (12 474 EUR 2026), and the remaining four fifths run with the payment-month tariff without touching the annual sixth. A new rule flag l10n_at_vergleich_bv marks the part paid for periods with a BV-Kasse entitlement: up to the parameterised cap at_lst_67_abs8_vergleich_bv (7 500 EUR 2026; § 67 Abs 8 is § 33a/§ 124b indexed) it is taxed at the flat 6 % rate with the contributions allocated pro rata (LStR Rz 1102b), while the excess falls back into the fifth rule. Both settlement rules stay payroll-levy liable (§ 41 Abs 4 lit b FLAG exempts only § 67 Abs 3 and 6) and SV-liable in the payment month, with per-Beitragsmonat roll-forward and insurance-period extension remaining documented manual options (lb-end-22, E-MVB 011-02-00-001). Identifiable severance components keep running through the existing statutory and voluntary severance machinery; a settlement expressly agreed as statutory severance is taxed in full under § 67 Abs 3 (VwGH 2013/13/0001 = ARD 6490/20/2016), and pension buy-outs (lit e) and social-plan payments (lit f) are out of scope. The wizard section requires confirmation of the settlement's itemisation and contested-claim status plus the BV entitlement, enforces the BV cap with the excess captured as remainder, and previews the free fifth, tariff basis and 6 % tax; the settlement memo lands on the payslip for the separate Lohnzettel workflow (LStR Rz 911a). The account matrix posts both settlement rules to the provisional 6200/6000 (workers 6000) and the new tax rule to 3540. Verified against the original statute text in .ris/ (EStG § 67 Abs 8 lit a) and the knowledge-base sources lb-end-21/22; KZ positioning of the lit-a tariff parts and their § 291d pot allocation remain documented verification points against LStR Rz 1087 ff.

Version 19.0.15.0.0 (private)
This commit is contained in:
2026-09-15 12:38:19 +02:00
parent 4b86bcb790
commit 472b19a4b2
12 changed files with 660 additions and 15 deletions
@@ -2,7 +2,9 @@
"""AP14-A: Endabrechnungs-Zyklus — UEL-Zweikomponenten (§ 67 Abs 8
lit d), SV-Verlängerung (§ 11 Abs 2 ASVG, sequentielle Fenster),
Abgangsentschädigung (§ 67 Abs 8 lit b Fünftel), Aufrechnung offener
Vorschüsse (§ 293 Abs 3 EO) und Zeitguthaben (§ 19e Abs 2 AZG)."""
Vorschüsse (§ 293 Abs 3 EO) und Zeitguthaben (§ 19e Abs 2 AZG).
AP15-C: Vergleichssummen (§ 67 Abs 8 lit a) — Rest-Fünftel,
BV-Anwartschafts-Teil mit 6 %/7.500-€-Deckel, Wizard."""
from datetime import date, timedelta
from odoo.exceptions import UserError
@@ -611,3 +613,188 @@ class EndabrechnungPrivateTest(TransactionCase):
self.assertEqual(slip.l10n_at_abf_frei_dienstzeit_monate, 180)
self.assertEqual(slip.l10n_at_abf_frei_nachweis,
'Vordienstzeiten zum Lohnkonto nachgewiesen')
# ------------------------------------------------------------------
# RF-VGL-1: Vergleichssumme Rest (§ 67 Abs 8 lit a, Fünftelregelung)
# ------------------------------------------------------------------
def test_18_vergleich_rest_fuenftel(self):
"""9.000 € Vergleich-Rest (lb-end-21 Beispiel 1): nach
§-62-Abzug ist 1/5 steuerfrei (Deckel 12.474 €), 4/5 laufen
tarifisch im Zuflussmonat OHNE Sechstel-Effekt; SV-pflichtig im
Zuflussmonat, voll LNK-pflichtig."""
emp_mit = self._employee(ueberzahlung=3000.0)
emp_ohne = self._employee(ueberzahlung=3000.0)
slip_mit = self._slip(emp_mit)
slip_ohne = self._slip(emp_ohne)
self._input(slip_mit, 'ATP_VERGLEICH', 9000.0)
self.assertAlmostEqual(self._total(slip_mit, 'VERGLEICH'), 9000.0,
places=2)
# Sechstel unverändert (Abs 2 nicht anzuwenden)
self.assertAlmostEqual(
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
0.0, places=2)
satz = sum(slip_mit._l10n_at_sv_satz(
'dn', k, staffel_basis=slip_mit._l10n_at_sv_basis_roh())
for k in ('kv', 'pv', 'av', 'ak', 'wf'))
bemessung = 9000.0 * (1.0 - satz / 100.0)
deckel = float(slip_mit._l10n_at_param('at_sv_hbg_monatlich')) \
* 9.0 / 5.0
frei = min(bemessung / 5.0, deckel)
self.assertAlmostEqual(slip_mit._at_fuenftel_frei_betr(), frei,
places=2)
self.assertAlmostEqual(slip_mit._at_fuenftel_tarif_basis(),
bemessung - frei, places=2)
svdn_diff = (slip_mit._l10n_at_svdn_gesamt()
- slip_ohne._l10n_at_svdn_gesamt())
self.assertAlmostEqual(
slip_mit._l10n_at_bemessung_laufen()
- slip_ohne._l10n_at_bemessung_laufen() + svdn_diff,
bemessung - frei, places=2)
# LNK-Basis um die volle Vergleichssumme erhöht
self.assertAlmostEqual(
slip_mit._at_lnk_basis('flaf')
- slip_ohne._at_lnk_basis('flaf'), 9000.0, places=2)
# SV-pflichtig im Zuflussmonat (rohe Basis vor HBG-Cap)
self.assertAlmostEqual(
slip_mit._l10n_at_sv_basis_roh()
- slip_ohne._l10n_at_sv_basis_roh(), 9000.0, places=2)
# ------------------------------------------------------------------
# RF-VGL-2: BV-Anwartschafts-Teil (§ 67 Abs 8 lit a, 6 %/7.500 €)
# ------------------------------------------------------------------
def test_19_vergleich_bv_sechs_prozent(self):
"""9.000 € Vergleichssumme mit BV-Anwartschaft (lb-end-21
Beispiel 2): erste 7.500 € nach anteiligem §-62-Abzug mit 6 %,
der Überhang von 1.500 € fällt in die Fünftelregelung zurück
(anteilige Beitragszuordnung, LStR Rz 1102b)."""
emp_mit = self._employee(ueberzahlung=3000.0)
emp_ohne = self._employee(ueberzahlung=3000.0)
slip_mit = self._slip(emp_mit)
slip_ohne = self._slip(emp_ohne)
self._input(slip_mit, 'ATP_VERGLEICH_BV', 9000.0)
self.assertAlmostEqual(self._total(slip_mit, 'VERGLEICH_BV'),
9000.0, places=2)
satz = sum(slip_mit._l10n_at_sv_satz(
'dn', k, staffel_basis=slip_mit._l10n_at_sv_basis_roh())
for k in ('kv', 'pv', 'av', 'ak', 'wf'))
steuersatz = float(slip_mit._l10n_at_param(
'at_endabrechnung')['abfertigung_steuersatz'])
self.assertAlmostEqual(
self._total(slip_mit, 'LST_VERGLEICH_BV'),
7500.0 * (1.0 - satz / 100.0) * steuersatz / 100.0, places=2)
# Überhang in die Fünftelregelung zurück
self.assertAlmostEqual(slip_mit._at_fuenftel_total(), 1500.0,
places=2)
deckel = float(slip_mit._l10n_at_param('at_sv_hbg_monatlich')) \
* 9.0 / 5.0
ueberhang_bem = 1500.0 * (1.0 - satz / 100.0)
self.assertAlmostEqual(
slip_mit._at_fuenftel_frei_betr(),
min(ueberhang_bem / 5.0, deckel), places=2)
# Sechstel unverändert; LNK und SV über die volle Summe
self.assertAlmostEqual(
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
0.0, places=2)
self.assertAlmostEqual(
slip_mit._at_lnk_basis('flaf')
- slip_ohne._at_lnk_basis('flaf'), 9000.0, places=2)
self.assertAlmostEqual(
slip_mit._l10n_at_sv_basis_roh()
- slip_ohne._l10n_at_sv_basis_roh(), 9000.0, places=2)
# ------------------------------------------------------------------
# RF-VGL-3: Deckel — Fünftel an 9 × HBG/5, BV-Teil an 7.500 €
# ------------------------------------------------------------------
def test_20_vergleich_deckel(self):
"""Deckelfälle (lb-end-21): der Fünftel-Deckel greift, wenn die
§-62-gekürzte Bemessung 5 × 12.474 € übersteigt — bei Brutto
100.000 € ist das der Fall; das 70.000-€-Beispiel der Quelle
meint die bereits gekürzte Bemessungsgrundlage. BV-Teil
10.000 € → 7.500 € begünstigt, 2.500 € zurück in die
Fünftelregelung."""
emp = self._employee(ueberzahlung=3000.0)
slip = self._slip(emp)
self._input(slip, 'ATP_VERGLEICH', 100000.0)
satz = sum(slip._l10n_at_sv_satz(
'dn', k, staffel_basis=slip._l10n_at_sv_basis_roh())
for k in ('kv', 'pv', 'av', 'ak', 'wf'))
bemessung = 100000.0 * (1.0 - satz / 100.0)
deckel = float(slip._l10n_at_param('at_sv_hbg_monatlich')) \
* 9.0 / 5.0
self.assertGreater(bemessung / 5.0, deckel)
self.assertAlmostEqual(slip._at_fuenftel_frei_betr(), deckel,
places=2)
self.assertAlmostEqual(
slip._at_fuenftel_tarif_basis(), bemessung - deckel, places=2)
slip.input_line_ids.filtered(
lambda i: i.input_type_id.code == 'ATP_VERGLEICH').write(
{'amount': 0.0})
self._input(slip, 'ATP_VERGLEICH_BV', 10000.0)
self.assertAlmostEqual(slip._at_vergleich_bv_beguenstigt(), 7500.0,
places=2)
self.assertAlmostEqual(slip._at_vergleich_bv_ueberhang(), 2500.0,
places=2)
self.assertAlmostEqual(slip._at_fuenftel_total(), 2500.0,
places=2)
steuersatz = float(slip._l10n_at_param(
'at_endabrechnung')['abfertigung_steuersatz'])
self.assertAlmostEqual(
self._total(slip, 'LST_VERGLEICH_BV'),
7500.0 * (1.0 - satz / 100.0) * steuersatz / 100.0, places=2)
# ------------------------------------------------------------------
# RF-VGL-4: Wizard-E2E und Validierungen (Bestätigungspflichten)
# ------------------------------------------------------------------
def test_21_vergleich_wizard(self):
emp = self._employee()
emp.version_id.write({
'structure_type_id': self.env.ref(
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
'l10n_at_abfertigung_neu': False,
})
basis = {
'employee_id': emp.id,
'austrittsdatum': date(2026, 3, 15),
'departure_reason_id': self.env.ref(
'l10n_at_hr_payroll.departure_reason_einvernehmlich').id,
'vergleich_aktiv': True,
'vergleich_rest_betrag': 9000.0,
'vergleich_nachweis':
'Außergerichtlicher Vergleich, Ansprüche strittig',
}
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create(
dict(basis))
with self.assertRaises(UserError):
wizard.action_payslip() # Aufgliederung nicht bestätigt
wizard.write({'vergleich_aufteilung_bestaetigt': True,
'vergleich_bv_betrag': 8000.0,
'vergleich_bv_anwartschaft_bestaetigt': True})
with self.assertRaises(UserError):
wizard.action_payslip() # BV-Teil über 7.500 €
wizard.write({'vergleich_bv_betrag': 5000.0,
'vergleich_bv_anwartschaft_bestaetigt': False})
with self.assertRaises(UserError):
wizard.action_payslip() # BV-Anwartschaft nicht bestätigt
wizard.write({'vergleich_bv_betrag': 7500.0,
'vergleich_bv_anwartschaft_bestaetigt': True})
ergebnis = wizard.action_payslip()
slip = self.env['hr.payslip'].browse(ergebnis['res_id'])
self.assertAlmostEqual(self._total(slip, 'VERGLEICH'), 9000.0,
places=2)
self.assertAlmostEqual(self._total(slip, 'VERGLEICH_BV'), 7500.0,
places=2)
self.assertEqual(
slip.l10n_at_vergleich_nachweis,
'Außergerichtlicher Vergleich, Ansprüche strittig')
satz = sum(slip._l10n_at_sv_satz(
'dn', k, staffel_basis=slip._l10n_at_sv_basis_roh())
for k in ('kv', 'pv', 'av', 'ak', 'wf'))
steuersatz = float(slip._l10n_at_param(
'at_endabrechnung')['abfertigung_steuersatz'])
self.assertAlmostEqual(
self._total(slip, 'LST_VERGLEICH_BV'),
7500.0 * (1.0 - satz / 100.0) * steuersatz / 100.0, places=2)