mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
[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:
@@ -1,3 +1,3 @@
|
||||
# Models are implemented as part of the approved implementation plan;
|
||||
# see personalverrechnung/PLAN-payroll-bgld.md and
|
||||
# personalverrechnung/RECHTSQUELLEN-Bgld.md.
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
from . import entlohnung
|
||||
from . import hr_version_bgld
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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,
|
||||
)
|
||||
@@ -0,0 +1,72 @@
|
||||
# Part of the gem360 onboarding. See LICENSE / project docs for details.
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrVersion(models.Model):
|
||||
"""GemBG-Besoldungsfelder auf der Payroll-Version des Mitarbeiters.
|
||||
|
||||
§ 66 Abs 2 Bgld. GemBG 2014: Die Vorrückung erfolgt mit dem ersten Tag
|
||||
jenes Monats, der auf die Vollendung weiterer vier Jahre des
|
||||
Besoldungsdienstalters folgt (Vorrückungstermin); für bestimmte Gruppen
|
||||
zwei Jahre (§ 150c Abs 2, § 150d ff). Überleitungsfälle (§§ 157a ff)
|
||||
können einen einmalig vorgezogenen Termin haben — daher ist der
|
||||
berechnete Termin manuell überschreibbar.
|
||||
"""
|
||||
_inherit = 'hr.version'
|
||||
|
||||
entlohnungsgruppe_id = fields.Many2one(
|
||||
'gemeinde.payroll.entlohnungsgruppe', index=True,
|
||||
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
||||
entlohnungsstufe = fields.Integer(
|
||||
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
||||
besoldungsdienstalter = fields.Date(
|
||||
string='Besoldungsdienstalter (ab)',
|
||||
groups="hr_payroll.group_hr_payroll_user", tracking=True)
|
||||
vorrueckungstermin = fields.Date(
|
||||
compute='_compute_vorrueckungstermin', store=True, readonly=False,
|
||||
groups="hr_payroll.group_hr_payroll_user", tracking=True,
|
||||
help="Vorschlag nach § 66 Abs 2 GemBG (BDA + Vorrückungszeitraum); "
|
||||
"bei Überleitungsfällen manuell korrigierbar.")
|
||||
|
||||
@api.depends('entlohnungsgruppe_id', 'besoldungsdienstalter')
|
||||
def _compute_vorrueckungstermin(self):
|
||||
for version in self:
|
||||
bda = version.besoldungsdienstalter
|
||||
gruppe = version.entlohnungsgruppe_id
|
||||
if not bda or not gruppe:
|
||||
version.vorrueckungstermin = False
|
||||
continue
|
||||
period = gruppe.vorrueckungszeitraum_jahre or 4
|
||||
today = fields.Date.context_today(version)
|
||||
elapsed_years = relativedelta(today, bda).years
|
||||
next_maturity = bda + relativedelta(
|
||||
years=(elapsed_years // period + 1) * period)
|
||||
version.vorrueckungstermin = (
|
||||
fields.Date.end_of(next_maturity, 'month')
|
||||
+ relativedelta(days=1)
|
||||
)
|
||||
|
||||
def _get_katalog_entgelt(self, datum=None):
|
||||
"""Katalog-Position (Monatsentgelt) der Version zum Stichtag."""
|
||||
self.ensure_one()
|
||||
return self.entlohnungsgruppe_id._get_entgelt(
|
||||
self.entlohnungsstufe, datum)
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
entlohnungsgruppe_id = fields.Many2one(
|
||||
related="version_id.entlohnungsgruppe_id", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
entlohnungsstufe = fields.Integer(
|
||||
related="version_id.entlohnungsstufe", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
besoldungsdienstalter = fields.Date(
|
||||
related="version_id.besoldungsdienstalter", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
vorrueckungstermin = fields.Date(
|
||||
related="version_id.vorrueckungstermin", inherited=True,
|
||||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||||
Reference in New Issue
Block a user