[IMP] l10n_at_hr_payroll_private: GP4 SV/DB-Anbindung (SVDN_SZ-Nachtrag, DG-Regelblock, FLAF/KommSt/DZ)

Anbindung der privatwirtschaftlichen Structure an den GP4-Kern nach
dem bewährten GP3-Muster (dünne 1-Zeilen-Regeln; Logik und Werte
verbleiben im Kern — hr.salary.rule.struct_id ist required, geteilte
Kern-Regeln sind mechanisch unmöglich).

SVDN_SZ-Nachtrag (§ 67 Abs 12): Die SV-DN auf sonstigen Bezügen war in
GP3 nur als Bemessungsbestandteil der §-67-Lohnsteuer wirksam, fehlte
aber als eigene Abzugszeile — das Netto in §-67-Monaten war um den
SV-DN(SZ) zu hoch (RF-L1: 290,79 € nach §-2a-Korrektur). Die Erwartungen
in test_01/test_20 wurden entsprechend korrigiert.

DG-Block (Kategorie ATP_DG, ohne Nettoeffekt): SVDG_KV/PV/AV/UV/WF/IESG
laufend, SVDG_SZ_* auf Sonderzahlungen, SVDG_UV_GFG (§-53a-Pauschale
geringfügig Beschäftigter) sowie die Lohnnebenkosten FLAFDB (§ 41
FLAG), KOMMST (§ 9 KommStG) und DZ (§§ 122/126 WKG, nur bei Company-
Flag l10n_at_wk_mitglied; Satz je Betriebsstätten-Bundesland).

Gemeinsame LNK-Basis in payslip_private.py (_at_lnk_basis): monatlich
gezahlte Arbeitslöhne nach Zufluss, ohne §-67-Abs-3/6-Bezüge (Kern-Flag
l10n_at_s67_abs), lit-f-Befreiung nur für FLAF und DZ (KommSt ohne),
Freibetragsstaffel 1.095/1.460 €.

Rechenfälle docs/rechenfaelle/sozialversicherung.md (RF-SV-1 bis -7)
und §-2a-Korrekturvermerk in lohnsteuer.md. Tests: test_sv_private
(8 Fälle); gesamt 74/74 grün auf odoo_dev.
This commit is contained in:
2026-09-11 23:22:13 +02:00
parent a2a823ad35
commit 04099fef39
10 changed files with 833 additions and 44 deletions
@@ -305,6 +305,78 @@ class HrPayslip(models.Model):
monate = {l.slip_id.id for l in linien}
return len(monate) >= 7, sum(l.total for l in linien) / 12.0
# ------------------------------------------------------------------
# Lohnnebenkosten FLAF-DG-Beitrag / KommSt / DZ (GP4; § 41 FLAG,
# § 5+§ 9 KommStG, §§ 122/126 WKG — Werte/Staffeln im Kern)
# ------------------------------------------------------------------
def _at_lnk_basis(self, steuer, result_rules=None):
"""LNK-Bemessungsgrundlage nach Freibetragsstaffel: monatlich
gezahlte Arbeitslöhne (laufend + im Monat gezahlte §-67-Bezüge,
Zuflussprinzip; kein SV-Abzug), ohne §-67-Abs-3/6-Bezüge
(Abfertigung/Abfindung). 'flaf'/'dz' zusätzlich ohne lit-f-AN
(Arbeitslöhne ab Folgemonat nach Vollendung des 60. Lebensjahres,
§ 41 Abs 4 lit f FLAG — die Kommunalsteuer hat KEINE lit-f-
Befreiung). Freibetrag: ≤ 1.095 € keine Abgabe, bis 1.460 € →
Freibetrag 1.095 €, darüber volle BG (at_lnk_freibetrag)."""
self.ensure_one()
basis = (self._l10n_at_brutto('laufend', result_rules)
+ self._l10n_at_brutto('sonstig', result_rules))
for rule in self.struct_id.rule_ids:
if rule.l10n_at_s67_abs in ('abs3', 'abs6'):
if result_rules is not None and rule.code in result_rules:
basis -= result_rules[rule.code]['total']
elif result_rules is None:
linie = self.line_ids.filtered(
lambda l: l.code == rule.code)
basis -= sum(linie.mapped('total'))
basis = max(basis, 0.0)
if steuer in ('flaf', 'dz'):
version = self.version_id
if (version.l10n_at_lnkp_frei_ab
and self.date_from >= version.l10n_at_lnkp_frei_ab):
return 0.0
freibetrag = self.env['hr.rule.parameter']._get_parameter_from_code(
'at_lnk_freibetrag', self.date_to, raise_if_not_found=False) or {}
grenze = float(freibetrag.get('grenze', 0.0))
if basis <= 0.0:
return 0.0
if basis <= grenze:
basis -= float(freibetrag.get('freibetrag', 0.0))
return max(basis, 0.0)
def _at_flaf_db(self, result_rules=None):
"""FLAF-Dienstgeberbeitrag § 41 FLAG (3,7 % ab 2025, 2,7 % ab
2028 — at_flaf_db; BG s. _at_lnk_basis)."""
self.ensure_one()
basis = self._at_lnk_basis('flaf', result_rules)
satz = float(self.env['hr.rule.parameter']._get_parameter_from_code(
'at_flaf_db', self.date_to, raise_if_not_found=False) or 0.0)
return basis * satz / 100.0
def _at_kommst(self, result_rules=None):
"""Kommunalsteuer § 9 KommStG 1993 (3 % — at_kommst_satz; BG
§ 5 Abs 2: ohne §-67-Abs-3/6-Bezüge, ohne lit-f-Befreiung)."""
self.ensure_one()
basis = self._at_lnk_basis('kommst', result_rules)
satz = float(self.env['hr.rule.parameter']._get_parameter_from_code(
'at_kommst_satz', self.date_to, raise_if_not_found=False) or 0.0)
return basis * satz / 100.0
def _at_dz(self, result_rules=None):
"""Zuschlag zum Dienstgeberbeitrag (§§ 122/126 WKG) für WKO-
Mitgliedsbetriebe: Landeskammersatz je Betriebsstätten-Bundesland
inkl. Bundeskammeranteil (at_dz_vektor; ⚠ WKO-Verifikation 8.11);
BG identisch FLAF-DB inkl. lit f (lb-lnk-07)."""
self.ensure_one()
if not self.company_id.l10n_at_wk_mitglied:
return 0.0
basis = self._at_lnk_basis('dz', result_rules)
vektor = self.env['hr.rule.parameter']._get_parameter_from_code(
'at_dz_vektor', self.date_to, raise_if_not_found=False) or {}
satz = float((vektor.get('laender') or {}).get(
self.company_id.l10n_at_bundesland or '', 0.0))
return basis * satz / 100.0
# ------------------------------------------------------------------
# All-in-Deckungsrechnung (§ 2g AVRAG) — minimale GP2-Umsetzung:
# Jahresvergleich geschuldete ÜStd-Vergütung vs. erhaltene Pauschale