Files
odoo-at-payroll/addons/l10n_at_hr_payroll_private/models/abfertigung_private.py
T
fegger d53e1e64a1 [IMP] l10n_at_hr_payroll_private: add unpaid care/parental leave family (AP16)
Close the parental/care-leave block after the pilot-client scope release (8.13): six unpaid full-release kinds join the generic parental-leave work entry — Bildungskarenz (AVRAG § 11, two to twelve months by agreement with the AMS-financed Weiterbildungsbeihilfe), Familienhospizkarenz for terminal accompaniment (AVRAG § 14a, three to six months), accompaniment of seriously ill children (§ 14b, five to nine months), children's rehabilitation leave (§ 14e, four weeks per year), Pflegekarenz (§ 14c, one to three months, undivided) and the Papa-Monat (VKG § 1a, one month, creditable toward seniority under § 1a Abs 7 VKG iVm § 15f MSchG while Bildungs-/Pflegekarenz is not). All are unpaid at the employer — the external bodies pay the replacement income (AK, AMS, Bundesamt, KV-Träger) — so the new negative KARENZ_ENTGELT rule stops the employer wage per working day through the shared Tagesatz helper whose exclusion list now covers EFZG, Mutterschutz and Karenz corrections. The Versichertenmeldung run emits M4 deregistrations at the end of the pay entitlement (the day before the release begins) and M3 re-registrations at the return date — omitted when the employment itself ends — with the eSV Karenzart strings logged as protocol information pending the special DM-ORG formats (GP7 track). The fictive BMSVG basis gains the § 7 Abs 4 Z 1–3 bridge: when a new maternity prohibition follows a parental-leave stretch directly or after employment of less than three months, the reference months fall before the preceding prohibition, and a re-founded employment within three months of the ended karenziert employment uses the single last full calendar month; contributions during the KBG period are FLAF-financed (§ 7 Abs 5) and during hospice/care leave Bund-financed (§ 7 Abs 6) — no employer rule, verified by test. Pflegeteilzeit and comparable reduced-hour arrangements pay the BV contribution on the pre-reduction wage via a new version field consumed by the BMSVG basis (§ 6 Abs 4). The endabrechnung wizard gains the § 23 Abs 8 AngG full-time re-rating preview (monthly salary ÷ work_time_rate, confirmable) with § 23a Abs 4a and UEL § 10 Abs 4 UrlG guidance. The Mutterschutz status now detects the § 163 ASVG Sonderwochengeld case (prohibition inside a Karenz — the KV-Träger pays the increased sickness benefit) and warns on § 166 ASVG suspension (full for >50 % continued pay, half at 50 %, activity and Pflegekarenzgeld hints). Verified against the original statute text in .ris/ (AVRAG §§ 11/11a/14a–14e, VKG § 1a, MSchG § 15f, BMSVG § 7 Abs 4–6, ASVG §§ 163/166) and the knowledge-base sources lb-kar/elt/kzs/pap/kbg; the vacation proration of leave stretches (§ 15f Abs 2 MSchG on the hr_holidays allocation side), the eSV special formats for the Karenz art block, the full Sonderwochengeld computation and the leave-overlap policy decision remain documented open points.

Version 19.0.17.0.0 (private)
2026-09-15 14:25:48 +02:00

