[IMP] l10n_at_hr_payroll_private: Wr. DAG, Buchungsbeleg, VWGJ-Einstufung, EFZG-Teilentgelt mit fiktiver BV (AP2d+AP3+AP9+AP4)

AP3 — Wiener Dienstgeberabgabe (U-Bahn-Steuer, RIS LrW GesNr
20000133 verifiziert): Regel WIEN_DAG (Kopfpauschale 2 Euro je DN
und angefangene Woche, Wochenlogik Montag-im-Monat, Befreiungen
Lehrling/55+/<=10 WStd/Karenz-Woche), Company-Flag-Verwertung,
Parameter at_wien_dag_satz, FI 6663/3560 (Referenz-Rest)

AP2d — Buchungsbeleg-Report am Lohnlauf (Konto/Belegtext/Soll/Haben
der FI-Buchung, balancierte Summen; test_account_private-Erweiterung)

AP9 — Vordienstzeiten -> Einstufung: Stufen-Schwelle vwgj_ab an
l10n.at.payroll.kv.gruppe.stufe (aus Labels 'nach N VWGJ'; -1 =
nicht gepflegt, ORM liest Integer-NULL als 0), CSV-Seed,
_get_stufe_fuer_vwgj/_get_naechste_vwgj, Vertragsfeld
l10n_at_vordienstzeit_monate + VWGJ-Vorschlag + Vorrueckungstermin
(S 17 Abs 5 Rahmen-KV, Erster des Erreichungsmonats) + Uebernahme-
Button (manueller Verwaltungsakt)

