# 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