[IMP] l10n_at_hr_payroll_private: add premiums at termination under § 16 AngG (AP14-B)

Close the premium-at-termination gap of the end-of-employment cycle: an aliquote periodic premium (annual, sales, bonus, 15th payment) whose entitlement period outlasts the termination is covered by § 16 AngG — the pro-rata amount follows the ratio of the elapsed service to the entitlement period, is due for employees regardless of the termination ground (loss clauses void, 9 ObA 82/13v), while performance-linked premiums for workers may validly hinge on non-fault grounds and premiums tied to a specific service (e.g. Bilanzgeld) are not aliquote without the service (lb-end-18). Two routes: PRAM_END (bemessung sonstig) covers premiums paid with the end-of-employment run — a special payment inside the annual sixth at the flat 6 % rate with the 620 EUR free amount and 2 615 EUR free threshold checked by the core, SV as special-payment contributions (KV/PV/AV without AK/WF) against the monthly HBG and the annual SZ cap, fully payroll-levy liable and BV-contributory at the uncapped 1.53 % rate (§ 6 Abs 1 iVm Abs 5 BMSVG). PRAM_END_TARIF (bemessung laufend plus the core l10n_at_s67_abs6_ueberhang flag) covers premiums due at a pre-agreed maturity because their amount only becomes determinable after the entitlement period ends (e.g. balance-sheet premiums): taxed like running pay with the payment-month tariff without touching the annual sixth (§ 67 Abs 10, not a § 67 Abs 8 lit c case per 98/14/0009 = ARD 5483/14/2004) — the SV contribution month rolls back into the termination year via a new payslip field consumed by an extended branch of the existing SV-basis-by-Beitragsmonat override (historical caps and rates, E-MVB 044-01-00-006), while the LSt stays in the payment month. The wizard section previews the § 16 pro-rata amount from the full premium, entitlement period and elapsed service, keeps the payable amount confirmable (KB agreement), requires the service/claim-basis confirmation, enforces the contribution month for the deferred case, and lands the memo on the payslip for the separate Lohnzettel workflow (LStR Rz 911a); the existing manual PRAM rule is untouched. The account matrix posts both rules to 6230. Verified against the original statute text in .ris/ (AngG § 16, BMSVG § 6 Abs 5) and the knowledge-base source lb-end-18; the full SZ roll-forward (storno plus recomputation against the termination year's SZ annual cap) and the mBGM storno/re-transmission remain open in the GP7 payroll-reporting track, and the BV attribution of the deferred premium remains in the payment month as a documented approximation.

Version 19.0.16.0.0 (private)
This commit is contained in:
2026-09-15 13:33:49 +02:00
parent 472b19a4b2
commit 850e99df20
10 changed files with 499 additions and 9 deletions
@@ -4,7 +4,9 @@ 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).
AP15-C: Vergleichssummen (§ 67 Abs 8 lit a) — Rest-Fünftel,
BV-Anwartschafts-Teil mit 6 %/7.500-€-Deckel, Wizard."""
BV-Anwartschafts-Teil mit 6 %/7.500-€-Deckel, Wizard.
AP14-B: Prämien bei Beendigung (§ 16 AngG-Aliquot, lb-end-18) —
sonstiger Bezug mit Endabrechnung, § 67 Abs 10 mit Rollung."""
from datetime import date, timedelta
from odoo.exceptions import UserError
@@ -798,3 +800,153 @@ class EndabrechnungPrivateTest(TransactionCase):
self.assertAlmostEqual(
self._total(slip, 'LST_VERGLEICH_BV'),
7500.0 * (1.0 - satz / 100.0) * steuersatz / 100.0, places=2)
# ------------------------------------------------------------------
# RF-PRM-1: Prämie mit Endabrechnung (§ 16 AngG, sonstiger Bezug)
# ------------------------------------------------------------------
def test_22_pram_end_mit_endabrechnung(self):
"""3.000 € aliquote Prämie, mit der Endabrechnung ausgezahlt
(lb-end-18): sonstiger Bezug im Jahressechstel (6 %-Band,
Sechstel unverändert), SZ-SV (ohne AK/WF), voll LNK-pflichtig,
BV-Beitrag 1,53 % ungecappt (§ 6 Abs 5 BMSVG)."""
emp_mit = self._employee(ueberzahlung=3000.0)
emp_ohne = self._employee(ueberzahlung=3000.0)
for emp in (emp_mit, emp_ohne):
emp.version_id.write({'l10n_at_abfertigung_neu': True})
slip_mit = self._slip(emp_mit)
slip_ohne = self._slip(emp_ohne)
self._input(slip_mit, 'ATP_PRAM_END', 3000.0)
self.assertAlmostEqual(self._total(slip_mit, 'PRAM_END'), 3000.0,
places=2)
# Sonstiger Bezug erhöht das Jahressechstel nicht
self.assertAlmostEqual(
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
0.0, places=2)
# SZ-Bemessung enthält die Prämie (SV-DN nach § 67 Abs 12
# abziehen, um den Brutto-Anteil zu isolieren)
svdn_sz_diff = (slip_mit._l10n_at_svdn_sz()
- slip_ohne._l10n_at_svdn_sz())
self.assertAlmostEqual(
slip_mit._l10n_at_sz_bemessung()
- slip_ohne._l10n_at_sz_bemessung() + svdn_sz_diff,
3000.0, places=2)
# SZ-SV: KV/PV/AV, ohne AK/WF (§ 62 Z 3/Z 5)
satz_sz = sum(slip_mit._l10n_at_sv_satz(
'dn', k, staffel_basis=slip_mit._l10n_at_sv_basis_roh())
for k in ('kv', 'pv', 'av'))
self.assertAlmostEqual(svdn_sz_diff, 3000.0 * satz_sz / 100.0,
places=2)
# LNK über die volle Prämie
self.assertAlmostEqual(
slip_mit._at_lnk_basis('flaf')
- slip_ohne._at_lnk_basis('flaf'), 3000.0, places=2)
# BV 1,53 % ungecappt (§ 6 Abs 1 iVm Abs 5 BMSVG)
satz_bv = float(slip_mit._l10n_at_param('at_sv_bv_dg'))
self.assertAlmostEqual(
slip_mit._at_bv_beitrag() - slip_ohne._at_bv_beitrag(),
3000.0 * satz_bv / 100.0, places=2)
# ------------------------------------------------------------------
# RF-PRM-2: Später fällige Prämie (§ 67 Abs 10) mit SV-Rollung
# ------------------------------------------------------------------
def test_23_pram_end_tarif_und_rollung(self):
"""3.000 € anteilige Prämie für Leistungen im Austrittsjahr 2025,
fällig und ausgezahlt im März 2026 (lb-end-18): tarifisch im
Zuflussmonat OHNE Sechstel-Erhöhung (§ 67 Abs 10); SV-Rollung
auf den Beitragsmonat im Austrittsjahr mit historischen Sätzen
und HBG (E-MVB 044)."""
emp_mit = self._employee(ueberzahlung=3000.0,
ende=date(2025, 12, 31))
emp_ohne = self._employee(ueberzahlung=3000.0,
ende=date(2025, 12, 31))
slip_mit = self._slip(emp_mit, monat=3, jahr=2026)
slip_ohne = self._slip(emp_ohne, monat=3, jahr=2026)
self._input(slip_mit, 'ATP_PRAM_END_TARIF', 3000.0)
self.assertAlmostEqual(self._total(slip_mit, 'PRAM_END_TARIF'),
3000.0, places=2)
# Sechstel unverändert (§ 67 Abs 10: kein Jahressechstel-Effekt)
self.assertAlmostEqual(
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
0.0, places=2)
# Tarifisch in der laufenden Bemessung des Zuflussmonats
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,
3000.0, places=2)
# LNK über die volle Prämie
self.assertAlmostEqual(
slip_mit._at_lnk_basis('flaf')
- slip_ohne._at_lnk_basis('flaf'), 3000.0, places=2)
# Rollung: SV-Beitragsmonat auf dem Lauf (Austrittsjahr 2025);
# historische HBG (2025: 6.450 €) und Sätze des Rollmonats
slip_mit.write({'l10n_at_pram_beitragsmonat': date(2025, 6, 1)})
slip_mit.compute_sheet()
basis = self._basis_monate(slip_mit)
self.assertAlmostEqual(basis[date(2025, 6, 1)], 3000.0, places=2)
# Im Zuflussmonat bleibt die Prämie außer Ansatz (§ 54 ASVG:
# Zuordnung zum Leistungszeitraum) — die Basis entspricht der
# eines Laufs ohne Prämie.
basis_ohne = self._basis_monate(slip_ohne)
self.assertAlmostEqual(basis[date(2026, 3, 1)],
basis_ohne[date(2026, 3, 1)], places=2)
# ------------------------------------------------------------------
# RF-PRM-3: Wizard-E2E und Validierungen (§-16-Aliquot, Rollung)
# ------------------------------------------------------------------
def test_24_pram_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,
'pram_aktiv': True,
'pram_voller_betrag': 6000.0,
'pram_anspruchszeitraum_monate': 12,
'pram_dienstzeit_monate': 3,
'pram_nachweis': 'KV-Prämienvereinbarung, Leistungszeitraum',
}
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create(
dict(basis))
self.assertAlmostEqual(wizard.pram_aliquot_vorschau, 1500.0,
places=2)
with self.assertRaises(UserError):
wizard.action_payslip() # Leistungsgrundlage nicht bestätigt
wizard.write({'pram_leistung_bestaetigt': True,
'pram_betrag': 1000.0})
ergebnis = wizard.action_payslip()
slip = self.env['hr.payslip'].browse(ergebnis['res_id'])
self.assertAlmostEqual(self._total(slip, 'PRAM_END'), 1000.0,
places=2)
self.assertEqual(
slip.l10n_at_pram_nachweis,
'KV-Prämienvereinbarung, Leistungszeitraum')
# Später fällig: Beitragsmonat der Rollung erforderlich
wizard2 = self.env[
'l10n.at.payroll.endabrechnung.wizard'].create({
**basis,
'pram_leistung_bestaetigt': True,
'pram_betrag': 1000.0,
'pram_zahlungsart': 'spaeter_faellig',
})
with self.assertRaises(UserError):
wizard2.action_payslip() # Rollungs-Beitragsmonat fehlt
wizard2.write({'pram_beitragsmonat': date(2026, 1, 1)})
ergebnis = wizard2.action_payslip()
slip2 = self.env['hr.payslip'].browse(ergebnis['res_id'])
self.assertAlmostEqual(self._total(slip2, 'PRAM_END_TARIF'),
1000.0, places=2)
self.assertEqual(slip2.l10n_at_pram_beitragsmonat,
date(2026, 1, 1))
basis = self._basis_monate(slip2)
self.assertIn(date(2026, 1, 1), basis)