AP4 — EFZG/Teilentgelt (AngG S 8/EFZG S 2 verifiziert, Gleichlauf
6/8/10/12+4 Wochen; lb-krs-07/08): EFZ-Phasenmaschine models/efzg.py
(Kalendertage-Kontingente je Arbeitsjahr, Krank-/AU-Topf getrennt,
AU-Urlaubsart ATP_AU), Regeln TEIL_ENTGELT_HALB/-50 % und
KRANKENTGELT_PHASE/-100 % je Arbeitstag (Tagesatz aus unreduzierter
Basis), fiktive BV-Grundlage S 7 Abs 3 BMSVG im _at_bv_basis_laufen-
Hook (50 % Vormonatsentgelt x Bezugs-KT/30), Parameter at_efz_stufen;
Krankengeldumlage S 62 ASVG = Falsch-Praemise (kein Baustein);
Rechenfaelle docs/rechenfaelle/efzg.md
This commit is contained in:
2026-09-12 23:17:57 +02:00
parent 34e6750fe3
commit 9b4221fcef
22 changed files with 1596 additions and 144 deletions
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
{
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
'version': '19.0.9.0.0',
'version': '19.0.9.2.0',
'author': 'ixsol / gem360',
'website': 'https://gem360.at',
'category': 'Human Resources/Payroll',
@@ -106,10 +106,13 @@ gem360-Brücke).
'data/input_types_private.xml',
'data/salary_rules_private.xml',
'data/salary_rules_lst_private.xml',
'data/salary_rules_efzg_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/bv_kasse_seed.xml',
'views/kv_views.xml',
'views/bv_views.xml',
@@ -117,6 +120,7 @@ gem360-Brücke).
'views/vertragsfelder_views.xml',
'views/mitarbeiter_views.xml',
'report/gp8_reports.xml',
'report/buchungsbeleg_report.xml',
'report/bv_report.xml',
],
'demo': ['data/demo.xml'],
@@ -1,132 +1,132 @@
id,gruppe_id:id,stufe,bezeichnung,gehalt,gueltig_ab
kv2203_st_ii_1,l10n_at_hr_payroll_private.kv2203_grp_ii,1,im 1. u 2. VWGJ,2059.41,2026-01-01
kv2203_st_ii_2,l10n_at_hr_payroll_private.kv2203_grp_ii,2,nach 2 VWGJ,2179.68,2026-01-01
kv2203_st_ii_3,l10n_at_hr_payroll_private.kv2203_grp_ii,3,nach 4 VWGJ,2315.46,2026-01-01
kv2203_st_ii_4,l10n_at_hr_payroll_private.kv2203_grp_ii,4,nach 6 VWGJ,2457.65,2026-01-01
kv2203_st_ii_5,l10n_at_hr_payroll_private.kv2203_grp_ii,5,nach 8 VWGJ,2599.83,2026-01-01
kv2203_st_ii_6,l10n_at_hr_payroll_private.kv2203_grp_ii,6,nach 10 VWGJ,2742.03,2026-01-01
kv2203_st_ii_7,l10n_at_hr_payroll_private.kv2203_grp_ii,7,nach 12 VWGJ,2863.88,2026-01-01
kv2203_st_ii_8,l10n_at_hr_payroll_private.kv2203_grp_ii,8,nach 15 VWGJ,3087.28,2026-01-01
kv2203_st_iii_1,l10n_at_hr_payroll_private.kv2203_grp_iii,1,im 1. u 2. VWGJ,2528.04,2026-01-01
kv2203_st_iii_2,l10n_at_hr_payroll_private.kv2203_grp_iii,2,nach 2 VWGJ,2705.03,2026-01-01
kv2203_st_iii_3,l10n_at_hr_payroll_private.kv2203_grp_iii,3,nach 4 VWGJ,2881.97,2026-01-01
kv2203_st_iii_4,l10n_at_hr_payroll_private.kv2203_grp_iii,4,nach 6 VWGJ,3058.96,2026-01-01
kv2203_st_iii_5,l10n_at_hr_payroll_private.kv2203_grp_iii,5,nach 8 VWGJ,3232.73,2026-01-01
kv2203_st_iii_6,l10n_at_hr_payroll_private.kv2203_grp_iii,6,nach 10 VWGJ,3409.53,2026-01-01
kv2203_st_iii_7,l10n_at_hr_payroll_private.kv2203_grp_iii,7,nach 12 VWGJ,3561.04,2026-01-01
kv2203_st_iii_8,l10n_at_hr_payroll_private.kv2203_grp_iii,8,nach 15 VWGJ,3838.85,2026-01-01
kv2203_st_iv_1,l10n_at_hr_payroll_private.kv2203_grp_iv,1,im 1. u 2. VWGJ,3146.27,2026-01-01
kv2203_st_iv_2,l10n_at_hr_payroll_private.kv2203_grp_iv,2,nach 2 VWGJ,3366.49,2026-01-01
kv2203_st_iv_3,l10n_at_hr_payroll_private.kv2203_grp_iv,3,nach 4 VWGJ,3586.73,2026-01-01
kv2203_st_iv_4,l10n_at_hr_payroll_private.kv2203_grp_iv,4,nach 6 VWGJ,3806.97,2026-01-01
kv2203_st_iv_5,l10n_at_hr_payroll_private.kv2203_grp_iv,5,nach 8 VWGJ,4027.20,2026-01-01
kv2203_st_iv_6,l10n_at_hr_payroll_private.kv2203_grp_iv,6,nach 10 VWGJ,4247.47,2026-01-01
kv2203_st_iv_7,l10n_at_hr_payroll_private.kv2203_grp_iv,7,nach 12 VWGJ,4436.22,2026-01-01
kv2203_st_iv_8,l10n_at_hr_payroll_private.kv2203_grp_iv,8,nach 15 VWGJ,4782.32,2026-01-01
kv2203_st_v_1,l10n_at_hr_payroll_private.kv2203_grp_v,1,im 1. u 2. VWGJ,3919.67,2026-01-01
kv2203_st_v_2,l10n_at_hr_payroll_private.kv2203_grp_v,2,nach 2 VWGJ,4194.02,2026-01-01
kv2203_st_v_3,l10n_at_hr_payroll_private.kv2203_grp_v,3,nach 4 VWGJ,4468.41,2026-01-01
kv2203_st_v_4,l10n_at_hr_payroll_private.kv2203_grp_v,4,nach 6 VWGJ,4742.77,2026-01-01
kv2203_st_v_5,l10n_at_hr_payroll_private.kv2203_grp_v,5,nach 8 VWGJ,5017.15,2026-01-01
kv2203_st_v_6,l10n_at_hr_payroll_private.kv2203_grp_v,6,nach 10 VWGJ,5291.57,2026-01-01
kv2203_st_v_7,l10n_at_hr_payroll_private.kv2203_grp_v,7,nach 12 VWGJ,5526.70,2026-01-01
kv2203_st_v_8,l10n_at_hr_payroll_private.kv2203_grp_v,8,nach 15 VWGJ,5957.86,2026-01-01
kv2203_st_vi_1,l10n_at_hr_payroll_private.kv2203_grp_vi,1,im 1. u 2. VWGJ,5482.40,2026-01-01
kv2203_st_vi_2,l10n_at_hr_payroll_private.kv2203_grp_vi,2,nach 2 VWGJ,6167.67,2026-01-01
kv2203_st_vi_3,l10n_at_hr_payroll_private.kv2203_grp_vi,3,nach 5 VWGJ,6853.01,2026-01-01
kv2203_st_m1_1,l10n_at_hr_payroll_private.kv2203_grp_m1,1,im 1. u 2. VWGJ,2427.72,2026-01-01
kv2203_st_m1_2,l10n_at_hr_payroll_private.kv2203_grp_m1,2,nach 2 VWGJ,2427.72,2026-01-01
kv2203_st_m1_3,l10n_at_hr_payroll_private.kv2203_grp_m1,3,nach 4 VWGJ,2586.57,2026-01-01
kv2203_st_m1_4,l10n_at_hr_payroll_private.kv2203_grp_m1,4,nach 6 VWGJ,2745.39,2026-01-01
kv2203_st_m1_5,l10n_at_hr_payroll_private.kv2203_grp_m1,5,nach 8 VWGJ,2904.19,2026-01-01
kv2203_st_m1_6,l10n_at_hr_payroll_private.kv2203_grp_m1,6,nach 10 VWGJ,3063.05,2026-01-01
kv2203_st_m1_7,l10n_at_hr_payroll_private.kv2203_grp_m1,7,nach 12 VWGJ,3199.16,2026-01-01
kv2203_st_m1_8,l10n_at_hr_payroll_private.kv2203_grp_m1,8,nach 15 VWGJ,3448.74,2026-01-01
kv2203_st_m2_1,l10n_at_hr_payroll_private.kv2203_grp_m2,1,im 1. u 2. VWGJ,3095.50,2026-01-01
kv2203_st_m2_2,l10n_at_hr_payroll_private.kv2203_grp_m2,2,nach 2 VWGJ,3095.50,2026-01-01
kv2203_st_m2_3,l10n_at_hr_payroll_private.kv2203_grp_m2,3,nach 4 VWGJ,3297.98,2026-01-01
kv2203_st_m2_4,l10n_at_hr_payroll_private.kv2203_grp_m2,4,nach 6 VWGJ,3500.52,2026-01-01
kv2203_st_m2_5,l10n_at_hr_payroll_private.kv2203_grp_m2,5,nach 8 VWGJ,3703.01,2026-01-01
kv2203_st_m2_6,l10n_at_hr_payroll_private.kv2203_grp_m2,6,nach 10 VWGJ,3905.53,2026-01-01
kv2203_st_m2_7,l10n_at_hr_payroll_private.kv2203_grp_m2,7,nach 12 VWGJ,4079.13,2026-01-01
kv2203_st_m2_8,l10n_at_hr_payroll_private.kv2203_grp_m2,8,nach 15 VWGJ,4397.37,2026-01-01
kv2203_st_m2fs_1,l10n_at_hr_payroll_private.kv2203_grp_m2fs,1,im 1. u 2. VWGJ,3241.68,2026-01-01
kv2203_st_m2fs_2,l10n_at_hr_payroll_private.kv2203_grp_m2fs,2,nach 2 VWGJ,3241.68,2026-01-01
kv2203_st_m2fs_3,l10n_at_hr_payroll_private.kv2203_grp_m2fs,3,nach 4 VWGJ,3453.74,2026-01-01
kv2203_st_m2fs_4,l10n_at_hr_payroll_private.kv2203_grp_m2fs,4,nach 6 VWGJ,3665.79,2026-01-01
kv2203_st_m2fs_5,l10n_at_hr_payroll_private.kv2203_grp_m2fs,5,nach 8 VWGJ,3877.83,2026-01-01
kv2203_st_m2fs_6,l10n_at_hr_payroll_private.kv2203_grp_m2fs,6,nach 10 VWGJ,4089.90,2026-01-01
kv2203_st_m2fs_7,l10n_at_hr_payroll_private.kv2203_grp_m2fs,7,nach 12 VWGJ,4271.68,2026-01-01
kv2203_st_m2fs_8,l10n_at_hr_payroll_private.kv2203_grp_m2fs,8,nach 15 VWGJ,4605.01,2026-01-01
kv2203_st_m3_1,l10n_at_hr_payroll_private.kv2203_grp_m3,1,im 1. u 2. VWGJ,3542.72,2026-01-01
kv2203_st_m3_2,l10n_at_hr_payroll_private.kv2203_grp_m3,2,nach 2 VWGJ,3542.72,2026-01-01
kv2203_st_m3_3,l10n_at_hr_payroll_private.kv2203_grp_m3,3,nach 4 VWGJ,3774.51,2026-01-01
kv2203_st_m3_4,l10n_at_hr_payroll_private.kv2203_grp_m3,4,nach 6 VWGJ,4006.27,2026-01-01
kv2203_st_m3_5,l10n_at_hr_payroll_private.kv2203_grp_m3,5,nach 8 VWGJ,4238.05,2026-01-01
kv2203_st_m3_6,l10n_at_hr_payroll_private.kv2203_grp_m3,6,nach 10 VWGJ,4469.81,2026-01-01
kv2203_st_m3_7,l10n_at_hr_payroll_private.kv2203_grp_m3,7,nach 12 VWGJ,4668.45,2026-01-01
kv2203_st_m3_8,l10n_at_hr_payroll_private.kv2203_grp_m3,8,nach 15 VWGJ,5032.68,2026-01-01
kv2748_st_i_1,l10n_at_hr_payroll_private.kv2748_grp_i,1,im 1. u 2. VWGJ,1960.54,2026-01-01
kv2748_st_i_2,l10n_at_hr_payroll_private.kv2748_grp_i,2,nach 2 VWGJ,2058.43,2026-01-01
kv2748_st_i_3,l10n_at_hr_payroll_private.kv2748_grp_i,3,nach 4 VWGJ,2190.76,2026-01-01
kv2748_st_i_4,l10n_at_hr_payroll_private.kv2748_grp_i,4,nach 6 VWGJ,2323.12,2026-01-01
kv2748_st_i_5,l10n_at_hr_payroll_private.kv2748_grp_i,5,nach 8 VWGJ,2455.45,2026-01-01
kv2748_st_i_6,l10n_at_hr_payroll_private.kv2748_grp_i,6,nach 10 VWGJ,2588.72,2026-01-01
kv2748_st_i_7,l10n_at_hr_payroll_private.kv2748_grp_i,7,nach 12 VWGJ,2703.75,2026-01-01
kv2748_st_i_8,l10n_at_hr_payroll_private.kv2748_grp_i,8,nach 15 VWGJ,2914.69,2026-01-01
kv2748_st_ii_1,l10n_at_hr_payroll_private.kv2748_grp_ii,1,im 1. u 2. VWGJ,2199.15,2026-01-01
kv2748_st_ii_2,l10n_at_hr_payroll_private.kv2748_grp_ii,2,nach 2 VWGJ,2350.62,2026-01-01
kv2748_st_ii_3,l10n_at_hr_payroll_private.kv2748_grp_ii,3,nach 4 VWGJ,2502.07,2026-01-01
kv2748_st_ii_4,l10n_at_hr_payroll_private.kv2748_grp_ii,4,nach 6 VWGJ,2655.45,2026-01-01
kv2748_st_ii_5,l10n_at_hr_payroll_private.kv2748_grp_ii,5,nach 8 VWGJ,2809.08,2026-01-01
kv2748_st_ii_6,l10n_at_hr_payroll_private.kv2748_grp_ii,6,nach 10 VWGJ,2962.72,2026-01-01
kv2748_st_ii_7,l10n_at_hr_payroll_private.kv2748_grp_ii,7,nach 12 VWGJ,3094.32,2026-01-01
kv2748_st_ii_8,l10n_at_hr_payroll_private.kv2748_grp_ii,8,nach 15 VWGJ,3335.74,2026-01-01
kv2748_st_iii_1,l10n_at_hr_payroll_private.kv2748_grp_iii,1,im 1. u 2. VWGJ,2727.40,2026-01-01
kv2748_st_iii_2,l10n_at_hr_payroll_private.kv2748_grp_iii,2,nach 2 VWGJ,2918.34,2026-01-01
kv2748_st_iii_3,l10n_at_hr_payroll_private.kv2748_grp_iii,3,nach 4 VWGJ,3109.21,2026-01-01
kv2748_st_iii_4,l10n_at_hr_payroll_private.kv2748_grp_iii,4,nach 6 VWGJ,3300.16,2026-01-01
kv2748_st_iii_5,l10n_at_hr_payroll_private.kv2748_grp_iii,5,nach 8 VWGJ,3491.06,2026-01-01
kv2748_st_iii_6,l10n_at_hr_payroll_private.kv2748_grp_iii,6,nach 10 VWGJ,3682.00,2026-01-01
kv2748_st_iii_7,l10n_at_hr_payroll_private.kv2748_grp_iii,7,nach 12 VWGJ,3845.61,2026-01-01
kv2748_st_iii_8,l10n_at_hr_payroll_private.kv2748_grp_iii,8,nach 15 VWGJ,4145.62,2026-01-01
kv2748_st_iv_1,l10n_at_hr_payroll_private.kv2748_grp_iv,1,im 1. u 2. VWGJ,3415.01,2026-01-01
kv2748_st_iv_2,l10n_at_hr_payroll_private.kv2748_grp_iv,2,nach 2 VWGJ,3654.05,2026-01-01
kv2748_st_iv_3,l10n_at_hr_payroll_private.kv2748_grp_iv,3,nach 4 VWGJ,3893.09,2026-01-01
kv2748_st_iv_4,l10n_at_hr_payroll_private.kv2748_grp_iv,4,nach 6 VWGJ,4132.15,2026-01-01
kv2748_st_iv_5,l10n_at_hr_payroll_private.kv2748_grp_iv,5,nach 8 VWGJ,4371.19,2026-01-01
kv2748_st_iv_6,l10n_at_hr_payroll_private.kv2748_grp_iv,6,nach 10 VWGJ,4610.26,2026-01-01
kv2748_st_iv_7,l10n_at_hr_payroll_private.kv2748_grp_iv,7,nach 12 VWGJ,4815.15,2026-01-01
kv2748_st_iv_8,l10n_at_hr_payroll_private.kv2748_grp_iv,8,nach 15 VWGJ,5190.77,2026-01-01
kv2748_st_ivm_1,l10n_at_hr_payroll_private.kv2748_grp_ivm,1,im 1.-4. VWGJ,3415.01,2026-01-01
kv2748_st_ivm_2,l10n_at_hr_payroll_private.kv2748_grp_ivm,2,nach 4 VWGJ,3654.05,2026-01-01
kv2748_st_ivm_3,l10n_at_hr_payroll_private.kv2748_grp_ivm,3,nach 6 VWGJ,3893.09,2026-01-01
kv2748_st_ivm_4,l10n_at_hr_payroll_private.kv2748_grp_ivm,4,nach 8 VWGJ,4132.15,2026-01-01
kv2748_st_ivm_5,l10n_at_hr_payroll_private.kv2748_grp_ivm,5,nach 10 VWGJ,4371.19,2026-01-01
kv2748_st_ivm_6,l10n_at_hr_payroll_private.kv2748_grp_ivm,6,nach 12 VWGJ,4610.26,2026-01-01
kv2748_st_ivm_7,l10n_at_hr_payroll_private.kv2748_grp_ivm,7,nach 14 VWGJ,4815.15,2026-01-01
kv2748_st_ivm_8,l10n_at_hr_payroll_private.kv2748_grp_ivm,8,nach 17 VWGJ,5047.10,2026-01-01
kv2748_st_v_1,l10n_at_hr_payroll_private.kv2748_grp_v,1,im 1. u 2. VWGJ,4299.70,2026-01-01
kv2748_st_v_2,l10n_at_hr_payroll_private.kv2748_grp_v,2,nach 2 VWGJ,4594.04,2026-01-01
kv2748_st_v_3,l10n_at_hr_payroll_private.kv2748_grp_v,3,nach 4 VWGJ,4888.37,2026-01-01
kv2748_st_v_4,l10n_at_hr_payroll_private.kv2748_grp_v,4,nach 6 VWGJ,5182.71,2026-01-01
kv2748_st_v_5,l10n_at_hr_payroll_private.kv2748_grp_v,5,nach 8 VWGJ,5477.00,2026-01-01
kv2748_st_v_6,l10n_at_hr_payroll_private.kv2748_grp_v,6,nach 10 VWGJ,5771.34,2026-01-01
kv2748_st_v_7,l10n_at_hr_payroll_private.kv2748_grp_v,7,nach 12 VWGJ,6023.65,2026-01-01
kv2748_st_v_8,l10n_at_hr_payroll_private.kv2748_grp_v,8,nach 15 VWGJ,6486.15,2026-01-01
kv2748_st_vom_1,l10n_at_hr_payroll_private.kv2748_grp_vom,1,im 1.-6. VWGJ,4299.70,2026-01-01
kv2748_st_vom_2,l10n_at_hr_payroll_private.kv2748_grp_vom,2,nach 6 VWGJ,4594.04,2026-01-01
kv2748_st_vom_3,l10n_at_hr_payroll_private.kv2748_grp_vom,3,nach 8 VWGJ,4888.37,2026-01-01
kv2748_st_vom_4,l10n_at_hr_payroll_private.kv2748_grp_vom,4,nach 10 VWGJ,5182.71,2026-01-01
kv2748_st_vom_5,l10n_at_hr_payroll_private.kv2748_grp_vom,5,nach 12 VWGJ,5477.00,2026-01-01
kv2748_st_vi_1,l10n_at_hr_payroll_private.kv2748_grp_vi,1,im 1. u 2. VWGJ,6021.80,2026-01-01
kv2748_st_vi_2,l10n_at_hr_payroll_private.kv2748_grp_vi,2,nach 2 VWGJ,6762.62,2026-01-01
kv2748_st_vi_3,l10n_at_hr_payroll_private.kv2748_grp_vi,3,nach 5 VWGJ,7503.50,2026-01-01
kv2748_st_meister_1,l10n_at_hr_payroll_private.kv2748_grp_meister,1,im 1. u 2. VWGJ,4130.76,2026-01-01
kv2748_st_meister_2,l10n_at_hr_payroll_private.kv2748_grp_meister,2,nach 2 VWGJ,4130.76,2026-01-01
kv2748_st_meister_3,l10n_at_hr_payroll_private.kv2748_grp_meister,3,nach 4 VWGJ,4303.13,2026-01-01
kv2748_st_meister_4,l10n_at_hr_payroll_private.kv2748_grp_meister,4,nach 6 VWGJ,4475.48,2026-01-01
kv2748_st_meister_5,l10n_at_hr_payroll_private.kv2748_grp_meister,5,nach 8 VWGJ,4647.84,2026-01-01
kv2748_st_meister_6,l10n_at_hr_payroll_private.kv2748_grp_meister,6,nach 10 VWGJ,4820.17,2026-01-01
kv2748_st_meister_7,l10n_at_hr_payroll_private.kv2748_grp_meister,7,nach 12 VWGJ,4992.53,2026-01-01
kv2748_st_meister_8,l10n_at_hr_payroll_private.kv2748_grp_meister,8,nach 15 VWGJ,5270.29,2026-01-01
id,gruppe_id:id,stufe,bezeichnung,gehalt,gueltig_ab,vwgj_ab
kv2203_st_ii_1,l10n_at_hr_payroll_private.kv2203_grp_ii,1,im 1. u 2. VWGJ,2059.41,2026-01-01,0
kv2203_st_ii_2,l10n_at_hr_payroll_private.kv2203_grp_ii,2,nach 2 VWGJ,2179.68,2026-01-01,2
kv2203_st_ii_3,l10n_at_hr_payroll_private.kv2203_grp_ii,3,nach 4 VWGJ,2315.46,2026-01-01,4
kv2203_st_ii_4,l10n_at_hr_payroll_private.kv2203_grp_ii,4,nach 6 VWGJ,2457.65,2026-01-01,6
kv2203_st_ii_5,l10n_at_hr_payroll_private.kv2203_grp_ii,5,nach 8 VWGJ,2599.83,2026-01-01,8
kv2203_st_ii_6,l10n_at_hr_payroll_private.kv2203_grp_ii,6,nach 10 VWGJ,2742.03,2026-01-01,10
kv2203_st_ii_7,l10n_at_hr_payroll_private.kv2203_grp_ii,7,nach 12 VWGJ,2863.88,2026-01-01,12
kv2203_st_ii_8,l10n_at_hr_payroll_private.kv2203_grp_ii,8,nach 15 VWGJ,3087.28,2026-01-01,15
kv2203_st_iii_1,l10n_at_hr_payroll_private.kv2203_grp_iii,1,im 1. u 2. VWGJ,2528.04,2026-01-01,0
kv2203_st_iii_2,l10n_at_hr_payroll_private.kv2203_grp_iii,2,nach 2 VWGJ,2705.03,2026-01-01,2
kv2203_st_iii_3,l10n_at_hr_payroll_private.kv2203_grp_iii,3,nach 4 VWGJ,2881.97,2026-01-01,4
kv2203_st_iii_4,l10n_at_hr_payroll_private.kv2203_grp_iii,4,nach 6 VWGJ,3058.96,2026-01-01,6
kv2203_st_iii_5,l10n_at_hr_payroll_private.kv2203_grp_iii,5,nach 8 VWGJ,3232.73,2026-01-01,8
kv2203_st_iii_6,l10n_at_hr_payroll_private.kv2203_grp_iii,6,nach 10 VWGJ,3409.53,2026-01-01,10
kv2203_st_iii_7,l10n_at_hr_payroll_private.kv2203_grp_iii,7,nach 12 VWGJ,3561.04,2026-01-01,12
kv2203_st_iii_8,l10n_at_hr_payroll_private.kv2203_grp_iii,8,nach 15 VWGJ,3838.85,2026-01-01,15
kv2203_st_iv_1,l10n_at_hr_payroll_private.kv2203_grp_iv,1,im 1. u 2. VWGJ,3146.27,2026-01-01,0
kv2203_st_iv_2,l10n_at_hr_payroll_private.kv2203_grp_iv,2,nach 2 VWGJ,3366.49,2026-01-01,2
kv2203_st_iv_3,l10n_at_hr_payroll_private.kv2203_grp_iv,3,nach 4 VWGJ,3586.73,2026-01-01,4
kv2203_st_iv_4,l10n_at_hr_payroll_private.kv2203_grp_iv,4,nach 6 VWGJ,3806.97,2026-01-01,6
kv2203_st_iv_5,l10n_at_hr_payroll_private.kv2203_grp_iv,5,nach 8 VWGJ,4027.20,2026-01-01,8
kv2203_st_iv_6,l10n_at_hr_payroll_private.kv2203_grp_iv,6,nach 10 VWGJ,4247.47,2026-01-01,10
kv2203_st_iv_7,l10n_at_hr_payroll_private.kv2203_grp_iv,7,nach 12 VWGJ,4436.22,2026-01-01,12
kv2203_st_iv_8,l10n_at_hr_payroll_private.kv2203_grp_iv,8,nach 15 VWGJ,4782.32,2026-01-01,15
kv2203_st_v_1,l10n_at_hr_payroll_private.kv2203_grp_v,1,im 1. u 2. VWGJ,3919.67,2026-01-01,0
kv2203_st_v_2,l10n_at_hr_payroll_private.kv2203_grp_v,2,nach 2 VWGJ,4194.02,2026-01-01,2
kv2203_st_v_3,l10n_at_hr_payroll_private.kv2203_grp_v,3,nach 4 VWGJ,4468.41,2026-01-01,4
kv2203_st_v_4,l10n_at_hr_payroll_private.kv2203_grp_v,4,nach 6 VWGJ,4742.77,2026-01-01,6
kv2203_st_v_5,l10n_at_hr_payroll_private.kv2203_grp_v,5,nach 8 VWGJ,5017.15,2026-01-01,8
kv2203_st_v_6,l10n_at_hr_payroll_private.kv2203_grp_v,6,nach 10 VWGJ,5291.57,2026-01-01,10
kv2203_st_v_7,l10n_at_hr_payroll_private.kv2203_grp_v,7,nach 12 VWGJ,5526.70,2026-01-01,12
kv2203_st_v_8,l10n_at_hr_payroll_private.kv2203_grp_v,8,nach 15 VWGJ,5957.86,2026-01-01,15
kv2203_st_vi_1,l10n_at_hr_payroll_private.kv2203_grp_vi,1,im 1. u 2. VWGJ,5482.40,2026-01-01,0
kv2203_st_vi_2,l10n_at_hr_payroll_private.kv2203_grp_vi,2,nach 2 VWGJ,6167.67,2026-01-01,2
kv2203_st_vi_3,l10n_at_hr_payroll_private.kv2203_grp_vi,3,nach 5 VWGJ,6853.01,2026-01-01,5
kv2203_st_m1_1,l10n_at_hr_payroll_private.kv2203_grp_m1,1,im 1. u 2. VWGJ,2427.72,2026-01-01,0
kv2203_st_m1_2,l10n_at_hr_payroll_private.kv2203_grp_m1,2,nach 2 VWGJ,2427.72,2026-01-01,2
kv2203_st_m1_3,l10n_at_hr_payroll_private.kv2203_grp_m1,3,nach 4 VWGJ,2586.57,2026-01-01,4
kv2203_st_m1_4,l10n_at_hr_payroll_private.kv2203_grp_m1,4,nach 6 VWGJ,2745.39,2026-01-01,6
kv2203_st_m1_5,l10n_at_hr_payroll_private.kv2203_grp_m1,5,nach 8 VWGJ,2904.19,2026-01-01,8
kv2203_st_m1_6,l10n_at_hr_payroll_private.kv2203_grp_m1,6,nach 10 VWGJ,3063.05,2026-01-01,10
kv2203_st_m1_7,l10n_at_hr_payroll_private.kv2203_grp_m1,7,nach 12 VWGJ,3199.16,2026-01-01,12
kv2203_st_m1_8,l10n_at_hr_payroll_private.kv2203_grp_m1,8,nach 15 VWGJ,3448.74,2026-01-01,15
kv2203_st_m2_1,l10n_at_hr_payroll_private.kv2203_grp_m2,1,im 1. u 2. VWGJ,3095.50,2026-01-01,0
kv2203_st_m2_2,l10n_at_hr_payroll_private.kv2203_grp_m2,2,nach 2 VWGJ,3095.50,2026-01-01,2
kv2203_st_m2_3,l10n_at_hr_payroll_private.kv2203_grp_m2,3,nach 4 VWGJ,3297.98,2026-01-01,4
kv2203_st_m2_4,l10n_at_hr_payroll_private.kv2203_grp_m2,4,nach 6 VWGJ,3500.52,2026-01-01,6
kv2203_st_m2_5,l10n_at_hr_payroll_private.kv2203_grp_m2,5,nach 8 VWGJ,3703.01,2026-01-01,8
kv2203_st_m2_6,l10n_at_hr_payroll_private.kv2203_grp_m2,6,nach 10 VWGJ,3905.53,2026-01-01,10
kv2203_st_m2_7,l10n_at_hr_payroll_private.kv2203_grp_m2,7,nach 12 VWGJ,4079.13,2026-01-01,12
kv2203_st_m2_8,l10n_at_hr_payroll_private.kv2203_grp_m2,8,nach 15 VWGJ,4397.37,2026-01-01,15
kv2203_st_m2fs_1,l10n_at_hr_payroll_private.kv2203_grp_m2fs,1,im 1. u 2. VWGJ,3241.68,2026-01-01,0
kv2203_st_m2fs_2,l10n_at_hr_payroll_private.kv2203_grp_m2fs,2,nach 2 VWGJ,3241.68,2026-01-01,2
kv2203_st_m2fs_3,l10n_at_hr_payroll_private.kv2203_grp_m2fs,3,nach 4 VWGJ,3453.74,2026-01-01,4
kv2203_st_m2fs_4,l10n_at_hr_payroll_private.kv2203_grp_m2fs,4,nach 6 VWGJ,3665.79,2026-01-01,6
kv2203_st_m2fs_5,l10n_at_hr_payroll_private.kv2203_grp_m2fs,5,nach 8 VWGJ,3877.83,2026-01-01,8
kv2203_st_m2fs_6,l10n_at_hr_payroll_private.kv2203_grp_m2fs,6,nach 10 VWGJ,4089.90,2026-01-01,10
kv2203_st_m2fs_7,l10n_at_hr_payroll_private.kv2203_grp_m2fs,7,nach 12 VWGJ,4271.68,2026-01-01,12
kv2203_st_m2fs_8,l10n_at_hr_payroll_private.kv2203_grp_m2fs,8,nach 15 VWGJ,4605.01,2026-01-01,15
kv2203_st_m3_1,l10n_at_hr_payroll_private.kv2203_grp_m3,1,im 1. u 2. VWGJ,3542.72,2026-01-01,0
kv2203_st_m3_2,l10n_at_hr_payroll_private.kv2203_grp_m3,2,nach 2 VWGJ,3542.72,2026-01-01,2
kv2203_st_m3_3,l10n_at_hr_payroll_private.kv2203_grp_m3,3,nach 4 VWGJ,3774.51,2026-01-01,4
kv2203_st_m3_4,l10n_at_hr_payroll_private.kv2203_grp_m3,4,nach 6 VWGJ,4006.27,2026-01-01,6
kv2203_st_m3_5,l10n_at_hr_payroll_private.kv2203_grp_m3,5,nach 8 VWGJ,4238.05,2026-01-01,8
kv2203_st_m3_6,l10n_at_hr_payroll_private.kv2203_grp_m3,6,nach 10 VWGJ,4469.81,2026-01-01,10
kv2203_st_m3_7,l10n_at_hr_payroll_private.kv2203_grp_m3,7,nach 12 VWGJ,4668.45,2026-01-01,12
kv2203_st_m3_8,l10n_at_hr_payroll_private.kv2203_grp_m3,8,nach 15 VWGJ,5032.68,2026-01-01,15
kv2748_st_i_1,l10n_at_hr_payroll_private.kv2748_grp_i,1,im 1. u 2. VWGJ,1960.54,2026-01-01,0
kv2748_st_i_2,l10n_at_hr_payroll_private.kv2748_grp_i,2,nach 2 VWGJ,2058.43,2026-01-01,2
kv2748_st_i_3,l10n_at_hr_payroll_private.kv2748_grp_i,3,nach 4 VWGJ,2190.76,2026-01-01,4
kv2748_st_i_4,l10n_at_hr_payroll_private.kv2748_grp_i,4,nach 6 VWGJ,2323.12,2026-01-01,6
kv2748_st_i_5,l10n_at_hr_payroll_private.kv2748_grp_i,5,nach 8 VWGJ,2455.45,2026-01-01,8
kv2748_st_i_6,l10n_at_hr_payroll_private.kv2748_grp_i,6,nach 10 VWGJ,2588.72,2026-01-01,10
kv2748_st_i_7,l10n_at_hr_payroll_private.kv2748_grp_i,7,nach 12 VWGJ,2703.75,2026-01-01,12
kv2748_st_i_8,l10n_at_hr_payroll_private.kv2748_grp_i,8,nach 15 VWGJ,2914.69,2026-01-01,15
kv2748_st_ii_1,l10n_at_hr_payroll_private.kv2748_grp_ii,1,im 1. u 2. VWGJ,2199.15,2026-01-01,0
kv2748_st_ii_2,l10n_at_hr_payroll_private.kv2748_grp_ii,2,nach 2 VWGJ,2350.62,2026-01-01,2
kv2748_st_ii_3,l10n_at_hr_payroll_private.kv2748_grp_ii,3,nach 4 VWGJ,2502.07,2026-01-01,4
kv2748_st_ii_4,l10n_at_hr_payroll_private.kv2748_grp_ii,4,nach 6 VWGJ,2655.45,2026-01-01,6
kv2748_st_ii_5,l10n_at_hr_payroll_private.kv2748_grp_ii,5,nach 8 VWGJ,2809.08,2026-01-01,8
kv2748_st_ii_6,l10n_at_hr_payroll_private.kv2748_grp_ii,6,nach 10 VWGJ,2962.72,2026-01-01,10
kv2748_st_ii_7,l10n_at_hr_payroll_private.kv2748_grp_ii,7,nach 12 VWGJ,3094.32,2026-01-01,12
kv2748_st_ii_8,l10n_at_hr_payroll_private.kv2748_grp_ii,8,nach 15 VWGJ,3335.74,2026-01-01,15
kv2748_st_iii_1,l10n_at_hr_payroll_private.kv2748_grp_iii,1,im 1. u 2. VWGJ,2727.40,2026-01-01,0
kv2748_st_iii_2,l10n_at_hr_payroll_private.kv2748_grp_iii,2,nach 2 VWGJ,2918.34,2026-01-01,2
kv2748_st_iii_3,l10n_at_hr_payroll_private.kv2748_grp_iii,3,nach 4 VWGJ,3109.21,2026-01-01,4
kv2748_st_iii_4,l10n_at_hr_payroll_private.kv2748_grp_iii,4,nach 6 VWGJ,3300.16,2026-01-01,6
kv2748_st_iii_5,l10n_at_hr_payroll_private.kv2748_grp_iii,5,nach 8 VWGJ,3491.06,2026-01-01,8
kv2748_st_iii_6,l10n_at_hr_payroll_private.kv2748_grp_iii,6,nach 10 VWGJ,3682.00,2026-01-01,10
kv2748_st_iii_7,l10n_at_hr_payroll_private.kv2748_grp_iii,7,nach 12 VWGJ,3845.61,2026-01-01,12
kv2748_st_iii_8,l10n_at_hr_payroll_private.kv2748_grp_iii,8,nach 15 VWGJ,4145.62,2026-01-01,15
kv2748_st_iv_1,l10n_at_hr_payroll_private.kv2748_grp_iv,1,im 1. u 2. VWGJ,3415.01,2026-01-01,0
kv2748_st_iv_2,l10n_at_hr_payroll_private.kv2748_grp_iv,2,nach 2 VWGJ,3654.05,2026-01-01,2
kv2748_st_iv_3,l10n_at_hr_payroll_private.kv2748_grp_iv,3,nach 4 VWGJ,3893.09,2026-01-01,4
kv2748_st_iv_4,l10n_at_hr_payroll_private.kv2748_grp_iv,4,nach 6 VWGJ,4132.15,2026-01-01,6
kv2748_st_iv_5,l10n_at_hr_payroll_private.kv2748_grp_iv,5,nach 8 VWGJ,4371.19,2026-01-01,8
kv2748_st_iv_6,l10n_at_hr_payroll_private.kv2748_grp_iv,6,nach 10 VWGJ,4610.26,2026-01-01,10
kv2748_st_iv_7,l10n_at_hr_payroll_private.kv2748_grp_iv,7,nach 12 VWGJ,4815.15,2026-01-01,12
kv2748_st_iv_8,l10n_at_hr_payroll_private.kv2748_grp_iv,8,nach 15 VWGJ,5190.77,2026-01-01,15
kv2748_st_ivm_1,l10n_at_hr_payroll_private.kv2748_grp_ivm,1,im 1.-4. VWGJ,3415.01,2026-01-01,0
kv2748_st_ivm_2,l10n_at_hr_payroll_private.kv2748_grp_ivm,2,nach 4 VWGJ,3654.05,2026-01-01,4
kv2748_st_ivm_3,l10n_at_hr_payroll_private.kv2748_grp_ivm,3,nach 6 VWGJ,3893.09,2026-01-01,6
kv2748_st_ivm_4,l10n_at_hr_payroll_private.kv2748_grp_ivm,4,nach 8 VWGJ,4132.15,2026-01-01,8
kv2748_st_ivm_5,l10n_at_hr_payroll_private.kv2748_grp_ivm,5,nach 10 VWGJ,4371.19,2026-01-01,10
kv2748_st_ivm_6,l10n_at_hr_payroll_private.kv2748_grp_ivm,6,nach 12 VWGJ,4610.26,2026-01-01,12
kv2748_st_ivm_7,l10n_at_hr_payroll_private.kv2748_grp_ivm,7,nach 14 VWGJ,4815.15,2026-01-01,14
kv2748_st_ivm_8,l10n_at_hr_payroll_private.kv2748_grp_ivm,8,nach 17 VWGJ,5047.10,2026-01-01,17
kv2748_st_v_1,l10n_at_hr_payroll_private.kv2748_grp_v,1,im 1. u 2. VWGJ,4299.70,2026-01-01,0
kv2748_st_v_2,l10n_at_hr_payroll_private.kv2748_grp_v,2,nach 2 VWGJ,4594.04,2026-01-01,2
kv2748_st_v_3,l10n_at_hr_payroll_private.kv2748_grp_v,3,nach 4 VWGJ,4888.37,2026-01-01,4
kv2748_st_v_4,l10n_at_hr_payroll_private.kv2748_grp_v,4,nach 6 VWGJ,5182.71,2026-01-01,6
kv2748_st_v_5,l10n_at_hr_payroll_private.kv2748_grp_v,5,nach 8 VWGJ,5477.00,2026-01-01,8
kv2748_st_v_6,l10n_at_hr_payroll_private.kv2748_grp_v,6,nach 10 VWGJ,5771.34,2026-01-01,10
kv2748_st_v_7,l10n_at_hr_payroll_private.kv2748_grp_v,7,nach 12 VWGJ,6023.65,2026-01-01,12
kv2748_st_v_8,l10n_at_hr_payroll_private.kv2748_grp_v,8,nach 15 VWGJ,6486.15,2026-01-01,15
kv2748_st_vom_1,l10n_at_hr_payroll_private.kv2748_grp_vom,1,im 1.-6. VWGJ,4299.70,2026-01-01,0
kv2748_st_vom_2,l10n_at_hr_payroll_private.kv2748_grp_vom,2,nach 6 VWGJ,4594.04,2026-01-01,6
kv2748_st_vom_3,l10n_at_hr_payroll_private.kv2748_grp_vom,3,nach 8 VWGJ,4888.37,2026-01-01,8
kv2748_st_vom_4,l10n_at_hr_payroll_private.kv2748_grp_vom,4,nach 10 VWGJ,5182.71,2026-01-01,10
kv2748_st_vom_5,l10n_at_hr_payroll_private.kv2748_grp_vom,5,nach 12 VWGJ,5477.00,2026-01-01,12
kv2748_st_vi_1,l10n_at_hr_payroll_private.kv2748_grp_vi,1,im 1. u 2. VWGJ,6021.80,2026-01-01,0
kv2748_st_vi_2,l10n_at_hr_payroll_private.kv2748_grp_vi,2,nach 2 VWGJ,6762.62,2026-01-01,2
kv2748_st_vi_3,l10n_at_hr_payroll_private.kv2748_grp_vi,3,nach 5 VWGJ,7503.50,2026-01-01,5
kv2748_st_meister_1,l10n_at_hr_payroll_private.kv2748_grp_meister,1,im 1. u 2. VWGJ,4130.76,2026-01-01,0
kv2748_st_meister_2,l10n_at_hr_payroll_private.kv2748_grp_meister,2,nach 2 VWGJ,4130.76,2026-01-01,2
kv2748_st_meister_3,l10n_at_hr_payroll_private.kv2748_grp_meister,3,nach 4 VWGJ,4303.13,2026-01-01,4
kv2748_st_meister_4,l10n_at_hr_payroll_private.kv2748_grp_meister,4,nach 6 VWGJ,4475.48,2026-01-01,6
kv2748_st_meister_5,l10n_at_hr_payroll_private.kv2748_grp_meister,5,nach 8 VWGJ,4647.84,2026-01-01,8
kv2748_st_meister_6,l10n_at_hr_payroll_private.kv2748_grp_meister,6,nach 10 VWGJ,4820.17,2026-01-01,10
kv2748_st_meister_7,l10n_at_hr_payroll_private.kv2748_grp_meister,7,nach 12 VWGJ,4992.53,2026-01-01,12
kv2748_st_meister_8,l10n_at_hr_payroll_private.kv2748_grp_meister,8,nach 15 VWGJ,5270.29,2026-01-01,15
1 id gruppe_id:id stufe bezeichnung gehalt gueltig_ab vwgj_ab
2 kv2203_st_ii_1 l10n_at_hr_payroll_private.kv2203_grp_ii 1 im 1. u 2. VWGJ 2059.41 2026-01-01 0
3 kv2203_st_ii_2 l10n_at_hr_payroll_private.kv2203_grp_ii 2 nach 2 VWGJ 2179.68 2026-01-01 2
4 kv2203_st_ii_3 l10n_at_hr_payroll_private.kv2203_grp_ii 3 nach 4 VWGJ 2315.46 2026-01-01 4
5 kv2203_st_ii_4 l10n_at_hr_payroll_private.kv2203_grp_ii 4 nach 6 VWGJ 2457.65 2026-01-01 6
6 kv2203_st_ii_5 l10n_at_hr_payroll_private.kv2203_grp_ii 5 nach 8 VWGJ 2599.83 2026-01-01 8
7 kv2203_st_ii_6 l10n_at_hr_payroll_private.kv2203_grp_ii 6 nach 10 VWGJ 2742.03 2026-01-01 10
8 kv2203_st_ii_7 l10n_at_hr_payroll_private.kv2203_grp_ii 7 nach 12 VWGJ 2863.88 2026-01-01 12
9 kv2203_st_ii_8 l10n_at_hr_payroll_private.kv2203_grp_ii 8 nach 15 VWGJ 3087.28 2026-01-01 15
10 kv2203_st_iii_1 l10n_at_hr_payroll_private.kv2203_grp_iii 1 im 1. u 2. VWGJ 2528.04 2026-01-01 0
11 kv2203_st_iii_2 l10n_at_hr_payroll_private.kv2203_grp_iii 2 nach 2 VWGJ 2705.03 2026-01-01 2
12 kv2203_st_iii_3 l10n_at_hr_payroll_private.kv2203_grp_iii 3 nach 4 VWGJ 2881.97 2026-01-01 4
13 kv2203_st_iii_4 l10n_at_hr_payroll_private.kv2203_grp_iii 4 nach 6 VWGJ 3058.96 2026-01-01 6
14 kv2203_st_iii_5 l10n_at_hr_payroll_private.kv2203_grp_iii 5 nach 8 VWGJ 3232.73 2026-01-01 8
15 kv2203_st_iii_6 l10n_at_hr_payroll_private.kv2203_grp_iii 6 nach 10 VWGJ 3409.53 2026-01-01 10
16 kv2203_st_iii_7 l10n_at_hr_payroll_private.kv2203_grp_iii 7 nach 12 VWGJ 3561.04 2026-01-01 12
17 kv2203_st_iii_8 l10n_at_hr_payroll_private.kv2203_grp_iii 8 nach 15 VWGJ 3838.85 2026-01-01 15
18 kv2203_st_iv_1 l10n_at_hr_payroll_private.kv2203_grp_iv 1 im 1. u 2. VWGJ 3146.27 2026-01-01 0
19 kv2203_st_iv_2 l10n_at_hr_payroll_private.kv2203_grp_iv 2 nach 2 VWGJ 3366.49 2026-01-01 2
20 kv2203_st_iv_3 l10n_at_hr_payroll_private.kv2203_grp_iv 3 nach 4 VWGJ 3586.73 2026-01-01 4
21 kv2203_st_iv_4 l10n_at_hr_payroll_private.kv2203_grp_iv 4 nach 6 VWGJ 3806.97 2026-01-01 6
22 kv2203_st_iv_5 l10n_at_hr_payroll_private.kv2203_grp_iv 5 nach 8 VWGJ 4027.20 2026-01-01 8
23 kv2203_st_iv_6 l10n_at_hr_payroll_private.kv2203_grp_iv 6 nach 10 VWGJ 4247.47 2026-01-01 10
24 kv2203_st_iv_7 l10n_at_hr_payroll_private.kv2203_grp_iv 7 nach 12 VWGJ 4436.22 2026-01-01 12
25 kv2203_st_iv_8 l10n_at_hr_payroll_private.kv2203_grp_iv 8 nach 15 VWGJ 4782.32 2026-01-01 15
26 kv2203_st_v_1 l10n_at_hr_payroll_private.kv2203_grp_v 1 im 1. u 2. VWGJ 3919.67 2026-01-01 0
27 kv2203_st_v_2 l10n_at_hr_payroll_private.kv2203_grp_v 2 nach 2 VWGJ 4194.02 2026-01-01 2
28 kv2203_st_v_3 l10n_at_hr_payroll_private.kv2203_grp_v 3 nach 4 VWGJ 4468.41 2026-01-01 4
29 kv2203_st_v_4 l10n_at_hr_payroll_private.kv2203_grp_v 4 nach 6 VWGJ 4742.77 2026-01-01 6
30 kv2203_st_v_5 l10n_at_hr_payroll_private.kv2203_grp_v 5 nach 8 VWGJ 5017.15 2026-01-01 8
31 kv2203_st_v_6 l10n_at_hr_payroll_private.kv2203_grp_v 6 nach 10 VWGJ 5291.57 2026-01-01 10
32 kv2203_st_v_7 l10n_at_hr_payroll_private.kv2203_grp_v 7 nach 12 VWGJ 5526.70 2026-01-01 12
33 kv2203_st_v_8 l10n_at_hr_payroll_private.kv2203_grp_v 8 nach 15 VWGJ 5957.86 2026-01-01 15
34 kv2203_st_vi_1 l10n_at_hr_payroll_private.kv2203_grp_vi 1 im 1. u 2. VWGJ 5482.40 2026-01-01 0
35 kv2203_st_vi_2 l10n_at_hr_payroll_private.kv2203_grp_vi 2 nach 2 VWGJ 6167.67 2026-01-01 2
36 kv2203_st_vi_3 l10n_at_hr_payroll_private.kv2203_grp_vi 3 nach 5 VWGJ 6853.01 2026-01-01 5
37 kv2203_st_m1_1 l10n_at_hr_payroll_private.kv2203_grp_m1 1 im 1. u 2. VWGJ 2427.72 2026-01-01 0
38 kv2203_st_m1_2 l10n_at_hr_payroll_private.kv2203_grp_m1 2 nach 2 VWGJ 2427.72 2026-01-01 2
39 kv2203_st_m1_3 l10n_at_hr_payroll_private.kv2203_grp_m1 3 nach 4 VWGJ 2586.57 2026-01-01 4
40 kv2203_st_m1_4 l10n_at_hr_payroll_private.kv2203_grp_m1 4 nach 6 VWGJ 2745.39 2026-01-01 6
41 kv2203_st_m1_5 l10n_at_hr_payroll_private.kv2203_grp_m1 5 nach 8 VWGJ 2904.19 2026-01-01 8
42 kv2203_st_m1_6 l10n_at_hr_payroll_private.kv2203_grp_m1 6 nach 10 VWGJ 3063.05 2026-01-01 10
43 kv2203_st_m1_7 l10n_at_hr_payroll_private.kv2203_grp_m1 7 nach 12 VWGJ 3199.16 2026-01-01 12
44 kv2203_st_m1_8 l10n_at_hr_payroll_private.kv2203_grp_m1 8 nach 15 VWGJ 3448.74 2026-01-01 15
45 kv2203_st_m2_1 l10n_at_hr_payroll_private.kv2203_grp_m2 1 im 1. u 2. VWGJ 3095.50 2026-01-01 0
46 kv2203_st_m2_2 l10n_at_hr_payroll_private.kv2203_grp_m2 2 nach 2 VWGJ 3095.50 2026-01-01 2
47 kv2203_st_m2_3 l10n_at_hr_payroll_private.kv2203_grp_m2 3 nach 4 VWGJ 3297.98 2026-01-01 4
48 kv2203_st_m2_4 l10n_at_hr_payroll_private.kv2203_grp_m2 4 nach 6 VWGJ 3500.52 2026-01-01 6
49 kv2203_st_m2_5 l10n_at_hr_payroll_private.kv2203_grp_m2 5 nach 8 VWGJ 3703.01 2026-01-01 8
50 kv2203_st_m2_6 l10n_at_hr_payroll_private.kv2203_grp_m2 6 nach 10 VWGJ 3905.53 2026-01-01 10
51 kv2203_st_m2_7 l10n_at_hr_payroll_private.kv2203_grp_m2 7 nach 12 VWGJ 4079.13 2026-01-01 12
52 kv2203_st_m2_8 l10n_at_hr_payroll_private.kv2203_grp_m2 8 nach 15 VWGJ 4397.37 2026-01-01 15
53 kv2203_st_m2fs_1 l10n_at_hr_payroll_private.kv2203_grp_m2fs 1 im 1. u 2. VWGJ 3241.68 2026-01-01 0
54 kv2203_st_m2fs_2 l10n_at_hr_payroll_private.kv2203_grp_m2fs 2 nach 2 VWGJ 3241.68 2026-01-01 2
55 kv2203_st_m2fs_3 l10n_at_hr_payroll_private.kv2203_grp_m2fs 3 nach 4 VWGJ 3453.74 2026-01-01 4
56 kv2203_st_m2fs_4 l10n_at_hr_payroll_private.kv2203_grp_m2fs 4 nach 6 VWGJ 3665.79 2026-01-01 6
57 kv2203_st_m2fs_5 l10n_at_hr_payroll_private.kv2203_grp_m2fs 5 nach 8 VWGJ 3877.83 2026-01-01 8
58 kv2203_st_m2fs_6 l10n_at_hr_payroll_private.kv2203_grp_m2fs 6 nach 10 VWGJ 4089.90 2026-01-01 10
59 kv2203_st_m2fs_7 l10n_at_hr_payroll_private.kv2203_grp_m2fs 7 nach 12 VWGJ 4271.68 2026-01-01 12
60 kv2203_st_m2fs_8 l10n_at_hr_payroll_private.kv2203_grp_m2fs 8 nach 15 VWGJ 4605.01 2026-01-01 15
61 kv2203_st_m3_1 l10n_at_hr_payroll_private.kv2203_grp_m3 1 im 1. u 2. VWGJ 3542.72 2026-01-01 0
62 kv2203_st_m3_2 l10n_at_hr_payroll_private.kv2203_grp_m3 2 nach 2 VWGJ 3542.72 2026-01-01 2
63 kv2203_st_m3_3 l10n_at_hr_payroll_private.kv2203_grp_m3 3 nach 4 VWGJ 3774.51 2026-01-01 4
64 kv2203_st_m3_4 l10n_at_hr_payroll_private.kv2203_grp_m3 4 nach 6 VWGJ 4006.27 2026-01-01 6
65 kv2203_st_m3_5 l10n_at_hr_payroll_private.kv2203_grp_m3 5 nach 8 VWGJ 4238.05 2026-01-01 8
66 kv2203_st_m3_6 l10n_at_hr_payroll_private.kv2203_grp_m3 6 nach 10 VWGJ 4469.81 2026-01-01 10
67 kv2203_st_m3_7 l10n_at_hr_payroll_private.kv2203_grp_m3 7 nach 12 VWGJ 4668.45 2026-01-01 12
68 kv2203_st_m3_8 l10n_at_hr_payroll_private.kv2203_grp_m3 8 nach 15 VWGJ 5032.68 2026-01-01 15
69 kv2748_st_i_1 l10n_at_hr_payroll_private.kv2748_grp_i 1 im 1. u 2. VWGJ 1960.54 2026-01-01 0
70 kv2748_st_i_2 l10n_at_hr_payroll_private.kv2748_grp_i 2 nach 2 VWGJ 2058.43 2026-01-01 2
71 kv2748_st_i_3 l10n_at_hr_payroll_private.kv2748_grp_i 3 nach 4 VWGJ 2190.76 2026-01-01 4
72 kv2748_st_i_4 l10n_at_hr_payroll_private.kv2748_grp_i 4 nach 6 VWGJ 2323.12 2026-01-01 6
73 kv2748_st_i_5 l10n_at_hr_payroll_private.kv2748_grp_i 5 nach 8 VWGJ 2455.45 2026-01-01 8
74 kv2748_st_i_6 l10n_at_hr_payroll_private.kv2748_grp_i 6 nach 10 VWGJ 2588.72 2026-01-01 10
75 kv2748_st_i_7 l10n_at_hr_payroll_private.kv2748_grp_i 7 nach 12 VWGJ 2703.75 2026-01-01 12
76 kv2748_st_i_8 l10n_at_hr_payroll_private.kv2748_grp_i 8 nach 15 VWGJ 2914.69 2026-01-01 15
77 kv2748_st_ii_1 l10n_at_hr_payroll_private.kv2748_grp_ii 1 im 1. u 2. VWGJ 2199.15 2026-01-01 0
78 kv2748_st_ii_2 l10n_at_hr_payroll_private.kv2748_grp_ii 2 nach 2 VWGJ 2350.62 2026-01-01 2
79 kv2748_st_ii_3 l10n_at_hr_payroll_private.kv2748_grp_ii 3 nach 4 VWGJ 2502.07 2026-01-01 4
80 kv2748_st_ii_4 l10n_at_hr_payroll_private.kv2748_grp_ii 4 nach 6 VWGJ 2655.45 2026-01-01 6
81 kv2748_st_ii_5 l10n_at_hr_payroll_private.kv2748_grp_ii 5 nach 8 VWGJ 2809.08 2026-01-01 8
82 kv2748_st_ii_6 l10n_at_hr_payroll_private.kv2748_grp_ii 6 nach 10 VWGJ 2962.72 2026-01-01 10
83 kv2748_st_ii_7 l10n_at_hr_payroll_private.kv2748_grp_ii 7 nach 12 VWGJ 3094.32 2026-01-01 12
84 kv2748_st_ii_8 l10n_at_hr_payroll_private.kv2748_grp_ii 8 nach 15 VWGJ 3335.74 2026-01-01 15
85 kv2748_st_iii_1 l10n_at_hr_payroll_private.kv2748_grp_iii 1 im 1. u 2. VWGJ 2727.40 2026-01-01 0
86 kv2748_st_iii_2 l10n_at_hr_payroll_private.kv2748_grp_iii 2 nach 2 VWGJ 2918.34 2026-01-01 2
87 kv2748_st_iii_3 l10n_at_hr_payroll_private.kv2748_grp_iii 3 nach 4 VWGJ 3109.21 2026-01-01 4
88 kv2748_st_iii_4 l10n_at_hr_payroll_private.kv2748_grp_iii 4 nach 6 VWGJ 3300.16 2026-01-01 6
89 kv2748_st_iii_5 l10n_at_hr_payroll_private.kv2748_grp_iii 5 nach 8 VWGJ 3491.06 2026-01-01 8
90 kv2748_st_iii_6 l10n_at_hr_payroll_private.kv2748_grp_iii 6 nach 10 VWGJ 3682.00 2026-01-01 10
91 kv2748_st_iii_7 l10n_at_hr_payroll_private.kv2748_grp_iii 7 nach 12 VWGJ 3845.61 2026-01-01 12
92 kv2748_st_iii_8 l10n_at_hr_payroll_private.kv2748_grp_iii 8 nach 15 VWGJ 4145.62 2026-01-01 15
93 kv2748_st_iv_1 l10n_at_hr_payroll_private.kv2748_grp_iv 1 im 1. u 2. VWGJ 3415.01 2026-01-01 0
94 kv2748_st_iv_2 l10n_at_hr_payroll_private.kv2748_grp_iv 2 nach 2 VWGJ 3654.05 2026-01-01 2
95 kv2748_st_iv_3 l10n_at_hr_payroll_private.kv2748_grp_iv 3 nach 4 VWGJ 3893.09 2026-01-01 4
96 kv2748_st_iv_4 l10n_at_hr_payroll_private.kv2748_grp_iv 4 nach 6 VWGJ 4132.15 2026-01-01 6
97 kv2748_st_iv_5 l10n_at_hr_payroll_private.kv2748_grp_iv 5 nach 8 VWGJ 4371.19 2026-01-01 8
98 kv2748_st_iv_6 l10n_at_hr_payroll_private.kv2748_grp_iv 6 nach 10 VWGJ 4610.26 2026-01-01 10
99 kv2748_st_iv_7 l10n_at_hr_payroll_private.kv2748_grp_iv 7 nach 12 VWGJ 4815.15 2026-01-01 12
100 kv2748_st_iv_8 l10n_at_hr_payroll_private.kv2748_grp_iv 8 nach 15 VWGJ 5190.77 2026-01-01 15
101 kv2748_st_ivm_1 l10n_at_hr_payroll_private.kv2748_grp_ivm 1 im 1.-4. VWGJ 3415.01 2026-01-01 0
102 kv2748_st_ivm_2 l10n_at_hr_payroll_private.kv2748_grp_ivm 2 nach 4 VWGJ 3654.05 2026-01-01 4
103 kv2748_st_ivm_3 l10n_at_hr_payroll_private.kv2748_grp_ivm 3 nach 6 VWGJ 3893.09 2026-01-01 6
104 kv2748_st_ivm_4 l10n_at_hr_payroll_private.kv2748_grp_ivm 4 nach 8 VWGJ 4132.15 2026-01-01 8
105 kv2748_st_ivm_5 l10n_at_hr_payroll_private.kv2748_grp_ivm 5 nach 10 VWGJ 4371.19 2026-01-01 10
106 kv2748_st_ivm_6 l10n_at_hr_payroll_private.kv2748_grp_ivm 6 nach 12 VWGJ 4610.26 2026-01-01 12
107 kv2748_st_ivm_7 l10n_at_hr_payroll_private.kv2748_grp_ivm 7 nach 14 VWGJ 4815.15 2026-01-01 14
108 kv2748_st_ivm_8 l10n_at_hr_payroll_private.kv2748_grp_ivm 8 nach 17 VWGJ 5047.10 2026-01-01 17
109 kv2748_st_v_1 l10n_at_hr_payroll_private.kv2748_grp_v 1 im 1. u 2. VWGJ 4299.70 2026-01-01 0
110 kv2748_st_v_2 l10n_at_hr_payroll_private.kv2748_grp_v 2 nach 2 VWGJ 4594.04 2026-01-01 2
111 kv2748_st_v_3 l10n_at_hr_payroll_private.kv2748_grp_v 3 nach 4 VWGJ 4888.37 2026-01-01 4
112 kv2748_st_v_4 l10n_at_hr_payroll_private.kv2748_grp_v 4 nach 6 VWGJ 5182.71 2026-01-01 6
113 kv2748_st_v_5 l10n_at_hr_payroll_private.kv2748_grp_v 5 nach 8 VWGJ 5477.00 2026-01-01 8
114 kv2748_st_v_6 l10n_at_hr_payroll_private.kv2748_grp_v 6 nach 10 VWGJ 5771.34 2026-01-01 10
115 kv2748_st_v_7 l10n_at_hr_payroll_private.kv2748_grp_v 7 nach 12 VWGJ 6023.65 2026-01-01 12
116 kv2748_st_v_8 l10n_at_hr_payroll_private.kv2748_grp_v 8 nach 15 VWGJ 6486.15 2026-01-01 15
117 kv2748_st_vom_1 l10n_at_hr_payroll_private.kv2748_grp_vom 1 im 1.-6. VWGJ 4299.70 2026-01-01 0
118 kv2748_st_vom_2 l10n_at_hr_payroll_private.kv2748_grp_vom 2 nach 6 VWGJ 4594.04 2026-01-01 6
119 kv2748_st_vom_3 l10n_at_hr_payroll_private.kv2748_grp_vom 3 nach 8 VWGJ 4888.37 2026-01-01 8
120 kv2748_st_vom_4 l10n_at_hr_payroll_private.kv2748_grp_vom 4 nach 10 VWGJ 5182.71 2026-01-01 10
121 kv2748_st_vom_5 l10n_at_hr_payroll_private.kv2748_grp_vom 5 nach 12 VWGJ 5477.00 2026-01-01 12
122 kv2748_st_vi_1 l10n_at_hr_payroll_private.kv2748_grp_vi 1 im 1. u 2. VWGJ 6021.80 2026-01-01 0
123 kv2748_st_vi_2 l10n_at_hr_payroll_private.kv2748_grp_vi 2 nach 2 VWGJ 6762.62 2026-01-01 2
124 kv2748_st_vi_3 l10n_at_hr_payroll_private.kv2748_grp_vi 3 nach 5 VWGJ 7503.50 2026-01-01 5
125 kv2748_st_meister_1 l10n_at_hr_payroll_private.kv2748_grp_meister 1 im 1. u 2. VWGJ 4130.76 2026-01-01 0
126 kv2748_st_meister_2 l10n_at_hr_payroll_private.kv2748_grp_meister 2 nach 2 VWGJ 4130.76 2026-01-01 2
127 kv2748_st_meister_3 l10n_at_hr_payroll_private.kv2748_grp_meister 3 nach 4 VWGJ 4303.13 2026-01-01 4
128 kv2748_st_meister_4 l10n_at_hr_payroll_private.kv2748_grp_meister 4 nach 6 VWGJ 4475.48 2026-01-01 6
129 kv2748_st_meister_5 l10n_at_hr_payroll_private.kv2748_grp_meister 5 nach 8 VWGJ 4647.84 2026-01-01 8
130 kv2748_st_meister_6 l10n_at_hr_payroll_private.kv2748_grp_meister 6 nach 10 VWGJ 4820.17 2026-01-01 10
131 kv2748_st_meister_7 l10n_at_hr_payroll_private.kv2748_grp_meister 7 nach 12 VWGJ 4992.53 2026-01-01 12
132 kv2748_st_meister_8 l10n_at_hr_payroll_private.kv2748_grp_meister 8 nach 15 VWGJ 5270.29 2026-01-01 15
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- AP4: EFZ-Stufen — AngG § 8 Abs 1 / EFZG § 2 Abs 1 (Gleichlauf -->
<!-- BGBl I 153/2017; EFZG RIS GesNr 10008308, AngG lokal — beide -->
<!-- verifiziert 2026-09-12): 6/8/10/12 Wochen je vollendete -->
<!-- Dienstjahre 0/1/15/25; zuzüglich je 4 Wochen halbes Entgelt. -->
<!-- AU/BK-Sonderkontingent § 8 Abs 2a AngG / § 2 Abs 5 EFZG: -->
<!-- 8 Wochen, ab 15 Dienstjahren 10 Wochen — ohne Halbsatz, je -->
<!-- Anlassfall. KV/BV-Kalenderjahr-Umstellung (§ 8 Abs 9 AngG) = -->
<!-- dokumentierte Option, hier nicht abgebildet. -->
<record id="hr_rule_parameter_at_efz_stufen" model="hr.rule.parameter">
<field name="name">EFZ-Stufen Krankenstand (AngG § 8 / EFZG § 2)</field>
<field name="code">at_efz_stufen</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_efz_stufen"
model="hr.rule.parameter.value">
<field name="rule_parameter_id"
ref="hr_rule_parameter_at_efz_stufen"/>
<field name="date_from">2018-07-01</field>
<field name="parameter_value">{'stufen': [[0, 6], [1, 8], [15, 10], [25, 12]], 'halb_wochen': 4, 'au_wochen': 8, 'au2_wochen': 10, 'au_ab_jahre': 15}</field>
</record>
</odoo>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Wr. Dienstgeberabgabe („U-Bahn-Steuer“): § 5 — 2 € je -->
<!-- Dienstnehmer:in und angefangene Woche. Quelle: RIS LrW, -->
<!-- Gesetzesnummer 20000133, geltende Fassung 12. 09. 2026. -->
<!-- Rückerstattungsgrenzen § 7 (218,02 / 2.180,19 €, +20/+10 %) -->
<!-- sind Jahreswerte der Erklärung — kein Abrechnungsparameter. -->
<record id="hr_rule_parameter_at_wien_dag_satz" model="hr.rule.parameter">
<field name="name">Wiener Dienstgeberabgabe je Woche (§ 5 Wr. DAG)</field>
<field name="code">at_wien_dag_satz</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_wien_dag_satz"
model="hr.rule.parameter.value">
<field name="rule_parameter_id"
ref="hr_rule_parameter_at_wien_dag_satz"/>
<field name="date_from">2012-06-01</field>
<field name="parameter_value">2.0</field>
</record>
</odoo>
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- AP4 EFZG/Teilentgelt: Reduktion des Dienstgeber-Entgelts in -->
<!-- der halben Phase (50 %) und der ÖGK-Krankengeld-Phase (100 %) -->
<!-- — Entgeltausfallsprinzip je Arbeitstag (AngG § 8 / EFZG § 2; -->
<!-- SV/Lohnsteuer laufen über die reduzierte laufende Basis, -->
<!-- lb-krs-07). Reihenfolge nach allen laufenden Bezügen (PRAM -->
<!-- seq 40), vor B13/SZ. -->
<!-- ============================================================ -->
<record id="rule_teil_entgelt_halb" model="hr.salary.rule">
<field name="name">Teilentgelt Krankenstand (halbes Entgelt, 4 Wochen)</field>
<field name="code">TEIL_ENTGELT_HALB</field>
<field name="sequence" eval="45"/>
<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[
efz = payslip._at_efz_status()
result = bool(efz and efz['halb_arbeitstage'])
]]></field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_python_compute"><![CDATA[
# Halbes Entgelt für die Halb-Phase-Arbeitstage (AngG § 8 Abs 1:
# je weitere vier Wochen das halbe Entgelt): Minderung um 50 % des
# vollen Tagesentgelts (Tagesatz ohne Teilentgelt-Reduktionen).
efz = payslip._at_efz_status()
result = -payslip._at_efz_tagesatz(result_rules) * 0.5 \
* len(efz['halb_arbeitstage'])
]]></field>
</record>
<record id="rule_krankentgelt_phase" model="hr.salary.rule">
<field name="name">Krankengeld-Phase (ÖGK; kein Dienstgeber-Entgelt)</field>
<field name="code">KRANKENTGELT_PHASE</field>
<field name="sequence" eval="46"/>
<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[
efz = payslip._at_efz_status()
result = bool(efz and efz['oegk_arbeitstage'])
]]></field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_python_compute"><![CDATA[
# ÖGK-Krankengeld-Phase: erschöpfter EFZ-Anspruch — der Dienstgeber
# schuldet kein Entgelt (Krankengeld zahlt die ÖGK, lb-krs-02);
# die fiktive BV-Grundlage läuft separat (§ 7 Abs 3 BMSVG).
efz = payslip._at_efz_status()
result = -payslip._at_efz_tagesatz(result_rules) \
* len(efz['oegk_arbeitstage'])
]]></field>
</record>
</odoo>
@@ -250,4 +250,26 @@ result = payslip._at_kommst(result_rules)
result = payslip._at_dz(result_rules)
]]></field>
</record>
<!-- Wiener Dienstgeberabgabe („U-Bahn-Steuer“, Wr. DAG § 5): -->
<!-- Kopfpauschale 2 € je DN/angefangene Woche, Company-Flag -->
<!-- l10n_at_wien_dag; Befreiungen/Wochenlogik s. models/wien_dag.py -->
<record id="rule_wien_dag" model="hr.salary.rule">
<field name="name">Wiener Dienstgeberabgabe (Wr. DAG)</field>
<field name="code">WIEN_DAG</field>
<field name="sequence" eval="95"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_dg"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python"><![CDATA[
ergebnis = payslip._at_wien_dag()
result = bool(ergebnis and ergebnis['betrag'] > 0)
]]></field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="amount_python_compute"><![CDATA[
ergebnis = payslip._at_wien_dag()
result = ergebnis['betrag']
]]></field>
</record>
</odoo>
@@ -0,0 +1,92 @@
# Rechenfälle EFZG/Teilentgelt (AP4)
Modell: `models/efzg.py` (EFZ-Phasenmaschine, fiktive BV),
Regeln `TEIL_ENTGELT_HALB`/`KRANKENTGELT_PHASE`
(`data/salary_rules_efzg_private.xml`), Parameter `at_efz_stufen`.
## Rechtsquellen (verifiziert 2026-09-12)
- **AngG § 8 Abs 1/2/2a** (lokal, RIS) und **EFZG § 2** (RIS GesNr
10008308, geltende Fassung): Stufen 6/8/10/12 Wochen je vollendeten
Dienstjahren 0 / 1 / 15 / 25, zuzüglich je 4 Wochen halbes Entgelt;
Gleichlauf seit 1. 7. 2018 (BGBl I 153/2017).
- **Wissensbasis lb-krs-08** (Arbeitsjahrprinzip, Kalendertage,
Stufensprung verlängert, Feiertage zählen nicht, AU-Sonderkontingent
8/10 Wochen ohne Halbsatz), **lb-krs-07** (SV/Lohnsteuer/BMSVG:
laufender Bezug; fiktive BV = 50 % Vormonatsentgelt ohne SZ,
§ 7 Abs 3 BMSVG; EFZ < 50 % beitragsfrei — Halbsatz 50 % ist
pflichtig), **lb-krs-02** (Krankengeld zahlt ÖGK, nicht der
Payslip).
- „Krankengeldumlage § 62 ASVG" = Falsch-Prämise
(RECHTSQUELLEN-Privat, 2026-09-10 ausgeräumt) — kein Baustein.
## Maschinenmodell
- **Kalendertage-Maschine:** Kontingentverbrauch in Kalendertagen je
Arbeitsjahr (Eintrittsdatum-Rhythmus); Krankheits- und AU-Topf
getrennt (AU nur voll, § 8 Abs 2a AngG / § 2 Abs 5 EFZG).
Stufenbewertung je Tag (Stufensprung im laufenden Stand verlängert).
- **Entgelt-Minderung je Arbeitstag** (Work Entries ATP_KRANK/ATP_AU):
Tagesatz = laufende Monatsbasis (ohne Teilentgelt-Reduktionen) ÷
Arbeitstage des Kalendermonats — Entgeltausfallsprinzip; negative
Brutto-Regeln `TEIL_ENTGELT_HALB` (50 %/AT) und
`KRANKENTGELT_PHASE` (100 %/AT) mit Bemessung `laufend`
SV/Lohnsteuer/LNK folgen automatisch über die reduzierte Basis.
- **Fiktive BV (§ 7 Abs 3 BMSVG):** während des Krankengeldbezugs
(Halb- + ÖGK-Phase) 50 % des Entgelts des Kalendermonats vor dem
Versicherungsfall (Episode = zusammenhängender Block), aliquot nach
Kalendertagen (30-Tage-Konvention), im
`_at_bv_basis_laufen`-Hook — fließt in BVG-Zeile und mBGM-BV-Basis.
## RF-EFZ-1 — Voll/Halb/ÖGK-Übergang (1. Arbeitsjahr, 6+4 Wochen)
Eintritt 1. 7. 2026 (Stufe 6 Wochen), KV-Gruppe II/1
(2.059,41 €), Krankenstand 1. 9.15. 11. 2026 (76 Kalendertage):
- Voll: Kalendertage 142 (1. 9.12. 10.) → September ohne Reduktion;
Oktober Arbeitstage 13.31. 10. (14 AT) → Halbphase.
- Halb: Kalendertage 4370 (13. 10.9. 11.); ÖGK ab 71 (10. 11.).
- November (21 AT): halb 6 AT (2.6., 9. 11.), ÖGK 4 AT
(10.13. 11.); Tagesatz 2.059,41 ÷ 21 = 98,07 € →
`TEIL_ENTGELT_HALB` = 294,21 €, `KRANKENTGELT_PHASE` = 392,28 €.
## RF-EFZ-2 — Kontingent-Zusammenrechnung
Wiederholte Verhinderung im selben Arbeitsjahr verbraucht dasselbe
Kalendertage-Kontingent (§ 8 Abs 2 AngG): 1.9.20.9. (20 KT) +
1.10.20.10. (20 KT) + 25.10.15.11. → KT 4142 voll, ab KT 43 halb
— der dritte Krankenstand startet in der Halbphase.
## RF-EFZ-3 — Stufe 8 Wochen (2.15. Jahr)
Eintritt 1. 7. 2024 (1 vollendetes Jahr): Stufe 8 Wochen — erst
Kalendertag 57 fällt aus der Vollphase.
## RF-EFZ-4 — AU-Sonderkontingent
Nach erschöpftem Krankheits-Kontingent gebührt für Arbeitsunfall/
Berufskrankheit (eigene Urlaubsart, WET `ATP_AU`) das
Sonderkontingent 8 Wochen (ab 15 Dienstjahren 10) vollständig —
unabhängig vom Krankheits-Topf, ohne Halbsatz.
## RF-EFZ-5 — Fiktive BV in der Krankengeldphase
November 2026 (RF-EFZ-1): Halb- + ÖGK-Kalendertage 15 → fiktive BG
= 50 % × Vormonatsbasis (Oktoberslip, 2.059,41 €) × 15/30 = 514,85 €;
BV-Basis = reduziertes laufendes Entgelt (1.372,93 €) + 514,85 €;
BVG = 1,53 % × 1.887,78 € = 28,92 €.
## Offene Punkte / dokumentierte Reste
- **Kalenderjahr-Umstellung** (§ 8 Abs 9 AngG / § 2 Abs 8 EFZG per
KV/BV): Merkmal + Sonderrechnung Umstellungsjahr — Option.
- **ÜStd-/Provisionsdurchschnitte** (13 Wochen / 12 Monate) bei
wechselnden Entgelten — vorbereitete Bezugsbasis folgt mit der
Zeit-Zuschlags-Engine.
- **SZ-Kürzung** bei nicht vollem EFZ (9 ObA 209/00a, „kann"-Regel)
— KV-abhängig, Option.
- **Meldung/Nachweis, Urlaubsunterbrechung § 5 UrlG, Beendigung
§ 5 EFZG** (EFZ über Ende hinaus): prozessuale Regeln —
Fristen-/Hinweismodell mit AP8 (Arbeits- und Entgeltbestätigung).
- Erstfenster-Teilmonate mit Krankengeld-Phase: fiktive BG nur in
beitragspflichtigen Läufen (Randfall, dokumentiert).
@@ -9,4 +9,7 @@ from . import account_chart_private
from . import account_posting_private
from . import mbgm
from . import versichertenmeldung
from . import fristen_private
from . import fristen_private
from . import buchungsbeleg
from . import wien_dag
from . import efzg
@@ -213,13 +213,18 @@ class HrPayslip(models.Model):
return 0.0
basis = self._l10n_at_sv_basis_roh(result_rules)
if pflichtig_ab is None or pflichtig_ab <= self.date_from:
return basis
# 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()
if pflichtig_ab > self.date_to:
# LZP liegt zur Gänze im beitragsfreien Erstfenster.
return 0.0
ende = min(self.date_to,
tage = (min(self.date_to,
self.version_id.contract_date_end or self.date_to)
tage = (ende - pflichtig_ab).days + 1
- pflichtig_ab).days + 1
if tage <= 0:
return 0.0
return basis * tage / max(self._l10n_at_lohnsteuertage(), 1)
@@ -103,6 +103,10 @@ L10N_AT_BUCHUNGSMATRIX = {
'credit': ('3550',)},
'rule_dz': {'debit': ('6662',), 'debit_arbeiter': ('6602',),
'credit': ('3542',)},
# Wr. DAG („U-Bahn-Steuer“): Aufwand 6663 (l10n_at-Template,
# KB lb-lnk-04 verifiziert); Credit 3560 Referenzbuchhaltungs-
# Rest (Stadtkasse Wien — Kandidat, fehlend-Warnung des Hooks).
'rule_wien_dag': {'debit': ('6663',), 'credit': ('3560',)},
}
@@ -0,0 +1,32 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""Buchungsbeleg der Lohnbuchhaltung (AP2d): Druck der FI-Buchung
eines Lohnlaufs — die account.move je Lohnlauf (bzw. je Payslip, wenn
nicht gebatcht bucht wird) mit Konto, Belegtext, Soll/Haben und
Summen; Kontrollgrößen stimmen mit dem Monatsjournal überein
(GP6-Matrix, KFS/BW 6)."""
from odoo import models
class L10nAtPayrollBuchungsbeleg(models.AbstractModel):
_name = 'report.l10n_at_hr_payroll_private.report_buchungsbeleg'
_description = 'Buchungsbeleg Lohnlauf (AP2d)'
def _get_report_values(self, docids, data=None):
runs = self.env['hr.payslip.run'].browse(docids)
laeufe = []
for run in runs:
# Batch-Buchung hängt am Lauf, sonst je Payslip-Move
# (hr_payroll_account schreibt move_id je Slip bzw. je Run).
moves = (run.move_id + run.slip_ids.mapped('move_id')) \
.filtered(lambda m: m.state != 'cancel')
zeilen = moves.line_ids.filtered(
lambda l: l.debit or l.credit).sorted(
key=lambda l: (l.account_id.code or '', l.id))
laeufe.append({
'run': run,
'moves': moves,
'zeilen': zeilen,
'summe_soll': sum(zeilen.mapped('debit')),
'summe_haben': sum(zeilen.mapped('credit')),
})
return {'docs': runs, 'laeufe': laeufe}
@@ -0,0 +1,270 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""EFZG/Teilentgelt (AP4) — Entgeltfortzahlung im Krankenstand.
Rechtsgrundlagen (verifiziert 2026-09-12):
- **AngG § 8 Abs 1/2/2a** und **EFZG § 2 Abs 1/3, § 2 Abs 5**
(Gleichlauf seit 1. 7. 2018, BGBl I 153/2017; EFZG RIS GesNr
10008308, AngG lokal — Stufen 6/8/10/12 Wochen je vollendeten
Dienstjahren <1 / 1-14 / 15-24 / ≥25, zuzüglich je 4 Wochen halbes
Entgelt; wiederholte Verhinderungen im selben Arbeitsjahr
zusammengerechnet; AU/BK-Sonderkontingent 8/10 Wochen ohne
Halbsatz, je Anlassfall, unabhängig vom Krankheits-Topf).
- **lb-krs-08/02/07** (Wissensbasis, Layer 2): Arbeitsjahrprinzip,
Kalendertage als Zähleinheit, Stufensprung im laufenden Stand
verlängert; SV: volle EFZ beitragspflichtig, EFZ < 50 %
beitragsfrei (hier: Halbsatz = 50 % → pflichtig); Lohnsteuer:
laufender Bezug; **BMSVG § 7 Abs 3**: während des Krankengeldbezugs
(Halb- und ÖGK-Phase) fiktive BG = 50 % des Entgelts des
Kalendermonats vor dem Versicherungsfall, SZ ausgenommen.
- Krankengeld selbst zahlt die ÖGK (läuft nicht durch den
Dienstgeber-Payslip; lb-krs-02) — der Payroll-Anteil ist die
Entgelt-Reduktion des Dienstgebers, die fiktive BV-Grundlage und
später die Arbeits-/Entgeltbestätigung (AP8).
- „Krankengeldumlage § 62 ASVG" ist eine Falsch-Prämise
(RECHTSQUELLEN-Privat 2026-09-10) — kein Umlage-Baustein.
Zähleinheiten: Kontingente in **Kalendertagen** (lb-krs-08: Praxis),
bezogen aus den validierten Abwesenheiten (hr.leave der AT-Urlaubsarten
Krankenstand/Arbeitsunfall); die **Entgelt-Minderung** je
Arbeitstag aus dem Arbeitseintragssatz (Tagesatz = laufende
Monatsbasis ÷ Arbeitstage des Monats — Entgeltausfallsprinzip).
Feiertage im Krankenstand werden vom Work-Entry-Modell automatisch
nicht als Krankenstandstage erzeugt (Kalender) — die 100 %-
Feiertagsentgelt-Regel lb-krs-08 bleibt damit gewahrt.
"""
from datetime import date, timedelta
from odoo import fields, models
# WET-Codes der Abwesenheits-Töpfe (AP5-Seed).
_KRANK_WET = 'ATP_KRANK'
_AU_WET = 'ATP_AU'
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
l10n_at_efz_info = fields.Char(
string='AT EFZ-Status (Krankenstand)', compute='_compute_efz_info',
help='Phasenstatus des Krankenstands im Abrechnungszeitraum: '
'volle/halbe Entgeltfortzahlung, ÖGK-Krankengeld-Phase, '
'Kontingente und Stufe (AngG § 8 / EFZG § 2).')
def _compute_efz_info(self):
for slip in self:
efz = slip._at_efz_status()
if not efz:
slip.l10n_at_efz_info = ''
continue
slip.l10n_at_efz_info = (
'Stufe %d (%d+%d Wo) · voll %d AT · halb %d AT · '
'ÖGK %d AT' % (
efz['stufe_wochen'], efz['stufe_wochen'],
efz['halb_wochen'], len(efz['voll_arbeitstage']),
len(efz['halb_arbeitstage']),
len(efz['oegk_arbeitstage'])))
# ------------------------------------------------------------------
# EFZ-Phasenbestimmung
# ------------------------------------------------------------------
def _at_efz_leaves(self):
"""Validierte Krankenstand-/AU-Abwesenheiten des AN als
(start, ende, ist_au)-Kalenderspannen."""
self.ensure_one()
typen = self.env['hr.leave.type'].search([
('work_entry_type_id.code', 'in',
(_KRANK_WET, _AU_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,
leave.holiday_status_id.work_entry_type_id.code == _AU_WET)
for leave in leaves
if leave.request_date_from and leave.request_date_to]
def _at_efz_arbeitsjahr_start(self):
"""Beginn des Arbeitsjahres, das die Abrechnungsperiode
überwiegend enthält (Eintrittsdatum-Rhythmus, lb-krs-08)."""
self.ensure_one()
beginn = self.version_id.contract_date_start
if not beginn:
return None
jahre = beginn.year
while True:
aj_start = date(jahre, beginn.month, beginn.day) \
if not (beginn.month == 2 and beginn.day == 29) \
else date(jahre, 3, 1)
aj_naechstes = date(jahre + 1, aj_start.month, aj_start.day)
if aj_start <= self.date_from < aj_naechstes:
return aj_start
jahre += 1
if jahre > beginn.year + 80:
return None
def _at_efz_status(self):
"""EFZ-Phasen je Abrechnungsperiode: {'stufe_wochen',
'halb_wochen', 'voll_arbeitstage', 'halb_arbeitstage',
'oegk_arbeitstage', 'oegk_kalendertage', 'episode_beginn',
'info'} — None ohne Krankenstand/AU im Arbeitsjahr.
Kalendertage-Maschine: Die Krankheits- und AU-Tage des
Arbeitsjahres werden in Kalenderreihenfolge den Phasen
zugeteilt (voll bis Stufen-Kontingent, halb zuzüglich
4 Wochen, danach ÖGK); AU ausschließlich voll (kein
Halbsatz, § 8 Abs 2a AngG / § 2 Abs 5 EFZG). Ein
Stufensprung im laufenden Stand verlängert das Kontingent
(Stufe je Tag neu bewertet, lb-krs-08)."""
self.ensure_one()
version = self.version_id
aj_start = self._at_efz_arbeitsjahr_start()
if not aj_start:
return None
endtag = max(self.date_to, aj_start)
krank_tage, au_tage = {}, {}
episode_start = None
for von, bis, ist_au in self._at_efz_leaves():
tag = von
while tag <= bis:
if aj_start <= tag <= endtag:
topf = au_tage if ist_au else krank_tage
topf[tag] = True
tag += timedelta(days=1)
if not krank_tage and not au_tage:
return None
params = self.env['hr.rule.parameter']._get_parameter_from_code(
'at_efz_stufen', self.date_from, raise_if_not_found=False) or {}
stufen = sorted(
(int(jahre), int(wochen))
for jahre, wochen in params.get('stufen', []))
halb_wochen = int(params.get('halb_wochen', 4))
au_wochen = int(params.get('au_wochen', 8))
au2_wochen = int(params.get('au2_wochen', 10))
au_ab_jahre = int(params.get('au_ab_jahre', 15))
def stufe_fuer(dienstjahre, tag_ist_au):
"""Anspruchs-Wochen am Stichtag (vollendete Jahre)."""
wochen = stufen[0][1] if stufen else 6
for jahre, stufe in stufen:
if dienstjahre >= jahre:
wochen = stufe
if tag_ist_au:
wochen = au2_wochen if dienstjahre >= au_ab_jahre \
else au_wochen
return wochen
beginn = version.contract_date_start
phasen = {}
verbraucht_krank_voll = verbraucht_krank_halb = 0
verbraucht_au = 0
for tag in sorted(set(krank_tage) | set(au_tage)):
dienstjahre = (tag.year - beginn.year
- ((tag.month, tag.day) < (beginn.month,
beginn.day)))
if tag in au_tage:
if verbraucht_au < stufe_fuer(dienstjahre, True) * 7:
phasen[tag] = 'voll'
else:
phasen[tag] = 'oegk'
verbraucht_au += 1
continue
wochen = stufe_fuer(dienstjahre, False)
if verbraucht_krank_voll < wochen * 7:
phasen[tag] = 'voll'
verbraucht_krank_voll += 1
elif verbraucht_krank_halb < halb_wochen * 7:
phasen[tag] = 'halb'
verbraucht_krank_halb += 1
else:
phasen[tag] = 'oegk'
# Arbeitstage je Phase im Abrechnungszeitraum (Work Entries)
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', 'in', (_KRANK_WET, _AU_WET)),
])
voll, halb, oegk = [], [], []
for eintrag in eintraege:
phase = phasen.get(eintrag.date)
if phase == 'voll':
voll.append(eintrag.date)
elif phase == 'halb':
halb.append(eintrag.date)
elif phase == 'oegk':
oegk.append(eintrag.date)
# Episode-Beginn für die fiktive BV (§ 7 Abs 3 BMSVG):
# frühester zusammenhängender Verhinderungsblock (Lücke = neue
# Episode), dessen Phase die Halb-/ÖGK-Phase erreicht.
episode_beginn = None
for tag in sorted(phasen):
if phasen[tag] in ('halb', 'oegk'):
block = tag
while (block - timedelta(days=1)) in phasen:
block = block - timedelta(days=1)
episode_beginn = block
break
dienstjahre = (self.date_from.year - beginn.year
- ((self.date_from.month, self.date_from.day)
< (beginn.month, beginn.day)))
return {
'stufe_wochen': stufe_fuer(dienstjahre, False),
'halb_wochen': halb_wochen,
'voll_arbeitstage': sorted(set(voll)),
'halb_arbeitstage': sorted(set(halb)),
'oegk_arbeitstage': sorted(set(oegk)),
# Kalendertage der Phasen IM ABRECHNUNGSZEITRAUM — Basis
# der fiktiven-BV-Aliquote (monatsscharf).
'oegk_kalendertage': sorted(
tag for tag in phasen
if self.date_from <= tag <= self.date_to
and phasen[tag] == 'oegk'),
'halb_kalendertage': sorted(
tag for tag in phasen
if self.date_from <= tag <= self.date_to
and phasen[tag] == 'halb'),
'episode_beginn': episode_beginn,
}
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."""
self.ensure_one()
basis = self._l10n_at_sv_basis_roh(result_rules)
for code in ('TEIL_ENTGELT_HALB', 'KRANKENTGELT_PHASE'):
if result_rules is not None and code in result_rules \
and result_rules[code]['total']:
basis -= result_rules[code]['total']
arbeitstage = self._at_arbeitstage(self.date_from, self.date_to)
return basis / max(arbeitstage, 1)
def _at_bv_fiktiv_krankengeld(self):
"""Fiktive BV-Grundlage während des Krankengeldbezugs
(§ 7 Abs 3 BMSVG, lb-krs-07): 50 % des Entgelts des
Kalendermonats vor dem Versicherungsfall (SZ ausgenommen),
aliquot nach Kalendertagen des Bezugs (Halb- + ÖGK-Phase,
30-Tage-Konvention). Bezug = letzter validierter Lauf vor
Episode-Beginn; ohne solchen: der laufende Monat (kurze
Dienstverhältnisse, lb-krs-02)."""
self.ensure_one()
efz = self._at_efz_status()
if not efz or not (efz['halb_kalendertage']
or efz['oegk_kalendertage']):
return 0.0
tage = len(efz['halb_kalendertage']) \
+ len(efz['oegk_kalendertage'])
vormonat = None
if efz['episode_beginn']:
vormonat = self.env['hr.payslip'].search([
('employee_id', '=', self.employee_id.id),
('state', 'in', ('validated', 'paid')),
('date_to', '<', efz['episode_beginn']),
], order='date_from desc', limit=1)
basis_monat = vormonat._l10n_at_sv_basis_roh() if vormonat \
else self._l10n_at_sv_basis_roh()
return 0.5 * basis_monat * tage / 30.0
@@ -3,11 +3,20 @@
hr.employee — Muster ``hr_version_bgld.py`` des GemBG-Moduls.
Die Stufenlogik der Referenz-KVs läuft über Verwendungsgruppenjahre
(VWGJ, Biennal-/Triennalsprünge, § 17 der Rahmen-KVs); die Vorrückung
selbst ist GP2-Thema (Wizard analog GemBG), hier werden die
Vertragsfelder und der Katalog-Lookup bereitgestellt.
(VWGJ, Biennal-/Triennalsprünge, § 17 der Rahmen-KVs — verifiziert
SI-2203: Abs 3 Einreihung, Abs 4 Anzahl der angerechneten VWGJ durch
die Firmenleitung mit Dienstzettel-Mitteilung, Abs 5 Vorrückung am
Ersten des Erreichungsmonats). AP9 ergänzt die Vordienstzeiten:
Die Firma erkennt Vordienstzeiten in Monaten an (Dienstzettel),
daraus leitet der Einstufungs-Vorschlag die Erfahrungsstufe ab
(``vwgj_ab`` der Stufenzeilen) und den nächsten Vorrückungstermin.
Freigabe bewusst manuell (action_einstufung_uebernehmen).
"""
from odoo import fields, models
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
class HrVersion(models.Model):
@@ -31,6 +40,35 @@ class HrVersion(models.Model):
help="Individuell vereinbartes Monatsgehalt; überschreibt das "
"Kataloggehalt der Gruppe/Stufe (Günstigkeitsvergleich, "
"z. B. § 18 Anrechnung der Rahmen-KVs).")
l10n_at_vordienstzeit_monate = fields.Integer(
string='AT Angerechnete Vordienstzeiten (Monate)',
groups="hr_payroll.group_hr_payroll_user", tracking=True,
help="Von der Firmenleitung anerkannte Vordienstzeiten in "
"Monaten (§ 17 Abs 4 Rahmen-KV: Anzahl der angerechneten "
"Verwendungsgruppenjahre — Dienstzettel-Mitteilungspflicht). "
"Fließen in den Einstufungs-Vorschlag und den nächsten "
"Vorrückungstermin ein. AP9.")
l10n_at_vwgj_monate = fields.Integer(
string='AT VWGJ gesamt (Monate)',
compute='_compute_l10n_at_vwgj',
groups="hr_payroll.group_hr_payroll_user",
help="Verwendungsgruppenjahre-Stand heute: Dienstzeit-Monate "
"seit Vertragsbeginn zuzüglich angerechnener "
"Vordienstzeiten.")
l10n_at_vwgj_stufe_vorschlag = fields.Integer(
string='AT Erfahrungsstufen-Vorschlag (VWGJ)',
compute='_compute_l10n_at_vwgj',
groups="hr_payroll.group_hr_payroll_user",
help="Vorgeschlagene Erfahrungsstufe aus den vwgj_ab-Schwellen "
"der Gruppen-Tabelle (nach N VWGJ); 0 = Schwellen "
"nicht gepflegt → manuelle Einstufung maßgeblich.")
l10n_at_naechste_vorrueckung = fields.Date(
string='AT Nächste VWGJ-Vorrückung',
compute='_compute_l10n_at_vwgj',
groups="hr_payroll.group_hr_payroll_user",
help="§ 17 Abs 5 Rahmen-KV: Erster des Monats, in dem die "
"nächste VWGJ-Schwelle erreicht wird (Vertragsbeginn + "
"Schwelle Vordienstzeiten).")
currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id')
l10n_at_personalgruppe = fields.Selection(
@@ -50,10 +88,73 @@ class HrVersion(models.Model):
self.ensure_one()
return self.kv_gruppe_id._get_gehalt(self.erfahrungsstufe, datum)
# ------------------------------------------------------------------
# AP9: Vordienstzeiten → VWGJ-Einstufungsvorschlag (§ 17 Rahmen-KV)
# ------------------------------------------------------------------
@api.depends('kv_gruppe_id', 'contract_date_start',
'l10n_at_vordienstzeit_monate', 'erfahrungsstufe')
def _compute_l10n_at_vwgj(self):
"""VWGJ-Stand heute (Dienstzeit + Vordienstzeiten), Stufen-
Vorschlag aus den vwgj_ab-Schwellen der Gruppe und der nächste
Vorrückungstermin (Erster des Erreichungsmonats, § 17 Abs 5)."""
heute = fields.Date.context_today(self)
for version in self:
version.l10n_at_vwgj_monate = 0
version.l10n_at_vwgj_stufe_vorschlag = 0
version.l10n_at_naechste_vorrueckung = False
if not (version.kv_gruppe_id
and version.contract_date_start):
continue
delta = relativedelta(heute, version.contract_date_start)
dienstzeit_monate = delta.years * 12 + delta.months
version.l10n_at_vwgj_monate = max(
dienstzeit_monate
+ (version.l10n_at_vordienstzeit_monate or 0), 0)
jahre = version.l10n_at_vwgj_monate // 12
version.l10n_at_vwgj_stufe_vorschlag = \
version.kv_gruppe_id._get_stufe_fuer_vwgj(jahre, heute)
naechste = version.kv_gruppe_id._get_naechste_vwgj(
jahre, heute)
if naechste:
erreichung = version.contract_date_start \
+ relativedelta(
months=naechste * 12
- (version.l10n_at_vordienstzeit_monate or 0))
# § 17 Abs 5: Erster des Monats, in dem die erhöhte
# Anzahl der VWGJ erreicht wird.
version.l10n_at_naechste_vorrueckung = erreichung \
.replace(day=1)
def action_einstufung_uebernehmen(self):
"""Vorschlag als Erfahrungsstufe übernehmen — bewusst ein
expliziter Verwaltungsakt (§ 17 Abs 4: Firmenleitung, mit
Dienstzettel bekanntzugeben); kein automatisches Schreiben."""
for version in self:
if version.l10n_at_vwgj_stufe_vorschlag:
version.erfahrungsstufe = \
version.l10n_at_vwgj_stufe_vorschlag
class HrEmployee(models.Model):
_inherit = 'hr.employee'
l10n_at_vordienstzeit_monate = fields.Integer(
related="version_id.l10n_at_vordienstzeit_monate", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
l10n_at_vwgj_monate = fields.Integer(
related="version_id.l10n_at_vwgj_monate", inherited=True,
groups="hr_payroll.group_hr_payroll_user")
l10n_at_vwgj_stufe_vorschlag = fields.Integer(
related="version_id.l10n_at_vwgj_stufe_vorschlag", inherited=True,
groups="hr_payroll.group_hr_payroll_user")
l10n_at_naechste_vorrueckung = fields.Date(
related="version_id.l10n_at_naechste_vorrueckung", inherited=True,
groups="hr_payroll.group_hr_payroll_user")
def action_einstufung_uebernehmen(self):
"""Delegation an die Version (Button am Mitarbeiterformular)."""
self.mapped('version_id').action_einstufung_uebernehmen()
kv_id = fields.Many2one(
related="version_id.kv_id", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
@@ -176,6 +176,42 @@ class L10nAtPayrollKvGruppe(models.Model):
('gueltig_ab', '<=', datum)],
order='gueltig_ab desc', limit=1)
def _get_stufe_fuer_vwgj(self, vwgj_jahre, datum=None):
"""Erfahrungsstufe für vollendete Verwendungsgruppenjahre
(VWGJ) zum Stichtag — AP9. Basis: vwgj_ab der Stufenzeilen
(„nach N VWGJ"; 0 = ab Beginn). Gibt die höchste Stufe mit
vwgj_ab <= vwgj_jahre zurück; 0, wenn die Gruppe keine
gepflegten Schwellen hat (manuelle Einstufung maßgeblich).
Rahmen-KV § 17 Abs 3-5 (Biennal-/Triennalsprünge, Vorrückung
am Ersten des Erreichungsmonats)."""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
# -1 = Schwelle nicht gepflegt (ORM liest Integer-NULL als 0 —
# deshalb Konvention -1 statt NULL; Domain > -1 filtert beides).
zeilen = self.env['l10n.at.payroll.kv.gruppe.stufe'].search(
[('gruppe_id', '=', self.id),
('gueltig_ab', '<=', datum),
('vwgj_ab', '>', -1)])
beste = zeilen.filtered(lambda z: z.vwgj_ab <= vwgj_jahre)
if not beste:
return 0
return max(beste.mapped('stufe'))
def _get_naechste_vwgj(self, vwgj_jahre, datum=None):
"""Nächste VWGJ-Schwelle oberhalb der erreichten Jahre (für
den Vorrückungstermin, § 17 Abs 5) — None an der Höchststufe."""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
zeilen = self.env['l10n.at.payroll.kv.gruppe.stufe'].search(
[('gruppe_id', '=', self.id),
('gueltig_ab', '<=', datum),
('vwgj_ab', '>', -1)])
schwellen = sorted(set(zeilen.mapped('vwgj_ab')))
for schwelle in schwellen:
if schwelle > vwgj_jahre:
return schwelle
return None
def _max_stufe(self, datum=None):
"""Höchste Stufe mit gültigem Monatsgehalt zum Stichtag."""
self.ensure_one()
@@ -202,6 +238,13 @@ class L10nAtPayrollKvGruppeStufe(models.Model):
bezeichnung = fields.Char(
help="Stufen-Label der KV-Tabelle, z. B. 'im 1. u 2. VWGJ' "
"oder 'nach 15 VWGJ'.")
vwgj_ab = fields.Integer(
string='Ab (vollendete) VWGJ', index=True, default=-1,
help="Vollendete Verwendungsgruppenjahre, ab denen die Stufe "
"gebührt — aus dem Stufen-Label (§ 17 Abs 3-5 "
"Rahmen-KV: Biennal-/Triennalsprünge). 0 = ab Beginn; "
"-1 = Schwelle nicht gepflegt → kein Einstufungs-"
"Vorschlag (manuelle Einstufung maßgeblich). AP9.")
gehalt = fields.Monetary(currency_field='currency_id')
currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id)
@@ -0,0 +1,113 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""Wiener Dienstgeberabgabe („U-Bahn-Steuer") — Wr. DAG, RIS LrW
Gesetzesnummer 20000133, geltende Fassung 12. 09. 2026 (verifiziert,
Quelle .firecrawl/ris/wien/WDGAG_*.html; § 9: die Einzahlungen fließen
der Stadt Wien zu und sind zur Errichtung einer Untergrundbahn zu
verwenden):
- **§ 5:** 2 € je Dienstnehmer:in und je angefangene Woche eines
bestehenden Dienstverhältnisses; **§ 2:** Beschäftigungsort Wien
(feste Arbeitsstätte, sonst Wohnsitz des Dienstnehmers; Bau-
ausführungen > 6 Monate).
- **§ 3 Befreiungen:** Gebietskörperschaften; Überschreiten des
55. Lebensjahres; Behinderte/Opferfürsorge/BEinstG; Lehrverhältnisse
(BAG); ≤ 10 Wochenstunden; Hausbesorger; gesetzliche Mutter-
schaftsverbote und Karenzurlaub auf gesetzlichem Anspruch;
Präsenzdienst/Zivildienst.
- **§ 6:** Selbstberechnung — Vormonatsschuld bis 15. des Folgemonats
an die Stadtkasse; Jahreserklärung bis 31. 3. inklusive befreiter
Dienstverhältnisse (Fristenkalender-Arten wien_dag/wien_dag_jahres).
Wochenlogik (Praxis-Annahme, im Parallellauf gegen die Magistrats-
Praxis zu verifizieren): „angefangene Woche" = Kalenderwoche, deren
Montag im Abrechnungsmonat liegt; abgabepflichtig, wenn das Dienst-
verhältnis an mindestens einem Tag der Woche besteht. Karenz-Wochen
(validierte ATP_KARENZ-Work Entries) sind befreit. Nicht abgebildet
(bis AP 7): Mutterschaftsverbote, Präsenzdienst/Zivildienst, Haus-
besorger, Behinderten-Status — manuelle Prüfung, Rest dokumentiert.
"""
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import models
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
def _at_wien_dag(self):
"""Wr. DAG je Beitragsmonat: {'wochen', 'satz', 'betrag',
'info'} — None, wenn die Company nicht erfasst ist; 0-Wochen
bei Befreiung. Kopfpauschale, nicht lohnabhängig (§ 5)."""
self.ensure_one()
if not self.company_id.l10n_at_wien_dag:
return None
version = self.version_id
employee = self.employee_id
monat_start = self.date_from
monat_ende = self.date_to
info = []
# Befreiungen § 3 (monatsweise Bewertung)
if version.l10n_at_lehrling:
return {'wochen': 0, 'satz': 0.0, 'betrag': 0.0,
'info': 'Befreit: Lehrverhältnis (§ 3 lit d).'}
if employee.birthday:
vollendet55 = employee.birthday + relativedelta(years=55)
if vollendet55 <= monat_ende:
return {'wochen': 0, 'satz': 0.0, 'betrag': 0.0,
'info': 'Befreit: 55. Lebensjahr überschritten '
'(§ 3 lit b, %s).' % vollendet55}
kalender = version.resource_calendar_id
wochenstunden = sum(
(att.hour_to or 0.0) - (att.hour_from or 0.0)
for att in kalender.attendance_ids) \
if kalender else 0.0
if wochenstunden <= 10.0:
return {'wochen': 0, 'satz': 0.0, 'betrag': 0.0,
'info': 'Befreit: ≤ 10 Wochenstunden (§ 3 lit e).'}
# Dienstverhältnis-Bestand im Monat (§ 1)
von = max(monat_start,
version.contract_date_start or monat_start)
bis = min(monat_ende,
version.contract_date_end or monat_ende)
if von > bis:
return {'wochen': 0, 'satz': 0.0, 'betrag': 0.0,
'info': 'Kein bestehendes Dienstverhältnis im '
'Abrechnungsmonat.'}
# Karenz-Tage des Monats (§ 3 lit g — Rest via WET ATP_KARENZ)
karenz_tage = set()
if self.env['hr.work.entry']._fields.get('leave_id'):
eintraege = self.env['hr.work.entry'].search([
('employee_id', '=', employee.id),
('date', '>=', monat_start),
('date', '<=', monat_ende),
('state', '!=', 'cancelled'),
('work_entry_type_id.code', '=', 'ATP_KARENZ'),
])
karenz_tage = {e.date for e in eintraege}
# Wochen mit Montag im Monat; abgabepflichtig, wenn das
# Dienstverhältnis an mindestens einem Tag der Woche besteht
# (Schnittmenge [DV] ∩ [Woche] nicht leer).
wochen = 0
tag = monat_start + timedelta(
days=(7 - monat_start.weekday()) % 7)
while tag <= monat_ende:
woche_ende = tag + timedelta(days=6)
if von <= woche_ende and bis >= tag:
karenz_in_woche = any(
tag + timedelta(days=i) in karenz_tage
for i in range((woche_ende - tag).days + 1)
if tag + timedelta(days=i) <= monat_ende)
if karenz_in_woche:
info.append('Karenz-Woche ab %s befreit (§ 3 lit g)'
% tag)
else:
wochen += 1
tag += timedelta(days=7)
satz = float(self.env['hr.rule.parameter']
._get_parameter_from_code(
'at_wien_dag_satz', monat_start,
raise_if_not_found=False) or 0.0)
return {'wochen': wochen, 'satz': satz,
'betrag': wochen * satz, 'info': '; '.join(info)}
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- AP2d: Buchungsbeleg der Lohnbuchhaltung am Lohnlauf. -->
<!-- ============================================================ -->
<record id="action_report_buchungsbeleg" model="ir.actions.report">
<field name="name">Buchungsbeleg (Lohnbuchhaltung)</field>
<field name="model">hr.payslip.run</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">l10n_at_hr_payroll_private.report_buchungsbeleg</field>
<field name="report_file">l10n_at_hr_payroll_private.report_buchungsbeleg</field>
<field name="paperformat_id" ref="hr_payroll.paperformat_euro_light"/>
<field name="binding_model_id" ref="hr_payroll.model_hr_payslip_run"/>
<field name="binding_type">report</field>
</record>
<template id="report_buchungsbeleg">
<t t-call="web.html_container">
<t t-foreach="laeufe" t-as="l">
<t t-call="web.external_layout">
<div class="page">
<h2>Buchungsbeleg — <t t-out="l['run'].name"/></h2>
<p class="text-muted">
<t t-foreach="l['moves']" t-as="m">
Beleg <t t-out="m.name"/> (Journal <t t-out="m.journal_id.name"/>,
Datum <t t-out="m.date"/>,
Status <t t-out="m.state"/>);
</t>
</p>
<table class="table table-sm">
<thead>
<tr>
<th>Konto</th>
<th>Kontobezeichnung</th>
<th>Belegtext</th>
<th class="text-end">Soll</th>
<th class="text-end">Haben</th>
</tr>
</thead>
<tbody>
<tr t-foreach="l['zeilen']" t-as="z">
<td><t t-out="z.account_id.code"/></td>
<td><t t-out="z.account_id.name"/></td>
<td><t t-out="z.name"/></td>
<td class="text-end">
<span t-if="z.debit" t-out="z.debit"
t-options='{"widget": "monetary", "display_currency": l["run"].company_id.currency_id}'/>
</td>
<td class="text-end">
<span t-if="z.credit" t-out="z.credit"
t-options='{"widget": "monetary", "display_currency": l["run"].company_id.currency_id}'/>
</td>
</tr>
<tr>
<td colspan="3"><strong>Summe</strong></td>
<td class="text-end"><strong><span t-out="l['summe_soll']"
t-options='{"widget": "monetary", "display_currency": l["run"].company_id.currency_id}'/></strong></td>
<td class="text-end"><strong><span t-out="l['summe_haben']"
t-options='{"widget": "monetary", "display_currency": l["run"].company_id.currency_id}'/></strong></td>
</tr>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
</odoo>
@@ -7,3 +7,6 @@ from . import test_bv_private
from . import test_account_private
from . import test_meldewesen_private
from . import test_reports_private
from . import test_wien_dag_private
from . import test_einstufung_private
from . import test_efzg_private
@@ -52,10 +52,10 @@ class AccountPrivateTest(TransactionCase):
(RF-BUCH-4: Fallback statt Erfindung)."""
for code in ('6000', '6200', '6225', '6230', '6240', '6310',
'6440', '6500', '6560', '6600', '6601', '6602',
'6660', '6661', '6662'):
'6660', '6661', '6662', '6663'):
if code not in ohne:
self._konto(code, 'expense')
for code in ('3540', '3541', '3542', '3550', '3600'):
for code in ('3540', '3541', '3542', '3550', '3560', '3600'):
self._konto(code, 'liability_current', reconcile=True)
if '3850' not in ohne:
self._konto('3850', 'liability_payable', reconcile=True)
@@ -276,4 +276,33 @@ class AccountPrivateTest(TransactionCase):
'SVDG_KV', 'SVDG_PV', 'SVDG_AV', 'SVDG_UV', 'SVDG_WF',
'SVDG_IESG', 'SVDG_SZ_KV', 'SVDG_SZ_PV', 'SVDG_SZ_AV',
'SVDG_SZ_UV', 'SVDG_SZ_IESG'))
self.assertAlmostEqual(self._saldo(lines, '6500'), dg, places=2)
self.assertAlmostEqual(self._saldo(lines, '6500'), dg, places=2)
# ------------------------------------------------------------------
# AP2d: Buchungsbeleg-Report am Lohnlauf
# ------------------------------------------------------------------
def test_05_buchungsbeleg_report(self):
"""Buchungsbeleg am Lohnlauf: Konto/Belegtext/Soll/Haben der
FI-Buchung, Summen balanciert (Soll = Haben)."""
self._kontenplan()
self._hook()
employee = self._employee()
slip = self._slip(employee, 11, confirm=True)
run = self.env['hr.payslip.run'].create({
'name': 'Beleg-Lauf 11/2026',
'date_start': date(2026, 11, 1),
'date_end': date(2026, 11, 30),
})
slip.payslip_run_id = run.id
werte = self.env[
'report.l10n_at_hr_payroll_private.report_buchungsbeleg'
]._get_report_values(run.ids)
self.assertEqual(len(werte['laeufe']), 1)
lauf = werte['laeufe'][0]
self.assertTrue(lauf['zeilen'])
self.assertAlmostEqual(lauf['summe_soll'], lauf['summe_haben'],
places=2)
konten = {z.account_id.code for z in lauf['zeilen']}
self.assertIn('6200', konten) # Gehälter (Debit)
self.assertIn('3850', konten) # NET-Verbindlichkeit
self.assertIn('3600', konten) # SV-Verrechnung
@@ -0,0 +1,232 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""AP4: EFZG/Teilentgelt — Phasenmaschine (Voll/Halb/ÖGK),
Kontingent-Zusammenrechnung je Arbeitsjahr, Stufen, AU-Sonderkontingent,
fiktive BV (§ 7 Abs 3 BMSVG). Rechenfälle: docs/rechenfaelle/efzg.md."""
import datetime
from odoo import SUPERUSER_ID
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class EfzgPrivateTest(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 EFZG 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.krankenstand_typ = self.env.ref(
'l10n_at_hr_payroll_private.leave_type_at_krankenstand')
self.au_typ = self.env.ref(
'l10n_at_hr_payroll_private.leave_type_at_arbeitsunfall')
def _employee(self, eintritt):
employee = self.env['hr.employee'].create({
'name': 'EFZG Test'})
version = self.env['hr.version'].search(
[('employee_id', '=', employee.id)], limit=1)
version.write({
'contract_date_start': eintritt,
'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, 9, 1), datetime.date(2026, 12, 31))
return employee
def _krankenstand(self, employee, von, bis, typ=None):
leave = self.env['hr.leave'].create({
'name': 'Krank %s' % von,
'employee_id': employee.id,
'holiday_status_id': (typ or self.krankenstand_typ).id,
'request_date_from': von,
'request_date_to': bis,
})
leave.with_user(SUPERUSER_ID)._action_validate()
return leave
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': 'EFZG %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 test_01_voll_halb_oegk_uebergang(self):
"""RF-EFZ-1: 1. Arbeitsjahr 6+4 Wochen — September voll,
Oktober Halbphase ab 13. 10., November halb (6 AT) + ÖGK
(4 AT) mit Reduktionslinien."""
employee = self._employee(datetime.date(2026, 7, 1))
self._krankenstand(
employee, datetime.date(2026, 9, 1),
datetime.date(2026, 11, 15))
slip_sep = self._slip(employee, 9)
slip_nov = self._slip(employee, 11)
efz = slip_nov._at_efz_status()
self.assertEqual(efz['stufe_wochen'], 6)
self.assertEqual(efz['halb_wochen'], 4)
self.assertEqual(efz['halb_arbeitstage'], [
datetime.date(2026, 11, 2), datetime.date(2026, 11, 3),
datetime.date(2026, 11, 4), datetime.date(2026, 11, 5),
datetime.date(2026, 11, 6), datetime.date(2026, 11, 9)])
self.assertEqual(efz['oegk_arbeitstage'], [
datetime.date(2026, 11, 10), datetime.date(2026, 11, 11),
datetime.date(2026, 11, 12), datetime.date(2026, 11, 13)])
# Tagesatz 2.059,41 / 21 AT = 98,07 € (die Regeln rechnen
# mit der unreduzierten laufenden Basis — Ergebnis der
# Regelzeilen prüfen)
tagesatz = 2059.41 / 21
self.assertAlmostEqual(self._total(slip_nov, 'TEIL_ENTGELT_HALB'),
-tagesatz * 0.5 * 6, places=2)
self.assertAlmostEqual(
self._total(slip_nov, 'KRANKENTGELT_PHASE'),
-tagesatz * 4, places=2)
# September: zur Gänze Vollphase — keine Reduktion.
efz_sep = slip_sep._at_efz_status()
self.assertEqual(len(efz_sep['voll_arbeitstage']), 22)
self.assertFalse(efz_sep['halb_arbeitstage'])
self.assertFalse(efz_sep['oegk_arbeitstage'])
self.assertFalse(self._total(slip_sep, 'TEIL_ENTGELT_HALB'))
self.assertFalse(self._total(slip_sep, 'KRANKENTGELT_PHASE'))
def test_02_kontingent_wiederholung(self):
"""RF-EFZ-2: Drei Krankenstände im selben Arbeitsjahr zählen
auf dasselbe Kalendertage-Kontingent (42 KT) — der dritte
startet in der Halbphase."""
employee = self._employee(datetime.date(2026, 7, 1))
self._krankenstand(
employee, datetime.date(2026, 9, 1),
datetime.date(2026, 9, 20)) # KT 120
self._krankenstand(
employee, datetime.date(2026, 10, 1),
datetime.date(2026, 10, 20)) # KT 2140
self._krankenstand(
employee, datetime.date(2026, 10, 25),
datetime.date(2026, 11, 15)) # KT 41 ff.
slip_nov = self._slip(employee, 11)
efz = slip_nov._at_efz_status()
# 25./26. 10. (So/Mo) sind KT 41/42 (voll), ab 27. 10. halb
self.assertIn(datetime.date(2026, 11, 2), efz['halb_arbeitstage'])
self.assertFalse(datetime.date(2026, 11, 2)
in efz['oegk_arbeitstage'])
def test_03_stufe_acht_wochen(self):
"""RF-EFZ-3: Ab 1 vollendeten Dienstjahr 8 Wochen — die
Halbphase beginnt erst bei Kalendertag 57."""
employee = self._employee(datetime.date(2024, 7, 1))
self._krankenstand(
employee, datetime.date(2026, 9, 1),
datetime.date(2026, 11, 30))
slip_nov = self._slip(employee, 11)
efz = slip_nov._at_efz_status()
self.assertEqual(efz['stufe_wochen'], 8)
# Voll bis 26. 10. (KT 56), halb ab 27. 10., ÖGK ab 24. 11.
self.assertIn(datetime.date(2026, 11, 2), efz['halb_arbeitstage'])
self.assertIn(datetime.date(2026, 11, 24), efz['oegk_arbeitstage'])
def test_04_au_sonderkontingent(self):
"""RF-EFZ-4: Erschöpfter Krankheits-Topf — Arbeitsunfall
gebührt trotzdem voll (eigener Topf, kein Halbsatz)."""
employee = self._employee(datetime.date(2026, 7, 1))
self._krankenstand(
employee, datetime.date(2026, 9, 1),
datetime.date(2026, 11, 15)) # Topf erschöpft
self._krankenstand(
employee, datetime.date(2026, 11, 16),
datetime.date(2026, 11, 30), typ=self.au_typ)
slip_nov = self._slip(employee, 11)
efz = slip_nov._at_efz_status()
# Krank: 1.6. 11. halb, ab 10. 11. ÖGK; AU-Tage 16.30. 11.
# bleiben VOLL (Sonderkontingent).
for tag in (16, 17, 18, 19, 20):
self.assertIn(datetime.date(2026, 11, tag),
efz['voll_arbeitstage'])
self.assertFalse(datetime.date(2026, 11, 16)
in efz['oegk_arbeitstage'])
def test_05_fiktive_bv_krankengeldphase(self):
"""RF-EFZ-5: Fiktive BG in der Krankengeldphase — 50 % des
Vormonatsentgelts × Halb-/ÖGK-Kalendertage/30 (§ 7 Abs 3
BMSVG); BV-Basis = reduziertes Entgelt + fiktiver Anteil."""
employee = self._employee(datetime.date(2026, 7, 1))
self._slip(employee, 8) # Vormonats-Bezug
self._krankenstand(
employee, datetime.date(2026, 9, 1),
datetime.date(2026, 11, 15))
self._slip(employee, 10)
slip_nov = self._slip(employee, 11)
efz = slip_nov._at_efz_status()
# Episode-Beginn 1. 9. — Vormonat = August (voll entlohnt,
# unverkürzt), nicht der teilvergütete Oktober.
self.assertEqual(efz['episode_beginn'],
datetime.date(2026, 9, 1))
halb_kt = len(efz['halb_kalendertage'])
oegk_kt = len(efz['oegk_kalendertage'])
self.assertEqual(halb_kt, 9) # 1.9. 11.
self.assertEqual(oegk_kt, 6) # 10.15. 11.
# Fiktive BG = 50 % August-Entgelt × 15/30 Kalendertage.
fiktiv = slip_nov._at_bv_fiktiv_krankengeld()
vormonat = slip_nov.env['hr.payslip'].search([
('employee_id', '=', slip_nov.employee_id.id),
('state', 'in', ('validated', 'paid')),
('date_to', '<', efz['episode_beginn']),
], order='date_from desc', limit=1)
self.assertEqual(vormonat.date_from, datetime.date(2026, 8, 1))
self.assertAlmostEqual(fiktiv, 0.5 * vormonat._l10n_at_sv_basis_roh()
* 15 / 30.0, places=2)
# BV-Grundlage = reduziertes laufendes Entgelt + fiktiver
# Anteil (komponentenweise — der November-Lauf trägt
# zusätzlich eine B13-SZ-Position in der sonstigen Grundlage,
# § 6 BMSVG: 1,53 % inkl. SZ).
laufend = slip_nov._l10n_at_sv_basis_roh()
self.assertAlmostEqual(slip_nov._at_bv_basis_laufen(),
laufend + fiktiv, places=2)
self.assertAlmostEqual(self._total(slip_nov, 'BVG'),
(slip_nov._at_bv_basis_laufen()
+ slip_nov._at_bv_basis_sonst()) * 0.0153,
places=2)
# Ohne Krankengeld-Phase wäre die Grundlage um den fiktiven
# Anteil kleiner.
self.assertAlmostEqual(self._total(slip_nov, 'TEIL_ENTGELT_HALB'),
-(2059.41 / 21) * 0.5 * 6, places=2)
def test_06_ohne_krankenstand_keine_wirkung(self):
"""Ohne Abwesenheit: keine Phasen, keine fiktive BG, keine
Teilentgelt-Zeilen."""
employee = self._employee(datetime.date(2026, 7, 1))
slip = self._slip(employee, 11)
self.assertFalse(slip._at_efz_status())
self.assertEqual(slip._at_bv_fiktiv_krankengeld(), 0.0)
self.assertFalse(self._total(slip, 'TEIL_ENTGELT_HALB'))
self.assertFalse(self._total(slip, 'KRANKENTGELT_PHASE'))
@@ -0,0 +1,138 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""AP9: Vordienstzeiten → Einstufung. Privat: VWGJ-Ermittlung nach
§ 17 Rahmen-KV (Schwellen aus den Stufen-Labels, Vorrückung am
Ersten des Erreichungsmonats), Vorschlag mit manueller Freigabe."""
import datetime
from freezegun import freeze_time
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class EinstufungPrivateTest(TransactionCase):
def setUp(self):
super().setUp()
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')
employee = self.env['hr.employee'].create({
'name': 'Einstufung Test'})
self.version = self.env['hr.version'].search(
[('employee_id', '=', employee.id)], limit=1)
self.employee = employee
def test_01_vwgj_schwellen(self):
"""vwgj_ab-Schwellen aus den Stufen-Labels: 0/2/4/…/15 →
Stufenzuordnung je vollendeten VWGJ (Höchststufe gedeckelt)."""
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(0,
datetime.date(2026, 7, 1)),
1)
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(1,
datetime.date(2026, 7, 1)),
1)
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(2,
datetime.date(2026, 7, 1)),
2)
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(4,
datetime.date(2026, 7, 1)),
3)
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(8,
datetime.date(2026, 7, 1)),
5)
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(15,
datetime.date(2026, 7, 1)),
8)
# Höchststufe bleibt auch nach weiteren Jahren
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(20,
datetime.date(2026, 7, 1)),
8)
# Nächste Schwelle über den erreichten Jahren (§ 17 Abs 5)
self.assertEqual(
self.gruppe_ii._get_naechste_vwgj(2,
datetime.date(2026, 7, 1)),
4)
self.assertFalse(self.gruppe_ii._get_naechste_vwgj(
15, datetime.date(2026, 7, 1)))
# Schwellen erst ab Katalog-Gültigkeit
self.assertEqual(
self.gruppe_ii._get_stufe_fuer_vwgj(8,
datetime.date(2025, 12, 31)),
0)
def test_02_vwgj_vorschlag_und_uebernahme(self):
"""Dienstzeit + Vordienstzeiten → Stufenvorschlag; Übernahme
schreibt die Erfahrungsstufe (manueller Verwaltungsakt)."""
with freeze_time('2026-09-12'):
self.version.write({
'contract_date_start': datetime.date(2020, 1, 1),
'kv_id': self.kv2203.id,
'kv_gruppe_id': self.gruppe_ii.id,
'erfahrungsstufe': 1,
'l10n_at_vordienstzeit_monate': 24,
})
# Dienstzeit 80 Monate (2020-01 bis 2026-09) + 24 = 104
# VWGJ-Monate → 8 vollendete Jahre → Stufe 5 (nach 8 VWGJ).
self.assertEqual(self.version.l10n_at_vwgj_monate, 104)
self.assertEqual(self.version.l10n_at_vwgj_stufe_vorschlag, 5)
# Nächste Schwelle 10 VWGJ: erreicht im Monat
# 2020-01 + (120 24) Monate = 2028-01-01 (§ 17 Abs 5).
self.assertEqual(self.version.l10n_at_naechste_vorrueckung,
datetime.date(2028, 1, 1))
self.assertEqual(self.version.erfahrungsstufe, 1)
self.version.action_einstufung_uebernehmen()
self.assertEqual(self.version.erfahrungsstufe, 5)
def test_03_ohne_vordienstzeiten(self):
"""Ohne Vordienstzeiten zählt allein die Dienstzeit; ohne
Gruppen-Schwellen gibt es keinen Vorschlag."""
with freeze_time('2026-09-12'):
self.version.write({
'contract_date_start': datetime.date(2026, 7, 1),
'kv_id': self.kv2203.id,
'kv_gruppe_id': self.gruppe_ii.id,
'l10n_at_vordienstzeit_monate': 0,
})
self.assertEqual(self.version.l10n_at_vwgj_monate, 2)
self.assertEqual(self.version.l10n_at_vwgj_stufe_vorschlag, 1)
# Nächste Schwelle 2 VWGJ: erreicht 2026-07 + 24 Monate
# → 1. 7. 2028 (§ 17 Abs 5).
self.assertEqual(self.version.l10n_at_naechste_vorrueckung,
datetime.date(2028, 7, 1))
# Ohne Gruppe: kein Vorschlag
self.version.kv_gruppe_id = False
self.assertEqual(self.version.l10n_at_vwgj_stufe_vorschlag, 0)
self.assertFalse(self.version.l10n_at_naechste_vorrueckung)
def test_04_vwgj_ohne_schwellen_kein_vorschlag(self):
"""Gruppe ohne gepflegte vwgj_ab (Import ohne Labels) →
Vorschlag 0, manuelle Einstufung bleibt maßgeblich."""
gruppe = self.env['l10n.at.payroll.kv.gruppe'].create({
'kv_id': self.kv2203.id,
'code': 'TEST-NS',
'name': 'Test ohne Schwellen',
'lohn_basis': 'monatlich',
})
self.env['l10n.at.payroll.kv.gruppe.stufe'].create({
'gruppe_id': gruppe.id,
'stufe': 1,
'bezeichnung': 'Erste Stufe',
'gehalt': 2000.0,
'gueltig_ab': datetime.date(2026, 1, 1),
})
with freeze_time('2026-09-12'):
self.version.write({
'contract_date_start': datetime.date(2020, 1, 1),
'kv_gruppe_id': gruppe.id,
'l10n_at_vordienstzeit_monate': 60,
})
self.assertEqual(self.version.l10n_at_vwgj_stufe_vorschlag, 0)
@@ -0,0 +1,179 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""AP3: Wiener Dienstgeberabgabe (Wr. DAG, „U-Bahn-Steuer") —
Wochenlogik, Befreiungen § 3, Regel, Fristen. Rechtsgrundlage RIS LrW
GesNr 20000133 (Fassung 12. 09. 2026, verifiziert)."""
import datetime
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class WienDagPrivateTest(TransactionCase):
def setUp(self):
super().setUp()
self.company = self.env.company
self.company.write({
'l10n_at_beitragskontonummer': '1412345678',
'l10n_at_wien_dag': True,
})
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 Wien-DAG 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
employee = self.env['hr.employee'].create({
'name': 'Wien-DAG Test', 'birthday': '1990-05-15'})
version = self.env['hr.version'].search(
[('employee_id', '=', employee.id)], limit=1)
version.write({
'contract_date_start': datetime.date(2020, 1, 1),
'kv_id': self.kv2203.id,
'kv_gruppe_id': self.gruppe_ii.id,
'erfahrungsstufe': 1,
'resource_calendar_id': self.calendar.id,
})
self.employee = employee
self.version = version
def _slip(self, monat, jahr=2026):
erster = datetime.date(jahr, 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': 'Wien-DAG %d/%d' % (monat, jahr),
'employee_id': self.employee.id,
'struct_id': self.structure.id,
'date_from': erster,
'date_to': letzter,
})
slip.compute_sheet()
return slip
def _line_total(self, slip, code):
return sum(slip.line_ids.filtered(
lambda l: l.code == code).mapped('total'))
def test_01_voller_monat_fuenf_wochen(self):
"""November 2026: Montage 2./9./16./23./30. → 5 angefangene
Wochen × 2 € = 10 € Regelbetrag WIEN_DAG."""
slip = self._slip(11)
ergebnis = slip._at_wien_dag()
self.assertEqual(ergebnis['wochen'], 5)
self.assertAlmostEqual(ergebnis['betrag'], 10.0, places=2)
self.assertAlmostEqual(self._line_total(slip, 'WIEN_DAG'),
10.0, places=2)
def test_02_eintritt_und_wochenlogik(self):
"""Eintritt 15. 11. (Sonntag): Wochen ab 9. 11. mit DV-Bestand
(9./16./23./30.) → 4 × 2 = 8 €."""
self.version.contract_date_start = datetime.date(2026, 11, 15)
slip = self._slip(11)
ergebnis = slip._at_wien_dag()
self.assertEqual(ergebnis['wochen'], 4)
self.assertAlmostEqual(ergebnis['betrag'], 8.0, places=2)
def test_03_befreiungen(self):
"""§ 3: Lehrling, 55. Lebensjahr, ≤ 10 Wochenstunden — je 0 €;
ohne Company-Flag kein Ergebnis."""
# Lehrling
self.version.l10n_at_lehrling = True
slip = self._slip(11)
self.assertEqual(slip._at_wien_dag()['betrag'], 0.0)
self.version.l10n_at_lehrling = False
# 55. Lebensjahr überschritten (Geburtstag 1. 5. 1971 → 55 am
# 1. 5. 2026, vor Monatsende)
self.employee.birthday = '1971-05-01'
slip = self._slip(11)
ergebnis = slip._at_wien_dag()
self.assertEqual(ergebnis['betrag'], 0.0)
self.assertIn('55. Lebensjahr', ergebnis['info'])
self.employee.birthday = '1990-05-15'
# ≤ 10 Wochenstunden (Kalender 2 Tage à 4 h = 8 h)
klein = self.env['resource.calendar'].create({
'name': 'AT gering 8h',
'tz': 'Europe/Vienna',
'hours_per_day': 4,
'attendance_ids': [
(0, 0, {'name': 'Tag %d' % wd, 'dayofweek': str(wd),
'hour_from': 8.0, 'hour_to': 12.0})
for wd in range(2)
],
})
self.version.resource_calendar_id = klein
slip = self._slip(11)
ergebnis = slip._at_wien_dag()
self.assertEqual(ergebnis['betrag'], 0.0)
self.assertIn('10 Wochenstunden', ergebnis['info'])
self.version.resource_calendar_id = self.calendar
# Ohne Flag: keine Abgabe
self.company.l10n_at_wien_dag = False
slip = self._slip(11)
self.assertFalse(slip._at_wien_dag())
self.assertFalse(self._line_total(slip, 'WIEN_DAG'))
def test_04_karenz_woche_befreit(self):
"""Eine Karenz-Woche (ATP_KARENZ-Work Entry) senkt die
abgabepflichtigen Wochen (5 → 4)."""
self.version.generate_work_entries(
datetime.date(2026, 11, 1), datetime.date(2026, 11, 30))
karenz_wet = self.env['hr.work.entry.type'].search([
('code', '=', 'ATP_KARENZ')])
karenz_typ = self.env.ref(
'l10n_at_hr_payroll_private.leave_type_at_karenz')
leave = self.env['hr.leave'].create({
'name': 'Karenz Test',
'employee_id': self.employee.id,
'holiday_status_id': karenz_typ.id,
'request_date_from': datetime.date(2026, 11, 16),
'request_date_to': datetime.date(2026, 11, 20),
})
from odoo import SUPERUSER_ID
leave.with_user(SUPERUSER_ID)._action_validate()
slip = self._slip(11)
ergebnis = slip._at_wien_dag()
self.assertEqual(ergebnis['wochen'], 4)
self.assertAlmostEqual(ergebnis['betrag'], 8.0, places=2)
self.assertIn('Karenz', ergebnis['info'])
def test_05_fristenkalender(self):
"""Cron erzeugt wien_dag-Fristen nur für Flag-Companies: 15.
des Folgemonats monatlich, 31. 3. Jahreserklärung."""
fristen = self.env['l10n.at.payroll.frist']
heute = datetime.date.today()
fristen._cron_fristen_erzeugen()
monat_frist = fristen.search([
('company_id', '=', self.company.id),
('fristart', '=', 'wien_dag')], limit=1)
self.assertTrue(monat_frist)
self.assertEqual(monat_frist.deadline.day, 15)
# Jahresfrist entsteht im Cron nur am 1. Jänner — direkter
# Aufruf der Sicherstellung prüft die Art selbst:
fristen._sicherstellen(
self.company, 'wien_dag_jahres', heute.year - 1, None,
datetime.date(heute.year, 3, 31))
self.assertTrue(fristen.search([
('company_id', '=', self.company.id),
('fristart', '=', 'wien_dag_jahres')]))
# Ohne Flag: keine Wien-Fristen
self.company.l10n_at_wien_dag = False
fristen.search([
('company_id', '=', self.company.id),
('fristart', 'like', 'wien_dag')]).unlink()
fristen._cron_fristen_erzeugen()
self.assertFalse(fristen.search([
('company_id', '=', self.company.id),
('fristart', 'like', 'wien_dag')]))
@@ -180,6 +180,19 @@
invisible="not kv_gruppe_id"/>
<field name="ueberzahlung"
invisible="not kv_gruppe_id"/>
<field name="l10n_at_vordienstzeit_monate"
invisible="not kv_gruppe_id"/>
<field name="l10n_at_vwgj_monate"
invisible="not kv_gruppe_id"/>
<field name="l10n_at_vwgj_stufe_vorschlag"
invisible="not kv_gruppe_id"/>
<field name="l10n_at_naechste_vorrueckung"
invisible="not kv_gruppe_id"/>
<button name="action_einstufung_uebernehmen"
string="Vorschlag übernehmen" type="object"
class="btn-secondary"
invisible="not kv_gruppe_id or not l10n_at_vwgj_stufe_vorschlag"
groups="hr_payroll.group_hr_payroll_manager"/>
</group>
</group>
</field>