[IMP] l10n_at_hr_payroll_private: Mutterschutz/Wochengeld — Entgelt-Stopp, volle §-162-Abs-3-Berechnung, fiktive BV (AP7)
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
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
|
||||
'version': '19.0.9.2.0',
|
||||
'version': '19.0.9.3.0',
|
||||
'author': 'ixsol / gem360',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -107,12 +107,14 @@ gem360-Brücke).
|
||||
'data/salary_rules_private.xml',
|
||||
'data/salary_rules_lst_private.xml',
|
||||
'data/salary_rules_efzg_private.xml',
|
||||
'data/salary_rules_msch_private.xml',
|
||||
'data/salary_rules_sv_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
'data/hr_payroll_account_private.xml',
|
||||
'data/rule_parameters_mbgm_private.xml',
|
||||
'data/rule_parameters_wien_private.xml',
|
||||
'data/rule_parameters_efzg_private.xml',
|
||||
'data/rule_parameters_msch_private.xml',
|
||||
'data/bv_kasse_seed.xml',
|
||||
'views/kv_views.xml',
|
||||
'views/bv_views.xml',
|
||||
|
||||
@@ -46,6 +46,16 @@
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<!-- AP7: Mutterschutz-Fristen (§§ 3/5 MSchG) — Wochengeldbezug, -->
|
||||
<!-- Entgelt gestoppt; Wochengeld zahlt die ÖGK. -->
|
||||
<record id="wett_atp_msch" model="hr.work.entry.type">
|
||||
<field name="name">Mutterschutz-Frist (MSchG §§ 3/5)</field>
|
||||
<field name="color">8</field>
|
||||
<field name="display_code">MSF</field>
|
||||
<field name="code">ATP_MSCH</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Urlaubsarten (hr.leave.type) mit WET-Verknüpfung -->
|
||||
<record id="leave_type_at_urlaub" model="hr.leave.type">
|
||||
@@ -82,6 +92,17 @@
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_au"/>
|
||||
</record>
|
||||
<record id="leave_type_at_mutterschutz" model="hr.leave.type">
|
||||
<field name="name">Mutterschutz-Frist (§§ 3/5 MSchG)</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="support_document" eval="True"/>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_msch"/>
|
||||
</record>
|
||||
<record id="leave_type_at_karenz" model="hr.leave.type">
|
||||
<field name="name">Unbezahlte Karenz</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- AP7: Satzungsmäßiger Sonderzahlungs-Prozentsatz des -->
|
||||
<!-- Wochengeldes (§ 162 Abs 3 ASVG; lb-msf-03, Stand 2026-07: -->
|
||||
<!-- 14 % / 17 % / 21 % je nach Ausmaß der Sonderzahlungen — -->
|
||||
<!-- ⚠ satzungsabhängig, vor Erstverwendung mandantenseitig gegen -->
|
||||
<!-- die Satzung des KV-Trägers verifizieren; hier 17 % als -->
|
||||
<!-- mittlerer Wert, kein verifizierter Mandantenwert). -->
|
||||
<record id="hr_rule_parameter_at_msch_sz_prozentsatz" model="hr.rule.parameter">
|
||||
<field name="name">Wochengeld-Sonderzahlungsprozentsatz (§ 162 Abs 3 ASVG)</field>
|
||||
<field name="code">at_msch_sz_prozentsatz</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_at_msch_sz_prozentsatz"
|
||||
model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id"
|
||||
ref="hr_rule_parameter_at_msch_sz_prozentsatz"/>
|
||||
<field name="date_from">2026-01-01</field>
|
||||
<field name="parameter_value">17.0</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- AP7: Mutterschutz — Entgelt-Stopp während der Beschäftigungs- -->
|
||||
<!-- verbote (MSchG §§ 3/5): Das Wochengeld zahlt die ÖGK (§ 162 -->
|
||||
<!-- Abs 1 ASVG), der Dienstgeber schuldet für die Verbots-Tage -->
|
||||
<!-- kein Entgelt (lb-msf-03). Negative Brutto-Zeile je Arbeitstag -->
|
||||
<!-- aus dem Arbeitseintragssatz (Muster KRANKENTGELT_PHASE); die -->
|
||||
<!-- fiktive BV-Grundlage läuft separat (§ 7 Abs 4 BMSVG). -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<record id="rule_msch_entgelt" model="hr.salary.rule">
|
||||
<field name="name">Mutterschutz-Frist (kein Dienstgeber-Entgelt; Wochengeld zahlt ÖGK)</field>
|
||||
<field name="code">MSCH_ENTGELT</field>
|
||||
<field name="sequence" eval="47"/>
|
||||
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
|
||||
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python"><![CDATA[
|
||||
msch = payslip._at_msch_status()
|
||||
result = bool(msch and msch['arbeitstage'])
|
||||
]]></field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="l10n_at_bemessung">laufend</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Beschäftigungsverbot je Arbeitstag: volle Tages-Entgelt-
|
||||
# Reduktion (Tagesatz ohne Teilentgelt-Reduktionen — Helper
|
||||
# teilt die Ausschlüsse mit EFZG und MSCH).
|
||||
msch = payslip._at_msch_status()
|
||||
result = -payslip._at_efz_tagesatz(result_rules) \
|
||||
* len(msch['arbeitstage'])
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,79 @@
|
||||
# Rechenfälle Mutterschutz/Wochengeld (AP7)
|
||||
|
||||
Modell: `models/mutterschutz.py`, Regel `MSCH_ENTGELT`
|
||||
(`data/salary_rules_msch_private.xml`), Parameter
|
||||
`at_msch_sz_prozentsatz` (⚠ satzungsabhängig, vor Erstverwendung
|
||||
verifizieren).
|
||||
|
||||
## Rechtsquellen (verifiziert 2026-09-12)
|
||||
|
||||
- **MSchG § 3 Abs 1** (Achtwochenfrist vor der voraussichtlichen
|
||||
Entbindung, ärztliches Zeugnis) und **§ 5 Abs 1** (8 Wochen
|
||||
Wochenbett; mindestens 12 bei Früh-/Mehrlingsgeburten oder
|
||||
Kaiserschnitt; Verlängerung um vorzeitig verkürzte Achtwochenfrist,
|
||||
max. 16 Wochen) — lokal, RIS.
|
||||
- **ASVG § 162 Abs 1/3/4** (lb-msf-03, Stand 2026-07): Wochengeld =
|
||||
**Leistung aus der Krankenversicherung**; Bemessung nach dem Ø-
|
||||
Arbeitsverdienst der letzten 13 Wochen (drei Kalendermonate),
|
||||
vermindert um Lohnsteuer, SV-Anteil, Kammerumlage, WBF-Beitrag,
|
||||
Aufwandersätze, SW-Beitrag; erhöht um den satzungsmäßigen
|
||||
**SZ-Prozentsatz (14/17/21 %)**; je Kalendertag.
|
||||
- **BMSVG § 7 Abs 4** (RIS, verifiziert): während des Anspruchs auf
|
||||
(Sonder-)Wochengeld bei aufrechtem Arbeitsverhältnis fiktive
|
||||
BV-Grundlage = Monatsentgelt aus den letzten **drei
|
||||
Kalendermonaten vor dem Versicherungsfall der Mutterschaft,
|
||||
einschließlich anteiliger Sonderzahlungen**; Spezial-Referenzmonat
|
||||
bei neuerlichem Verbot unmittelbar nach Karenz (Z 1–3, dort Monat
|
||||
vor dem der Karenz vorangegangenen Verbot) — dokumentierte Option.
|
||||
|
||||
## Rollenverteilung (klärt die frühere Plan-Annahme)
|
||||
|
||||
Die ÖGK zahlt das Wochengeld direkt (§ 162 Abs 1 ASVG) — der
|
||||
Dienstgeber zahlt für die Verbotstage **kein Entgelt**
|
||||
(`MSCH_ENTGELT`-Reduktion je Arbeitstag), liefert die
|
||||
Bemessungsdaten (hier vollständig berechnet; AEB mit AP8) und führt
|
||||
die BV auf fiktiver Grundlage weiter (§ 7 Abs 4 BMSVG).
|
||||
Eine „Dienstgeber-Wochengeldzahlung" (deutsches
|
||||
Mutterschaftsgeld-Modell) existiert in Österreich nicht.
|
||||
|
||||
## RF-MS-1 — Entgelt-Stopp und fiktive BV
|
||||
|
||||
Eintritt 1. 7. 2026, KV-Gruppe II/1 (2.059,41 €), Referenzläufe
|
||||
Aug/Sep/Okt voll entlohnt; Mutterschutz-Frist 1.–15. 11. 2026
|
||||
(15 Kalendertage, 10 Arbeitstage):
|
||||
|
||||
- `MSCH_ENTGELT` = −(2.059,41 ÷ 21 AT) × 10 = −980,67 €.
|
||||
- Fiktive BV-BG (§ 7 Abs 4) = Ø-Monatsentgelt der Monate Aug–Okt
|
||||
(je 2.059,41, keine SZ-Zeilen) × 15/30 = 1.029,71 €.
|
||||
- Wochengeld-Ausweis: Ø-Netto der drei Referenzläufe + 17 %
|
||||
SZ-Prozentsatz ÷ 92 Kalendertage (31+30+31).
|
||||
|
||||
## RF-MS-2 — Wochengeld-Berechnung (§ 162 Abs 3)
|
||||
|
||||
Je Kalendertag: (Σ Netto der drei Referenzmonate × (1 + SZ-%)) ÷
|
||||
Kalendertage der drei Monate. Kurzes Dienstverhältnis: Ø über die
|
||||
vorhandenen Monate; ohne jede Historie: Netto des laufenden Monats
|
||||
(lb-msf-03). SZ-Fortzahlung während des Bezugs (vereinbart) wäre
|
||||
reale Grundlage statt der fiktiven — Option.
|
||||
|
||||
## RF-MS-3 — Fristenmodell
|
||||
|
||||
Schutzfristen als Urlaubsart „Mutterschutz-Frist" (WET `ATP_MSCH`,
|
||||
Krankmeldungspflicht-Feld zweckentfremdbar); die Berechnung der
|
||||
Achtwochenfrist aus dem ärztlichen Zeugnis (§ 3 Abs 2 MSchG) und
|
||||
Verlängerungen (§ 5 Abs 1) bleibt beim Mandanten.
|
||||
|
||||
## Offene Punkte / dokumentierte Reste
|
||||
|
||||
- **Sonderwochengeld** (§ 163 ASVG, Günstigkeitsvergleich § 162
|
||||
Abs 3b; Versicherungsfall ≤ 13 Wochen nach Karenzende bzw.
|
||||
Teilzeit nach KBG) — Option, braucht Verdienthistorie vor dem
|
||||
letzten Entgeltanspruch inkl. Valorisierung.
|
||||
- **Ruhen** (§ 166 ASVG): EFZ > 50 %, Erwerbseinkommen,
|
||||
Pflegekarenzgeld — Abrechnungs-/Melde-Schnittstelle (AP8-Kontext).
|
||||
- **SZ-Prozentsatz** 14/17/21 % satzungsabhängig — Parameter vor
|
||||
Erstverwendung gegen die Satzung des KV-Trägers verifizieren.
|
||||
- **Karenz-Referenzmonat** (§ 7 Abs 4 Z 1–3 BMSVG): Folgeschwangerschaft
|
||||
unmittelbar nach Karenz — Sonder-Bemessungsmonat, Option.
|
||||
- **AEB SART 75/76** (Arbeits- und Entgeltbestätigung Wochengeld,
|
||||
seit 2025 inkl. Prämien-/Provisionsfelder netto) — AP8.
|
||||
@@ -13,3 +13,4 @@ from . import fristen_private
|
||||
from . import buchungsbeleg
|
||||
from . import wien_dag
|
||||
from . import efzg
|
||||
from . import mutterschutz
|
||||
@@ -214,11 +214,11 @@ class HrPayslip(models.Model):
|
||||
basis = self._l10n_at_sv_basis_roh(result_rules)
|
||||
if pflichtig_ab is None or pflichtig_ab <= self.date_from:
|
||||
# AP4: fiktive BG während des Krankengeldbezugs (§ 7 Abs 3
|
||||
# BMSVG) — 50 % des Vormonatsentgelts aliquot nach
|
||||
# Kalendertagen der Halb-/ÖGK-Phase; nur in beitrags-
|
||||
# pflichtigen Läufen (Erstfenster-Teilmonate: dokumentierter
|
||||
# Randfall).
|
||||
return basis + self._at_bv_fiktiv_krankengeld()
|
||||
# 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
|
||||
|
||||
@@ -233,10 +233,13 @@ class HrPayslip(models.Model):
|
||||
def _at_efz_tagesatz(self, result_rules=None):
|
||||
"""Voller Tages-Entgeltsatz des Monats (Entgeltausfalls-
|
||||
prinzip): laufende Monatsbasis ohne die eigenen
|
||||
Teilentgelt-Reduktionen ÷ Arbeitstage des Kalendermonats."""
|
||||
Teilentgelt-Reduktionen ÷ Arbeitstage des Kalendermonats.
|
||||
Geteilter Helper von EFZG (TEIL_ENTGELT_HALB,
|
||||
KRANKENTGELT_PHASE) und Mutterschutz (MSCH_ENTGELT, AP7)."""
|
||||
self.ensure_one()
|
||||
basis = self._l10n_at_sv_basis_roh(result_rules)
|
||||
for code in ('TEIL_ENTGELT_HALB', 'KRANKENTGELT_PHASE'):
|
||||
for code in ('TEIL_ENTGELT_HALB', 'KRANKENTGELT_PHASE',
|
||||
'MSCH_ENTGELT'):
|
||||
if result_rules is not None and code in result_rules \
|
||||
and result_rules[code]['total']:
|
||||
basis -= result_rules[code]['total']
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
# 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
|
||||
@@ -10,3 +10,4 @@ from . import test_reports_private
|
||||
from . import test_wien_dag_private
|
||||
from . import test_einstufung_private
|
||||
from . import test_efzg_private
|
||||
from . import test_mutterschutz_private
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP7: Mutterschutz/Wochengeld — Entgelt-Stopp während der
|
||||
Beschäftigungsverbote, volle Wochengeld-Berechnung (§ 162 Abs 3
|
||||
ASVG) als AEB-Datenlieferant, fiktive BV-Grundlage (§ 7 Abs 4
|
||||
BMSVG). Rechenfälle: docs/rechenfaelle/mutterschutz.md."""
|
||||
import datetime
|
||||
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class MutterschutzPrivateTest(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.company = self.env.company
|
||||
self.structure = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_at_privat')
|
||||
self.kv2203 = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv_si_2203')
|
||||
self.gruppe_ii = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv2203_grp_ii')
|
||||
self.calendar = self.env['resource.calendar'].create({
|
||||
'name': 'AT MSchG 40h',
|
||||
'tz': 'Europe/Vienna',
|
||||
'hours_per_day': 8,
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Tag %d' % wd, 'dayofweek': str(wd),
|
||||
'hour_from': 8.0, 'hour_to': 16.0})
|
||||
for wd in range(5)
|
||||
],
|
||||
})
|
||||
self.company.resource_calendar_id = self.calendar
|
||||
self.msch_typ = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.leave_type_at_mutterschutz')
|
||||
|
||||
def _employee(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Mutterschutz Test', 'sex': 'female'})
|
||||
version = self.env['hr.version'].search(
|
||||
[('employee_id', '=', employee.id)], limit=1)
|
||||
version.write({
|
||||
'contract_date_start': datetime.date(2026, 7, 1),
|
||||
'kv_id': self.kv2203.id,
|
||||
'kv_gruppe_id': self.gruppe_ii.id,
|
||||
'erfahrungsstufe': 1,
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
})
|
||||
version.generate_work_entries(
|
||||
datetime.date(2026, 8, 1), datetime.date(2026, 12, 31))
|
||||
return employee
|
||||
|
||||
def _slip(self, employee, monat):
|
||||
erster = datetime.date(2026, monat, 1)
|
||||
letzter = (erster.replace(day=28)
|
||||
+ datetime.timedelta(days=4)).replace(day=1) \
|
||||
- datetime.timedelta(days=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'MSchG %d/2026' % monat,
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': erster,
|
||||
'date_to': letzter,
|
||||
})
|
||||
slip.compute_sheet()
|
||||
slip.action_payslip_done()
|
||||
return slip
|
||||
|
||||
def _total(self, slip, code):
|
||||
return sum(slip.line_ids.filtered(
|
||||
lambda l: l.code == code).mapped('total'))
|
||||
|
||||
def _mutterschutz(self, employee, von, bis):
|
||||
leave = self.env['hr.leave'].create({
|
||||
'name': 'Mutterschutz %s' % von,
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': self.msch_typ.id,
|
||||
'request_date_from': von,
|
||||
'request_date_to': bis,
|
||||
})
|
||||
leave.with_user(SUPERUSER_ID)._action_validate()
|
||||
|
||||
def test_01_entgelt_stopp_und_fiktive_bv(self):
|
||||
"""RF-MS-1: Mutterschutz-Frist 1.-15. 11. (15 KT, 10 AT) —
|
||||
MSCH_ENTGELT = -Tagesatz x 10 AT; fiktive BV-Grundlage =
|
||||
Ø-Monatsentgelt Aug-Okt x 15/30 (§ 7 Abs 4 BMSVG)."""
|
||||
employee = self._employee()
|
||||
referenz_slips = [self._slip(employee, m) for m in (8, 9, 10)]
|
||||
self._mutterschutz(
|
||||
employee, datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 15))
|
||||
slip_nov = self._slip(employee, 11)
|
||||
msch = slip_nov._at_msch_status()
|
||||
self.assertEqual(msch['episode_beginn'],
|
||||
datetime.date(2026, 11, 1))
|
||||
self.assertEqual(len(msch['kalendertage']), 15)
|
||||
self.assertEqual(len(msch['arbeitstage']), 10)
|
||||
tagesatz = 2059.41 / 21
|
||||
self.assertAlmostEqual(self._total(slip_nov, 'MSCH_ENTGELT'),
|
||||
-tagesatz * 10, places=2)
|
||||
# Fiktive BV: Referenzmonate Aug/Sep/Okt, je 2.059,41 laufend
|
||||
fiktiv = slip_nov._at_bv_fiktiv_wochengeld()
|
||||
self.assertAlmostEqual(fiktiv, 2059.41 * 15 / 30.0, places=2)
|
||||
basis = slip_nov._at_bv_basis_laufen()
|
||||
laufend = slip_nov._l10n_at_sv_basis_roh()
|
||||
self.assertAlmostEqual(basis, laufend + fiktiv, places=2)
|
||||
|
||||
def test_02_wochengeld_berechnung(self):
|
||||
"""RF-MS-2: § 162 Abs 3 ASVG — Tagesatz = (Summe Netto der
|
||||
drei Referenzmonate x (1 + 17 %)) ÷ Kalendertage (92)."""
|
||||
employee = self._employee()
|
||||
referenz_slips = [self._slip(employee, m) for m in (8, 9, 10)]
|
||||
netto = sum(
|
||||
sum(s.line_ids.filtered(lambda l: l.code == 'NET')
|
||||
.mapped('total')) for s in referenz_slips)
|
||||
self._mutterschutz(
|
||||
employee, datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 15))
|
||||
slip_nov = self._slip(employee, 11)
|
||||
wochengeld = slip_nov._at_wochengeld()
|
||||
self.assertEqual(wochengeld['referenz_monate'], [
|
||||
datetime.date(2026, 8, 1), datetime.date(2026, 9, 1),
|
||||
datetime.date(2026, 10, 1)])
|
||||
self.assertEqual(wochengeld['kalendertage_basis'], 92)
|
||||
self.assertAlmostEqual(wochengeld['netto_drei_monate'],
|
||||
netto, places=2)
|
||||
self.assertAlmostEqual(
|
||||
wochengeld['tagesatz'],
|
||||
netto * 1.17 / 92.0, places=4)
|
||||
|
||||
def test_03_ohne_mutterschutz_keine_wirkung(self):
|
||||
"""Ohne Frist: kein MSCH-Status, keine Reduktion, keine
|
||||
fiktive BV, kein Wochengeld-Ausweis."""
|
||||
employee = self._employee()
|
||||
slip = self._slip(employee, 11)
|
||||
self.assertFalse(slip._at_msch_status())
|
||||
self.assertFalse(self._total(slip, 'MSCH_ENTGELT'))
|
||||
self.assertEqual(slip._at_bv_fiktiv_wochengeld(), 0.0)
|
||||
self.assertEqual(slip._at_wochengeld()['tagesatz'], 0.0)
|
||||
|
||||
def test_04_kurzes_dienstverhaeltnis_fallback(self):
|
||||
"""Ohne Referenzläufe: Wochengeld aus dem laufenden Monat
|
||||
(tatsächlicher Arbeitszeitraum, lb-msf-03)."""
|
||||
employee = self._employee()
|
||||
self._mutterschutz(
|
||||
employee, datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 15))
|
||||
slip_nov = self._slip(employee, 11)
|
||||
wochengeld = slip_nov._at_wochengeld()
|
||||
netto = sum(slip_nov.line_ids.filtered(
|
||||
lambda l: l.code == 'NET').mapped('total'))
|
||||
self.assertAlmostEqual(wochengeld['netto_drei_monate'],
|
||||
netto, places=2)
|
||||
self.assertAlmostEqual(
|
||||
wochengeld['tagesatz'], netto * 1.17 / 30.0, places=4)
|
||||
Reference in New Issue
Block a user