Files
odoo-at-payroll/addons/l10n_at_gemeinde_payroll/models/entlohnung.py
T
fegger 30270f9877 [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.
2026-09-09 11:55:15 +02:00

98 lines
4.3 KiB
Python

# Part of the gem360 onboarding. See LICENSE / project docs for details.
from odoo import _, api, fields, models
class GemeindePayrollEntlohnungsgruppe(models.Model):
"""Entlohnungsgruppe des Bgld. GemBG 2014 (z. B. bv1, kb2, av1).
Die Entlohnungsstruktur des GemBG ist schemenbasiert (Entlohnungs-
schemata I, II, Ia, IIa, kb, av sowie Sondergruppen je §). Eine Gruppe
gehört zu genau einem Schema; die Monatsentgelte je Stufe stehen auf
``gemeinde.payroll.entlohnungsstufe`` (Jahreswerte aus den Bgld.
Bezügeanpassungs-Sammelgesetzen, siehe personalverrechnung/).
"""
_name = 'gemeinde.payroll.entlohnungsgruppe'
_description = 'GemBG Entlohnungsgruppe (Bgld.)'
_order = 'bundesland, schema, code'
code = fields.Char(required=True, index=True)
schema = fields.Char(
required=True, index=True,
help="Entlohnungsschema gemäß GemBG (z. B. I, II, Ia, IIa, kb, av "
"oder §-Kennung für Sondergruppen).")
paragraph = fields.Char(
help="Paragraf der Monatsentgelt-Tabelle im Bgld. GemBG 2014.")
bundesland = fields.Selection(
[('bgld', 'Burgenland')], required=True, default='bgld', index=True)
vorrueckungszeitraum_jahre = fields.Integer(
string='Vorrückungszeitraum (Jahre)', required=True, default=4,
help="§ 66 Abs 2 GemBG: 4 Jahre; abweichend 2 Jahre für bestimmte "
"Gruppen (§ 150c Abs 2, Betreuungspersonen nach § 150d ff).")
active = fields.Boolean(default=True)
entlohnungsstufe_ids = fields.One2many(
'gemeinde.payroll.entlohnungsstufe', 'entlohnungsgruppe_id',
string='Entlohnungsstufen')
_unique_group = models.Constraint(
'unique (bundesland, schema, code)',
"Eine Entlohnungsgruppe existiert je Schema und Bundesland nur einmal.")
@api.depends('code', 'schema')
def _compute_display_name(self):
for gruppe in self:
gruppe.display_name = f"{gruppe.code} ({gruppe.schema})"
def _get_entgelt(self, stufe, datum=None):
"""Liefert die Entlohnungsstufen-Position zum Stichtag (oder leer)."""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
return self.env['gemeinde.payroll.entlohnungsstufe'].search(
[('entlohnungsgruppe_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 Monatsentgelt zum Stichtag."""
self.ensure_one()
datum = datum or fields.Date.context_today(self)
stufe = self.env['gemeinde.payroll.entlohnungsstufe'].search(
[('entlohnungsgruppe_id', '=', self.id),
('gueltig_ab', '<=', datum)],
order='stufe desc', limit=1)
return stufe.stufe or 0
class GemeindePayrollEntlohnungsstufe(models.Model):
"""Monatsentgelt einer Entlohnungsgruppe in einer Stufe ab einem
Stichtag (Jahresversionen aus den Bezügeanpassungs-Tabellen)."""
_name = 'gemeinde.payroll.entlohnungsstufe'
_description = 'GemBG Monatsentgelt je Entlohnungsstufe (Bgld.)'
_order = 'entlohnungsgruppe_id, stufe, gueltig_ab desc'
_rec_names_search = ['entlohnungsgruppe_id']
entlohnungsgruppe_id = fields.Many2one(
'gemeinde.payroll.entlohnungsgruppe', required=True, index=True,
ondelete='cascade')
stufe = fields.Integer(required=True, index=True)
monatsentgelt = 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_stufe = models.Constraint(
'unique (entlohnungsgruppe_id, stufe, gueltig_ab)',
"Je Entlohnungsgruppe und Stufe gibt es nur einen Wert je Stichtag.")
@api.depends('entlohnungsgruppe_id', 'stufe', 'monatsentgelt', '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.entlohnungsgruppe_id.code,
stufe=stufe.stufe,
betrag=stufe.monatsentgelt,
ab=stufe.gueltig_ab,
)