# Part of the odoo-at-payroll project. License: LGPL-3. """AP18: Nachtschwerarbeitsmonat-Zähler (NSchG Art XI Abs 6) — RIS-verifiziert 2026-09-16 (NOR40144608, RIS GesNr 10008502, BGBl 1981/354): - **≥ 6 Arbeitstage** Nachtschwerarbeit im Kalendermonat → Nachtschwer- arbeitsmonat; - **< 6 Tage**: Rückblick-Fenster — Monat + 1 Vormonat ≥ **12** Tage bzw. Monat + 2 Vormonate ≥ **18** Tage; - (DZR > 3 Monate: Monat + 5 Vormonate ≥ 36 Tage — dokumentierter Rest, kein Durchrechnungszeitraum-Datenmodell); - **Randmonat**: die 6 erwarteten Nachtschwerarbeitstage werden nur deswegen nicht erreicht, weil die Arbeit nicht am Monats-Ersten begonnen bzw. am Letzten geendet hat — v1 mit Pro-rata-Proxy (Tage ≥ 6 × belegte Monatskalendertage / Monatskalendertage, aufgerundet) implementiert ⚠ (Arbeitszeiteinteilung der Muster im Parallellauf verifizieren); - Arbeitsunterbrechungen bleiben außer Betracht, solange die PV-Pflicht- versicherung weiterbesteht. Datenbasis: Work Entries des Typs ``ATP_NACHTSCHWER`` (mandantenseitige Klassifikation der Nachtschwerarbeit nach NSchG Art VII Abs 2/4 bzw. ASchV § 1 — die Katalog-Kriterien kann die Engine nicht ableiten). Abgleich mit der E01-Belegung (mBGM-Ergb, AP0/TASY) als Protokoll-Hinweis in der mBGM-Generierung.""" from dateutil.relativedelta import relativedelta from odoo import api, fields, models _NACHTSCHWER_WET = 'ATP_NACHTSCHWER' class HrVersion(models.Model): _inherit = 'hr.version' def _l10n_at_nachtschwer_tage(self, monat_erster, monat_letzter): """Tage mit Nachtschwerarbeit-WETs im (Teil-)Monat.""" self.ensure_one() eintraege = self.env['hr.work.entry'].search([ ('employee_id', '=', self.employee_id.id), ('date', '>=', monat_erster), ('date', '<=', monat_letzter), ('state', '!=', 'cancelled'), ('work_entry_type_id.code', '=', _NACHTSCHWER_WET), ]) return len({eintrag.date for eintrag in eintraege}) class HrPayslip(models.Model): _inherit = 'hr.payslip' l10n_at_nachtschwerarbeitsmonat = fields.Boolean( string='AT Nachtschwerarbeitsmonat', compute='_compute_l10n_at_nachtschwerarbeitsmonat', store=True, help='NSchG Art XI Abs 6 (RIS NOR40144608): ≥ 6 Nachtschwerarbeits-' 'tage im Monat bzw. Rückblick-Fenster (12/18; 36 bei DZR > ' '3 Monate — dokumentierter Rest) bzw. Randmonat (Pro-rata-' 'Proxy ⚠). Grundlage NSchAB 3,80 % (Art XI Abs 3 iVm ' 'Sistierung; TASY-ERGB E01).') @api.depends('date_from', 'date_to', 'version_id') def _compute_l10n_at_nachtschwerarbeitsmonat(self): for slip in self: slip.l10n_at_nachtschwerarbeitsmonat = \ slip._l10n_at_nachtschwerarbeitsmonat(slip.date_from) def _l10n_at_nachtschwerarbeitsmonat(self, stichtag): """Art XI Abs 6: 6/12/18-Fenster + Randmonat-Proxy.""" self.ensure_one() version = self.version_id if not version: return False erster = stichtag.replace(day=1) letzter = (erster + relativedelta(months=1)) \ - relativedelta(days=1) monatstage = (letzter - erster).days + 1 tage = version._l10n_at_nachtschwer_tage(erster, letzter) if tage >= 6: return True # Randmonat (Art XI Abs 6 letzter Satz): die sechs erwarteten # Nachtschwerarbeitstage werden nur deswegen nicht erreicht, # weil die Arbeit nicht am Monats-Ersten begonnen bzw. am # Letzten geendet hat — v1 Pro-rata-Proxy. beginn = max(erster, self.date_from) ende = min(letzter, self.date_to) if (beginn, ende) != (erster, letzter): belegte = (ende - beginn).days + 1 erforderlich = int(-(-6 * belegte // monatstage)) # ceil if tage >= erforderlich: return True # Rückblick-Fenster (12/18): Monat + 1 bzw. + 2 Vormonate. tage_1 = version._l10n_at_nachtschwer_tage( erster - relativedelta(months=1), erster - relativedelta(days=1)) if tage + tage_1 >= 12: return True tage_2 = version._l10n_at_nachtschwer_tage( erster - relativedelta(months=2), erster - relativedelta(days=1)) if tage + tage_1 + tage_2 >= 18: return True return False