mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
290b432b73
Rollenklärung (lb-msf-03, ASVG § 162 Abs 1): Wochengeld ist eine KV-Leistung — die ÖGK zahlt; der Dienstgeber stoppt das Entgelt in den Beschäftigungsfristen (MSchG §§ 3/5), liefert die Bemessungsdaten (Arbeits-/Entgeltbestätigung folgt mit AP8) und fuehrt die Abfertigung Neu auf fiktiver Grundlage. - models/mutterschutz.py: _at_msch_status (Kalendertage/Arbeitstage/ Episode aus der neuen Urlaubsart 'Mutterschutz-Frist', WET ATP_MSCH), _at_wochengeld — volle Wochengeld-Rechnung § 162 Abs 3 ASVG: Ø-Netto der drei Referenzmonate (letzte 13 Wochen) x (1 + satzungs- SZ-Prozentsatz 14/17/21 %, Parameter at_msch_sz_prozentsatz, vor Erstverwendung mandantenseitig zu verifizieren) ÷ Kalender- tage; Fallback kurzes DV (lb-msf-03) - Regel MSCH_ENTGELT: Entgelt-Stopp je Arbeitstag der Frist (negativ, Brutto; Tagesatz-Helper mit EFZG geteilt) - Fiktive BV-Grundlage § 7 Abs 4 BMSVG (RIS verifiziert): Monatsentgelt-Ø der letzten drei Kalendermonate vor dem Versicherungs- fall der Mutterschaft inkl. anteiliger Sonderzahlungen x Bezugs-KT/30, im _at_bv_basis_laufen-Hook neben der Krankengeld-Fiktion (AP4) - Rechenfaelle docs/rechenfaelle/mutterschutz.md; 4 Tests (Entgelt-Stopp + fiktive BV, §-162-Abs-3-Arithmetik, Wirkungslosigkeit ohne Frist, kurzes-DV-Fallback); Optionen dokumentiert: Sonderwochengeld § 163, Ruhen § 166, Karenz-Referenzmonat § 7 Abs 4 Z 1-3
229 lines
10 KiB
Python
229 lines
10 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
|
"""AP7: Mutterschutz/Wochengeld (MSchG §§ 3/5, ASVG § 162, BMSVG
|
|
§ 7 Abs 4 — verifiziert 2026-09-12 gegen RIS bzw. Wissensbasis
|
|
lb-msf-03, Stand 2026-07).
|
|
|
|
**Klargestellte Rollenverteilung** (lb-msf-03, § 162 Abs 1 ASVG): Das
|
|
Wochengeld ist eine **Leistung aus der Krankenversicherung** — die ÖGK
|
|
zahlt es direkt an die Arbeitnehmerin. Der Dienstgeber
|
|
1. schuldet für die Beschäftigungsverbots-Tage **kein Entgelt**
|
|
(Regel ``MSCH_ENTGELT`` stoppt die arbeitsseitigen Bezüge),
|
|
2. liefert die **Bemessungsdaten** (13-Wochen-/3-Kalendermonats-
|
|
Basis; Arbeits- und Entgeltbestätigung folgt mit AP8) — hier als
|
|
vollständige Wochengeld-Berechnung nach § 162 Abs 3 ASVG:
|
|
Ø-Arbeitsverdienst der letzten drei Kalendermonate (Geld- und
|
|
Sachbezüge, Entstehungsprinzip), vermindert um Lohnsteuer,
|
|
SV-Anteil, Kammerumlage, Wohnbauförderungsbeitrag, Aufwandersätze
|
|
und Schlechtwetterentschädigungsbeitrag (= Netto der
|
|
Referenzläufe), erhöht um den satzungsmäßigen Sonderzahlungs-
|
|
Prozentsatz (14/17/21 %, ``at_msch_sz_prozentsatz`` — mandanten-
|
|
seitig zu pflegen), je Kalendertag,
|
|
3. zahlt die **Abfertigung Neu auf fiktiver Grundlage** weiter
|
|
(§ 7 Abs 4 BMSVG): fiktive BG = Monatsentgelt, berechnet nach dem
|
|
in den letzten drei Kalendermonaten vor dem Versicherungsfall der
|
|
Mutterschaft gebührenden Entgelt **einschließlich anteiliger
|
|
Sonderzahlungen** (SZ-Fortzahlung während des Bezugs = vereinbarte
|
|
Ausnahme, dokumentierte Option), aliquot nach Kalendertagen
|
|
(30-Tage-Konvention).
|
|
|
|
Schutzfristen: § 3 MSchG Achtwochenfrist vor der voraussichtlichen
|
|
Entbindung; § 5 MSchG acht Wochen Wochenbett (mindestens zwölf bei
|
|
Früh-/Mehrlingsgeburten oder Kaiserschnitt; Verlängerung um
|
|
vorzeitig verkürzte Achtwochenfrist, max. 16 Wochen) — als
|
|
Urlaubsart „Mutterschutz-Frist" (WET ``ATP_MSCH``) zu erfassen; die
|
|
Fristberechnung aus dem ärztlichen Zeugnis bleibt beim Mandanten.
|
|
|
|
Urlaubsanspruch erwächst während des Wochengeldbezugs (lb-msf-03);
|
|
Sonderwochengeld (§ 163 ASVG, Günstigkeitsvergleich § 162 Abs 3b) =
|
|
dokumentierte Option.
|
|
"""
|
|
from datetime import date, timedelta
|
|
|
|
from odoo import fields, models
|
|
|
|
_MSCH_WET = 'ATP_MSCH'
|
|
|
|
|
|
class HrPayslip(models.Model):
|
|
_inherit = 'hr.payslip'
|
|
|
|
l10n_at_msch_info = fields.Char(
|
|
string='AT Mutterschutz/Wochengeld-Status',
|
|
compute='_compute_msch_info',
|
|
help='Wochengeld je Kalendertag (§ 162 Abs 3 ASVG), '
|
|
'Bezugstage und fiktive BV-Grundlage (§ 7 Abs 4 BMSVG) '
|
|
'im Abrechnungszeitraum.')
|
|
|
|
def _compute_msch_info(self):
|
|
for slip in self:
|
|
msch = slip._at_msch_status()
|
|
if not msch:
|
|
slip.l10n_at_msch_info = ''
|
|
continue
|
|
wochengeld = slip._at_wochengeld()
|
|
slip.l10n_at_msch_info = (
|
|
'%d Kalendertage Bezug · WG %.2f/Kalendertag '
|
|
'(Ø-Netto %.2f + %.0f %%)' % (
|
|
len(msch['kalendertage']),
|
|
wochengeld['tagesatz'],
|
|
wochengeld['netto_drei_monate'],
|
|
wochengeld['sz_prozent']))
|
|
|
|
def _at_msch_leaves(self):
|
|
"""Validierte Mutterschutz-Frist-Abwesenheiten (ATP_MSCH)."""
|
|
self.ensure_one()
|
|
typen = self.env['hr.leave.type'].search([
|
|
('work_entry_type_id.code', '=', _MSCH_WET)])
|
|
leaves = self.env['hr.leave'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('holiday_status_id', 'in', typen.ids),
|
|
('state', 'in', ('validate', 'validate1')),
|
|
])
|
|
return [
|
|
(leave.request_date_from, leave.request_date_to)
|
|
for leave in leaves
|
|
if leave.request_date_from and leave.request_date_to]
|
|
|
|
def _at_msch_status(self):
|
|
"""Mutterschutz-Tage je Abrechnungsperiode: {'kalendertage',
|
|
'arbeitstage', 'episode_beginn'} — None ohne Frist im Zeitraum.
|
|
|
|
Episode-Beginn = Anfang des zusammenhängenden Fristenblocks,
|
|
der in die Abrechnungsperiode reicht (Versicherungsfall der
|
|
Mutterschaft, § 120 Abs 1 Z 3 ASVG) — Bemessungsstichtag für
|
|
Wochengeld (§ 162 Abs 3) und fiktive BV (§ 7 Abs 4 BMSVG)."""
|
|
self.ensure_one()
|
|
tage = {}
|
|
for von, bis in self._at_msch_leaves():
|
|
tag = von
|
|
while tag <= bis:
|
|
tage[tag] = True
|
|
tag += timedelta(days=1)
|
|
in_periode = sorted(
|
|
tag for tag in tage
|
|
if self.date_from <= tag <= self.date_to)
|
|
if not in_periode:
|
|
return None
|
|
block = in_periode[0]
|
|
while (block - timedelta(days=1)) in tage:
|
|
block = block - timedelta(days=1)
|
|
eintraege = self.env['hr.work.entry'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('date', '>=', self.date_from),
|
|
('date', '<=', self.date_to),
|
|
('state', '!=', 'cancelled'),
|
|
('work_entry_type_id.code', '=', _MSCH_WET),
|
|
])
|
|
return {
|
|
'kalendertage': in_periode,
|
|
'arbeitstage': sorted({e.date for e in eintraege}),
|
|
'episode_beginn': block,
|
|
}
|
|
|
|
def _at_msch_referenz_monate(self, episode_beginn):
|
|
"""Die drei (vollen) Kalendermonate vor dem Versicherungsfall
|
|
der Mutterschaft (§ 162 Abs 3: letzte 13 Wochen; § 7 Abs 4
|
|
BMSVG: letzte drei Kalendermonate). Versicherungsfall im Monat
|
|
X → Monate X-3, X-2, X-1 (aufsteigend)."""
|
|
self.ensure_one()
|
|
stichtag = episode_beginn or self.date_from
|
|
erster = date(stichtag.year, stichtag.month, 1)
|
|
monate = []
|
|
hilf = erster
|
|
for _ in range(3):
|
|
hilf = (hilf - timedelta(days=1)).replace(day=1)
|
|
monate.append(hilf)
|
|
return list(reversed(monate))
|
|
|
|
def _at_msch_referenz_slip(self, monats_beginn):
|
|
"""Vollständiger validierter Referenzlauf des Monats (oder
|
|
leer)."""
|
|
self.ensure_one()
|
|
monats_ende = (monats_beginn.replace(day=28)
|
|
+ timedelta(days=4)).replace(day=1) \
|
|
- timedelta(days=1)
|
|
return self.env['hr.payslip'].search([
|
|
('employee_id', '=', self.employee_id.id),
|
|
('state', 'in', ('validated', 'paid')),
|
|
('date_from', '<=', monats_beginn),
|
|
('date_to', '>=', monats_ende),
|
|
], order='date_from desc', limit=1)
|
|
|
|
def _at_wochengeld(self):
|
|
"""Wochengeld-Berechnung nach § 162 Abs 3 ASVG (volle
|
|
Berechnung, AP7 — Ausweis/AEB-Daten; Zahlung durch die ÖGK):
|
|
|
|
Ø-Arbeitsverdienst der drei Referenzmonate, vermindert um
|
|
Lohnsteuer, SV-Anteil, Kammerumlage, Wohnbauförderungsbeitrag,
|
|
Aufwandersätze und Schlechtwetterentschädigungsbeitrag (=
|
|
Netto der Referenzläufe), erhöht um den satzungsmäßigen
|
|
SZ-Prozentsatz, je Kalendertag der drei Monate. Kurzes
|
|
Dienstverhältnis: Ø über die vorhandenen Monate (lb-msf-03);
|
|
ohne jede Historie der laufende Monat."""
|
|
self.ensure_one()
|
|
sz_prozent = float(self.env['hr.rule.parameter']
|
|
._get_parameter_from_code(
|
|
'at_msch_sz_prozentsatz', self.date_from,
|
|
raise_if_not_found=False) or 0.0)
|
|
ergebnis = {
|
|
'netto_drei_monate': 0.0, 'sz_prozent': sz_prozent,
|
|
'kalendertage_basis': 0, 'tagesatz': 0.0,
|
|
'referenz_monate': [], 'episode_beginn': None,
|
|
}
|
|
msch = self._at_msch_status()
|
|
if not msch:
|
|
return ergebnis
|
|
ergebnis['episode_beginn'] = msch['episode_beginn']
|
|
netto, kalendertage, gefundene = 0.0, 0, []
|
|
for monat in self._at_msch_referenz_monate(
|
|
msch['episode_beginn']):
|
|
ergebnis['referenz_monate'].append(monat)
|
|
monats_ende = (monat.replace(day=28)
|
|
+ timedelta(days=4)).replace(day=1) \
|
|
- timedelta(days=1)
|
|
kalendertage += monats_ende.day
|
|
slip = self._at_msch_referenz_slip(monat)
|
|
if slip:
|
|
gefundene.append(monat)
|
|
netto += sum(slip.line_ids.filtered(
|
|
lambda l: l.code == 'NET').mapped('total'))
|
|
if not gefundene:
|
|
# Ohne jede Historie: Netto des laufenden Monats
|
|
# (tatsächlicher Arbeitszeitraum, lb-msf-03).
|
|
netto = sum(self.line_ids.filtered(
|
|
lambda l: l.code == 'NET').mapped('total'))
|
|
kalendertage = 30
|
|
ergebnis['netto_drei_monate'] = netto
|
|
ergebnis['kalendertage_basis'] = kalendertage
|
|
ergebnis['tagesatz'] = netto * (1.0 + sz_prozent / 100.0) \
|
|
/ max(kalendertage, 1)
|
|
return ergebnis
|
|
|
|
def _at_bv_fiktiv_wochengeld(self):
|
|
"""Fiktive BV-Grundlage während des Wochengeldbezugs
|
|
(§ 7 Abs 4 BMSVG, verifiziert): Monatsentgelt-Ø aus den
|
|
letzten drei Kalendermonaten vor dem Versicherungsfall der
|
|
Mutterschaft **einschließlich anteiliger Sonderzahlungen**
|
|
(hier: laufende + sonstige Bezüge der Referenzläufe;
|
|
SZ-Fortzahlung während des Bezugs = vereinbarte Ausnahme,
|
|
Option), aliquot nach Kalendertagen des Bezugs
|
|
(30-Tage-Konvention)."""
|
|
self.ensure_one()
|
|
msch = self._at_msch_status()
|
|
if not msch or not msch['kalendertage']:
|
|
return 0.0
|
|
summe, monate = 0.0, 0
|
|
for monat in self._at_msch_referenz_monate(
|
|
msch['episode_beginn']):
|
|
slip = self._at_msch_referenz_slip(monat)
|
|
if not slip:
|
|
continue
|
|
summe += (slip._l10n_at_brutto('laufend')
|
|
+ slip._l10n_at_brutto('sonstig'))
|
|
monate += 1
|
|
if not monate:
|
|
# Ohne Historie: laufender Monat (kurzes DV, lb-msf-03).
|
|
summe = (self._l10n_at_brutto('laufend')
|
|
+ self._l10n_at_brutto('sonstig'))
|
|
monate = 1
|
|
return summe / monate * len(msch['kalendertage']) / 30.0 |