268 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Part of the odoo-at-payroll project. License: LGPL-3.
"""Abfertigung Neu (BMSVG) — GP5: laufender BV-Beitrag (§ 6) mit
EIGENER BV-Grundlage, Anwartschafts-Fortschreibung (§ 15) und
Endabrechnung (§§ 14-17).
Verifizierte Grundlagen (RECHTSQUELLEN-Privat Abschn. 8 ✅; KB
lb-vor-03/lb-vor-06/lb-end-03, Stand 2026-08):
- § 6 Abs 1: DG-Beitrag 1,53 vH des monatlichen Entgelts und der
Sonderzahlungen an die Mitarbeitervorsorgekasse (Abfuhr über den
KV-Träger, Fälligkeit 15. Folgemonat, Baubereich BUAK — GP7).
- § 6 Abs 5 iVm § 49 ASVG: Bemessung ohne GFG und HBG → eigene,
ungecappte BV-Grundlage. Bewusst KEIN Rückgriff auf die SV-SZ-Basis
des Kerns: deren §-49-Abs-3-Z-7-Ausschluss für §-67-Abs-3/6-Bezüge
(Abfertigung/Abfindung) ist dort nicht umgesetzt; die BV-Grundlage
schließt diese Zeilen korrekt aus (Nutzer-Entscheidung 2026-09-11).
- Geltungsbereich § 46/§ 1 Abs 2 Z 1: private Arbeitsverhältnisse ab
1. 1. 2003 (Flag am Vertrag); geringfügig Beschäftigte und Lehrlinge
sind ERFASST — BVG läuft auch ohne SV-Hauptbeiträge.
- Beitragsfreier erster Monat (§ 6 Abs 1), taggenau: Fenster =
[Eintritt, Eintritt + 1 Monat), Beitragspflicht ab Monatsjubiläum
(Eintritt 15. 06. → frei bis 14. 07., Pflicht ab 15. 07.; KB-Beispiel
4.500/30 × 17 = 2.550). Monatsende-Klippe (Eintritt 31. 01. → kein
31. 02.): Fenster bis Monatsultimo, Pflicht ab Folgemonatserstem
(ABGB-Fristrechnung). AV ≤ 1 Monat → zur Gänze beitragsfrei (auch
Urlaubsersatzleistung). Wiederzulassung binnen 12 Monaten →
Beitragspflicht ab dem 1. TAG; Vor-AV beliebiger Dauer (auch
< 1 Monat; OGH 9 ObA 30/16a) — schlägt auch die Kurz-AV-Freiheit
des laufenden AV (Nutzer-Entscheidung 2026-09-11).
- Sonderzahlungen: keine aliquote Kürzung um den beitragsfreien
Monat; beitragsfrei nur, wenn ihr Beitragsmonat ins Fenster fällt.
- §§ 14-17: Anwartschaft = gutgebrachte Beiträge + Erträge (KEINE
Monatsentgelt-Staffel); Verfügungsausschluss bei AN-Kündigung,
verschuldeter Entlassung, unberechtigtem vorzeitigem Austritt oder
weniger als 36 Beitragsmonaten (alle Beitragszeiten zählen, daher
manuelle Vor-Arbeitgeber-Monate am Mitarbeiter); Fälligkeit am Ende
des zweitfolgenden Monats nach Geltendmachung (§ 16); fünf
Verfügungsoptionen, ohne Erklärung binnen 6 Monaten
Weiterveranlagung (§ 17).
"""
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import fields, models
class HrVersion(models.Model):
_inherit = 'hr.version'
l10n_at_abfertigung_neu = fields.Boolean(
string='AT Abfertigung Neu (BMSVG)',
default=True,
groups="hr_payroll.group_hr_payroll_user", tracking=True,
help='Dienstverhältnis unterliegt dem BMSVG (§ 46: AV ab '
'1. 1. 2003; § 1 Abs 2 Z 1: Länder/Gemeinden ausgenommen). '
'Für Alt-Verhältnisse (§§ 23 f AngG) und § 47-Übertritte '
'abwählen — dann läuft keine BVG-Regel. Geringfügig '
'Beschäftigte und Lehrlinge sind erfasst.')
l10n_at_bv_kasse_id = fields.Many2one(
'l10n.at.bv.kasse', string='AT Mitarbeitervorsorgekasse',
index=True, groups="hr_payroll.group_hr_payroll_user",
tracking=True,
help='Gewählte BV-Kasse dieses Dienstverhältnisses (lb-vor-02); '
'Anwartschaftskonto und §-17-Verfügungen laufen über diese '
'Kasse.')
class HrEmployee(models.Model):
_inherit = 'hr.employee'
l10n_at_abfertigung_neu = fields.Boolean(
related="version_id.l10n_at_abfertigung_neu", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
l10n_at_bv_kasse_id = fields.Many2one(
related="version_id.l10n_at_bv_kasse_id", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
l10n_at_bv_ertraege = fields.Monetary(
string='AT BV-Erträge (Kassen-Auszug)',
currency_field='currency_id',
groups="hr_payroll.group_hr_payroll_user",
help='Veranlagungserträge der Mitarbeitervorsorgekasse laut '
'Kassen-Auszug (§ 15 BMSVG: Auszahlung der Anwartschaft = '
'gutgebrachte Beiträge + Erträge) — manuell pflegen.')
l10n_at_bv_monate_extern = fields.Integer(
string='AT BV-Beitragsmonate Vor-Arbeitgeber',
groups="hr_payroll.group_hr_payroll_user",
help='Beitragsmonate aus Vor-Beschäftigungen (§ 14 Abs 2 BMSVG: '
'alle Beitragszeiten über Arbeitgeber hinweg summierbar — '
'36-Monats-Kriterium) — laut Auszug der MV-Kasse pflegen.')
def _at_bv_anwartschaft(self):
"""Anwartschafts-Fortschreibung (§ 15 BMSVG): gutgebrachte
Beiträge aus validierten Läufen, je Beitragsmonat/Jahr
gruppiert (Refund-Läufe netten über negative Linien; Monate
zählen auch aus negativen Linien — der Monat ist belegt)."""
self.ensure_one()
monate = set()
total = 0.0
jahre = {}
slips = self.env['hr.payslip'].search([
('employee_id', '=', self.id),
('state', 'not in', ('draft', 'cancel')),
])
for line in slips.line_ids.filtered(
lambda l: l.code == 'BVG'):
if not line.total:
continue
monat = (line.l10n_at_beitragsmonat
or line.date_from).replace(day=1)
monate.add(monat)
total += line.total
eintrag = jahre.setdefault(
monat.year, {'monate': set(), 'summe': 0.0})
eintrag['monate'].add(monat)
eintrag['summe'] += line.total
return {
'monate': len(monate),
'beitraege': total,
'jahre': [(jahr, len(w['monate']), w['summe'])
for jahr, w in sorted(jahre.items())],
}
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
# ------------------------------------------------------------------
# Beitragsfreies Erstfenster (§ 6 Abs 1 BMSVG, taggenau)
# ------------------------------------------------------------------
def _at_bv_fenster(self):
"""(pflichtig_ab, kurz_av) des Arbeitsverhältnisses:
pflichtig_ab = erstes beitragspflichtiges Datum (None =
Beitragspflicht ab dem 1. Tag bzw. ohne bewertbare
Vertragsdaten); kurz_av = True → AV ≤ 1 Monat, zur Gänze
beitragsfrei. Reihenfolge: Wiederzulassung schlägt alles,
dann Kurz-AV-Freiheit, dann taggenaues Fenster."""
self.ensure_one()
version = self.version_id
start = version.contract_date_start
if not start:
# Ohne Vertragsdaten ist die Befreiung nicht bewertbar →
# konservativ Vollpflicht.
return None, False
# Wiederzulassung binnen 12 Monaten: Beitragspflicht ab dem
# 1. Tag; Vor-Dienstverhältnis beliebiger Dauer (auch
# < 1 Monat) löst die Ausnahme aus.
prior = self.env['hr.version'].search([
('employee_id', '=', self.employee_id.id),
('id', '!=', version.id),
('contract_date_end', '!=', False),
('contract_date_end', '>=',
start - relativedelta(months=12)),
('contract_date_end', '<', start),
], limit=1)
if prior:
return None, False
# Monatsjubiläum, taggenau; Monatsende-Klippe: existiert der
# Jubiläumstag im Folgemonat nicht (Eintritt 31. 01.), endet
# das Fenster am Monatsultimo, Pflicht ab Folgemonatserstem.
jubilaeum = start + relativedelta(months=1)
if jubilaeum.day < start.day:
jubilaeum += timedelta(days=1)
end = version.contract_date_end
if end and end < jubilaeum:
# AV ≤ 1 Monat → zur Gänze beitragsfrei (lb-vor-03, auch
# eine bloße Urlaubsersatzleistung).
return None, True
return jubilaeum, False
# ------------------------------------------------------------------
# Eigene BV-Grundlage (§ 6 Abs 5 iVm § 49 ASVG — ohne GFG/HBG)
# ------------------------------------------------------------------
def _at_bv_sonst_monate(self, result_rules=None):
"""[(Beitragsmonat, sonstige BV-Basis)]: sonstige Bezüge ohne
§-67-Abs-3/6-Zeilen (Abfertigung/Abfindung sind nach
§ 49 Abs 3 Z 7 ASVG beitragsfrei, damit auch nicht BV-
pflichtig), gruppiert nach Regel-Beitragsmonat mit voller
Tagesgenauigkeit (Anspruchsprinzip, Muster
_l10n_at_sv_basis_laufen_monate) — die Fenster-Prüfung braucht
die Fälligkeit taggenau (SZ fällig 20. 07. ist bei Fenster-
Ende 15. 07. pflichtig, der Monatsanfang wäre es nicht)."""
self.ensure_one()
gruppen = {}
for rule in self.struct_id.rule_ids:
if (rule.l10n_at_bemessung != 'sonstig'
or rule.l10n_at_s67_abs != 'keine'):
continue
werte = None
if result_rules is not None:
if rule.code in result_rules:
werte = result_rules[rule.code]
else:
linie = self.line_ids.filtered(
lambda l: l.code == rule.code)
if linie:
werte = {'total': sum(linie.mapped('total'))}
if not werte:
continue
monat = rule.l10n_at_beitragsmonat or self.date_from
gruppen[monat] = gruppen.get(monat, 0.0) + werte['total']
return sorted(gruppen.items())
def _at_bv_basis_laufen(self, result_rules=None):
"""Laufende BV-Grundlage: §-49-bereinigtes Entgelt ohne
HBG-Cap (§ 6 Abs 5), aliquotiert um das beitragsfreie
Erstfenster — 30-Tage-Monat (KB-Beispiel 4.500/30 × 17 =
2.550); Teilmonate über die Lohnsteuertage, um Doppelabzüge
zu vermeiden (das Entgelt ist für die Teilzeit bereits
aliquotiert). AP16 (§ 6 Abs 4 BMSVG): während einer
Arbeitszeit-Reduzierung mit gesetztem Feld
l10n_at_bv_vor_entgelt (z. B. Pflegeteilzeit § 14d AVRAG)
läuft der Beitrag auf dem Vor-Reduzierungs-Entgelt."""
self.ensure_one()
pflichtig_ab, kurz_av = self._at_bv_fenster()
if kurz_av:
return 0.0
vor_entgelt = self.version_id.l10n_at_bv_vor_entgelt
basis = self._l10n_at_sv_basis_roh(result_rules)
if vor_entgelt:
basis = max(basis, float(vor_entgelt))
if pflichtig_ab is None or pflichtig_ab <= self.date_from:
# AP4: fiktive BG während des Krankengeldbezugs (§ 7 Abs 3
# BMSVG) und des Wochengeldbezugs (§ 7 Abs 4, AP7) — nur
# in beitragspflichtigen Läufen (Erstfenster-Teilmonate:
# dokumentierter Randfall).
return basis + self._at_bv_fiktiv_krankengeld() \
+ self._at_bv_fiktiv_wochengeld()
if pflichtig_ab > self.date_to:
# LZP liegt zur Gänze im beitragsfreien Erstfenster.
return 0.0
tage = (min(self.date_to,
self.version_id.contract_date_end or self.date_to)
- pflichtig_ab).days + 1
if tage <= 0:
return 0.0
return basis * tage / max(self._l10n_at_lohnsteuertage(), 1)
def _at_bv_basis_sonst(self, result_rules=None):
"""BV-Grundlage sonstiger Bezüge (Sonderzahlungen & Co.):
ungecappt (kein HBG-/Jahresdeckel, § 6 Abs 5) und ohne
§-67-Abs-3/6-Zeilen; keine aliquote Kürzung um das freie
Fenster — frei bleibt nur, was im Fenster fällig wird
(Beitragsmonat im Fenster, lb-vor-03)."""
self.ensure_one()
pflichtig_ab, kurz_av = self._at_bv_fenster()
if kurz_av:
return 0.0
total = 0.0
for monat, basis in self._at_bv_sonst_monate(result_rules):
if pflichtig_ab is not None and monat < pflichtig_ab:
continue
total += basis
return total
def _at_bv_beitrag(self, result_rules=None):
"""BV-Beitrag (§ 6 Abs 1 BMSVG): 1,53 % DG (at_sv_bv_dg) auf
die eigene Grundlage laufend + sonstig; ATP_DG-Zeile ohne
Nettoeffekt (FI-Zuordnung über hr_payroll_account, GP6).
Läuft bewusst auch für geringfügig Beschäftigte und
Lehrlinge (§ 46, § 1 Abs 2 Z 1; Jahreszahlung + 2,5 %
Zinsenzuschlag der Geringfügigen = Zahlungsmodus GP7/GP8)."""
self.ensure_one()
if not self.version_id.l10n_at_abfertigung_neu:
return 0.0
basis = (self._at_bv_basis_laufen(result_rules)
+ self._at_bv_basis_sonst(result_rules))
satz = float(self._l10n_at_param('at_sv_bv_dg'))
return basis * satz / 100.0