[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:
@@ -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
|
||||
|
||||
|
@@ -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 1–42 (1. 9.–12. 10.) → September ohne Reduktion;
|
||||
Oktober Arbeitstage 13.–31. 10. (14 AT) → Halbphase.
|
||||
- Halb: Kalendertage 43–70 (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 41–42 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 1–20
|
||||
self._krankenstand(
|
||||
employee, datetime.date(2026, 10, 1),
|
||||
datetime.date(2026, 10, 20)) # KT 21–40
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user