[ADD] l10n_at_hr_payroll_private: KV framework (GP1)

GP1 of the approved General-AT implementation plan: the KV
framework for the Austrian private economy on the Odoo 19
hr_payroll engine.

- l10n.at.payroll.kv: collective agreement master data with chamber,
  union, validity date and a trace link to the KV library
  (personalverrechnung/quellen/kv/)
- l10n.at.payroll.kv.wert: versioned KV parameters following the
  hr.rule.parameter.value pattern (Python literal, unique per
  KV/code/date); consumed via employee.kv_id._wert(code, date)
- l10n.at.payroll.kv.gruppe / .stufe: Verwendungsgruppen with
  Erfahrungsstufen year tables, unique(gruppe, stufe, gueltig_ab),
  Stichtag lookup
- hr.version / hr.employee contract fields: kv_id, kv_gruppe_id,
  erfahrungsstufe, ueberzahlung
- import wizard for KV intake and yearly updates: table paste
  (Gruppe;Stufe;Gehalt[;Bezeichnung]), preview, >10% jump warning
  (warn lines never applied automatically), new gueltig_ab versions,
  historical values untouched
- seed D2 (SI-2203 Handwerk und Gewerbe, SI-2748 Metallgewerbe):
  wage tables from 1. 1. 2026 curated from the KV library texts -
  18 groups, 131 stage values, 27 parameters, including the special
  progression groups IV-M / V-OM (SI-2748) and the discontinued
  VG I (SI-2203)
- tests: test_kv_katalog (seed lookups, Stichtag behaviour,
  versioning, parameters, wizard, contract fields)

