[IMP] l10n_at_gemeinde_payroll: implement AP1 pay scale model (GemBG Bgld.)

Katalogmodell nach Implementierungsplan AP1 (freigegeben 2026-09-09):

- ``gemeinde.payroll.entlohnungsgruppe`` (Code, Schema, §, Bundesland,
  Vorrückungszeitraum je Gruppe; § 66 Abs 2 GemBG: 4 Jahre, abweichend
  2 Jahre für Betreuungspersonen-/Sondergruppen) und
  ``gemeinde.payroll.entlohnungsstufe`` (Stufe, Monatsentgelt, Stichtag,
  unique je Gruppe/Stufe/Stichtag) mit Stichtags-Lookup-Helfern.
- Seed 2026: 35 Entlohnungsgruppen, 433 Werte (Schemata I/gv, II/gh,
  Ia/bv, IIa/bh, kb, av sowie §-Gruppen 150c/151/151c), Werte aus der
  konsolidierten GemBG-Fassung 09.09.2026 (Wert 1.7.2026 gemäß Artikel 4
  RV 0715/XXIII. GP; 2025er-Stichtagsannahme für nicht 2026 angepasste
  Tabellen als AP0-Verifikationspunkt dokumentiert). Generator mit
  Herkunftsnachweis: personalverrechnung/tools/extract_gembg_katalog.py.
- GemBG-Felder auf der Payroll-Version (hr.version) plus Employee-Spiegel
  nach dem Engine-Muster (related/inherited): Entlohnungsgruppe, Stufe,
  Besoldungsdienstalter, Vorrückungstermin — berechnet nach § 66 Abs 2
  (erster Tag des Monats nach Vollendung weiterer n Jahre BDA), manuell
  überschreibbar für Überleitungsfälle (§§ 157a ff).
- Vorrückungs-Wizard (§ 66 Abs 2): fällige Versionen zum Stichtag,
  Stufenerhöhung mit Höchststufen-Schutz und Ergebnis-Notification —
  bewusst kein stilles Mutieren im Lohnlauf (Dokumentationspflicht).
- Views/Menüs (Personalverrechnung Bgld. unter Payroll), Security auf
  hr_payroll-Gruppen, Employee-Form-Anbindung in payroll_group.
- Tests (Rechenfälle AP1): Seed-Werte inkl. Quellenanomalie kb1a/6,
  Stichtags-Lookup, Jahresversionierung, Vorrückungstermin (BDA 15.03.2020
  -> 1.4.2028), Wizard-Anwendung mit Höchststufen-Skip.

Statische Validierung grün (py_compile, XML, Manifest, CSV-Integrität);
Runtime-Testlauf auf dem Dev-Host (gem360_dev) ausständig, wie im
Implementierungsplan M2 vorgesehen.
This commit is contained in:
2026-09-09 11:55:15 +02:00
parent 4e79c8b8b1
commit 30270f9877
15 changed files with 1159 additions and 10 deletions
@@ -0,0 +1,2 @@
# Part of the gem360 onboarding. See LICENSE / project docs for details.
from . import test_katalog
@@ -0,0 +1,95 @@
# Part of the gem360 onboarding. See LICENSE / project docs for details.
from datetime import date
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class KatalogTest(TransactionCase):
"""Rechenfälle AP1: Katalog-Seed 2026, Stichtags-Lookup, Versionierung,
Vorrückungstermin nach § 66 Abs 2 GemBG und Vorrückungs-Wizard."""
def test_01_seed_2026_values(self):
"""Seed-Werte der konsolidierten GemBG-Fassung (Wert 1.7.2026)."""
av1 = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_av_av1')
bv1 = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_ia_bv1')
kb1a = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_kb_kb1a')
gb1a = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp__151c_gb1a')
gv1 = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_i_gv1')
stichtag = date(2026, 7, 1)
self.assertEqual(av1._get_entgelt(1, stichtag).monatsentgelt, 7135.20)
self.assertEqual(av1._get_entgelt(12, stichtag).monatsentgelt, 8712.50)
self.assertEqual(bv1._get_entgelt(1, stichtag).monatsentgelt, 5329.00)
# Quellenanomalie "3843,80" (fehlender Tausenderpunkt) korrekt geparst
self.assertEqual(kb1a._get_entgelt(6, stichtag).monatsentgelt, 3843.80)
self.assertEqual(gb1a._get_entgelt(1, stichtag).monatsentgelt, 3328.20)
# gv1 endet laut Tabelle bei Stufe 11
self.assertEqual(gv1._max_stufe(stichtag), 11)
self.assertEqual(av1._max_stufe(stichtag), 12)
def test_02_lookup_before_gueltig_ab(self):
"""Vor dem Stichtag gibt es keinen Wert (Vorrückungsbasis alt)."""
av1 = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_av_av1')
self.assertFalse(av1._get_entgelt(1, date(2026, 6, 30)))
def test_03_versioning(self):
"""Jahresversionen: neuer Wert greift erst ab seinem Stichtag."""
av1 = self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_av_av1')
self.env['gemeinde.payroll.entlohnungsstufe'].create({
'entlohnungsgruppe_id': av1.id,
'stufe': 1,
'monatsentgelt': 7300.00,
'gueltig_ab': date(2027, 7, 1),
})
self.assertEqual(
av1._get_entgelt(1, date(2026, 12, 31)).monatsentgelt, 7135.20)
self.assertEqual(
av1._get_entgelt(1, date(2027, 7, 1)).monatsentgelt, 7300.00)
def _version_mit_katalog(self, bda, stufe=1):
employee = self.env['hr.employee'].create({'name': 'Test GemBG'})
version = self.env['hr.version'].search(
[('employee_id', '=', employee.id)], limit=1)
if not version:
version = self.env['hr.version'].create({
'employee_id': employee.id,
'date_start': date(2020, 1, 1),
})
version.write({
'entlohnungsgruppe_id':
self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_av_av1').id,
'entlohnungsstufe': stufe,
'besoldungsdienstalter': bda,
})
return version
def test_04_vorrueckungstermin(self):
"""§ 66 Abs 2: 4-Jahres-Rhythmus, erster Tag des Folgemonats.
BDA 15.03.2020, heute >= 2026: die nächste Vollendung weiterer
4 Jahre ist 15.03.2028 → Vorrückungstermin 1.4.2028.
"""
version = self._version_mit_katalog(date(2020, 3, 15))
self.assertEqual(version.vorrueckungstermin, date(2028, 4, 1))
def test_05_wizard_apply(self):
"""Wizard erhöht die Stufe fälliger Versionen und überspringt
die Höchststufe."""
version = self._version_mit_katalog(date(2014, 3, 15), stufe=3)
version.vorrueckungstermin = date(2024, 4, 1)
max_stufe = version.entlohnungsgruppe_id._max_stufe(date(2024, 4, 1))
wizard = self.env['gemeinde.payroll.vorrueckung.wizard'].create({
'stichtag': date(2024, 4, 1),
})
self.assertIn(version, wizard.version_ids)
wizard.action_apply()
self.assertEqual(version.entlohnungsstufe, 4)
version_top = self._version_mit_katalog(date(2014, 3, 15),
stufe=max_stufe)
version_top.vorrueckungstermin = date(2024, 4, 1)
wizard2 = self.env['gemeinde.payroll.vorrueckung.wizard'].create({
'stichtag': date(2024, 4, 1),
})
wizard2.action_apply()
self.assertEqual(version_top.entlohnungsstufe, max_stufe)