mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
[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:
@@ -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