@@ -0,0 +1,347 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo >
<!-- ============================================================ -->
<!-- GP2 Regelinventar AT - Privatwirtschaft (Plan 4.2). -->
<!-- Datenquelle ausschließlich kv.wert (Vorlauf 2026 - 09 - 11 -->
<!-- verifiziert) bzw. Kern - Rule - Parameter — keine Literale in -->
<!-- Regelcode. § - Anker je Code s. RECHTSQUELLEN - Privat 2/3 und -->
<!-- docs/rechenfaelle/. -->
<!-- ============================================================ -->
<!-- KVGEO: KV - Gruppenentgelt § 17 KV (Stufen - Lookup zum Stichtag, -->
<!-- Überzahlung überschreibt, Teilzeit via work_time_rate, -->
<!-- Prorate Ein - /Austritt § 17 Abs 2 als Arbeitstage - Faktor). -->
<record id= "rule_kvgeo" model= "hr.salary.rule" >
<field name= "name" > KV-Gruppenentgelt (§ 17 KV)</field>
<field name= "code" > KVGEO</field>
<field name= "sequence" eval= "1" />
<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" > result = bool(employee.kv_id and employee.kv_gruppe_id and (employee.ueberzahlung or employee.erfahrungsstufe))</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# § 17 KV: Stufen-Mindestgrundgehalt zum Stichtag; individuelle
# Überzahlung überschreibt den Katalogwert (Basis = Vollzeit-
# Katalogwert × work_time_rate). Prorate Ein-/Austritt § 17 Abs 2:
# Arbeitstage-Faktor aus dem Ressourcenkalender (user decision;
# für Mo– Sa-Kalender identisch mit ÷26 × Werktage).
result = payslip._at_kv_gehalt_basis() * payslip._at_arbeitstage_faktor()
]]> </field>
</record>
<!-- NACHT_SV: Sondervergütung Nachtarbeit § 6 KV (22– 6, €/h, -->
<!-- kumulativ zu allen Vergütungen; Höchstzuschlag § 5 Abs 7 -->
<!-- betrifft nur Zuschläge, nicht diese Vergütung). -->
<record id= "rule_nacht_sv" model= "hr.salary.rule" >
<field name= "name" > Nacht-Sondervergütung (§ 6 KV, 22– 6 Uhr)</field>
<field name= "code" > NACHT_SV</field>
<field name= "sequence" eval= "2" />
<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" > result = payslip._at_nacht_sv_stunden() > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# § 6 KV: €-Satz je Nachtstunde (22– 6), unabhängig von Gehalt,
# Werk-/Sonn-/Feiertag und Zuschlagsstatus; Version je date_from
# (2748: 2,71 € ab 2026, 2,76 € ab 2027).
satz = float(employee.kv_id._wert('nacht_sonderverguetung', payslip.date_to))
result = payslip._at_nacht_sv_stunden() * satz
]]> </field>
</record>
<!-- Überstunden § 5 KV: Grundvergütung + Zuschlag; Basis = -->
<!-- Monatsgehalt ÷ Teilungsfaktor (2203: 150; 2748: 143). -->
<record id= "rule_ue_std" model= "hr.salary.rule" >
<field name= "name" > Überstunden Tag (§ 5 Abs 2 KV)</field>
<field name= "code" > UE_STD</field>
<field name= "sequence" eval= "10" />
<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" > result = 'ATP_UE_STD' in worked_days and worked_days['ATP_UE_STD'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# Grundstunde ÷ Teilungsfaktor ÜStd (§ 5 Abs 6 KV) + Zuschlag
# Tag (kv.wert ue_zuschlag_tag). Höchstzuschlag § 5 Abs 7 über
# die WET-Klassifikation (je Stunde genau ein Zuschlagstyp).
grund = payslip._at_grundstunde_ue()
zuschlag = float(employee.kv_id._wert('ue_zuschlag_tag', payslip.date_to))
stunden = worked_days['ATP_UE_STD'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<record id= "rule_ue_nacht" model= "hr.salary.rule" >
<field name= "name" > Überstunden Nacht 22– 6 (§ 5 Abs 2 KV)</field>
<field name= "code" > UE_NACHT</field>
<field name= "sequence" eval= "11" />
<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" > result = 'ATP_UE_NACHT' in worked_days and worked_days['ATP_UE_NACHT'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# 100 %-Zuschlag-Fenster 20– 6 (kv.wert ue_fenster_von/bis);
# hier der Anteil 22– 6 (€-Sondervergütung läuft über NACHT_SV).
grund = payslip._at_grundstunde_ue()
zuschlag = float(employee.kv_id._wert('ue_zuschlag_nacht', payslip.date_to))
stunden = worked_days['ATP_UE_NACHT'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<record id= "rule_ue_nacht_frueh" model= "hr.salary.rule" >
<field name= "name" > Überstunden 20– 22 Uhr (§ 5 Abs 2 KV)</field>
<field name= "code" > UE_NACHT_FRUEH</field>
<field name= "sequence" eval= "12" />
<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" > result = 'ATP_UE_NACHT_FRUEH' in worked_days and worked_days['ATP_UE_NACHT_FRUEH'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# Randfenster 20– 22 des 100 %-Zuschlags — keine €-Sondervergütung
# (Abgrenzung RF-Z6: Zuschlag ja, Nacht-SV nein).
grund = payslip._at_grundstunde_ue()
zuschlag = float(employee.kv_id._wert('ue_zuschlag_nacht', payslip.date_to))
stunden = worked_days['ATP_UE_NACHT_FRUEH'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<record id= "rule_ue_sofei" model= "hr.salary.rule" >
<field name= "name" > Sonn-/Feiertagsüberstunden Tag (§ 5 Abs 4/5 KV)</field>
<field name= "code" > UE_SOFEI</field>
<field name= "sequence" eval= "13" />
<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" > result = 'ATP_UE_SOFEI' in worked_days and worked_days['ATP_UE_SOFEI'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# SonntagsüStd § 5 Abs 4, FeiertagsüStd ab erster Stunde § 5 Abs 5
# iVm ARG — Zuschlag je kv.wert so_feiertag_zuschlag.
grund = payslip._at_grundstunde_ue()
zuschlag = float(employee.kv_id._wert('so_feiertag_zuschlag', payslip.date_to))
stunden = worked_days['ATP_UE_SOFEI'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<record id= "rule_ue_sofei_nacht" model= "hr.salary.rule" >
<field name= "name" > Sonn-/Feiertagsüberstunden Nacht 22– 6 (§ 5 Abs 4/5 KV)</field>
<field name= "code" > UE_SOFEI_NACHT</field>
<field name= "sequence" eval= "14" />
<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" > result = 'ATP_UE_SOFEI_NACHT' in worked_days and worked_days['ATP_UE_SOFEI_NACHT'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# Zuschlagskumulation So/Fei × Nacht: nur der höchste Zuschlag
# (§ 5 Abs 7) = 100 % einmal (Höchstzuschlag über WET-Klasse);
# €-Sondervergütung separat über NACHT_SV.
grund = payslip._at_grundstunde_ue()
zuschlag = float(employee.kv_id._wert('so_feiertag_zuschlag', payslip.date_to))
stunden = worked_days['ATP_UE_SOFEI_NACHT'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<!-- Mehrarbeit: Band KV - NAZ→40 h § 4a (nur KVs mit verkürzter -->
<!-- NAZ, z. B. 2748: 38,5→40; Basis ÷ Teilungsfaktor Norm). -->
<record id= "rule_mehr_vz" model= "hr.salary.rule" >
<field name= "name" > Mehrarbeit Band KV-NAZ→40 h (§ 4a KV)</field>
<field name= "code" > MEHR_VZ</field>
<field name= "sequence" eval= "20" />
<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[
result = 'ATP_MEHR_VZ' in worked_days and worked_days['ATP_MEHR_VZ'].number_of_hours > 0 and bool(employee.kv_id._wert('mehrarbeit_zuschlag', payslip.date_to, raise_if_not_found=False))
]]> </field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# § 4a KV: Zuschlag auf die Normalstunde (Basis ÷ Teilungsfaktor
# Normalstunde, 2748: ÷ 167); zählt nicht gegen das ÜStd-Ausmaß;
# Stunden mit > 50 %-Zuschlag sind ÜStd (§ 4a letzter Satz —
# Klassifikation stromauf).
grund = payslip._at_grundstunde_norm()
zuschlag = float(employee.kv_id._wert('mehrarbeit_zuschlag', payslip.date_to))
stunden = worked_days['ATP_MEHR_VZ'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<record id= "rule_mehr_vz_nacht" model= "hr.salary.rule" >
<field name= "name" > Mehrarbeit Band Nacht 22– 6 (§ 4a KV)</field>
<field name= "code" > MEHR_VZ_NACHT</field>
<field name= "sequence" eval= "21" />
<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[
result = 'ATP_MEHR_VZ_NACHT' in worked_days and worked_days['ATP_MEHR_VZ_NACHT'].number_of_hours > 0 and bool(employee.kv_id._wert('mehrarbeit_zuschlag', payslip.date_to, raise_if_not_found=False))
]]> </field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
grund = payslip._at_grundstunde_norm()
zuschlag = float(employee.kv_id._wert('mehrarbeit_zuschlag', payslip.date_to))
stunden = worked_days['ATP_MEHR_VZ_NACHT'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<!-- Mehrarbeit Teilzeit § 19d AZG: gesetzlich 25 % (Kern - -->
<!-- Parameter); KV - Abweichung über kv.wert code -->
<!-- mehrarbeit_teilzeit_zuschlag (user decision: KV vor Gesetz). -->
<record id= "rule_mehr_tz" model= "hr.salary.rule" >
<field name= "name" > Mehrarbeit Teilzeit (§ 19d AZG)</field>
<field name= "code" > MEHR_TZ</field>
<field name= "sequence" eval= "22" />
<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" > result = 'ATP_MEHR_TZ' in worked_days and worked_days['ATP_MEHR_TZ'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# § 19d AZG: Stunden über Teilzeit-Ausmaß bis Vollzeit-NAZ;
# Zuschlag 25 % gesetzlich (Kern-Parameter), KV kann besser
# stellen (kv.wert-Code mehrarbeit_teilzeit_zuschlag).
grund = payslip._at_grundstunde_norm()
kv_zuschlag = employee.kv_id._wert('mehrarbeit_teilzeit_zuschlag', payslip.date_to, raise_if_not_found=False)
zuschlag = float(kv_zuschlag) if kv_zuschlag else float(payslip._rule_parameter('at_mehrarbeit_teilzeit_zuschlag'))
stunden = worked_days['ATP_MEHR_TZ'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<record id= "rule_mehr_tz_nacht" model= "hr.salary.rule" >
<field name= "name" > Mehrarbeit Teilzeit Nacht 22– 6 (§ 19d AZG)</field>
<field name= "code" > MEHR_TZ_NACHT</field>
<field name= "sequence" eval= "23" />
<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" > result = 'ATP_MEHR_TZ_NACHT' in worked_days and worked_days['ATP_MEHR_TZ_NACHT'].number_of_hours > 0</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
grund = payslip._at_grundstunde_norm()
kv_zuschlag = employee.kv_id._wert('mehrarbeit_teilzeit_zuschlag', payslip.date_to, raise_if_not_found=False)
zuschlag = float(kv_zuschlag) if kv_zuschlag else float(payslip._rule_parameter('at_mehrarbeit_teilzeit_zuschlag'))
stunden = worked_days['ATP_MEHR_TZ_NACHT'].number_of_hours
result = stunden * grund * (1 + zuschlag / 100.0)
]]> </field>
</record>
<!-- URL_UE: Ø regelmäßige Überstunden im Urlaubsmonat (§ 2 Abs 2 -->
<!-- GKV iVm § 5 Abs 11 (2203) / Abs 10 (2748): regelmäßig = -->
<!-- ≥ 7 der letzten 12 Kalendermonate; Durchschnitt 12 Monate). -->
<record id= "rule_url_ue" model= "hr.salary.rule" >
<field name= "name" > Urlaubsentgelt: Ø regelmäßige Überstunden (§ 6 UrlG)</field>
<field name= "code" > URL_UE</field>
<field name= "sequence" eval= "30" />
<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[
result = payslip._at_urlaub_ue_avg()[0] and any(wd.work_entry_type_id.is_leave for wd in payslip.worked_days_line_ids)
]]> </field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# § 6 UrlG Abs 2: laufendes (monatsweises) Entgelt bleibt
# unverändert (KVGEO läuft weiter); zusätzlich der monatliche
# Durchschnitt der regelmäßigen ÜStd, anteilig nach
# Urlaubstagen ÷ Arbeitstage des Monats (Konvention RF-U2).
regelmaessig, avg = payslip._at_urlaub_ue_avg()
urlaubstage = sum(wd.number_of_days for wd in payslip.worked_days_line_ids if wd.work_entry_type_id.is_leave)
arbeitstage = payslip._at_arbeitstage(payslip.date_from, payslip.date_to)
result = avg * urlaubstage / arbeitstage if arbeitstage and regelmaessig else 0.0
]]> </field>
</record>
<!-- PRAM: manuelle Prämien/Ausgleichszahlungen (Input). -->
<record id= "rule_pram" model= "hr.salary.rule" >
<field name= "name" > Prämie/Ausgleich (manuell)</field>
<field name= "code" > PRAM</field>
<field name= "sequence" eval= "40" />
<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" > result = 'ATP_PRAMIE' in inputs and inputs['ATP_PRAMIE'].amount</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
result = inputs['ATP_PRAMIE'].amount
]]> </field>
</record>
<!-- B13/B14: 13./14. Bezug § 11 KV — begünstigte Bezüge iSd -->
<!-- § 67 EStG (Markierung/Sechstel - Engine folgt GP3 im Kern). -->
<record id= "rule_b13" model= "hr.salary.rule" >
<field name= "name" > 13. Bezug (§ 11 KV)</field>
<field name= "code" > B13</field>
<field name= "sequence" eval= "50" />
<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" > result = payslip._at_b13_faellig()</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# Basis je kv.wert sz13_basis_monat: 'auszahlung' (2203) =
# Monatsgehalt des Auszahlungsmonats; 'november' (2748) =
# November-Gehalt (§ 11 Abs 2). Aliquote Ein-/Austritt § 11
# Abs 4 über sz_aliquote_methode ('kalendertage': KT/365;
# 'monate': volle Monate/12); Mischberechnung Beschäftigungs-
# ausmaß (2203) über die Jahres-Versionen.
basis_datum = payslip._at_sz_basis_datum('sz13_basis_monat')
result = payslip._at_sz_basis(basis_datum) * payslip._at_sz_dienstzeit_faktor()
]]> </field>
</record>
<record id= "rule_b14" model= "hr.salary.rule" >
<field name= "name" > 14. Bezug / Urlaubszuschuss (§ 11 KV)</field>
<field name= "code" > B14</field>
<field name= "sequence" eval= "51" />
<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" > result = payslip._at_b14_faellig()</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# Fälligkeit § 11 Abs 3: bei Urlaubsantritt (R1: erstfälliger
# Urlaubsmonat, längster-Teil-Verfeinerung dokumentiert), ohne
# Urlaub mit der Dezember-Abrechnung (sz14_no_urlaub_monat),
# bei Austritt aliquote Endabrechnung; ohne KV-Code Fallback
# Juni (user decision). Basis = Auszahlungsmonat-Gehalt.
result = payslip._at_sz_basis(payslip.date_to) * payslip._at_sz_dienstzeit_faktor()
]]> </field>
</record>
<record id= "rule_b14_rueck" model= "hr.salary.rule" >
<field name= "name" > 14. Bezug Rückverrechnung bei Austritt (§ 11 Abs 4 KV)</field>
<field name= "code" > B14_RUECK</field>
<field name= "sequence" eval= "52" />
<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[
austritt = version.contract_date_end and payslip.date_from <= version.contract_date_end <= payslip.date_to
result = bool(austritt and payslip._at_sz_bereits_bezahlt('B14'))
]]> </field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
# § 11 Abs 4, 2. Satz: bei Austritt nach vollem B14-Bezug ist
# der anteilige Überbezug in der Endabrechnung abzuziehen.
bezahlt = payslip._at_sz_bereits_bezahlt('B14')
result = bezahlt * (payslip._at_sz_dienstzeit_faktor() - 1.0)
]]> </field>
</record>
<!-- NETTO (Kategoriensummen; DN - Abzüge folgen GP3/GP4). -->
<record id= "rule_netto" model= "hr.salary.rule" >
<field name= "name" > Netto</field>
<field name= "code" > NETTO</field>
<field name= "sequence" eval= "100" />
<field name= "category_id" ref= "l10n_at_hr_payroll_private.category_atp_netto" />
<field name= "struct_id" ref= "l10n_at_hr_payroll_private.structure_at_privat" />
<field name= "condition_select" > none</field>
<field name= "amount_select" > code</field>
<field name= "amount_python_compute" > <![CDATA[
result = categories['ATP_BRUTTO'] - categories['ATP_ABZUG']
]]> </field>
</record>
</odoo>