mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
0ea7f54682
Laufender Beitrag (§ 6 BMSVG): Regel BVG (Kategorie ATP_DG, ohne Nettoeffekt) auf EIGENER, ungecappter BV-Grundlage — §-49-bereinigt, ohne HBG-/Jahresdeckel (§ 6 Abs 5 iVm § 49 ASVG) und ohne §-67-Abs-3/6-Zeilen (§ 49 Abs 3 Z 7 ASVG); bewusst kein Rückgriff auf die SV-SZ-Basis des Kerns (Nutzer-Entscheidung eigene BV-Grundlage; deren Z-7-Ausschluss fehlt — GP4-Beobachtung bleibt offen dokumentiert). Beitragsfreier erster Monat taggenau: Fenster [Eintritt, Eintritt + 1 Monat), Beitragspflicht ab Monatsjubiläum (15. 06. → 15. 07.; KB-Beispiel lb-vor-03: 4.500/30 × 17 = 2.550 — die ab-Folgetag-Lesart war ein Off-by-one und wurde verworfen); 30-Tage-Aliquotierung über die Lohnsteuertage (Teilmonate ohne Doppelabzug); Monatsende-Klippe (Eintritt 31. 01.): Fenster bis Monatsultimo, Pflicht ab Folgemonatserstem (ABGB-Fristrechnung); AV ≤ 1 Monat zur Gänze beitragsfrei. Wiederzulassung binnen 12 Monaten → Beitragspflicht ab dem 1. Tag; Vor-AV beliebiger Dauer (auch < 1 Monat, Nutzer-Klarstellung) und schlägt auch die Kurz-AV-Freiheit des laufenden AV (OGH 9 ObA 30/16a; Suche über frühere hr.version mit contract_date_end im 12-Monats-Fenster). Sonderzahlungen werden nicht um das Fenster gekürzt, bleiben frei nur bei Beitragsmonat (voller Datumstag, taggenau) im Fenster. Geringfügig Beschäftigte und Lehrlinge sind erfasst — BVG läuft auch im §-53a-Fall. Anwartschaft (§ 15: gutgebrachte Beiträge + Erträge, keine Monatsentgelt-Staffel) on-the-fly aus validierten Läufen; Anwartschaftsreport (QWeb auf hr.employee) und Endabrechnungs-Wizard (§§ 14-17: §-14-Abs-2-Verfügungsmatrix, < 36 Beitragsmonate sperrt immer, §-16-Fälligkeit am Ende des zweitfolgenden Monats, 6-Monats-Erklärungsfrist, 5 Verfügungsoptionen) — die Auszahlung erfolgt durch die MV-Kasse, nicht über den Payslip. Stammdaten: Vertrags-Flag l10n_at_abfertigung_neu (§ 46) + Modell l10n.at.bv.kasse (Leitzahl für eSV/mBGM; Seed der 7 Kassen-Namen noupdate, lb-gsv-11 — Leitzahlen aus Mandats-Unterlagen) + Mitarbeiter-Felder l10n_at_bv_ertraege/l10n_at_bv_monate_extern (Kassen-Auszug). Bewusst nicht: §-7-BMSVG-Fiktivfälle (dokumentierte Option), Geringfügigen-Jahreszahlung + 2,5 % (GP7), BV-Provider (Option). Tests: test_bv_private (10 Fälle, RF-BV1-10); gesamt 84/84 grün (frischer -i-Lauf auf Wegwerf-DB). Rechenfälle docs/rechenfaelle/abfertigung.md. 19.0.5.0.0.
257 lines
12 KiB
Python
257 lines
12 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)."""
|
||
self.ensure_one()
|
||
pflichtig_ab, kurz_av = self._at_bv_fenster()
|
||
if kurz_av:
|
||
return 0.0
|
||
basis = self._l10n_at_sv_basis_roh(result_rules)
|
||
if pflichtig_ab is None or pflichtig_ab <= self.date_from:
|
||
return basis
|
||
if pflichtig_ab > self.date_to:
|
||
# LZP liegt zur Gänze im beitragsfreien Erstfenster.
|
||
return 0.0
|
||
ende = min(self.date_to,
|
||
self.version_id.contract_date_end or self.date_to)
|
||
tage = (ende - 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 |