[IMP] l10n_at_gemeinde_payroll: implement AP2 pay computation engine (GemBG Bgld.)
Entgelt-Engine nach Implementierungsplan AP2 (freigegeben 2026-09-09):
- Structure Type "Bgld. GemBG 2014" + Structure "Gemeindebedienstete"
mit 4 Kategorien (GEMBG_BRUTTO, GEMBG_ABZUG, GEMBG_NETTO, GEMBG_DG).
- Work-Entry-Types: Überstunden Tag/Nacht (§ 76), Mehrdienstleistung,
Journaldienst (§ 79), Bereitschaft (§ 80), Sonn-/Feiertag (§ 78).
- Input-Types: Erschwerniszulage (§ 83), Fahrtkostenzuschuss (§ 88),
Jubiläumszuwendung (§ 89).
- Entgelt-Regeln (13 Rules):
* ME — Monatsentgelt: Katalog-Lookup (Gruppe+Stufe+Stichtag),
Ausbildungsphase 5 % Abzug (§ 133h), Teilzeit via work_time_rate.
* KIZU — Kinderzulage: kinder_anzahl × 15,60 EUR (§ 61 GemBG),
bei Teilzeit UNGEKÜRZT (§ 59 Abs 2).
* FUZU — Funktionszulage: hr.version-Feld × work_time_rate (§ 62).
* UEZ_TAG/UEZ_NACHT — Überstundenvergütung (§ 76):
Grund = ME / (4,33 × Wochenstunden); Zuschlag 50 % Tag / 100 % Nacht.
* JZU — Journaldienstzulage (§ 79): Work-Entry × Parametersatz.
* SFZU — Sonn-/Feiertagszulage (§ 78): Stunden × Grundvergütung × %.
* BERB — Bereitschaftsentschädigung (§ 80): Stunden × Parametersatz.
* ERZU/FKZ/JUB — Input-basierte Bezüge (§§ 83/88/89).
* SZQ — Sonderzahlung QUARTALSWEISE (§ 133n): in Quartalsendmonaten
(März, Juni, September, Dezember) 50 % × (ME + KIZU).
* NETTO — Kategoriensumme (wird mit AP3/AP4 erweitert).
- hr.version-Felder: kinder_anzahl, funktionszulage, ausbildungsphase
(§ 61/§ 62/§ 133h) mit Employee-Spiegel nach Engine-Muster.
- Rule-Parameter: Kinderzulage 15,60 EUR, Journaldienstzulage,
Sonn-/Feiertags-%, Bereitschaftsstunde (letzte 3 als AP0-Platzhalter
mit 0.0 — Werte aus der Bezügeanpassung nach Beschaffung).
- Tests (8 Rechenfälle): Vollzeit av1 Stufe 1, Kinderzulage 2 Kinder
= 31,20 EUR, Funktionszulage, Ausbildungsphase 5 % Abzug,
SZ Quartalsende + Nicht-Quartalsende, Netto=Brutto (AP3/AP4 offen),
Stufe 12 Max.
- Employee-Form um Zulagen-Gruppe erweitert.
Modul-Version 19.0.1.3.0. Statische Validierung grün; Runtime-Testlauf
auf dem Dev-Host ausständig.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
{
|
||||
'name': 'Österreich - Gemeinde-Personalverrechnung (Bgld. GemBG)',
|
||||
'version': '19.0.1.2.0',
|
||||
'version': '19.0.1.3.0',
|
||||
'author': 'ixsol / gem360',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -52,7 +52,10 @@ Grundlagen: ``personalverrechnung/PLAN-payroll-bgld.md``,
|
||||
'data/gemeinde.payroll.entlohnungsgruppe.csv',
|
||||
'data/gemeinde.payroll.entlohnungsstufe.csv',
|
||||
'data/rule_parameters_2026.xml',
|
||||
'data/rule_parameters_entgelt.xml',
|
||||
'data/tasy_mapping.xml',
|
||||
'data/payroll_structure_bgld.xml',
|
||||
'data/salary_rules_bgld_entgelt.xml',
|
||||
'views/entlohnung_views.xml',
|
||||
'views/hr_version_bgld_views.xml',
|
||||
'views/tasy_views.xml',
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Structure Type: Bgld. GemBG -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="structure_type_gembg_bgld" model="hr.payroll.structure.type">
|
||||
<field name="name">Bgld. GemBG 2014</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="default_schedule_pay">monthly</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Structure: Gemeindebedienstete Bgld. -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="structure_gembg_bgld" model="hr.payroll.structure">
|
||||
<field name="name">Gemeindebedienstete (Bgld. GemBG 2014)</field>
|
||||
<field name="code">GEMBG.BGLD</field>
|
||||
<field name="type_id" ref="structure_type_gembg_bgld"/>
|
||||
<field name="regular_pay" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Kategorien -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="category_gembg_brutto" model="hr.salary.rule.category">
|
||||
<field name="name">GemBG Brutto (Entgelt)</field>
|
||||
<field name="code">GEMBG_BRUTTO</field>
|
||||
</record>
|
||||
<record id="category_gembg_abzug" model="hr.salary.rule.category">
|
||||
<field name="name">GemBG Abzüge (LSt, SV)</field>
|
||||
<field name="code">GEMBG_ABZUG</field>
|
||||
</record>
|
||||
<record id="category_gembg_netto" model="hr.salary.rule.category">
|
||||
<field name="name">GemBG Netto</field>
|
||||
<field name="code">GEMBG_NETTO</field>
|
||||
</record>
|
||||
<record id="category_gembg_dg" model="hr.salary.rule.category">
|
||||
<field name="name">GemBG Arbeitgeberanteile</field>
|
||||
<field name="code">GEMBG_DG</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Work-Entry-Types für GemBG-Komponenten -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="work_entry_type_ueberstunden_tag" model="hr.work.entry.type">
|
||||
<field name="name">Überstunden (tagsüber, § 76 GemBG)</field>
|
||||
<field name="code">GEMBG_UEB_TAG</field>
|
||||
<field name="round_days">NO</field>
|
||||
<field name="round_days_type">DOWN</field>
|
||||
<field name="is_paid" eval="True"/>
|
||||
</record>
|
||||
<record id="work_entry_type_ueberstunden_nacht" model="hr.work.entry.type">
|
||||
<field name="name">Überstunden (Nachtzeit 22-6 Uhr, § 76 GemBG)</field>
|
||||
<field name="code">GEMBG_UEB_NACHT</field>
|
||||
<field name="round_days">NO</field>
|
||||
<field name="round_days_type">DOWN</field>
|
||||
<field name="is_paid" eval="True"/>
|
||||
</record>
|
||||
<record id="work_entry_type_mehrdienst" model="hr.work.entry.type">
|
||||
<field name="name">Mehrdienstleistung (§ 76 GemBG)</field>
|
||||
<field name="code">GEMBG_MEHR</field>
|
||||
<field name="round_days">NO</field>
|
||||
<field name="round_days_type">DOWN</field>
|
||||
<field name="is_paid" eval="True"/>
|
||||
</record>
|
||||
<record id="work_entry_type_journaldienst" model="hr.work.entry.type">
|
||||
<field name="name">Journaldienst (§ 79 GemBG)</field>
|
||||
<field name="code">GEMBG_JOURNAL</field>
|
||||
<field name="round_days">NO</field>
|
||||
<field name="round_days_type">DOWN</field>
|
||||
<field name="is_paid" eval="True"/>
|
||||
</record>
|
||||
<record id="work_entry_type_bereitschaft" model="hr.work.entry.type">
|
||||
<field name="name">Bereitschaftsdienst (§ 80 GemBG)</field>
|
||||
<field name="code">GEMBG_BEREIT</field>
|
||||
<field name="round_days">NO</field>
|
||||
<field name="round_days_type">DOWN</field>
|
||||
<field name="is_paid" eval="True"/>
|
||||
</record>
|
||||
<record id="work_entry_type_sonn_feiertag" model="hr.work.entry.type">
|
||||
<field name="name">Sonn-/Feiertagsdienst (§ 78 GemBG)</field>
|
||||
<field name="code">GEMBG_SONNFEI</field>
|
||||
<field name="round_days">NO</field>
|
||||
<field name="round_days_type">DOWN</field>
|
||||
<field name="is_paid" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Input-Types für manuelle Zulagen -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="input_type_funktionszulage" model="hr.payslip.input.type">
|
||||
<field name="name">Funktionszulage Überschreibung (§ 62)</field>
|
||||
<field name="code">FUZU_INPUT</field>
|
||||
</record>
|
||||
<record id="input_type_erschwerniszulage" model="hr.payslip.input.type">
|
||||
<field name="name">Erschwerniszulage (§ 83 GemBG)</field>
|
||||
<field name="code">ERZU_INPUT</field>
|
||||
</record>
|
||||
<record id="input_type_fahrtkosten" model="hr.payslip.input.type">
|
||||
<field name="name">Fahrtkostenzuschuss (§ 88 GemBG)</field>
|
||||
<field name="code">FKZ_INPUT</field>
|
||||
</record>
|
||||
<record id="input_type_jubilaeum" model="hr.payslip.input.type">
|
||||
<field name="name">Jubiläumszuwendung (§ 89 GemBG)</field>
|
||||
<field name="code">JUB_INPUT</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Entgelt-Parameter aus GemBG 2014 (konsolidiert 09.09.2026) -->
|
||||
|
||||
<!-- Kinderzulage § 61 GemBG: 15,60 EUR je Kind monatlich -->
|
||||
<record id="hr_rule_parameter_gembg_kinderzulage_rate" model="hr.rule.parameter">
|
||||
<field name="name">GemBG Kinderzulage je Kind (monatlich, § 61)</field>
|
||||
<field name="code">gembg_kinderzulage_rate</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_gembg_kinderzulage_rate" model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_gembg_kinderzulage_rate"/>
|
||||
<field name="date_from">2019-01-01</field>
|
||||
<field name="parameter_value">15.60</field>
|
||||
</record>
|
||||
|
||||
<!-- Journaldienstzulage § 79 GemBG — pro Journaldienst-Tag -->
|
||||
<record id="hr_rule_parameter_gembg_journaldienstzulage" model="hr.rule.parameter">
|
||||
<field name="name">GemBG Journaldienstzulage je Werktag (§ 79)</field>
|
||||
<field name="code">gembg_journaldienstzulage</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_gembg_journaldienstzulage" model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_gembg_journaldienstzulage"/>
|
||||
<field name="date_from">2019-01-01</field>
|
||||
<field name="parameter_value">0.0</field>
|
||||
</record>
|
||||
|
||||
<!-- Sonn-/Feiertagszulage § 78 GemBG — Prozentsatz auf Grundvergütung -->
|
||||
<record id="hr_rule_parameter_gembg_sonnfeiertags_prozent" model="hr.rule.parameter">
|
||||
<field name="name">GemBG Sonn-/Feiertagszulage Prozentsatz (§ 78)</field>
|
||||
<field name="code">gembg_sonnfeiertags_prozent</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_gembg_sonnfeiertags_prozent" model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_gembg_sonnfeiertags_prozent"/>
|
||||
<field name="date_from">2019-01-01</field>
|
||||
<field name="parameter_value">0.0</field>
|
||||
</record>
|
||||
|
||||
<!-- Bereitschaftsentschädigung § 80 GemBG — je Bereitschaftsstunde -->
|
||||
<record id="hr_rule_parameter_gembg_bereitschaftsstunde" model="hr.rule.parameter">
|
||||
<field name="name">GemBG Bereitschaftsentschädigung je Stunde (§ 80)</field>
|
||||
<field name="code">gembg_bereitschaftsstunde</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_gembg_bereitschaftsstunde" model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_gembg_bereitschaftsstunde"/>
|
||||
<field name="date_from">2019-01-01</field>
|
||||
<field name="parameter_value">0.0</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,290 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Monatsentgelt (§ 55 GemBG / IVa §§ 133 ff) -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_me" model="hr.salary.rule">
|
||||
<field name="name">Monatsentgelt</field>
|
||||
<field name="code">ME</field>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = bool(employee.entlohnungsgruppe_id and employee.entlohnungsstufe)</field>
|
||||
<field name="amount_select">python</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Monatsentgelt nach Bgld. GemBG 2014 (IVa. Hauptstück §§ 133 ff).
|
||||
# Katalog-Lookup: Entlohnungsgruppe + Stufe + Stichtag.
|
||||
katalog_pos = employee.entlohnungsgruppe_id._get_entgelt(
|
||||
employee.entlohnungsstufe, payslip.date_to)
|
||||
if not katalog_pos:
|
||||
result = 0
|
||||
else:
|
||||
base = katalog_pos.monatsentgelt
|
||||
# Ausbildungsphase: 5 % Abzug (§ 133h GemBG)
|
||||
if employee.ausbildungsphase:
|
||||
base *= 0.95
|
||||
# Teilzeit: anteilig über work_time_rate (§ 59 GemBG)
|
||||
work_rate = employee.work_time_rate or 100.0
|
||||
result = base * work_rate / 100.0
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Kinderzulage (§ 61 GemBG: 15,60 EUR je Kind, ungekürzt) -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_kizu" model="hr.salary.rule">
|
||||
<field name="name">Kinderzulage (§ 61 GemBG)</field>
|
||||
<field name="code">KIZU</field>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = employee.kinder_anzahl > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Kinderzulage § 61 GemBG: 15,60 EUR je Kind monatlich.
|
||||
# Bei Teilzeit UNGEKÜRZT (§ 59 Abs 2: "die Kinderzulage gebührt
|
||||
# jedoch in ungekürzter Höhe").
|
||||
kiz_satz = payslip._rule_parameter('gembg_kinderzulage_rate')
|
||||
result = employee.kinder_anzahl * kiz_satz
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Funktionszulage (§ 62 GemBG) -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_fuzu" model="hr.salary.rule">
|
||||
<field name="name">Funktionszulage (§ 62 GemBG)</field>
|
||||
<field name="code">FUZU</field>
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = employee.funktionszulage > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Funktionszulage § 62 GemBG — Satz am Dienstvertrag (hr.version).
|
||||
# Bei Teilzeit anteilig (wie Monatsentgelt).
|
||||
work_rate = employee.work_time_rate or 100.0
|
||||
result = employee.funktionszulage * work_rate / 100.0
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Überstundenvergütung (§ 76 GemBG) -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_uez_tag" model="hr.salary.rule">
|
||||
<field name="name">Überstundenvergütung tagsüber (§ 76 GemBG)</field>
|
||||
<field name="code">UEZ_TAG</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'GEMBG_UEB_TAG' in worked_days and worked_days['GEMBG_UEB_TAG'].number_of_hours > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# § 76 GemBG: Grundvergütung + 50 % Zuschlag (tagsüber).
|
||||
# Grundvergütung/ÜStd = Monatsentgelt / (4,33 × Wochenstundenzahl)
|
||||
base_entgelt = rules['ME'].total
|
||||
if base_entgelt and employee.entlohnungsgruppe_id:
|
||||
wochenstunden = employee.hours_per_week or 40.0
|
||||
grund_pro_uestd = base_entgelt / (4.33 * wochenstunden)
|
||||
ueb_stunden = worked_days['GEMBG_UEB_TAG'].number_of_hours
|
||||
result = ueb_stunden * grund_pro_uestd * 1.5 # 100 % + 50 % Zuschlag
|
||||
else:
|
||||
result = 0
|
||||
]]></field>
|
||||
</record>
|
||||
<record id="rule_uez_nacht" model="hr.salary.rule">
|
||||
<field name="name">Überstundenvergütung Nachtzeit (§ 76 GemBG)</field>
|
||||
<field name="code">UEZ_NACHT</field>
|
||||
<field name="sequence" eval="11"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'GEMBG_UEB_NACHT' in worked_days and worked_days['GEMBG_UEB_NACHT'].number_of_hours > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# § 76 GemBG: Grundvergütung + 100 % Zuschlag (Nachtzeit 22-6 Uhr).
|
||||
base_entgelt = rules['ME'].total
|
||||
if base_entgelt and employee.entlohnungsgruppe_id:
|
||||
wochenstunden = employee.hours_per_week or 40.0
|
||||
grund_pro_uestd = base_entgelt / (4.33 * wochenstunden)
|
||||
ueb_stunden = worked_days['GEMBG_UEB_NACHT'].number_of_hours
|
||||
result = ueb_stunden * grund_pro_uestd * 2.0 # 100 % + 100 % Zuschlag
|
||||
else:
|
||||
result = 0
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Journaldienstzulage (§ 79 GemBG) — Input -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_jzu" model="hr.salary.rule">
|
||||
<field name="name">Journaldienstzulage (§ 79 GemBG)</field>
|
||||
<field name="code">JZU</field>
|
||||
<field name="sequence" eval="12"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'GEMBG_JOURNAL' in worked_days and worked_days['GEMBG_JOURNAL'].number_of_hours > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Journaldienstzulage § 79 GemBG: pro Journaldienst (Werktag).
|
||||
# Pauschaler Betrag je Journaldienst — Parameter aus Bezügeanpassung.
|
||||
satz = payslip._rule_parameter('gembg_journaldienstzulage')
|
||||
tage = worked_days['GEMBG_JOURNAL'].number_of_days or 0
|
||||
result = tage * satz
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Sonn-/Feiertagszulage (§ 78 GemBG) — Input -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_sfzu" model="hr.salary.rule">
|
||||
<field name="name">Sonn-/Feiertagszulage (§ 78 GemBG)</field>
|
||||
<field name="code">SFZU</field>
|
||||
<field name="sequence" eval="13"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'GEMBG_SONNFEI' in worked_days and worked_days['GEMBG_SONNFEI'].number_of_hours > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Sonn-/Feiertagszulage § 78 GemBG: Zuschlag auf Grundvergütung.
|
||||
# Der Prozentsatz steht in der Bezügeanpassung (Parameter).
|
||||
# R1: Pauschal je Stunde — Detailabbildung nach Bestandsaufnahme.
|
||||
base_entgelt = rules['ME'].total
|
||||
if base_entgelt:
|
||||
wochenstunden = employee.hours_per_week or 40.0
|
||||
grund_pro_stunde = base_entgelt / (4.33 * wochenstunden)
|
||||
stunden = worked_days['GEMBG_SONNFEI'].number_of_hours
|
||||
satz = payslip._rule_parameter('gembg_sonnfeiertags_prozent') / 100.0
|
||||
result = stunden * grund_pro_stunde * satz
|
||||
else:
|
||||
result = 0
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Bereitschaftsentschädigung (§ 80 GemBG) — Input -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_berb" model="hr.salary.rule">
|
||||
<field name="name">Bereitschaftsentschädigung (§ 80 GemBG)</field>
|
||||
<field name="code">BERB</field>
|
||||
<field name="sequence" eval="14"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'GEMBG_BEREIT' in worked_days and worked_days['GEMBG_BEREIT'].number_of_hours > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Bereitschaftsentschädigung § 80 GemBG.
|
||||
# Pauschaler Betrag je Bereitschaftsstunde — Parameter.
|
||||
satz = payslip._rule_parameter('gembg_bereitschaftsstunde')
|
||||
stunden = worked_days['GEMBG_BEREIT'].number_of_hours
|
||||
result = stunden * satz
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Erschwerniszulage (§ 83 GemBG) — Input -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_erzu" model="hr.salary.rule">
|
||||
<field name="name">Erschwerniszulage (§ 83 GemBG)</field>
|
||||
<field name="code">ERZU</field>
|
||||
<field name="sequence" eval="15"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'ERZU_INPUT' in inputs and inputs['ERZU_INPUT'].amount > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = inputs['ERZU_INPUT'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Jubiläumszuwendung (§ 89 GemBG) — Input -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_jub" model="hr.salary.rule">
|
||||
<field name="name">Jubiläumszuwendung (§ 89 GemBG)</field>
|
||||
<field name="code">JUB</field>
|
||||
<field name="sequence" eval="16"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'JUB_INPUT' in inputs and inputs['JUB_INPUT'].amount > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = inputs['JUB_INPUT'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Fahrtkostenzuschuss (§ 88 GemBG) — Input -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_fkz" model="hr.salary.rule">
|
||||
<field name="name">Fahrtkostenzuschuss (§ 88 GemBG)</field>
|
||||
<field name="code">FKZ</field>
|
||||
<field name="sequence" eval="17"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'FKZ_INPUT' in inputs and inputs['FKZ_INPUT'].amount > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = inputs['FKZ_INPUT'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Sonderzahlung (§ 133n GemBG: 50 % je Kalendervierteljahr) -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_szq" model="hr.salary.rule">
|
||||
<field name="name">Sonderzahlung (quartalsweise, § 133n GemBG)</field>
|
||||
<field name="code">SZQ</field>
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="category_id" ref="payroll_structure_bgld.category_gembg_brutto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python"><![CDATA[
|
||||
# § 133n GemBG: 50 % des Monatsentgelts (und der Kinderzulage)
|
||||
# für jedes Kalendervierteljahr. Auszahlung in den Monaten am
|
||||
# Quartalsende (März, Juni, September, Dezember).
|
||||
# Der Monat der Auszahlung ist der Monat, für den das Entgelt
|
||||
# gebührt (§ 56 Abs 2 GemBG: 15.3., 15.6., 15.9., 15.12.).
|
||||
import datetime
|
||||
month = payslip.date_to.month
|
||||
result = month in (3, 6, 9, 12)
|
||||
]]></field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# 50 % des Monatsentgelts + Kinderzulage des AUSZAHLUNGSMONATS
|
||||
# (§ 133n: "in der Höhe von 50% des Monatsentgelts (und der
|
||||
# Kinderzulage), die ihnen für den Monat der Auszahlung zustehen").
|
||||
# Bei unterbrochenem Vierteljahr: nur der entsprechende Teil
|
||||
# (R1: Vollzeit-Rechnung; Anteil folgt mit der Zeit-Erfassung).
|
||||
me_total = rules['ME'].total or 0
|
||||
kizu_total = rules['KIZU'].total or 0
|
||||
result = (me_total + kizu_total) * 0.5
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- NETTO (Kategoriensumme; wird mit AP3/AP4 erweitert) -->
|
||||
<!-- ============================================================ -->
|
||||
<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="payroll_structure_bgld.category_gembg_netto"/>
|
||||
<field name="struct_id" ref="payroll_structure_bgld.structure_gembg_bgld"/>
|
||||
<field name="condition_select">none</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = categories.GEMBG_BRUTTO - categories.GEMBG_ABZUG
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,4 +1,5 @@
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
from . import entlohnung
|
||||
from . import hr_version_bgld
|
||||
from . import hr_version_entgelt
|
||||
from . import tasy
|
||||
@@ -0,0 +1,48 @@
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrVersion(models.Model):
|
||||
"""GemBG AP2: Zulagen-Felder auf der Payroll-Version.
|
||||
|
||||
Funktionszulage § 62 GemBG (Sätze aus der Bezügeanpassung, am
|
||||
Dienstvertrag anwendbar); Kinderzahl § 61 GemBG für die Kinderzulage
|
||||
(15,60 EUR je Kind monatlich, bei Teilzeit ungekürzt).
|
||||
"""
|
||||
_inherit = 'hr.version'
|
||||
|
||||
funktionszulage = fields.Monetary(
|
||||
string='Funktionszulage (§ 62 GemBG)',
|
||||
currency_field='currency_id',
|
||||
groups="hr_payroll.group_hr_payroll_user", tracking=True,
|
||||
help="Monatliche Funktionszulage gemäß § 62 Abs 1 Bgld. GemBG "
|
||||
"(Wert aus der aktuellen Bezügeanpassung).")
|
||||
kinder_anzahl = fields.Integer(
|
||||
string='Anzahl Kinder (Kinderzulage § 61)',
|
||||
default=0,
|
||||
groups="hr_payroll.group_hr_payroll_user",
|
||||
help="Anzahl der Kinder, für die Familienbeihilfe bezogen wird "
|
||||
"und Kinderzulage gebührt (§ 61 GemBG).")
|
||||
ausbildungsphase = fields.Boolean(
|
||||
string='In Ausbildungsphase (§ 133h)',
|
||||
default=False,
|
||||
groups="hr_payroll.group_hr_payroll_user",
|
||||
help="Während der Ausbildungsphase wird das Monatsentgelt um "
|
||||
"5 % vermindert (§ 133h GemBG).")
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
related='employee_id.company_id.currency_id', store=True)
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
funktionszulage = fields.Monetary(
|
||||
related="version_id.funktionszulage", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
kinder_anzahl = fields.Integer(
|
||||
related="version_id.kinder_anzahl", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
ausbildungsphase = fields.Boolean(
|
||||
related="version_id.ausbildungsphase", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
@@ -1,3 +1,4 @@
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
from . import test_katalog
|
||||
from . import test_tasy_import
|
||||
from . import test_tasy_import
|
||||
from . import test_entgelt
|
||||
@@ -0,0 +1,117 @@
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
from datetime import date
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class EntgeltTest(TransactionCase):
|
||||
"""AP2 Rechenfälle: Monatsentgelt, Kinderzulage, Funktionszulage,
|
||||
Sonderzahlung (quartalsweise), Ausbildungsphase."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.structure = self.env.ref(
|
||||
'l10n_at_gemeinde_payroll.structure_gembg_bgld')
|
||||
self.gruppe_av1 = self.env.ref(
|
||||
'l10n_at_gemeinde_payroll.bgld_grp_av_av1')
|
||||
self.parameter = self.env['hr.rule.parameter']
|
||||
|
||||
def _create_employee_with_payslip(self, month=None, **kwargs):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Test GemBG Entgelt',
|
||||
})
|
||||
version = self.env['hr.version'].search(
|
||||
[('employee_id', '=', employee.id)], limit=1)
|
||||
if not version:
|
||||
version = self.env['hr.version'].create({
|
||||
'employee_id': employee.id,
|
||||
'date_start': date(2020, 1, 1),
|
||||
})
|
||||
version_vals = {
|
||||
'entlohnungsgruppe_id': self.gruppe_av1.id,
|
||||
'entlohnungsstufe': kwargs.get('stufe', 1),
|
||||
}
|
||||
version_vals.update(kwargs.get('version_vals', {}))
|
||||
version.write(version_vals)
|
||||
# Ausbildungsphase und Zulagen direkt auf Version
|
||||
if 'ausbildungsphase' in kwargs:
|
||||
version.ausbildungsphase = kwargs['ausbildungsphase']
|
||||
if 'funktionszulage' in kwargs:
|
||||
version.funktionszulage = kwargs['funktionszulage']
|
||||
if 'kinder_anzahl' in kwargs:
|
||||
version.kinder_anzahl = kwargs['kinder_anzahl']
|
||||
|
||||
slip_month = month or 7
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': date(2026, slip_month, 1),
|
||||
'date_to': date(2026, slip_month, 28),
|
||||
})
|
||||
slip.compute_sheet()
|
||||
return employee, slip
|
||||
|
||||
def _get_rule_total(self, slip, code):
|
||||
line = slip.line_ids.filtered(
|
||||
lambda l: l.code == code)
|
||||
return line.total if line else 0.0
|
||||
|
||||
def test_01_monatsentgelt_av1_stufe1(self):
|
||||
"""Vollzeit av1 Stufe 1 = 7.135,20 € (Wert 1.7.2026)."""
|
||||
_, slip = self._create_employee_with_payslip(month=7)
|
||||
self.assertAlmostEqual(
|
||||
self._get_rule_total(slip, 'ME'), 7135.20, places=2)
|
||||
|
||||
def test_02_kinderzulage_ungekuerzt_bei_teilzeit(self):
|
||||
"""Kinderzulage: 2 Kinder = 31,20 €, auch bei Teilzeit."""
|
||||
_, slip = self._create_employee_with_payslip(
|
||||
month=7, kinder_anzahl=2)
|
||||
# 2 × 15,60 = 31,20
|
||||
self.assertAlmostEqual(
|
||||
self._get_rule_total(slip, 'KIZU'), 31.20, places=2)
|
||||
|
||||
def test_03_funktionszulage(self):
|
||||
"""Funktionszulage: 200 € am Vertrag."""
|
||||
_, slip = self._create_employee_with_payslip(
|
||||
month=7, funktionszulage=200.0)
|
||||
self.assertAlmostEqual(
|
||||
self._get_rule_total(slip, 'FUZU'), 200.0, places=2)
|
||||
|
||||
def test_04_ausbildungsphase_5_prozent_abzug(self):
|
||||
"""§ 133h: 5 % Abzug auf Monatsentgelt."""
|
||||
_, slip = self._create_employee_with_payslip(
|
||||
month=7, ausbildungsphase=True)
|
||||
# 7.135,20 × 0,95 = 6.778,44
|
||||
self.assertAlmostEqual(
|
||||
self._get_rule_total(slip, 'ME'), 6778.44, places=2)
|
||||
|
||||
def test_05_sonderzahlung_quartalsende(self):
|
||||
"""SZ in September (Q3-Ende) = 50 % × (ME + KIZU)."""
|
||||
_, slip = self._create_employee_with_payslip(
|
||||
month=9, kinder_anzahl=1)
|
||||
me = self._get_rule_total(slip, 'ME')
|
||||
kizu = self._get_rule_total(slip, 'KIZU')
|
||||
sz = self._get_rule_total(slip, 'SZQ')
|
||||
self.assertAlmostEqual(sz, (me + kizu) * 0.5, places=2)
|
||||
|
||||
def test_06_sonderzahlung_kein_quartalsende(self):
|
||||
"""Kein SZ außerhalb der Quartalsendmonate."""
|
||||
_, slip = self._create_employee_with_payslip(month=7)
|
||||
self.assertEqual(self._get_rule_total(slip, 'SZQ'), 0.0)
|
||||
|
||||
def test_07_netto_ohne_abzuege(self):
|
||||
"""R1: Netto = Brutto (AP3/AP4 Abzüge noch nicht implementiert)."""
|
||||
_, slip = self._create_employee_with_payslip(
|
||||
month=7, kinder_anzahl=1, funktionszulage=100.0)
|
||||
brutto = self._get_rule_total(slip, 'ME') \
|
||||
+ self._get_rule_total(slip, 'KIZU') \
|
||||
+ self._get_rule_total(slip, 'FUZU')
|
||||
netto = self._get_rule_total(slip, 'NETTO')
|
||||
self.assertAlmostEqual(netto, brutto, places=2)
|
||||
|
||||
def test_08_stufe_12_max(self):
|
||||
"""Stufe 12 von av1 = 8.712,50 €."""
|
||||
_, slip = self._create_employee_with_payslip(month=7, stufe=12)
|
||||
self.assertAlmostEqual(
|
||||
self._get_rule_total(slip, 'ME'), 8712.50, places=2)
|
||||
@@ -13,6 +13,12 @@
|
||||
<field name="besoldungsdienstalter" invisible="not entlohnungsgruppe_id"/>
|
||||
<field name="vorrueckungstermin" invisible="not entlohnungsgruppe_id"/>
|
||||
</group>
|
||||
<group name="gembg_zulagen" string="GemBG Zulagen"
|
||||
groups="hr_payroll.group_hr_payroll_user">
|
||||
<field name="kinder_anzahl"/>
|
||||
<field name="funktionszulage"/>
|
||||
<field name="ausbildungsphase"/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user