d53e1e64a1
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)
268 lines
13 KiB
Python
268 lines
13 KiB
Python
# 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 |