Statically validated (py_compile, XML well-formedness, CSV
consistency, wizard logic simulation); runtime acceptance pending
PostgreSQL on the dev host.
This commit is contained in:
2026-09-09 22:10:26 +02:00
parent 6e444b088a
commit 67374611c6
16 changed files with 1274 additions and 12 deletions
+45 -12
View File
@@ -1,20 +1,53 @@
# l10n_at_hr_payroll_private (Gerüst)
# l10n_at_hr_payroll_private (General-AT)
General-AT-Payroll (KV-basierte Privatwirtschaft: KV-Gehaltskataloge,
AZG/UrlG-Arbeitszeit, 13./14. Bezüge, Abfertigung Neu).
AZG/UrlG-Arbeitszeit, 13./14. Bezüge, Abfertigung Neu) für Odoo 19.
**Implementierungsplan (freigegeben, M0):**
[`personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md`](../../personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md)
· verbindliche Rechtslage:
[`personalverrechnung/RECHTSQUELLEN-Privat.md`](../../personalverrechnung/RECHTSQUELLEN-Privat.md)
Umsetzung startet mit **M1 (GP0)**: `RECHTSQUELLEN-Privat.md`,
RIS-/findok-Verifikationen, Odoo-API-Neuverifikation,
Pilotmandant-Definition, UGB-Buchungsmatrix. Kein Manifest/Code
bis M2 (GP1).
**GP1 umgesetzt (KV-Framework):**
**Kern-Vorfinanzierung (D3):** AP3 (Lohnsteuer) und AP4 (SV/DB-Regeln)
werden im Kern-Modul `l10n_at_hr_payroll` auf General-AT-Kosten gebaut
und von beiden Produkten konsumiert (die GemBG-Structure erbt);
bei Bgld.-Auftragserteilung Anrechnung/Umlegung per Change-Request.
- `l10n.at.payroll.kv` — KV-Stammdaten mit Trace-Link zur KV-Library
(`library_variant_id``personalverrechnung/quellen/kv/kv-catalog.json`)
und Quellen-URL der Arbeitgeberkammer;
- `l10n.at.payroll.kv.wert` — versionierte KV-Eckwerte (Muster
`hr.rule.parameter.value`, KV-Scope): Regelkonsum über
`employee.kv_id._wert('code', payslip.date_to)`;
- `l10n.at.payroll.kv.gruppe` / `.stufe` — Verwendungs-/Lohngruppen mit
Erfahrungsstufen, Jahrestabellen je `gueltig_ab`
(unique(Gruppe, Stufe, Stichtag));
- Vertragsfelder auf `hr.version` (durchgereicht auf `hr.employee`):
`kv_id`, `kv_gruppe_id`, `erfahrungsstufe`, `ueberzahlung`;
- Import-Wizard „KV-Katalog importieren": Tabellenpaste/CSV
(Gruppe;Stufe;Gehalt[;Bezeichnung]) mit Vorschau, Sprungwarnung
> 10 % (Warnzeilen werden nie automatisch übernommen) und Versionierung
über `gueltig_ab` — historische Werte bleiben unangetastet.
**Referenz-KVs R1 (D2):** SI-2203 Handwerk und Gewerbe +
SI-2748 Metallgewerbe (Feinjustierung je Pilotmandant in GP0).
**Seed D2 (Referenz-KVs R1):** SI-2203 Handwerk und Gewerbe" +
SI-2748 Metallgewerbe", Gehaltstabellen ab 1. 1. 2026 kuratiert aus den
KV-Library-Volltexten (`quellen/kv/oegb/texts/`). Besonderheiten im Seed:
- SI-2203: VG I ist ab 1. 1. 2026 aufgelassen (Vorrückung nach § 17 Abs 6);
Meistergruppen M IM III als eigene Gruppen (M II ohne/mit Fachschule);
- SI-2748: eigene Vorrückungsverläufe als eigene Gruppen (IV-M = Meister
in VG IV bis „nach 17 VWGJ", V-OM = Obermeister bis „nach 12 VWGJ");
Nacht-Sondervergütung 2,71 € (2026) bzw. 2,76 € (2027) als
Jahresversionen.
**Kern-Vorfinanzierung (D3):** Lohnsteuer (AP3) und SV/DB-Regeln (AP4)
werden im Kern-Modul `l10n_at_hr_payroll` gebaut und von beiden
Produkten konsumiert; bei Bgld.-Auftragserteilung Anrechnung per
Change-Request.
**Offen (GP2+):** Entgelt-Engine (KVGEO, AZG-Zuschläge, B13/B14,
Urlaubsentgelt), Lohnsteuer/SV, Abfertigung Neu, Meldewesen/Belege,
FI-Buchung UGB.
Tests: `test_kv_katalog` (Seed, Stichtags-Lookup, Versionierung,
Eckwerte, Wizard/Sprungwarnung, Vertragsfelder) —
`python3 odoo/odoo-bin -d <testdb> -i l10n_at_hr_payroll_private
--test-tags /l10n_at_hr_payroll_private --test-enable --stop-after-init
--addons-path=odoo_enterprise/odoo/addons,addons`
@@ -0,0 +1,3 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
from . import models
from . import wizard
@@ -0,0 +1,49 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
{
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
'version': '19.0.1.0.0',
'author': 'ixsol / gem360',
'website': 'https://gem360.at',
'category': 'Human Resources/Payroll',
'description': """
General-AT-Personalverrechnung (KV-basierte Privatwirtschaft)
==============================================================
Monatsverrechnung für Beschäftigte der österreichischen Privatwirtschaft
nach dem **KV-Regime** auf der Odoo-19 ``hr_payroll``-Engine. Fachliche
Grundlagen: ``personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md`` und
``personalverrechnung/RECHTSQUELLEN-Privat.md`` (GP0-Verifikationen).
- **GP1 KV-Framework (dieser Stand):** Kollektivvertrags-Stammdaten
(``l10n.at.payroll.kv``) mit Trace-Link zur KV-Library, versionierte
KV-Eckwerte (``kv.wert`` — Muster ``hr.rule.parameter.value``,
Konsum über ``employee.kv_id._wert(code, datum)``), Verwendungs-/
Lohngruppen mit Erfahrungsstufen (``kv.gruppe`` / ``kv.gruppe.stufe``,
Jahrestabellen je ``gueltig_ab``), KV-Einstufung am Vertrag
(``hr.version``: KV, Gruppe, Erfahrungsstufe, Überzahlung) sowie
ein Import-Wizard für KV-Aufnahme und Jahresupdate (Vorschau,
Sprungwarnung > 10 %, versionierte Werte — TASY-Muster).
- **Seed D2 (Referenz-KVs R1):** SI-2203 „Handwerk und Gewerbe" +
SI-2748 „Metallgewerbe" (Gehaltstabellen ab 1. 1. 2026, kuratiert aus
den KV-Library-Volltexten).
- **Folgen (GP2+):** Entgelt-Engine (KVGEO, AZG-Zuschläge, 13./14.
Bezüge), Lohnsteuer/SV im Bundes-Kern (D3-Vorfinanzierung),
Abfertigung Neu, Meldewesen/Belege, FI-Buchung UGB.
Kein VRV-Bezug (Gemeinde-Schiene bleibt ``l10n_at_gemeinde_payroll`` +
gem360-Brücke).
""",
'depends': ['l10n_at_hr_payroll', 'hr_payroll_account'],
'data': [
'security/ir.model.access.csv',
'data/kv_seed.xml',
'data/l10n.at.payroll.kv.gruppe.csv',
'data/l10n.at.payroll.kv.gruppe.stufe.csv',
'data/l10n.at.payroll.kv.wert.csv',
'views/kv_views.xml',
],
'countries': ['at'],
'license': 'LGPL-3',
'installable': True,
'auto_install': False,
}
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Referenz-KVs R1 (Plan-Entscheidung D2), kuratiert aus den
KV-Library-Volltexten (Gehaltstabellen der Lohnrunde ab
1. 1. 2026). Gruppen/Stufen/Werte siehe CSV-Datendateien. -->
<record id="kv_si_2203" model="l10n.at.payroll.kv">
<field name="name">Handwerk und Gewerbe (Angestellte)</field>
<field name="code">SI-2203</field>
<field name="arbeitgeberkammer">wko</field>
<field name="gewerkschaft">GPA</field>
<field name="gueltig_ab">2026-01-01</field>
<field name="library_variant_id">SI-2203_de</field>
<field name="quelle_url">https://www.wko.at/kollektivvertrag/kollektivvertrag-gewerbe-handwerk-und-dienstleistung-2026</field>
</record>
<record id="kv_si_2748" model="l10n.at.payroll.kv">
<field name="name">Metallgewerbe (Angestellte)</field>
<field name="code">SI-2748</field>
<field name="arbeitgeberkammer">wko</field>
<field name="gewerkschaft">GPA</field>
<field name="gueltig_ab">2026-01-01</field>
<field name="library_variant_id">SI-2748_de</field>
<field name="quelle_url">https://www.wko.at/kollektivvertrag/metallgewerbe-kollektivvertrag-angestellte</field>
</record>
</odoo>
@@ -0,0 +1,19 @@
id,kv_id:id,code,name,lohn_basis
kv2203_grp_ii,l10n_at_hr_payroll_private.kv_si_2203,II,Verwendungsgruppe II,monatlich
kv2203_grp_iii,l10n_at_hr_payroll_private.kv_si_2203,III,Verwendungsgruppe III,monatlich
kv2203_grp_iv,l10n_at_hr_payroll_private.kv_si_2203,IV,Verwendungsgruppe IV,monatlich
kv2203_grp_v,l10n_at_hr_payroll_private.kv_si_2203,V,Verwendungsgruppe V,monatlich
kv2203_grp_vi,l10n_at_hr_payroll_private.kv_si_2203,VI,Verwendungsgruppe VI,monatlich
kv2203_grp_m1,l10n_at_hr_payroll_private.kv_si_2203,M1,Meistergruppe M I (Hilfsmeister / Betriebsaufseher),monatlich
kv2203_grp_m2,l10n_at_hr_payroll_private.kv_si_2203,M2,Meistergruppe M II (Meister ohne Fachschule),monatlich
kv2203_grp_m2fs,l10n_at_hr_payroll_private.kv_si_2203,M2FS,Meistergruppe M II (Meister mit Fachschule),monatlich
kv2203_grp_m3,l10n_at_hr_payroll_private.kv_si_2203,M3,Meistergruppe M III (Obermeister),monatlich
kv2748_grp_i,l10n_at_hr_payroll_private.kv_si_2748,I,Verwendungsgruppe I,monatlich
kv2748_grp_ii,l10n_at_hr_payroll_private.kv_si_2748,II,Verwendungsgruppe II,monatlich
kv2748_grp_iii,l10n_at_hr_payroll_private.kv_si_2748,III,Verwendungsgruppe III,monatlich
kv2748_grp_iv,l10n_at_hr_payroll_private.kv_si_2748,IV,Verwendungsgruppe IV,monatlich
kv2748_grp_ivm,l10n_at_hr_payroll_private.kv_si_2748,IV-M,Verwendungsgruppe IV - Meister (eigener Vorrückungsverlauf),monatlich
kv2748_grp_v,l10n_at_hr_payroll_private.kv_si_2748,V,Verwendungsgruppe V,monatlich
kv2748_grp_vom,l10n_at_hr_payroll_private.kv_si_2748,V-OM,Verwendungsgruppe V - Obermeister (eigener Vorrückungsverlauf),monatlich
kv2748_grp_vi,l10n_at_hr_payroll_private.kv_si_2748,VI,Verwendungsgruppe VI,monatlich
kv2748_grp_meister,l10n_at_hr_payroll_private.kv_si_2748,MEISTER,Meistergruppe,monatlich
1 id kv_id:id code name lohn_basis
2 kv2203_grp_ii l10n_at_hr_payroll_private.kv_si_2203 II Verwendungsgruppe II monatlich
3 kv2203_grp_iii l10n_at_hr_payroll_private.kv_si_2203 III Verwendungsgruppe III monatlich
4 kv2203_grp_iv l10n_at_hr_payroll_private.kv_si_2203 IV Verwendungsgruppe IV monatlich
5 kv2203_grp_v l10n_at_hr_payroll_private.kv_si_2203 V Verwendungsgruppe V monatlich
6 kv2203_grp_vi l10n_at_hr_payroll_private.kv_si_2203 VI Verwendungsgruppe VI monatlich
7 kv2203_grp_m1 l10n_at_hr_payroll_private.kv_si_2203 M1 Meistergruppe M I (Hilfsmeister / Betriebsaufseher) monatlich
8 kv2203_grp_m2 l10n_at_hr_payroll_private.kv_si_2203 M2 Meistergruppe M II (Meister ohne Fachschule) monatlich
9 kv2203_grp_m2fs l10n_at_hr_payroll_private.kv_si_2203 M2FS Meistergruppe M II (Meister mit Fachschule) monatlich
10 kv2203_grp_m3 l10n_at_hr_payroll_private.kv_si_2203 M3 Meistergruppe M III (Obermeister) monatlich
11 kv2748_grp_i l10n_at_hr_payroll_private.kv_si_2748 I Verwendungsgruppe I monatlich
12 kv2748_grp_ii l10n_at_hr_payroll_private.kv_si_2748 II Verwendungsgruppe II monatlich
13 kv2748_grp_iii l10n_at_hr_payroll_private.kv_si_2748 III Verwendungsgruppe III monatlich
14 kv2748_grp_iv l10n_at_hr_payroll_private.kv_si_2748 IV Verwendungsgruppe IV monatlich
15 kv2748_grp_ivm l10n_at_hr_payroll_private.kv_si_2748 IV-M Verwendungsgruppe IV - Meister (eigener Vorrückungsverlauf) monatlich
16 kv2748_grp_v l10n_at_hr_payroll_private.kv_si_2748 V Verwendungsgruppe V monatlich
17 kv2748_grp_vom l10n_at_hr_payroll_private.kv_si_2748 V-OM Verwendungsgruppe V - Obermeister (eigener Vorrückungsverlauf) monatlich
18 kv2748_grp_vi l10n_at_hr_payroll_private.kv_si_2748 VI Verwendungsgruppe VI monatlich
19 kv2748_grp_meister l10n_at_hr_payroll_private.kv_si_2748 MEISTER Meistergruppe monatlich
@@ -0,0 +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
1 id gruppe_id:id stufe bezeichnung gehalt gueltig_ab
2 kv2203_st_ii_1 l10n_at_hr_payroll_private.kv2203_grp_ii 1 im 1. u 2. VWGJ 2059.41 2026-01-01
3 kv2203_st_ii_2 l10n_at_hr_payroll_private.kv2203_grp_ii 2 nach 2 VWGJ 2179.68 2026-01-01
4 kv2203_st_ii_3 l10n_at_hr_payroll_private.kv2203_grp_ii 3 nach 4 VWGJ 2315.46 2026-01-01
5 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
7 kv2203_st_ii_6 l10n_at_hr_payroll_private.kv2203_grp_ii 6 nach 10 VWGJ 2742.03 2026-01-01
8 kv2203_st_ii_7 l10n_at_hr_payroll_private.kv2203_grp_ii 7 nach 12 VWGJ 2863.88 2026-01-01
9 kv2203_st_ii_8 l10n_at_hr_payroll_private.kv2203_grp_ii 8 nach 15 VWGJ 3087.28 2026-01-01
10 kv2203_st_iii_1 l10n_at_hr_payroll_private.kv2203_grp_iii 1 im 1. u 2. VWGJ 2528.04 2026-01-01
11 kv2203_st_iii_2 l10n_at_hr_payroll_private.kv2203_grp_iii 2 nach 2 VWGJ 2705.03 2026-01-01
12 kv2203_st_iii_3 l10n_at_hr_payroll_private.kv2203_grp_iii 3 nach 4 VWGJ 2881.97 2026-01-01
13 kv2203_st_iii_4 l10n_at_hr_payroll_private.kv2203_grp_iii 4 nach 6 VWGJ 3058.96 2026-01-01
14 kv2203_st_iii_5 l10n_at_hr_payroll_private.kv2203_grp_iii 5 nach 8 VWGJ 3232.73 2026-01-01
15 kv2203_st_iii_6 l10n_at_hr_payroll_private.kv2203_grp_iii 6 nach 10 VWGJ 3409.53 2026-01-01
16 kv2203_st_iii_7 l10n_at_hr_payroll_private.kv2203_grp_iii 7 nach 12 VWGJ 3561.04 2026-01-01
17 kv2203_st_iii_8 l10n_at_hr_payroll_private.kv2203_grp_iii 8 nach 15 VWGJ 3838.85 2026-01-01
18 kv2203_st_iv_1 l10n_at_hr_payroll_private.kv2203_grp_iv 1 im 1. u 2. VWGJ 3146.27 2026-01-01
19 kv2203_st_iv_2 l10n_at_hr_payroll_private.kv2203_grp_iv 2 nach 2 VWGJ 3366.49 2026-01-01
20 kv2203_st_iv_3 l10n_at_hr_payroll_private.kv2203_grp_iv 3 nach 4 VWGJ 3586.73 2026-01-01
21 kv2203_st_iv_4 l10n_at_hr_payroll_private.kv2203_grp_iv 4 nach 6 VWGJ 3806.97 2026-01-01
22 kv2203_st_iv_5 l10n_at_hr_payroll_private.kv2203_grp_iv 5 nach 8 VWGJ 4027.20 2026-01-01
23 kv2203_st_iv_6 l10n_at_hr_payroll_private.kv2203_grp_iv 6 nach 10 VWGJ 4247.47 2026-01-01
24 kv2203_st_iv_7 l10n_at_hr_payroll_private.kv2203_grp_iv 7 nach 12 VWGJ 4436.22 2026-01-01
25 kv2203_st_iv_8 l10n_at_hr_payroll_private.kv2203_grp_iv 8 nach 15 VWGJ 4782.32 2026-01-01
26 kv2203_st_v_1 l10n_at_hr_payroll_private.kv2203_grp_v 1 im 1. u 2. VWGJ 3919.67 2026-01-01
27 kv2203_st_v_2 l10n_at_hr_payroll_private.kv2203_grp_v 2 nach 2 VWGJ 4194.02 2026-01-01
28 kv2203_st_v_3 l10n_at_hr_payroll_private.kv2203_grp_v 3 nach 4 VWGJ 4468.41 2026-01-01
29 kv2203_st_v_4 l10n_at_hr_payroll_private.kv2203_grp_v 4 nach 6 VWGJ 4742.77 2026-01-01
30 kv2203_st_v_5 l10n_at_hr_payroll_private.kv2203_grp_v 5 nach 8 VWGJ 5017.15 2026-01-01
31 kv2203_st_v_6 l10n_at_hr_payroll_private.kv2203_grp_v 6 nach 10 VWGJ 5291.57 2026-01-01
32 kv2203_st_v_7 l10n_at_hr_payroll_private.kv2203_grp_v 7 nach 12 VWGJ 5526.70 2026-01-01
33 kv2203_st_v_8 l10n_at_hr_payroll_private.kv2203_grp_v 8 nach 15 VWGJ 5957.86 2026-01-01
34 kv2203_st_vi_1 l10n_at_hr_payroll_private.kv2203_grp_vi 1 im 1. u 2. VWGJ 5482.40 2026-01-01
35 kv2203_st_vi_2 l10n_at_hr_payroll_private.kv2203_grp_vi 2 nach 2 VWGJ 6167.67 2026-01-01
36 kv2203_st_vi_3 l10n_at_hr_payroll_private.kv2203_grp_vi 3 nach 5 VWGJ 6853.01 2026-01-01
37 kv2203_st_m1_1 l10n_at_hr_payroll_private.kv2203_grp_m1 1 im 1. u 2. VWGJ 2427.72 2026-01-01
38 kv2203_st_m1_2 l10n_at_hr_payroll_private.kv2203_grp_m1 2 nach 2 VWGJ 2427.72 2026-01-01
39 kv2203_st_m1_3 l10n_at_hr_payroll_private.kv2203_grp_m1 3 nach 4 VWGJ 2586.57 2026-01-01
40 kv2203_st_m1_4 l10n_at_hr_payroll_private.kv2203_grp_m1 4 nach 6 VWGJ 2745.39 2026-01-01
41 kv2203_st_m1_5 l10n_at_hr_payroll_private.kv2203_grp_m1 5 nach 8 VWGJ 2904.19 2026-01-01
42 kv2203_st_m1_6 l10n_at_hr_payroll_private.kv2203_grp_m1 6 nach 10 VWGJ 3063.05 2026-01-01
43 kv2203_st_m1_7 l10n_at_hr_payroll_private.kv2203_grp_m1 7 nach 12 VWGJ 3199.16 2026-01-01
44 kv2203_st_m1_8 l10n_at_hr_payroll_private.kv2203_grp_m1 8 nach 15 VWGJ 3448.74 2026-01-01
45 kv2203_st_m2_1 l10n_at_hr_payroll_private.kv2203_grp_m2 1 im 1. u 2. VWGJ 3095.50 2026-01-01
46 kv2203_st_m2_2 l10n_at_hr_payroll_private.kv2203_grp_m2 2 nach 2 VWGJ 3095.50 2026-01-01
47 kv2203_st_m2_3 l10n_at_hr_payroll_private.kv2203_grp_m2 3 nach 4 VWGJ 3297.98 2026-01-01
48 kv2203_st_m2_4 l10n_at_hr_payroll_private.kv2203_grp_m2 4 nach 6 VWGJ 3500.52 2026-01-01
49 kv2203_st_m2_5 l10n_at_hr_payroll_private.kv2203_grp_m2 5 nach 8 VWGJ 3703.01 2026-01-01
50 kv2203_st_m2_6 l10n_at_hr_payroll_private.kv2203_grp_m2 6 nach 10 VWGJ 3905.53 2026-01-01
51 kv2203_st_m2_7 l10n_at_hr_payroll_private.kv2203_grp_m2 7 nach 12 VWGJ 4079.13 2026-01-01
52 kv2203_st_m2_8 l10n_at_hr_payroll_private.kv2203_grp_m2 8 nach 15 VWGJ 4397.37 2026-01-01
53 kv2203_st_m2fs_1 l10n_at_hr_payroll_private.kv2203_grp_m2fs 1 im 1. u 2. VWGJ 3241.68 2026-01-01
54 kv2203_st_m2fs_2 l10n_at_hr_payroll_private.kv2203_grp_m2fs 2 nach 2 VWGJ 3241.68 2026-01-01
55 kv2203_st_m2fs_3 l10n_at_hr_payroll_private.kv2203_grp_m2fs 3 nach 4 VWGJ 3453.74 2026-01-01
56 kv2203_st_m2fs_4 l10n_at_hr_payroll_private.kv2203_grp_m2fs 4 nach 6 VWGJ 3665.79 2026-01-01
57 kv2203_st_m2fs_5 l10n_at_hr_payroll_private.kv2203_grp_m2fs 5 nach 8 VWGJ 3877.83 2026-01-01
58 kv2203_st_m2fs_6 l10n_at_hr_payroll_private.kv2203_grp_m2fs 6 nach 10 VWGJ 4089.90 2026-01-01
59 kv2203_st_m2fs_7 l10n_at_hr_payroll_private.kv2203_grp_m2fs 7 nach 12 VWGJ 4271.68 2026-01-01
60 kv2203_st_m2fs_8 l10n_at_hr_payroll_private.kv2203_grp_m2fs 8 nach 15 VWGJ 4605.01 2026-01-01
61 kv2203_st_m3_1 l10n_at_hr_payroll_private.kv2203_grp_m3 1 im 1. u 2. VWGJ 3542.72 2026-01-01
62 kv2203_st_m3_2 l10n_at_hr_payroll_private.kv2203_grp_m3 2 nach 2 VWGJ 3542.72 2026-01-01
63 kv2203_st_m3_3 l10n_at_hr_payroll_private.kv2203_grp_m3 3 nach 4 VWGJ 3774.51 2026-01-01
64 kv2203_st_m3_4 l10n_at_hr_payroll_private.kv2203_grp_m3 4 nach 6 VWGJ 4006.27 2026-01-01
65 kv2203_st_m3_5 l10n_at_hr_payroll_private.kv2203_grp_m3 5 nach 8 VWGJ 4238.05 2026-01-01
66 kv2203_st_m3_6 l10n_at_hr_payroll_private.kv2203_grp_m3 6 nach 10 VWGJ 4469.81 2026-01-01
67 kv2203_st_m3_7 l10n_at_hr_payroll_private.kv2203_grp_m3 7 nach 12 VWGJ 4668.45 2026-01-01
68 kv2203_st_m3_8 l10n_at_hr_payroll_private.kv2203_grp_m3 8 nach 15 VWGJ 5032.68 2026-01-01
69 kv2748_st_i_1 l10n_at_hr_payroll_private.kv2748_grp_i 1 im 1. u 2. VWGJ 1960.54 2026-01-01
70 kv2748_st_i_2 l10n_at_hr_payroll_private.kv2748_grp_i 2 nach 2 VWGJ 2058.43 2026-01-01
71 kv2748_st_i_3 l10n_at_hr_payroll_private.kv2748_grp_i 3 nach 4 VWGJ 2190.76 2026-01-01
72 kv2748_st_i_4 l10n_at_hr_payroll_private.kv2748_grp_i 4 nach 6 VWGJ 2323.12 2026-01-01
73 kv2748_st_i_5 l10n_at_hr_payroll_private.kv2748_grp_i 5 nach 8 VWGJ 2455.45 2026-01-01
74 kv2748_st_i_6 l10n_at_hr_payroll_private.kv2748_grp_i 6 nach 10 VWGJ 2588.72 2026-01-01
75 kv2748_st_i_7 l10n_at_hr_payroll_private.kv2748_grp_i 7 nach 12 VWGJ 2703.75 2026-01-01
76 kv2748_st_i_8 l10n_at_hr_payroll_private.kv2748_grp_i 8 nach 15 VWGJ 2914.69 2026-01-01
77 kv2748_st_ii_1 l10n_at_hr_payroll_private.kv2748_grp_ii 1 im 1. u 2. VWGJ 2199.15 2026-01-01
78 kv2748_st_ii_2 l10n_at_hr_payroll_private.kv2748_grp_ii 2 nach 2 VWGJ 2350.62 2026-01-01
79 kv2748_st_ii_3 l10n_at_hr_payroll_private.kv2748_grp_ii 3 nach 4 VWGJ 2502.07 2026-01-01
80 kv2748_st_ii_4 l10n_at_hr_payroll_private.kv2748_grp_ii 4 nach 6 VWGJ 2655.45 2026-01-01
81 kv2748_st_ii_5 l10n_at_hr_payroll_private.kv2748_grp_ii 5 nach 8 VWGJ 2809.08 2026-01-01
82 kv2748_st_ii_6 l10n_at_hr_payroll_private.kv2748_grp_ii 6 nach 10 VWGJ 2962.72 2026-01-01
83 kv2748_st_ii_7 l10n_at_hr_payroll_private.kv2748_grp_ii 7 nach 12 VWGJ 3094.32 2026-01-01
84 kv2748_st_ii_8 l10n_at_hr_payroll_private.kv2748_grp_ii 8 nach 15 VWGJ 3335.74 2026-01-01
85 kv2748_st_iii_1 l10n_at_hr_payroll_private.kv2748_grp_iii 1 im 1. u 2. VWGJ 2727.40 2026-01-01
86 kv2748_st_iii_2 l10n_at_hr_payroll_private.kv2748_grp_iii 2 nach 2 VWGJ 2918.34 2026-01-01
87 kv2748_st_iii_3 l10n_at_hr_payroll_private.kv2748_grp_iii 3 nach 4 VWGJ 3109.21 2026-01-01
88 kv2748_st_iii_4 l10n_at_hr_payroll_private.kv2748_grp_iii 4 nach 6 VWGJ 3300.16 2026-01-01
89 kv2748_st_iii_5 l10n_at_hr_payroll_private.kv2748_grp_iii 5 nach 8 VWGJ 3491.06 2026-01-01
90 kv2748_st_iii_6 l10n_at_hr_payroll_private.kv2748_grp_iii 6 nach 10 VWGJ 3682.00 2026-01-01
91 kv2748_st_iii_7 l10n_at_hr_payroll_private.kv2748_grp_iii 7 nach 12 VWGJ 3845.61 2026-01-01
92 kv2748_st_iii_8 l10n_at_hr_payroll_private.kv2748_grp_iii 8 nach 15 VWGJ 4145.62 2026-01-01
93 kv2748_st_iv_1 l10n_at_hr_payroll_private.kv2748_grp_iv 1 im 1. u 2. VWGJ 3415.01 2026-01-01
94 kv2748_st_iv_2 l10n_at_hr_payroll_private.kv2748_grp_iv 2 nach 2 VWGJ 3654.05 2026-01-01
95 kv2748_st_iv_3 l10n_at_hr_payroll_private.kv2748_grp_iv 3 nach 4 VWGJ 3893.09 2026-01-01
96 kv2748_st_iv_4 l10n_at_hr_payroll_private.kv2748_grp_iv 4 nach 6 VWGJ 4132.15 2026-01-01
97 kv2748_st_iv_5 l10n_at_hr_payroll_private.kv2748_grp_iv 5 nach 8 VWGJ 4371.19 2026-01-01
98 kv2748_st_iv_6 l10n_at_hr_payroll_private.kv2748_grp_iv 6 nach 10 VWGJ 4610.26 2026-01-01
99 kv2748_st_iv_7 l10n_at_hr_payroll_private.kv2748_grp_iv 7 nach 12 VWGJ 4815.15 2026-01-01
100 kv2748_st_iv_8 l10n_at_hr_payroll_private.kv2748_grp_iv 8 nach 15 VWGJ 5190.77 2026-01-01
101 kv2748_st_ivm_1 l10n_at_hr_payroll_private.kv2748_grp_ivm 1 im 1.-4. VWGJ 3415.01 2026-01-01
102 kv2748_st_ivm_2 l10n_at_hr_payroll_private.kv2748_grp_ivm 2 nach 4 VWGJ 3654.05 2026-01-01
103 kv2748_st_ivm_3 l10n_at_hr_payroll_private.kv2748_grp_ivm 3 nach 6 VWGJ 3893.09 2026-01-01
104 kv2748_st_ivm_4 l10n_at_hr_payroll_private.kv2748_grp_ivm 4 nach 8 VWGJ 4132.15 2026-01-01
105 kv2748_st_ivm_5 l10n_at_hr_payroll_private.kv2748_grp_ivm 5 nach 10 VWGJ 4371.19 2026-01-01
106 kv2748_st_ivm_6 l10n_at_hr_payroll_private.kv2748_grp_ivm 6 nach 12 VWGJ 4610.26 2026-01-01
107 kv2748_st_ivm_7 l10n_at_hr_payroll_private.kv2748_grp_ivm 7 nach 14 VWGJ 4815.15 2026-01-01
108 kv2748_st_ivm_8 l10n_at_hr_payroll_private.kv2748_grp_ivm 8 nach 17 VWGJ 5047.10 2026-01-01
109 kv2748_st_v_1 l10n_at_hr_payroll_private.kv2748_grp_v 1 im 1. u 2. VWGJ 4299.70 2026-01-01
110 kv2748_st_v_2 l10n_at_hr_payroll_private.kv2748_grp_v 2 nach 2 VWGJ 4594.04 2026-01-01
111 kv2748_st_v_3 l10n_at_hr_payroll_private.kv2748_grp_v 3 nach 4 VWGJ 4888.37 2026-01-01
112 kv2748_st_v_4 l10n_at_hr_payroll_private.kv2748_grp_v 4 nach 6 VWGJ 5182.71 2026-01-01
113 kv2748_st_v_5 l10n_at_hr_payroll_private.kv2748_grp_v 5 nach 8 VWGJ 5477.00 2026-01-01
114 kv2748_st_v_6 l10n_at_hr_payroll_private.kv2748_grp_v 6 nach 10 VWGJ 5771.34 2026-01-01
115 kv2748_st_v_7 l10n_at_hr_payroll_private.kv2748_grp_v 7 nach 12 VWGJ 6023.65 2026-01-01
116 kv2748_st_v_8 l10n_at_hr_payroll_private.kv2748_grp_v 8 nach 15 VWGJ 6486.15 2026-01-01
117 kv2748_st_vom_1 l10n_at_hr_payroll_private.kv2748_grp_vom 1 im 1.-6. VWGJ 4299.70 2026-01-01
118 kv2748_st_vom_2 l10n_at_hr_payroll_private.kv2748_grp_vom 2 nach 6 VWGJ 4594.04 2026-01-01
119 kv2748_st_vom_3 l10n_at_hr_payroll_private.kv2748_grp_vom 3 nach 8 VWGJ 4888.37 2026-01-01
120 kv2748_st_vom_4 l10n_at_hr_payroll_private.kv2748_grp_vom 4 nach 10 VWGJ 5182.71 2026-01-01
121 kv2748_st_vom_5 l10n_at_hr_payroll_private.kv2748_grp_vom 5 nach 12 VWGJ 5477.00 2026-01-01
122 kv2748_st_vi_1 l10n_at_hr_payroll_private.kv2748_grp_vi 1 im 1. u 2. VWGJ 6021.80 2026-01-01
123 kv2748_st_vi_2 l10n_at_hr_payroll_private.kv2748_grp_vi 2 nach 2 VWGJ 6762.62 2026-01-01
124 kv2748_st_vi_3 l10n_at_hr_payroll_private.kv2748_grp_vi 3 nach 5 VWGJ 7503.50 2026-01-01
125 kv2748_st_meister_1 l10n_at_hr_payroll_private.kv2748_grp_meister 1 im 1. u 2. VWGJ 4130.76 2026-01-01
126 kv2748_st_meister_2 l10n_at_hr_payroll_private.kv2748_grp_meister 2 nach 2 VWGJ 4130.76 2026-01-01
127 kv2748_st_meister_3 l10n_at_hr_payroll_private.kv2748_grp_meister 3 nach 4 VWGJ 4303.13 2026-01-01
128 kv2748_st_meister_4 l10n_at_hr_payroll_private.kv2748_grp_meister 4 nach 6 VWGJ 4475.48 2026-01-01
129 kv2748_st_meister_5 l10n_at_hr_payroll_private.kv2748_grp_meister 5 nach 8 VWGJ 4647.84 2026-01-01
130 kv2748_st_meister_6 l10n_at_hr_payroll_private.kv2748_grp_meister 6 nach 10 VWGJ 4820.17 2026-01-01
131 kv2748_st_meister_7 l10n_at_hr_payroll_private.kv2748_grp_meister 7 nach 12 VWGJ 4992.53 2026-01-01
132 kv2748_st_meister_8 l10n_at_hr_payroll_private.kv2748_grp_meister 8 nach 15 VWGJ 5270.29 2026-01-01
@@ -0,0 +1,28 @@
id,kv_id:id,code,name,parameter_value,date_from
kv2203_wert_naz,l10n_at_hr_payroll_private.kv_si_2203,naz,Normalarbeitszeit (Wochenstunden - § 4 Abs 1),40,2026-01-01
kv2203_wert_teilungsfaktor_ue,l10n_at_hr_payroll_private.kv_si_2203,teilungsfaktor_ue,Teilungsfaktor Überstunde (1/x des Monatsgehalts - § 5 Abs 6),150,2026-01-01
kv2203_wert_prorate,l10n_at_hr_payroll_private.kv_si_2203,prorate_divisor,Aliquoter Divisor Ein-/Austritt (§ 17 Abs 2),26,2026-01-01
kv2203_wert_ue_tag,l10n_at_hr_payroll_private.kv_si_2203,ue_zuschlag_tag,Überstundenzuschlag Tag (%),50,2026-01-01
kv2203_wert_ue_nacht,l10n_at_hr_payroll_private.kv_si_2203,ue_zuschlag_nacht,Überstundenzuschlag 20-6 Uhr (%),100,2026-01-01
kv2203_wert_sofei,l10n_at_hr_payroll_private.kv_si_2203,so_feiertag_zuschlag,Zuschlag Sonn-/Feiertagsüberstunden (%),100,2026-01-01
kv2203_wert_nacht_eur,l10n_at_hr_payroll_private.kv_si_2203,nacht_sonderverguetung,Sondervergütung Nachtarbeit 22-6 Uhr (EUR/h - § 6 Abs 1),2.62,2026-01-01
kv2203_wert_sz13_monat,l10n_at_hr_payroll_private.kv_si_2203,sz13_monat,Auszahlmonat 13. Bezug (spätestens 30. 11.),11,2026-01-01
kv2203_wert_sz14_faelligkeit,l10n_at_hr_payroll_private.kv_si_2203,sz14_faelligkeit,Fälligkeit 14. Bezug (§ 11 Abs 3),'urlaub',2026-01-01
kv2203_wert_sz13_name,l10n_at_hr_payroll_private.kv_si_2203,sz13_name,Bezeichnung 13. Bezug,'Weihnachtsremuneration',2026-01-01
kv2203_wert_sz14_name,l10n_at_hr_payroll_private.kv_si_2203,sz14_name,Bezeichnung 14. Bezug,'Urlaubszuschuss',2026-01-01
kv2203_wert_ue_hoechster,l10n_at_hr_payroll_private.kv_si_2203,ue_hoechster_zuschlag,Bei Zuschlagskumulation nur der höchste Zuschlag (§ 5 Abs 7),True,2026-01-01
kv2748_wert_naz,l10n_at_hr_payroll_private.kv_si_2748,naz,Normalarbeitszeit (Wochenstunden - § 4 Abs 1),38.5,2026-01-01
kv2748_wert_teilungsfaktor_ue,l10n_at_hr_payroll_private.kv_si_2748,teilungsfaktor_ue,Teilungsfaktor Überstunde (1/x des Monatsgehalts - § 5 Abs 6),143,2026-01-01
kv2748_wert_teilungsfaktor_norm,l10n_at_hr_payroll_private.kv_si_2748,teilungsfaktor_norm,Teilungsfaktor Normalarbeitsstunde/Mehrarbeit (§ 5 Abs 6),167,2026-01-01
kv2748_wert_mehrarbeit,l10n_at_hr_payroll_private.kv_si_2748,mehrarbeit_zuschlag,Mehrarbeit Zuschlag § 4a (Stunden zwischen NAZ und 40),50,2026-01-01
kv2748_wert_prorate,l10n_at_hr_payroll_private.kv_si_2748,prorate_divisor,Aliquoter Divisor Ein-/Austritt (§ 17 Abs 2),26,2026-01-01
kv2748_wert_ue_tag,l10n_at_hr_payroll_private.kv_si_2748,ue_zuschlag_tag,Überstundenzuschlag Tag (%),50,2026-01-01
kv2748_wert_ue_nacht,l10n_at_hr_payroll_private.kv_si_2748,ue_zuschlag_nacht,Überstundenzuschlag 20-6 Uhr bzw. 3.+ ÜStd nach 19 Uhr (%),100,2026-01-01
kv2748_wert_sofei,l10n_at_hr_payroll_private.kv_si_2748,so_feiertag_zuschlag,Zuschlag Sonn-/Feiertagsüberstunden (%),100,2026-01-01
kv2748_wert_nacht_eur,l10n_at_hr_payroll_private.kv_si_2748,nacht_sonderverguetung,Sondervergütung Nachtarbeit 22-6 Uhr (EUR/h - § 6),2.71,2026-01-01
kv2748_wert_nacht_eur_2027,l10n_at_hr_payroll_private.kv_si_2748,nacht_sonderverguetung,Sondervergütung Nachtarbeit 22-6 Uhr (EUR/h - § 6),2.76,2027-01-01
kv2748_wert_sz13_monat,l10n_at_hr_payroll_private.kv_si_2748,sz13_monat,Auszahlmonat 13. Bezug (spätestens 1. 12.),12,2026-01-01
kv2748_wert_sz14_faelligkeit,l10n_at_hr_payroll_private.kv_si_2748,sz14_faelligkeit,Fälligkeit 14. Bezug (§ 11 Abs 3),'urlaub',2026-01-01
kv2748_wert_sz13_name,l10n_at_hr_payroll_private.kv_si_2748,sz13_name,Bezeichnung 13. Bezug,'13. Monatsgehalt (Weihnachtsremuneration)',2026-01-01
kv2748_wert_sz14_name,l10n_at_hr_payroll_private.kv_si_2748,sz14_name,Bezeichnung 14. Bezug,'14. Monatsgehalt (Urlaubszuschuss)',2026-01-01
kv2748_wert_ue_hoechster,l10n_at_hr_payroll_private.kv_si_2748,ue_hoechster_zuschlag,Bei Zuschlagskumulation nur der höchste Zuschlag (§ 5 Abs 7),True,2026-01-01
1 id kv_id:id code name parameter_value date_from
2 kv2203_wert_naz l10n_at_hr_payroll_private.kv_si_2203 naz Normalarbeitszeit (Wochenstunden - § 4 Abs 1) 40 2026-01-01
3 kv2203_wert_teilungsfaktor_ue l10n_at_hr_payroll_private.kv_si_2203 teilungsfaktor_ue Teilungsfaktor Überstunde (1/x des Monatsgehalts - § 5 Abs 6) 150 2026-01-01
4 kv2203_wert_prorate l10n_at_hr_payroll_private.kv_si_2203 prorate_divisor Aliquoter Divisor Ein-/Austritt (§ 17 Abs 2) 26 2026-01-01
5 kv2203_wert_ue_tag l10n_at_hr_payroll_private.kv_si_2203 ue_zuschlag_tag Überstundenzuschlag Tag (%) 50 2026-01-01
6 kv2203_wert_ue_nacht l10n_at_hr_payroll_private.kv_si_2203 ue_zuschlag_nacht Überstundenzuschlag 20-6 Uhr (%) 100 2026-01-01
7 kv2203_wert_sofei l10n_at_hr_payroll_private.kv_si_2203 so_feiertag_zuschlag Zuschlag Sonn-/Feiertagsüberstunden (%) 100 2026-01-01
8 kv2203_wert_nacht_eur l10n_at_hr_payroll_private.kv_si_2203 nacht_sonderverguetung Sondervergütung Nachtarbeit 22-6 Uhr (EUR/h - § 6 Abs 1) 2.62 2026-01-01
9 kv2203_wert_sz13_monat l10n_at_hr_payroll_private.kv_si_2203 sz13_monat Auszahlmonat 13. Bezug (spätestens 30. 11.) 11 2026-01-01
10 kv2203_wert_sz14_faelligkeit l10n_at_hr_payroll_private.kv_si_2203 sz14_faelligkeit Fälligkeit 14. Bezug (§ 11 Abs 3) 'urlaub' 2026-01-01
11 kv2203_wert_sz13_name l10n_at_hr_payroll_private.kv_si_2203 sz13_name Bezeichnung 13. Bezug 'Weihnachtsremuneration' 2026-01-01
12 kv2203_wert_sz14_name l10n_at_hr_payroll_private.kv_si_2203 sz14_name Bezeichnung 14. Bezug 'Urlaubszuschuss' 2026-01-01
13 kv2203_wert_ue_hoechster l10n_at_hr_payroll_private.kv_si_2203 ue_hoechster_zuschlag Bei Zuschlagskumulation nur der höchste Zuschlag (§ 5 Abs 7) True 2026-01-01
14 kv2748_wert_naz l10n_at_hr_payroll_private.kv_si_2748 naz Normalarbeitszeit (Wochenstunden - § 4 Abs 1) 38.5 2026-01-01
15 kv2748_wert_teilungsfaktor_ue l10n_at_hr_payroll_private.kv_si_2748 teilungsfaktor_ue Teilungsfaktor Überstunde (1/x des Monatsgehalts - § 5 Abs 6) 143 2026-01-01
16 kv2748_wert_teilungsfaktor_norm l10n_at_hr_payroll_private.kv_si_2748 teilungsfaktor_norm Teilungsfaktor Normalarbeitsstunde/Mehrarbeit (§ 5 Abs 6) 167 2026-01-01
17 kv2748_wert_mehrarbeit l10n_at_hr_payroll_private.kv_si_2748 mehrarbeit_zuschlag Mehrarbeit Zuschlag § 4a (Stunden zwischen NAZ und 40) 50 2026-01-01
18 kv2748_wert_prorate l10n_at_hr_payroll_private.kv_si_2748 prorate_divisor Aliquoter Divisor Ein-/Austritt (§ 17 Abs 2) 26 2026-01-01
19 kv2748_wert_ue_tag l10n_at_hr_payroll_private.kv_si_2748 ue_zuschlag_tag Überstundenzuschlag Tag (%) 50 2026-01-01
20 kv2748_wert_ue_nacht l10n_at_hr_payroll_private.kv_si_2748 ue_zuschlag_nacht Überstundenzuschlag 20-6 Uhr bzw. 3.+ ÜStd nach 19 Uhr (%) 100 2026-01-01
21 kv2748_wert_sofei l10n_at_hr_payroll_private.kv_si_2748 so_feiertag_zuschlag Zuschlag Sonn-/Feiertagsüberstunden (%) 100 2026-01-01
22 kv2748_wert_nacht_eur l10n_at_hr_payroll_private.kv_si_2748 nacht_sonderverguetung Sondervergütung Nachtarbeit 22-6 Uhr (EUR/h - § 6) 2.71 2026-01-01
23 kv2748_wert_nacht_eur_2027 l10n_at_hr_payroll_private.kv_si_2748 nacht_sonderverguetung Sondervergütung Nachtarbeit 22-6 Uhr (EUR/h - § 6) 2.76 2027-01-01
24 kv2748_wert_sz13_monat l10n_at_hr_payroll_private.kv_si_2748 sz13_monat Auszahlmonat 13. Bezug (spätestens 1. 12.) 12 2026-01-01
25 kv2748_wert_sz14_faelligkeit l10n_at_hr_payroll_private.kv_si_2748 sz14_faelligkeit Fälligkeit 14. Bezug (§ 11 Abs 3) 'urlaub' 2026-01-01
26 kv2748_wert_sz13_name l10n_at_hr_payroll_private.kv_si_2748 sz13_name Bezeichnung 13. Bezug '13. Monatsgehalt (Weihnachtsremuneration)' 2026-01-01
27 kv2748_wert_sz14_name l10n_at_hr_payroll_private.kv_si_2748 sz14_name Bezeichnung 14. Bezug '14. Monatsgehalt (Urlaubszuschuss)' 2026-01-01
28 kv2748_wert_ue_hoechster l10n_at_hr_payroll_private.kv_si_2748 ue_hoechster_zuschlag Bei Zuschlagskumulation nur der höchste Zuschlag (§ 5 Abs 7) True 2026-01-01
@@ -0,0 +1,3 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
from . import kollektivvertrag
from . import hr_version_private
@@ -0,0 +1,58 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""KV-Einstufung am Arbeitsvertrag (hr.version) und Durchreichung auf
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.
"""
from odoo import fields, models
class HrVersion(models.Model):
_inherit = 'hr.version'
kv_id = fields.Many2one(
'l10n.at.payroll.kv', string='Kollektivvertrag', index=True,
groups="hr_payroll.group_hr_payroll_user", tracking=True)
kv_gruppe_id = fields.Many2one(
'l10n.at.payroll.kv.gruppe', string='Verwendungsgruppe', index=True,
groups="hr_payroll.group_hr_payroll_user", tracking=True)
erfahrungsstufe = fields.Integer(
string='Erfahrungsstufe',
groups="hr_payroll.group_hr_payroll_user", tracking=True,
help="Stufenindex der KV-Gehaltstabelle (1 = unterste Stufe, z. B. "
"'im 1. u 2. Verwendungsgruppenjahr').")
ueberzahlung = fields.Monetary(
string='Überzahlung (individuelles Gehalt)',
currency_field='currency_id',
groups="hr_payroll.group_hr_payroll_user", tracking=True,
help="Individuell vereinbartes Monatsgehalt; überschreibt das "
"Kataloggehalt der Gruppe/Stufe (Günstigkeitsvergleich, "
"z. B. § 18 Anrechnung der Rahmen-KVs).")
currency_id = fields.Many2one(
'res.currency', related='company_id.currency_id')
def _get_kv_gehalt(self, datum=None):
"""Katalog-Position (Monatsgehalt) der Version zum Stichtag."""
self.ensure_one()
return self.kv_gruppe_id._get_gehalt(self.erfahrungsstufe, datum)
class HrEmployee(models.Model):
_inherit = 'hr.employee'
kv_id = fields.Many2one(
related="version_id.kv_id", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
kv_gruppe_id = fields.Many2one(
related="version_id.kv_gruppe_id", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
erfahrungsstufe = fields.Integer(
related="version_id.erfahrungsstufe", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
ueberzahlung = fields.Monetary(
related="version_id.ueberzahlung", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user",
currency_field='currency_id')
@@ -0,0 +1,225 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""KV-Katalogmodelle (GP1): Kollektivvertrag, versionierte KV-Eckwerte,
Verwendungs-/Lohngruppen mit Erfahrungsstufen.
Datenbasis ist die KV-Library (``personalverrechnung/quellen/kv/``);
die Seed-Kataloge der Referenz-KVs SI-2203/SI-2748 (Plan-Entscheidung
D2) sind aus den Volltexten kuratiert. Eckwerte folgen dem Muster
``hr.rule.parameter.value`` (Python-Literal, Versionierung über den
Stichtag), sind aber KV-scoped und werden daher bewusst nicht über
``payslip._rule_parameter`` konsumiert, sondern über
``employee.kv_id._wert('code', datum)``.
"""
from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
class L10nAtPayrollKv(models.Model):
"""Kollektivvertrag als Entgelt-Rechtsquelle der Privatwirtschaft.
Normative Wirkung über ArbVG § 12; Quellenpolitik der KV-Library:
Main Source = Arbeitgeberkammer, kollektivvertrag.at als
Crosscheck-Baseline.
"""
_name = 'l10n.at.payroll.kv'
_description = 'Kollektivvertrag (AT Privatwirtschaft)'
_order = 'name'
name = fields.Char(required=True)
code = fields.Char(
required=True, index=True,
help="Kurzbezeichnung, üblicherweise die KV-Library-Variante "
"(z. B. SI-2203).")
arbeitgeberkammer = fields.Selection([
('wko', 'Wirtschaftskammer Österreich (WKO)'),
('lwk', 'Landwirtschaftskammer'),
('aerztekammer', 'Ärztekammer'),
('apotheke', 'Apothekerkammer'),
('rechtsanwaltskammer', 'Rechtsanwaltskammer'),
('notariatskammer', 'Notariatskammer'),
])
gewerkschaft = fields.Char(
help="Verantwortliche Gewerkschaft(en), z. B. GPA; mehrere "
"kommagetrennt.")
gueltig_ab = fields.Date(
string='Fassung gültig ab',
help="Gültigkeit der hinterlegten KV-Fassung (Lohnrunde), z. B. "
"2026-01-01 für die Gehaltstabellen ab 1. 1. 2026.")
library_variant_id = fields.Char(
string='KV-Library-Variante',
help="Trace-Link: variant_id im Katalog "
"personalverrechnung/quellen/kv/kv-catalog.json.")
quelle_url = fields.Char(
string='Quelle (Arbeitgeberkammer)',
help="Veröffentlichung der zuständigen Arbeitgeberkammer "
"(Main Source gemäß KV-Library-Quellenarchitektur).")
active = fields.Boolean(default=True)
gruppe_ids = fields.One2many(
'l10n.at.payroll.kv.gruppe', 'kv_id', string='Verwendungsgruppen')
wert_ids = fields.One2many(
'l10n.at.payroll.kv.wert', 'kv_id', string='KV-Eckwerte')
_unique_kv = models.Constraint(
'unique (code)',
"Ein Kollektivvertrag existiert je Code nur einmal.")
def _wert(self, code, datum=None, raise_if_not_found=True):
"""Eckwert zum Stichtag (z. B. 'naz', 'sz13_monat').
Rückgabe wie hr.rule.parameter: Python-Literal aus
parameter_value (Zahl, String oder Boolean).
"""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
wert = self.env['l10n.at.payroll.kv.wert'].search([
('kv_id', '=', self.id),
('code', '=', code),
('date_from', '<=', datum)], order='date_from desc', limit=1)
if wert:
return safe_eval(wert.parameter_value)
if raise_if_not_found:
raise UserError(_(
"Kein KV-Eckwert %(code)s für %(kv)s zum %(datum)s "
"gefunden.", code=code, kv=self.display_name,
datum=datum))
return None
@api.depends('name', 'code')
def _compute_display_name(self):
for kv in self:
kv.display_name = f"{kv.name} ({kv.code})" if kv.code else kv.name
class L10nAtPayrollKvWert(models.Model):
"""Versionierter KV-Eckwert (Muster: hr.rule.parameter.value)."""
_name = 'l10n.at.payroll.kv.wert'
_description = 'KV-Eckwert (versioniert)'
_order = 'kv_id, code, date_from desc'
kv_id = fields.Many2one(
'l10n.at.payroll.kv', required=True, index=True, ondelete='cascade')
code = fields.Char(
required=True, index=True,
help="Code für den Regelkonsum, z. B. 'naz' "
"(Normalarbeitszeit), 'teilungsfaktor_ue', "
"'sz13_monat', 'sz14_faelligkeit'.")
name = fields.Char(string='Bezeichnung')
parameter_value = fields.Text(
required=True,
help="Python-Literal (Zahl, String oder Boolean) — Muster "
"hr.rule.parameter.value.")
date_from = fields.Date(
string='Gültig ab', required=True, index=True,
default=lambda self: fields.Date.today().replace(month=1, day=1))
_unique_kv_wert = models.Constraint(
'unique (kv_id, code, date_from)',
"Je KV, Code und Stichtag gibt es nur einen Wert.")
@api.constrains('parameter_value')
def _check_parameter_value(self):
for wert in self:
try:
safe_eval(wert.parameter_value)
except Exception:
raise UserError(_(
"Ungültiger KV-Eckwert für %(code)s: "
"Python-Literal (Zahl, 'String' oder Boolean) erwartet.",
code=wert.code))
class L10nAtPayrollKvGruppe(models.Model):
"""Verwendungs-/Lohngruppe eines Kollektivvertrages (z. B. VG III).
Die Monatsgehälter je Erfahrungsstufe stehen auf
``l10n.at.payroll.kv.gruppe.stufe`` (Jahreswerte, ein Wert je
Stichtag). Angestellte/Arbeiter-Unterschiede laufen über die
``lohn_basis`` und die KV-Eckwerte, nicht über eigene Strukturen
(Plan 4.2).
"""
_name = 'l10n.at.payroll.kv.gruppe'
_description = 'KV-Verwendungs-/Lohngruppe'
_order = 'kv_id, code'
kv_id = fields.Many2one(
'l10n.at.payroll.kv', required=True, index=True, ondelete='cascade')
code = fields.Char(required=True, index=True)
name = fields.Char(string='Bezeichnung')
lohn_basis = fields.Selection([
('monatlich', 'Monatsgehalt (vollzeit)'),
('wochenstunden', 'Wochenstunden-Basis (Arbeiter)'),
], required=True, default='monatlich')
active = fields.Boolean(default=True)
stufe_ids = fields.One2many(
'l10n.at.payroll.kv.gruppe.stufe', 'gruppe_id',
string='Erfahrungsstufen')
_unique_kv_gruppe = models.Constraint(
'unique (kv_id, code)',
"Je Kollektivvertrag existiert eine Gruppe nur einmal.")
@api.depends('kv_id', 'code', 'name')
def _compute_display_name(self):
for gruppe in self:
gruppe.display_name = (
f"{gruppe.name or gruppe.code} "
f"[{gruppe.kv_id.display_name}]")
def _get_gehalt(self, stufe, datum=None):
"""Stufen-Position (Monatsgehalt) zum Stichtag (oder leer)."""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
return self.env['l10n.at.payroll.kv.gruppe.stufe'].search(
[('gruppe_id', '=', self.id),
('stufe', '=', stufe),
('gueltig_ab', '<=', datum)],
order='gueltig_ab desc', limit=1)
def _max_stufe(self, datum=None):
"""Höchste Stufe mit gültigem Monatsgehalt zum Stichtag."""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
stufe = self.env['l10n.at.payroll.kv.gruppe.stufe'].search(
[('gruppe_id', '=', self.id),
('gueltig_ab', '<=', datum)],
order='stufe desc', limit=1)
return stufe.stufe or 0
class L10nAtPayrollKvGruppeStufe(models.Model):
"""Monatsgehalt einer KV-Gruppe in einer Erfahrungsstufe ab einem
Stichtag (Jahrestabellen aus der KV-Lohnrunde)."""
_name = 'l10n.at.payroll.kv.gruppe.stufe'
_description = 'KV-Monatsgehalt je Erfahrungsstufe'
_order = 'gruppe_id, stufe, gueltig_ab desc'
_rec_names_search = ['gruppe_id']
gruppe_id = fields.Many2one(
'l10n.at.payroll.kv.gruppe', required=True, index=True,
ondelete='cascade')
stufe = fields.Integer(required=True, index=True)
bezeichnung = fields.Char(
help="Stufen-Label der KV-Tabelle, z. B. 'im 1. u 2. VWGJ' "
"oder 'nach 15 VWGJ'.")
gehalt = fields.Monetary(currency_field='currency_id')
currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id)
gueltig_ab = fields.Date(
required=True, index=True,
default=lambda self: fields.Date.today().replace(month=1, day=1))
_unique_kv_stufe = models.Constraint(
'unique (gruppe_id, stufe, gueltig_ab)',
"Je Gruppe und Stufe gibt es nur einen Wert je Stichtag.")
@api.depends('gruppe_id', 'stufe', 'gehalt', 'gueltig_ab')
def _compute_display_name(self):
for stufe in self:
stufe.display_name = _(
"%(gruppe)s, Stufe %(stufe)s (%(betrag)s ab %(ab)s)",
gruppe=stufe.gruppe_id.code,
stufe=stufe.stufe,
betrag=stufe.gehalt,
ab=stufe.gueltig_ab,
)
@@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_l10n_at_payroll_kv_user,L10n AT Payroll KV User,model_l10n_at_payroll_kv,hr_payroll.group_hr_payroll_user,1,0,0,0
access_l10n_at_payroll_kv_manager,L10n AT Payroll KV Manager,model_l10n_at_payroll_kv,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_l10n_at_payroll_kv_wert_user,L10n AT Payroll KV-Wert User,model_l10n_at_payroll_kv_wert,hr_payroll.group_hr_payroll_user,1,0,0,0
access_l10n_at_payroll_kv_wert_manager,L10n AT Payroll KV-Wert Manager,model_l10n_at_payroll_kv_wert,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_l10n_at_payroll_kv_gruppe_user,L10n AT Payroll KV-Gruppe User,model_l10n_at_payroll_kv_gruppe,hr_payroll.group_hr_payroll_user,1,0,0,0
access_l10n_at_payroll_kv_gruppe_manager,L10n AT Payroll KV-Gruppe Manager,model_l10n_at_payroll_kv_gruppe,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_l10n_at_payroll_kv_gruppe_stufe_user,L10n AT Payroll KV-Gruppe-Stufe User,model_l10n_at_payroll_kv_gruppe_stufe,hr_payroll.group_hr_payroll_user,1,0,0,0
access_l10n_at_payroll_kv_gruppe_stufe_manager,L10n AT Payroll KV-Gruppe-Stufe Manager,model_l10n_at_payroll_kv_gruppe_stufe,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_l10n_at_payroll_kv_import_wizard,L10n AT Payroll KV-Import Wizard,model_l10n_at_payroll_kv_import_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
access_l10n_at_payroll_kv_import_line,L10n AT Payroll KV-Import Line,model_l10n_at_payroll_kv_import_line,hr_payroll.group_hr_payroll_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_l10n_at_payroll_kv_user L10n AT Payroll KV User model_l10n_at_payroll_kv hr_payroll.group_hr_payroll_user 1 0 0 0
3 access_l10n_at_payroll_kv_manager L10n AT Payroll KV Manager model_l10n_at_payroll_kv hr_payroll.group_hr_payroll_manager 1 1 1 1
4 access_l10n_at_payroll_kv_wert_user L10n AT Payroll KV-Wert User model_l10n_at_payroll_kv_wert hr_payroll.group_hr_payroll_user 1 0 0 0
5 access_l10n_at_payroll_kv_wert_manager L10n AT Payroll KV-Wert Manager model_l10n_at_payroll_kv_wert hr_payroll.group_hr_payroll_manager 1 1 1 1
6 access_l10n_at_payroll_kv_gruppe_user L10n AT Payroll KV-Gruppe User model_l10n_at_payroll_kv_gruppe hr_payroll.group_hr_payroll_user 1 0 0 0
7 access_l10n_at_payroll_kv_gruppe_manager L10n AT Payroll KV-Gruppe Manager model_l10n_at_payroll_kv_gruppe hr_payroll.group_hr_payroll_manager 1 1 1 1
8 access_l10n_at_payroll_kv_gruppe_stufe_user L10n AT Payroll KV-Gruppe-Stufe User model_l10n_at_payroll_kv_gruppe_stufe hr_payroll.group_hr_payroll_user 1 0 0 0
9 access_l10n_at_payroll_kv_gruppe_stufe_manager L10n AT Payroll KV-Gruppe-Stufe Manager model_l10n_at_payroll_kv_gruppe_stufe hr_payroll.group_hr_payroll_manager 1 1 1 1
10 access_l10n_at_payroll_kv_import_wizard L10n AT Payroll KV-Import Wizard model_l10n_at_payroll_kv_import_wizard hr_payroll.group_hr_payroll_user 1 1 1 1
11 access_l10n_at_payroll_kv_import_line L10n AT Payroll KV-Import Line model_l10n_at_payroll_kv_import_line hr_payroll.group_hr_payroll_user 1 1 1 1
@@ -0,0 +1,2 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
from . import test_kv_katalog
@@ -0,0 +1,192 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""GP1-Rechenfälle: Seed D2 (SI-2203/SI-2748), Stichtags-Lookup,
Versionierung, KV-Eckwerte, Import-Wizard (Sprungwarnung) und
Vertragsfelder (Implementierungsplan-Privat, Tests 6.1)."""
from datetime import date
from odoo.exceptions import UserError
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class KvKatalogTest(TransactionCase):
"""KV-Framework: Katalog, versionierte Eckwerte, Wizard, Vertrag."""
def test_01_seed_2203_values(self):
"""Seed SI-2203: Gehaltstabelle ab 1. 1. 2026 (KV-Library-Volltext)."""
stichtag = date(2026, 6, 1)
grp_iii = self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_iii')
self.assertEqual(grp_iii._get_gehalt(1, stichtag).gehalt, 2528.04)
self.assertEqual(grp_iii._get_gehalt(3, stichtag).gehalt, 2881.97)
self.assertEqual(grp_iii._get_gehalt(8, stichtag).gehalt, 3838.85)
# VG VI endet bei Stufe 3 (nach 5 VWGJ)
grp_vi = self.env.ref('l10n_at_hr_payroll_private.kv2203_grp_vi')
self.assertEqual(grp_vi._max_stufe(stichtag), 3)
self.assertEqual(grp_vi._get_gehalt(3, stichtag).gehalt, 6853.01)
# Meistergruppe M II mit abgeschlossener Fachschule
m2fs = self.env.ref('l10n_at_hr_payroll_private.kv2203_grp_m2fs')
self.assertEqual(m2fs._get_gehalt(1, stichtag).gehalt, 3241.68)
# Stufen-Label der Tabelle bleibt erhalten
self.assertEqual(
grp_iii._get_gehalt(3, stichtag).bezeichnung, 'nach 4 VWGJ')
def test_02_seed_2748_values(self):
"""Seed SI-2748: Gehaltstabelle ab 1. 1. 2026 inkl. der
Sonder-Vorrückungsverläufe (Meister VG IV, Obermeister VG V)."""
stichtag = date(2026, 6, 1)
grp_i = self.env.ref('l10n_at_hr_payroll_private.kv2748_grp_i')
self.assertEqual(grp_i._get_gehalt(1, stichtag).gehalt, 1960.54)
grp_ivm = self.env.ref('l10n_at_hr_payroll_private.kv2748_grp_ivm')
self.assertEqual(grp_ivm._get_gehalt(8, stichtag).gehalt, 5047.10)
self.assertEqual(
grp_ivm._get_gehalt(8, stichtag).bezeichnung, 'nach 17 VWGJ')
grp_vom = self.env.ref('l10n_at_hr_payroll_private.kv2748_grp_vom')
self.assertEqual(grp_vom._get_gehalt(2, stichtag).gehalt, 4594.04)
meister = self.env.ref(
'l10n_at_hr_payroll_private.kv2748_grp_meister')
self.assertEqual(meister._get_gehalt(8, stichtag).gehalt, 5270.29)
self.assertEqual(meister._max_stufe(stichtag), 8)
def test_03_wert_lookup(self):
"""KV-Eckwerte (Muster hr.rule.parameter.value): Konsum über
kv_id._wert(code, datum); Strings als Python-Literal."""
kv2203 = self.env.ref('l10n_at_hr_payroll_private.kv_si_2203')
kv2748 = self.env.ref('l10n_at_hr_payroll_private.kv_si_2748')
stichtag = date(2026, 6, 1)
self.assertEqual(kv2203._wert('naz', stichtag), 40)
self.assertEqual(kv2203._wert('teilungsfaktor_ue', stichtag), 150)
self.assertEqual(kv2203._wert('sz13_monat', stichtag), 11)
self.assertEqual(kv2203._wert('sz14_faelligkeit', stichtag), 'urlaub')
self.assertTrue(kv2203._wert('ue_hoechster_zuschlag', stichtag))
self.assertEqual(kv2748._wert('naz', stichtag), 38.5)
self.assertEqual(kv2748._wert('teilungsfaktor_norm', stichtag), 167)
self.assertEqual(kv2748._wert('mehrarbeit_zuschlag', stichtag), 50)
# Jahresversion: Nacht-Sondervergütung 2,71 € (2026) / 2,76 € (2027)
self.assertEqual(
kv2748._wert('nacht_sonderverguetung', stichtag), 2.71)
self.assertEqual(kv2748._wert(
'nacht_sonderverguetung', date(2027, 6, 1)), 2.76)
def test_04_lookup_before_gueltig_ab(self):
"""Vor dem Stichtag gibt es keinen Wert (laut, nicht 0)."""
grp_iii = self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_iii')
self.assertFalse(grp_iii._get_gehalt(1, date(2025, 12, 31)))
kv2203 = self.env.ref('l10n_at_hr_payroll_private.kv_si_2203')
with self.assertRaises(UserError):
kv2203._wert('naz', date(2025, 12, 31))
self.assertIsNone(
kv2203._wert('naz', date(2025, 12, 31),
raise_if_not_found=False))
def test_05_versioning(self):
"""Jahresversionen: neuer Wert greift erst ab seinem Stichtag."""
grp_iii = self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_iii')
self.env['l10n.at.payroll.kv.gruppe.stufe'].create({
'gruppe_id': grp_iii.id,
'stufe': 1,
'gehalt': 2600.00,
'gueltig_ab': date(2027, 1, 1),
})
self.assertEqual(
grp_iii._get_gehalt(1, date(2026, 12, 31)).gehalt, 2528.04)
self.assertEqual(
grp_iii._get_gehalt(1, date(2027, 1, 1)).gehalt, 2600.00)
def test_06_wizard_import(self):
"""Import schreibt neue gueltig_ab-Versionen (Jahresupdate) und
legt neue Gruppen an (Aufnahme); Sprünge > 10 % werden verwarnt
und nie automatisch übernommen (TASY-Muster)."""
kv = self.env.ref('l10n_at_hr_payroll_private.kv_si_2203')
wizard = self.env['l10n.at.payroll.kv.import.wizard'].create({
'kv_id': kv.id,
'gueltig_ab': date(2027, 1, 1),
'eingabe': (
'III;1;2900,00;nach 2 VWGJ\n' # +14,7 % -> Sprungwarnung
'III;1;2600,00\n' # +2,8 % -> unauffällig
'X1;1;2000,00\n' # neue Gruppe (Aufnahme)
),
})
wizard.action_parse()
self.assertEqual(len(wizard.line_ids), 3)
sprung = wizard.line_ids.filtered(
lambda line: line.neuer_wert == 2900.0)
self.assertFalse(sprung.uebernehmen)
self.assertIn('Sprung', sprung.warnung)
unauffaellig = wizard.line_ids.filtered(
lambda line: line.neuer_wert == 2600.0)
self.assertTrue(unauffaellig.uebernehmen)
self.assertFalse(unauffaellig.warnung)
aufnahme = wizard.line_ids.filtered(
lambda line: line.gruppe_code == 'X1')
self.assertTrue(aufnahme.uebernehmen)
self.assertIn('Neue Gruppe', aufnahme.warnung)
wizard.action_apply()
grp_iii = self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_iii')
self.assertEqual(
grp_iii._get_gehalt(1, date(2027, 1, 1)).gehalt, 2600.00)
# historischer Wert bleibt unangetastet
self.assertEqual(
grp_iii._get_gehalt(1, date(2026, 12, 31)).gehalt, 2528.04)
# Warnzeile wurde nicht geschrieben
self.assertFalse(
grp_iii.stufe_ids.filtered(lambda s: s.gehalt == 2900.0))
# neue Gruppe mit Stufe angelegt
grp_x1 = self.env['l10n.at.payroll.kv.gruppe'].search([
('kv_id', '=', kv.id), ('code', '=', 'X1')])
self.assertTrue(grp_x1)
self.assertEqual(
grp_x1._get_gehalt(1, date(2027, 1, 1)).gehalt, 2000.00)
def test_07_wizard_bestehende_version_aktualisieren(self):
"""Erneuter Import zum selben Stichtag aktualisiert die
bestehende Version statt zu verdoppeln (unique-Constraint)."""
kv = self.env.ref('l10n_at_hr_payroll_private.kv_si_2203')
wizard = self.env['l10n.at.payroll.kv.import.wizard'].create({
'kv_id': kv.id,
'gueltig_ab': date(2026, 1, 1),
'eingabe': 'III;1;2600,00\n',
})
wizard.action_parse()
line = wizard.line_ids[0]
self.assertTrue(line.uebernehmen)
self.assertIn('aktualisiert', line.warnung)
self.assertEqual(line.alter_wert, 2528.04)
wizard.action_apply()
grp_iii = self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_iii')
self.assertEqual(len(grp_iii.stufe_ids.filtered(
lambda s: s.stufe == 1)), 1)
self.assertEqual(
grp_iii._get_gehalt(1, date(2026, 6, 1)).gehalt, 2600.00)
def test_08_vertragsfelder(self):
"""KV-Einstufung am Vertrag (hr.version) und Durchreichung auf
hr.employee; Katalog-Lookup über die Version."""
employee = self.env['hr.employee'].create({'name': 'Test KV'})
version = self.env['hr.version'].search(
[('employee_id', '=', employee.id)], limit=1)
if not version:
version = self.env['hr.version'].create({
'employee_id': employee.id,
'date_start': date(2026, 1, 1),
})
employee.write({
'kv_id':
self.env.ref('l10n_at_hr_payroll_private.kv_si_2203').id,
'kv_gruppe_id':
self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_iii').id,
'erfahrungsstufe': 3,
'ueberzahlung': 3000.00,
})
self.assertEqual(version.kv_id.code, 'SI-2203')
self.assertEqual(version.kv_gruppe_id.code, 'III')
self.assertEqual(version.erfahrungsstufe, 3)
self.assertEqual(version.ueberzahlung, 3000.00)
self.assertEqual(
version._get_kv_gehalt(date(2026, 6, 1)).gehalt, 2881.97)
@@ -0,0 +1,270 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="kv_list_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.list</field>
<field name="model">l10n.at.payroll.kv</field>
<field name="arch" type="xml">
<list string="Kollektivverträge">
<field name="code"/>
<field name="name"/>
<field name="arbeitgeberkammer"/>
<field name="gewerkschaft"/>
<field name="gueltig_ab"/>
<field name="active" column_invisible="1"/>
</list>
</field>
</record>
<record id="kv_form_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.form</field>
<field name="model">l10n.at.payroll.kv</field>
<field name="arch" type="xml">
<form string="Kollektivvertrag">
<sheet>
<group>
<group>
<field name="name"/>
<field name="code"/>
<field name="arbeitgeberkammer"/>
<field name="gewerkschaft"/>
</group>
<group>
<field name="gueltig_ab"/>
<field name="library_variant_id"/>
<field name="quelle_url" widget="url"/>
<field name="active" widget="boolean_toggle"/>
</group>
</group>
<notebook>
<page string="Verwendungsgruppen" name="gruppen">
<field name="gruppe_ids">
<list editable="bottom">
<field name="code"/>
<field name="name"/>
<field name="lohn_basis"/>
<field name="active" widget="boolean_toggle"/>
</list>
</field>
</page>
<page string="KV-Eckwerte" name="werte">
<field name="wert_ids">
<list editable="bottom">
<field name="code"/>
<field name="name"/>
<field name="parameter_value"/>
<field name="date_from"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="kv_gruppe_form_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.gruppe.form</field>
<field name="model">l10n.at.payroll.kv.gruppe</field>
<field name="arch" type="xml">
<form string="KV-Verwendungsgruppe">
<sheet>
<group>
<group>
<field name="kv_id"/>
<field name="code"/>
<field name="name"/>
</group>
<group>
<field name="lohn_basis"/>
<field name="active" widget="boolean_toggle"/>
</group>
</group>
<notebook>
<page string="Erfahrungsstufen (Jahrestabellen)" name="stufen">
<field name="stufe_ids">
<list editable="bottom">
<field name="stufe"/>
<field name="bezeichnung"/>
<field name="gehalt"/>
<field name="gueltig_ab"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="kv_gruppe_list_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.gruppe.list</field>
<field name="model">l10n.at.payroll.kv.gruppe</field>
<field name="arch" type="xml">
<list string="KV-Verwendungsgruppen">
<field name="kv_id"/>
<field name="code"/>
<field name="name"/>
<field name="lohn_basis"/>
</list>
</field>
</record>
<record id="kv_wert_list_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.wert.list</field>
<field name="model">l10n.at.payroll.kv.wert</field>
<field name="arch" type="xml">
<list string="KV-Eckwerte" multi_edit="1">
<field name="kv_id"/>
<field name="code"/>
<field name="name"/>
<field name="parameter_value"/>
<field name="date_from"/>
</list>
</field>
</record>
<record id="kv_gruppe_stufe_list_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.gruppe.stufe.list</field>
<field name="model">l10n.at.payroll.kv.gruppe.stufe</field>
<field name="arch" type="xml">
<list string="KV-Monatsgehälter">
<field name="gruppe_id"/>
<field name="stufe"/>
<field name="bezeichnung"/>
<field name="gehalt" sum="Summe"/>
<field name="gueltig_ab"/>
</list>
</field>
</record>
<record id="action_kv" model="ir.actions.act_window">
<field name="name">Kollektivverträge</field>
<field name="res_model">l10n.at.payroll.kv</field>
<field name="view_mode">list,form</field>
</record>
<record id="action_kv_gruppe" model="ir.actions.act_window">
<field name="name">KV-Verwendungsgruppen</field>
<field name="res_model">l10n.at.payroll.kv.gruppe</field>
<field name="view_mode">list,form</field>
</record>
<record id="action_kv_wert" model="ir.actions.act_window">
<field name="name">KV-Eckwerte</field>
<field name="res_model">l10n.at.payroll.kv.wert</field>
<field name="view_mode">list</field>
<field name="context">{'search_default_groupby_kv_id': 1}</field>
</record>
<record id="action_kv_gruppe_stufe" model="ir.actions.act_window">
<field name="name">KV-Monatsgehälter (Stufen)</field>
<field name="res_model">l10n.at.payroll.kv.gruppe.stufe</field>
<field name="view_mode">list</field>
<field name="context">{'search_default_groupby_gruppe_id': 1}</field>
</record>
<!-- KV-Einstufung am Mitarbeitervertrag (Muster hr_version_bgld) -->
<record id="payroll_hr_employee_view_form_kv" model="ir.ui.view">
<field name="name">hr.employee.form.kv</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr_payroll.payroll_hr_employee_view_form"/>
<field name="arch" type="xml">
<group name="payroll_group" position="inside">
<group name="kv_einstufung" string="KV-Einstufung"
groups="hr_payroll.group_hr_payroll_user">
<field name="kv_id"/>
<field name="kv_gruppe_id"
domain="[('kv_id', '=', kv_id)]"
invisible="not kv_id"/>
<field name="erfahrungsstufe"
invisible="not kv_gruppe_id"/>
<field name="ueberzahlung"
invisible="not kv_gruppe_id"/>
</group>
</group>
</field>
</record>
<!-- KV-Katalog-Import -->
<record id="kv_import_wizard_form_view" model="ir.ui.view">
<field name="name">l10n.at.payroll.kv.import.wizard.form</field>
<field name="model">l10n.at.payroll.kv.import.wizard</field>
<field name="arch" type="xml">
<form string="KV-Katalog importieren (Aufnahme/Jahresupdate)">
<group>
<group>
<field name="kv_id"/>
<field name="gueltig_ab"/>
</group>
</group>
<field name="eingabe" placeholder="Gruppe;Stufe;Gehalt[;Bezeichnung] — z. B. III;1;2528,04;im 1. u 2. VWGJ"/>
<notebook>
<page string="Vorschau" name="vorschau">
<field name="line_ids">
<list editable="bottom" create="0" delete="0">
<field name="uebernehmen" widget="boolean_toggle"/>
<field name="gruppe_code"/>
<field name="gruppe_name"/>
<field name="stufe"/>
<field name="bezeichnung"/>
<field name="alter_wert"/>
<field name="neuer_wert"/>
<field name="currency_id" column_invisible="1"/>
<field name="warnung" decoration-danger="warnung"/>
</list>
</field>
</page>
<page string="Protokoll" name="protokoll">
<field name="protokoll"/>
</page>
</notebook>
<footer>
<button string="1) Tabelle analysieren" name="action_parse"
type="object" class="btn-secondary"/>
<button string="2) Ausgewählte Werte übernehmen"
name="action_apply" type="object"
class="btn-primary" invisible="not line_ids"/>
<button string="Abbrechen" class="btn-secondary"
special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_kv_import_wizard" model="ir.actions.act_window">
<field name="name">KV-Katalog importieren</field>
<field name="res_model">l10n.at.payroll.kv.import.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_l10n_at_payroll_private_root"
name="AT-Payroll (Privat)"
parent="hr_work_entry_enterprise.menu_hr_payroll_root"
sequence="95"/>
<menuitem id="menu_l10n_at_payroll_private_kv"
name="Kollektivverträge"
parent="menu_l10n_at_payroll_private_root"
action="action_kv" sequence="10"/>
<menuitem id="menu_l10n_at_payroll_private_gruppen"
name="KV-Verwendungsgruppen"
parent="menu_l10n_at_payroll_private_root"
action="action_kv_gruppe" sequence="15"/>
<menuitem id="menu_l10n_at_payroll_private_werte"
name="KV-Eckwerte"
parent="menu_l10n_at_payroll_private_root"
action="action_kv_wert" sequence="20"/>
<menuitem id="menu_l10n_at_payroll_private_stufen"
name="KV-Monatsgehälter (Stufen)"
parent="menu_l10n_at_payroll_private_root"
action="action_kv_gruppe_stufe" sequence="30"/>
<menuitem id="menu_l10n_at_payroll_private_import"
name="KV-Katalog importieren"
parent="menu_l10n_at_payroll_private_root"
action="action_kv_import_wizard" sequence="40"/>
</odoo>
@@ -0,0 +1,2 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
from . import kv_katalog_import
@@ -0,0 +1,211 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""KV-Katalog-Import (GP1): Neuaufnahme und Jahresupdate.
Tabellenpaste/CSV (Gruppe;Stufe;Gehalt[;Bezeichnung]) wird geparst,
gegen den bestehenden Katalog plausibilisiert (Sprungwarnung > 10 %
nach TASY-Muster; Warnzeilen werden nie automatisch übernommen) und
schreibt nach expliziter Bestätigung **neue ``gueltig_ab``-Versionen**
— historische Werte bleiben unangetastet. Quelle der Tabellen sind die
KV-Library-Volltexte bzw. Kammer-Lohntafeln (Quellenarchitektur des
RUNBOOK).
"""
import re
from odoo import _, fields, models
_BETRAG_MUSTER = re.compile(r'^\d{1,3}(?:\.\d{3})+(?:,\d+)?$')
class L10nAtPayrollKvImportLine(models.TransientModel):
_name = 'l10n.at.payroll.kv.import.line'
_description = 'KV-Katalog-Import: Vorschauzeile'
wizard_id = fields.Many2one(
'l10n.at.payroll.kv.import.wizard', required=True, ondelete='cascade')
gruppe_code = fields.Char(string='Gruppe', required=True)
gruppe_name = fields.Char(string='Gruppenbezeichnung', readonly=True)
stufe = fields.Integer(required=True)
bezeichnung = fields.Char(
string='Stufen-Label',
help="Optionales Stufen-Label der KV-Tabelle, z. B. 'nach 2 VWGJ'.")
alter_wert = fields.Monetary(string='Alter Wert', readonly=True)
neuer_wert = fields.Monetary(string='Neuer Wert', required=True)
currency_id = fields.Many2one(related='wizard_id.currency_id')
uebernehmen = fields.Boolean(default=True)
warnung = fields.Char(
help="Leer = unauffällig; sonst Plausibilitätshinweis. "
"Warnzeilen (Sprung > 10 %) werden nie automatisch "
"übernommen.")
class L10nAtPayrollKvImportWizard(models.TransientModel):
_name = 'l10n.at.payroll.kv.import.wizard'
_description = 'KV-Katalog importieren (Aufnahme/Jahresupdate)'
kv_id = fields.Many2one(
'l10n.at.payroll.kv', string='Kollektivvertrag', required=True)
gueltig_ab = fields.Date(
string='Gültig ab', required=True,
default=lambda self: fields.Date.today().replace(month=1, day=1),
help="Stichtag der neuen Version (z. B. 1. 1. des Jahresgehalts "
"der KV-Runde). Bestehende Werte zu diesem Stichtag werden "
"aktualisiert, historische nie verändert.")
eingabe = fields.Text(
string='Tabellen (CSV)',
help="Eine Zeile je Stufenwert — Semikolon- oder "
"Tabulator-getrennt (Dezimalkomma erlaubt):\n"
"Gruppe;Stufe;Gehalt[;Bezeichnung]\n"
"z. B. III;1;2528,04;im 1. u 2. VWGJ")
line_ids = fields.One2many(
'l10n.at.payroll.kv.import.line', 'wizard_id', string='Vorschau')
protokoll = fields.Text(readonly=True)
currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id)
# ------------------------------------------------------------------
# Parsing
# ------------------------------------------------------------------
@staticmethod
def _parse_betrag(text):
"""'2.059,41 €' / '2528.04' / '2 059,41' → float (oder None)."""
text = (text or '').strip().replace('', '').replace('EUR', '')
text = re.sub(r'\s', '', text)
if not text:
return None
if _BETRAG_MUSTER.match(text):
# Österreichische Schreibweise: Punkt = Tausender
text = text.replace('.', '').replace(',', '.')
else:
text = text.replace(',', '.')
try:
return float(text)
except ValueError:
return None
def _trenner(self, zeile):
if ';' in zeile:
return ';'
if '\t' in zeile:
return '\t'
return ','
# ------------------------------------------------------------------
# Actions
# ------------------------------------------------------------------
def action_parse(self):
"""Tabellen parsen und Vorschau (inkl. Plausibilität) aufbauen."""
self.ensure_one()
stufen_obj = self.env['l10n.at.payroll.kv.gruppe.stufe']
vortag = fields.Date.subtract(self.gueltig_ab, days=1)
line_vals, protokoll = [], []
for roh in (self.eingabe or '').splitlines():
zeile = roh.strip()
if not zeile or zeile.startswith('#'):
continue
teile = [t.strip() for t in zeile.split(self._trenner(zeile))]
if len(teile) < 3:
protokoll.append(
_('IGNORIERT (zu kurz): %(zeile)s', zeile=zeile))
continue
gruppe_code, stufe_text, betrag_text = teile[:3]
bezeichnung = teile[3] if len(teile) > 3 else ''
try:
stufe = int(stufe_text)
except ValueError:
protokoll.append(
_('IGNORIERT (Stufe keine Zahl): %(zeile)s',
zeile=zeile))
continue
betrag = self._parse_betrag(betrag_text)
if betrag is None:
protokoll.append(
_('IGNORIERT (Betrag unlesbar): %(zeile)s',
zeile=zeile))
continue
gruppe = self.env['l10n.at.payroll.kv.gruppe'].search([
('kv_id', '=', self.kv_id.id),
('code', '=', gruppe_code)], limit=1)
warnung = ''
alter_wert = 0.0
if not gruppe:
warnung = _('Neue Gruppe — wird mit der Übernahme angelegt.')
else:
bestehend = stufen_obj.search([
('gruppe_id', '=', gruppe.id),
('stufe', '=', stufe),
('gueltig_ab', '=', self.gueltig_ab)], limit=1)
if bestehend:
# Version zum Stichtag existiert schon: deren Wert
# wird aktualisiert — Referenz für die Plausibilität.
alter_wert = bestehend.gehalt
warnung = _(
'Bestehende Version vom %(ab)s wird aktualisiert.',
ab=self.gueltig_ab)
else:
alt = gruppe._get_gehalt(stufe, vortag)
alter_wert = alt.gehalt if alt else 0.0
if alter_wert and abs(
betrag - alter_wert) / alter_wert > 0.10:
warnung += ' ' + _(
'Sprung > 10 %% (%(alt)s%(neu)s)',
alt=alter_wert, neu=betrag)
line_vals.append((0, 0, {
'gruppe_code': gruppe_code,
'gruppe_name': gruppe.name or '',
'stufe': stufe,
'bezeichnung': bezeichnung,
'alter_wert': alter_wert,
'neuer_wert': betrag,
'uebernehmen': 'Sprung' not in warnung,
'warnung': warnung,
}))
self.line_ids = [(5, 0, 0)] + line_vals
self.protokoll = '\n'.join(protokoll)
return True
def action_apply(self):
"""Ausgewählte Zeilen als neue ``gueltig_ab``-Versionen schreiben."""
self.ensure_one()
gruppen_obj = self.env['l10n.at.payroll.kv.gruppe']
stufen_obj = self.env['l10n.at.payroll.kv.gruppe.stufe']
geschrieben = 0
for line in self.line_ids.filtered('uebernehmen'):
gruppe = gruppen_obj.search([
('kv_id', '=', self.kv_id.id),
('code', '=', line.gruppe_code)], limit=1)
if not gruppe:
gruppe = gruppen_obj.create({
'kv_id': self.kv_id.id,
'code': line.gruppe_code,
'name': line.gruppe_name or line.gruppe_code,
})
stufe = stufen_obj.search([
('gruppe_id', '=', gruppe.id),
('stufe', '=', line.stufe),
('gueltig_ab', '=', self.gueltig_ab)], limit=1)
werte = {
'gehalt': line.neuer_wert,
'bezeichnung': line.bezeichnung,
}
if stufe:
stufe.write(werte)
else:
stufen_obj.create(dict(werte, gruppe_id=gruppe.id,
stufe=line.stufe,
gueltig_ab=self.gueltig_ab))
geschrieben += 1
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': _('%(count)s Stufenwert(e) geschrieben '
'(gültig ab %(ab)s).',
count=geschrieben, ab=self.gueltig_ab),
'next': {'type': 'ir.actions.act_window_close'},
},
}