From 0abc1383d1e057ebcbaf40dbda2368f34592a4d4 Mon Sep 17 00:00:00 2001 From: fegger Date: Sat, 12 Sep 2026 23:18:07 +0200 Subject: [PATCH] =?UTF-8?q?[FIX]=20l10n=5Fat=5Fgemeinde=5Fpayroll:=20Modul?= =?UTF-8?q?=20erstmals=20installierbar=20=E2=80=94=209=20Odoo-19-Latenzfeh?= =?UTF-8?q?ler;=20BDA-Vordienstzeiten-Vorschlag=20(AP10+AP9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AP10 — Latenzfehler des nie installierten Vorlaufs: - regular_pay auf hr.payroll.structure entfernt (Feld existiert nicht) - is_paid/round_days/round_days_type von WET-Records entfernt - Falscher XML-ID-Praefix payroll_structure_bgld. -> Modul-Referenzen - amount_select 'python' -> 'code' (Selection) - import in Regelcode entfernt (safe_eval verbietet IMPORT_NAME) - rules['ME'].total -> result_rules['ME']['total'] (Odoo 19 befuellt rules nie; SZQ-Sonderzahlung rechnete gegen Null) - categories.X -> categories['X'] (NETTO-Regel) - work_time_rate /100 entfernt (Odoo 19 speichert Bruchteil 0..1) - nie gelaufene Tests: Payslip-name-Pflichtfeld, Stichtag vor Katalog-Gueltigkeit (Wizard-Skip korrekt), date_start ist Compute aus contract_date_start AP9 — BDA-Vordienstzeiten (GemBG S 67 Abs 1-3, RIS LrB GesNr 20001002 verifiziert): Feld vordienstzeiten_monate (Help dokumentiert Anrechnungskatalog inkl. Obergrenzen), Compute bda_vorschlag (Vertragsbeginn - Monate), Uebernahme-Button als Verwaltungsakt, View-Erweiterung --- .agents/MEMORY.md | 165 ++++++++++++++++++ .../l10n_at_gemeinde_payroll/__manifest__.py | 2 +- .../data/payroll_structure_bgld.xml | 22 +-- .../data/salary_rules_bgld_entgelt.xml | 75 ++++---- .../models/hr_version_bgld.py | 55 +++++- .../tests/test_entgelt.py | 1 + .../tests/test_katalog.py | 44 ++++- .../views/hr_version_bgld_views.xml | 7 + 8 files changed, 306 insertions(+), 65 deletions(-) diff --git a/.agents/MEMORY.md b/.agents/MEMORY.md index 5f71140..cf7413d 100644 --- a/.agents/MEMORY.md +++ b/.agents/MEMORY.md @@ -2,6 +2,153 @@ ## Current focus +**AP 4 EFZG/Teilentgelt umgesetzt — 144/144 Tests grün** +(2026-09-12): + +- **Rechtsverifikation:** lb-krs-08-⚠Marke erledigt — EFZG von RIS + beschafft (Bundesnormen **GesNr 10008308**, + .firecrawl/ris/EFZG_*GeltendeFassung.html) und **§ 2 Abs 1/3/5 + verifiziert**; § 8 Abs 1/2/2a AngG lokal verifiziert — + **Gleichlauf bestätigt** (6/8/10/12 Wochen bei 0/1/15/25 + vollendeten DJ + je 4 Wochen halb). „56 Tage" = AU-Topf 8 Wochen + (lb-asc-15), KEINE eigene Regel. **KUG § 62 ASVG = Falsch- + Prämisse** (RECHTSQUELLEN-Privat 2026-09-10: § 62 regelt + Beitragsabrechnung; deutsches U1-Vorbild) — kein Baustein. +- **EFZ-Maschine** `models/efzg.py`: `_at_efz_status()` — + Kalendertage-Kontingente je Arbeitsjahr (Eintrittsdatum-Rhythmus), + Krank- und AU-Topf getrennt (AU nur voll), Stufe je Tag + (Stufensprung verlängert), Phasen je Arbeitstag aus den + ATP_KRANK/ATP_AU-Work Entries (neue Urlaubsart Arbeitsunfall); + Episode-Beginn für fiktive BV; Info-Feld am Payslip. +- **Regeln** TEIL_ENTGELT_HALB (−50 %/AT) + KRANKENTGELT_PHASE + (−100 %/AT) als negative Brutto-Zeilen (seq 45/46, Tagesatz = + unreduzierte laufende Basis ÷ Arbeitstage — + `_at_efz_tagesatz(result_rules)` rechnet die eigenen Reduktionen + heraus); SV/LSt/LNK folgen über die reduzierte laufende Basis + (lb-krs-07). +- **Fiktive BV § 7 Abs 3 BMSVG** in `_at_bv_basis_laufen`-Hook: + 50 % Vormonatsentgelt (Episode-Beginn-Monat davor, SZ ausgenommen) + × Halb-/ÖGK-Kalendertage/30 — Halbphase zählt mit (Krankengeld + ruht zur Hälfte, Quellbeispiel lb-krs-07); fließt in BVG-Zeile + und mBGM-BV-Basis. +- Parameter `at_efz_stufen` (data/rule_parameters_efzg_private.xml); + Rechenfälle docs/rechenfaelle/efzg.md; 6 Tests (Übergang, + Kontingent, Stufe 8 Wo, AU-Topf, fiktive BV, Wirkungslos ohne + Stand). November-B13-SZ-Bezug in BV-Grundlage inklusive (§ 6 + BMSVG) — Tests komponentenweise. +- Version: Privat **19.0.9.2.0**. 144/144 (64+106+18) frischer + -i-Lauf, py/XML-Sweep. +- **Offen:** AP 7 (Wochengeld/Mutterschutz), AP 8 (AEB SART 70/75) + — EFZ-WETs sind verdrahtet; dokumentierte Reste: Kalenderjahr- + Umstellung § 8 Abs 9 AngG, ÜStd-/Provisionsdurchschnitte, SZ- + Kürzung „kann"-Regel. + +**AP 9 Vordienstzeiten/Einstufung umgesetzt — 138/138 Tests grün** +(2026-09-12): + +- **Rechtsbeschaffung:** Bgld. GemBG 2014 via RIS **LrB GesNr + 20001002** (geltende Fassung, Quelle + `.firecrawl/ris/bgld/GemBG_2014_GeltendeFassung.html`) — **§ 67 + verifiziert**: BDA = Dienstzeit + anrechenbare Vordienstzeiten + (Abs 1); Pflicht-Anrechnung EWR-ÖD/EU-Dienste (Abs 2 Z 1 f), + Grundwehr/Ausbildung max 6 M, Zivil/Ersatz max 9 M (Z 4), + einschlägige Berufstätigkeit/Verwaltungspraktikum **max 10 Jahre** + (Abs 3). Rahmen-KV § 17 (SI-2203 Volltext) verifiziert: Abs 4 + Anzahl angerechneter VWGJ durch Firmenleitung + Dienstzettel, + Abs 5 Vorrückung am **Ersten des Erreichungsmonats**. +- **Privat (AP9b):** Stufen-Neu-Feld `vwgj_ab` (aus Labels "nach N + VWGJ", 0=ab Beginn, **-1=nicht gepflegt** — ORM liest Integer- + NULL als 0, deshalb -1-Konvention + Domain `> -1`); CSV-Seed um + vwgj_ab ergänzt (131 Zeilen); Gruppen-Methoden + `_get_stufe_fuer_vwgj`/`_get_naechste_vwgj` (kollektivvertrag.py); + Vertragsfeld `l10n_at_vordienstzeit_monate` + Compute + `l10n_at_vwgj_monate`/`l10n_at_vwgj_stufe_vorschlag`/ + `l10n_at_naechste_vorrueckung` (§ 17 Abs 5) + Button + `action_einstufung_uebernehmen` (bewusst manueller + Verwaltungsakt); KV-Einstufungs-Gruppe im View erweitert. + Tests: test_einstufung_private 4 (freeze_time für Determinismus). +- **GemBG (AP9a):** `vordienstzeiten_monate` (Help dokumentiert den + kompletten §-67-Katalog) + Compute `bda_vorschlag` (= Beginn − + Monate) + Button `action_bda_uebernehmen`; View am + Besoldungs-Block. Test test_06 (BDA-Übernahme triggert + §-66-Abs-2-Vorrückungstermin). +- Versionen: Privat **19.0.9.1.0**, GemBG **19.0.1.6.0**. + Validierung: 138/138 frischer -i-Lauf, py/XML-Sweep, DB entfernt. +- **Offen:** AP 4 (EFZG/Teilentgelt + fiktive BV), AP 7 + (Wochengeld/Mutterschutz), AP 8 (AEB SART 70/75). + +**AP 5 + AP 2a–d + AP 3 + AP 10 umgesetzt — 133/133 Tests grün** +(2026-09-12, Folgeturn nach AP0/AP1, Reihenfolge lt. Plan): + +- **AP5 Abwesenheiten:** hr_holidays + hr_work_entry_holidays als + Pflicht-Dependence des Privatmoduls; AT-WETs ATP_URLAUB/ATP_KRANK/ + ATP_KARENZ (is_leave) + country-gebundene AT-Urlaubsarten mit + WET-Verknüpfung (data/absence_private.xml, Odoo-Lokalisations- + muster); Karenz-Wochen erkennbar via Work Entries. +- **AP2a–d Reports:** Monatsjournal mit eigenem **Dienstgeberanteile- + Block** (ATP_DG-Kategorie, vorher still ausfiltert), neuer Report + **Dienstgeber-Gesamtkosten** (je AN Brutto + LNK aufgeschlüsselt/kumuliert + + Laufsummen, Spalten FLAF/KommSt/DZ/BV/Wr. DAG), **Jahreslohnkonto + je DN** + **Betriebsjahreslohnkonto** (Wizard + Monatsmatrix, + models/monatsreports.py + report/jahresreports.xml), **Buchungsbeleg** + am Lohnlauf (private, models/buchungsbeleg.py). + **Latenz-Bug:** models/monatsreports.py war NIE in models/__init__.py + importiert — sämtliche GP7-Reportmodelle waren toter Code! +- **AP3 U-Bahn-Steuer:** Wr. DAG via RIS **verifiziert** (LrW GesNr + 20000133, Fassung 12.09.2026, Quelle .firecrawl/ris/wien/WDGAG_*.html; + 2 €/Woche § 5, Befreiungen § 3, 15./31.3. § 6, Rückerstattung § 7 + 218,02/2.180,19 €) — Implementierung: Company-Flag l10n_at_wien_dag + (Kern), Regel WIEN_DAG (models/wien_dag.py — Wochenlogik Montag-im- + Monat + Befreiungen Lehrling/55+/≤10WStd/Karenz-Woche), Fristenarten + wien_dag/wien_dag_jahres (cron-geflaggt), FI 6663 (3560 Referenz- + rest), DG-Kosten-Spalte. **Wochenlektüre „Montag beginnt im Monat" = + Praxis-Annahme, Parallellauf verifizieren.** +- **AP10 GemBG-Fixpaket:** Modul erstmals **installierbar** — 9 + Latenzfehler behoben: regular_pay, is_paid+round_days* auf WETs, + falscher XML-ID-Präfix (payroll_structure_bgld.), amount_select + 'python'→'code', safe_eval-Import, rules→result_rules (Odoo 19 + befült rules NIE), categories['X'], work_time_rate-÷100 (Bruchteil + 0..1), nie gelaufene Tests (Payslip-name, Stichtag vor Katalog- + gültigkeit, date_start ist Compute aus contract_date_start). +- Versionen: Kern **19.0.6.0.0**, Privat **19.0.9.0.0**, GemBG + **19.0.1.5.0**. Validierung: 133/133 (17 GemBG + 64 Kern + 92 + Privat), frischer -i-Lauf aller 3 Module, py/XML-Sweep, DB entfernt. +- **Offen:** AP 9 (Vordienstzeiten — jetzt entblockt), AP 4 (EFZG/ + Teilentgelt), AP 7 (Wochengeld/Mutterschutz), AP 8 (AEB SART 70/75). + +**AP0 TASY-Katalog (Variante b) + AP1 AGRD umgesetzt und grün (109/109 +Tests)** (2026-09-12, Nutzer-Freigabe des AP-Plans mit +Scope-Entscheidungen): — Kernmodul: neue Katalogmodelle +`models/tasy_katalog.py` (10 Modelle: tasy.bsgr/ergaenzung/ +dienstgebergruppe/tarifgruppe/tarifset/tarif/tarifset.zuord/ +zuabschlag/zuabschlag.steuerung [+ m2m]), Import-Wizard +`l10n.at.payroll.tasy.katalog.wizard` in `models/tasy.py` +(sektionsscharf — root.iter() fasste auch Referenz-Elemente!), +Sicherheit, Views/Menus `views/tasy_katalog_views.xml`; +Auflösungs-API `_l10n_at_find(bsgr, ergs, dgg, stichtag)` → +`_l10n_at_saetze(basis, stichtag)`. Privatmodul: mBGM liest Katalog +vor `at_mbgm_tarif` (quellen-Fallback), Company-Feld +`l10n_at_dienstgebergruppe` (Default NBALG), Vertrags-m2m +`l10n_at_ergaenzung_ids`, **ERGB wird real belegt** (15 Stellen im T1), +NSchAB-Flag nur noch Parameter-Fallback. **BSGR-Korrektur:** +Lehrlinge arbeiter→**B045**, angestellte→**B044** (TASY: B044 = +„Angestelltenlehrlinge"), geringf./fallweise→**B010/B030** je +Personalgruppe; **NSchAB-Feldumbenennung** Schlechtwetter→Nachtschwer- +arbeit (3,80 % = NB-Zweig DG ab 2020, verifiziert; SW-Zweig = 0,7/0,7). +**AP1** — `l10n_at_agrd` Char→Selection mit vollständiger D.22-Liste +(33 Codes, lokal verifiziert), Seed `data/hr_departure_reason_seed.xml` +(noupdate; hr-Defaults fired→01/resigned→02/retired→16 vorverbunden + +7 AT-Ursachen), View-Erbe auf Abgangsursachen-Form/Liste. +Nutzer-Entscheidungen: hr_holidays als **Pflicht-Dependence**; +Wochengeld **volle Berechnung**; TASY-Import **alle Träger**; AGRD +Migration OK; Buchungsbeleg **Lohnlauf-Ebene**; GemBG-Fix-Paket **ja**. +Validierung: 109/109 grün auf Wegwerf-DB (frischer -i-Lauf; +test_tasy_katalog 5 inkl. Realexport — Fund: altersgrenzeMinJahr +ist **Dezimal** (60.5) → Float-Felder), py/XML-Sweep ok, DB entfernt. +**Nächste APs:** AP 5 (holidays/WETs), AP 2a-d (Reports), AP 3 +(U-Bahn nach RIS-Klärung), AP 4/7/8 (EFZG/Wochengeld/AEB), AP 10/9 +(GemBG-Fix → Vordienstzeiten). + **GP9 UI/Security/Doku umgesetzt — GP1–GP9 komplett, AP9 abgeschlossen, Projekt M8 final** (2026-09-12): Kern 19.0.5.1.0 — Company-Stammdaten-UI (BKNR/LStNr/Bundesland/WKO/WF-Wien — Reiter @@ -1078,6 +1225,24 @@ B17-Inhalt/B18; Kleinverifikationen s. KB-KONFLIKTE „Nächste Schritte“). `IMPLEMENTIERUNGSPLAN-Bgld.md` Abschnitt 8. - Odoo-Upgrades künftig in beiden Repos (gem360 + dieses) gegen denselben Odoo-Pin validieren. +- **Audit ÖGK-Tarifgruppensystem (2026-09-12, Nutzer-Audit-Frage):** Um- + fang nur „Normalbetriebe Allgemeiner Teil": mBGM-BSGR B001/B002 mit + normativ korrekten T01 39,05 %/T02 37,55 % (gegen TASY-LSWH-Export + 2026-07-01 verifiziert: KV 3,87/3,78, PV 10,25/12,55, AK 0,5 DN gilt + für B001 **und** B002, Kern-Splitting stimmt); ERGB (E01 NSch, E02 SW, + E03 Schulpflicht …) nie belegt — NSchAB läuft stattdessen über Company- + Flag +3,80 % auf B001; nur 6 Abschläge (A01–A03/A09/A10/A15) von ~30 + ÖGK-Handbuch-Abschlägen; keine Dienstgebergruppen-Konzept. **Zwei + BSGR-Befunde vor Parametrisierung fixen:** (1) `_l10n_at_bsgr` mappt + Lehrling+`arbeiter` auf **B044** — TASY: B044=Angestelltenlehrlinge, + B045=Arbeiterlehrlinge → **invertiert** (BBER-Mapping in versicher- + tenmeldung.py ist korrekt, D.39: 03=Arbeiterlehrling); (2) gering- + fügig/fallweise immer **B030** (Gfg. Ang.), B010 (Gfg. Arb.) wird nie + emittiert. Beide heute folgenlos (Lauf bricht ohne B044/B045/B010/ + B030-Parameter listenweise ab), falsch sobald parametrisiert. TASY- + Import mappt nur 3 BBB + 8 Tarifzweige (erweiterbares Mapping-Modell, + kein BSGR/ERGB/Tarifgruppen-Import). Kein Code-Fix ohne Nutzer- + Freigabe (Audit-only). ## Decisions & conventions diff --git a/addons/l10n_at_gemeinde_payroll/__manifest__.py b/addons/l10n_at_gemeinde_payroll/__manifest__.py index 08846c9..632f502 100644 --- a/addons/l10n_at_gemeinde_payroll/__manifest__.py +++ b/addons/l10n_at_gemeinde_payroll/__manifest__.py @@ -1,7 +1,7 @@ # Part of the odoo-at-payroll project. License: LGPL-3. { 'name': 'Österreich - Gemeinde-Personalverrechnung (Bgld. GemBG)', - 'version': '19.0.1.4.0', + 'version': '19.0.1.6.0', 'author': 'ixsol / gem360', 'website': 'https://gem360.at', 'category': 'Human Resources/Payroll', diff --git a/addons/l10n_at_gemeinde_payroll/data/payroll_structure_bgld.xml b/addons/l10n_at_gemeinde_payroll/data/payroll_structure_bgld.xml index 1647f09..1eb106c 100644 --- a/addons/l10n_at_gemeinde_payroll/data/payroll_structure_bgld.xml +++ b/addons/l10n_at_gemeinde_payroll/data/payroll_structure_bgld.xml @@ -16,7 +16,6 @@ Gemeindebedienstete (Bgld. GemBG 2014) GEMBG.BGLD - @@ -42,47 +41,32 @@ + Überstunden (tagsüber, § 76 GemBG) GEMBG_UEB_TAG - NO - DOWN - Überstunden (Nachtzeit 22-6 Uhr, § 76 GemBG) GEMBG_UEB_NACHT - NO - DOWN - Mehrdienstleistung (§ 76 GemBG) GEMBG_MEHR - NO - DOWN - Journaldienst (§ 79 GemBG) GEMBG_JOURNAL - NO - DOWN - Bereitschaftsdienst (§ 80 GemBG) GEMBG_BEREIT - NO - DOWN - Sonn-/Feiertagsdienst (§ 78 GemBG) GEMBG_SONNFEI - NO - DOWN - diff --git a/addons/l10n_at_gemeinde_payroll/data/salary_rules_bgld_entgelt.xml b/addons/l10n_at_gemeinde_payroll/data/salary_rules_bgld_entgelt.xml index 21d863f..81aac27 100644 --- a/addons/l10n_at_gemeinde_payroll/data/salary_rules_bgld_entgelt.xml +++ b/addons/l10n_at_gemeinde_payroll/data/salary_rules_bgld_entgelt.xml @@ -7,11 +7,11 @@ Monatsentgelt ME - - + + python result = bool(employee.entlohnungsgruppe_id and employee.entlohnungsstufe) - python + code @@ -37,8 +38,8 @@ else: Kinderzulage (§ 61 GemBG) KIZU - - + + python result = employee.kinder_anzahl > 0 code @@ -58,16 +59,16 @@ result = employee.kinder_anzahl * kiz_satz Funktionszulage (§ 62 GemBG) FUZU - - + + python result = employee.funktionszulage > 0 code @@ -78,8 +79,8 @@ result = employee.funktionszulage * work_rate / 100.0 Überstundenvergütung tagsüber (§ 76 GemBG) UEZ_TAG - - + + python result = 'GEMBG_UEB_TAG' in worked_days and worked_days['GEMBG_UEB_TAG'].number_of_hours > 0 code @@ -100,8 +101,8 @@ else: Überstundenvergütung Nachtzeit (§ 76 GemBG) UEZ_NACHT - - + + python result = 'GEMBG_UEB_NACHT' in worked_days and worked_days['GEMBG_UEB_NACHT'].number_of_hours > 0 code @@ -125,8 +126,8 @@ else: Journaldienstzulage (§ 79 GemBG) JZU - - + + python result = 'GEMBG_JOURNAL' in worked_days and worked_days['GEMBG_JOURNAL'].number_of_hours > 0 code @@ -146,8 +147,8 @@ result = tage * satz Sonn-/Feiertagszulage (§ 78 GemBG) SFZU - - + + python result = 'GEMBG_SONNFEI' in worked_days and worked_days['GEMBG_SONNFEI'].number_of_hours > 0 code @@ -174,8 +175,8 @@ else: Bereitschaftsentschädigung (§ 80 GemBG) BERB - - + + python result = 'GEMBG_BEREIT' in worked_days and worked_days['GEMBG_BEREIT'].number_of_hours > 0 code @@ -195,8 +196,8 @@ result = stunden * satz Erschwerniszulage (§ 83 GemBG) ERZU - - + + python result = 'ERZU_INPUT' in inputs and inputs['ERZU_INPUT'].amount > 0 code @@ -212,8 +213,8 @@ result = inputs['ERZU_INPUT'].amount Jubiläumszuwendung (§ 89 GemBG) JUB - - + + python result = 'JUB_INPUT' in inputs and inputs['JUB_INPUT'].amount > 0 code @@ -229,8 +230,8 @@ result = inputs['JUB_INPUT'].amount Fahrtkostenzuschuss (§ 88 GemBG) FKZ - - + + python result = 'FKZ_INPUT' in inputs and inputs['FKZ_INPUT'].amount > 0 code @@ -246,8 +247,8 @@ result = inputs['FKZ_INPUT'].amount Sonderzahlung (quartalsweise, § 133n GemBG) SZQ - - + + python @@ -266,8 +266,9 @@ result = month in (3, 6, 9, 12) # Kinderzulage), die ihnen für den Monat der Auszahlung zustehen"). # Bei unterbrochenem Vierteljahr: nur der entsprechende Teil # (R1: Vollzeit-Rechnung; Anteil folgt mit der Zeit-Erfassung). -me_total = rules['ME'].total or 0 -kizu_total = rules['KIZU'].total or 0 +# Odoo 19 befült result_rules (nicht rules) mit den Regelsummen. +me_total = result_rules['ME']['total'] or 0 +kizu_total = result_rules['KIZU']['total'] or 0 result = (me_total + kizu_total) * 0.5 ]]> @@ -279,12 +280,12 @@ result = (me_total + kizu_total) * 0.5 Netto NETTO - - + + none code \ No newline at end of file diff --git a/addons/l10n_at_gemeinde_payroll/models/hr_version_bgld.py b/addons/l10n_at_gemeinde_payroll/models/hr_version_bgld.py index 9d4c66f..0710846 100644 --- a/addons/l10n_at_gemeinde_payroll/models/hr_version_bgld.py +++ b/addons/l10n_at_gemeinde_payroll/models/hr_version_bgld.py @@ -24,12 +24,55 @@ class HrVersion(models.Model): besoldungsdienstalter = fields.Date( string='Besoldungsdienstalter (ab)', groups="hr_payroll.group_hr_payroll_user", tracking=True) + vordienstzeiten_monate = fields.Integer( + string='Anerkannte Vordienstzeiten (Monate)', + groups="hr_payroll.group_hr_payroll_user", tracking=True, + help='AP9 — Bgld. GemBG § 67: Das Besoldungsdienstalter umfasst ' + 'die im Dienstverhältnis verbrachten wirksamen Zeiten ' + 'ZUSAMMEN mit den anrechenbaren Vordienstzeiten ' + '(Abs 1). Anzurechnen sind u. a. Zeiten in einem ' + 'Dienstverhältnis zu einer Gebietskörperschaft/einem ' + 'Gemeindeverband (EWR/Türkei/Schweiz) sowie EU- und ' + 'zwischenstaatliche Dienstverhältnisse (Abs 2 Z 1 f); ' + 'Grundwehr-/Ausbildungsdienst zusammen max. 6 Monate, ' + 'Zivil-/Ersatzdienst max. 9 Monate (Abs 2 Z 4); über ' + 'die Pflichtzeiten hinaus einschlägige Berufstätigkeit ' + 'oder ein Verwaltungspraktikum max. 10 Jahre (Abs 3). ' + 'Doppelanrechnung desselben Zeitraums unzulässig. ' + 'Hier: die vom Bescheid anerkannten Monate gesamt.') + bda_vorschlag = fields.Date( + string='BDA-Vorschlag (Vordienstzeiten)', + compute='_compute_bda_vorschlag', + groups="hr_payroll.group_hr_payroll_user", + help='Vorschlag: Vertragsbeginn zurückverlegt um die ' + 'anerkannten Vordienstzeiten (§ 67 Abs 1). Übernahme ' + 'bewusst manuell — Besoldungsdienstalter ist ein ' + 'dokumentationspflichtiger Verwaltungsakt (s. Vorrückungs-' + 'Wizard).') 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('contract_date_start', 'vordienstzeiten_monate') + def _compute_bda_vorschlag(self): + for version in self: + if not (version.contract_date_start + and version.vordienstzeiten_monate): + version.bda_vorschlag = False + continue + version.bda_vorschlag = version.contract_date_start \ + - relativedelta(months=version.vordienstzeiten_monate) + + def action_bda_uebernehmen(self): + """BDA-Vorschlag als Besoldungsdienstalter übernehmen — ein + expliziter Verwaltungsakt, kein stilles Schreiben im Lohnlauf + (Muster: Vorrückungs-Wizard § 66 Abs 2).""" + for version in self: + if version.bda_vorschlag: + version.besoldungsdienstalter = version.bda_vorschlag + @api.depends('entlohnungsgruppe_id', 'besoldungsdienstalter') def _compute_vorrueckungstermin(self): for version in self: @@ -67,6 +110,16 @@ class HrEmployee(models.Model): besoldungsdienstalter = fields.Date( related="version_id.besoldungsdienstalter", inherited=True, readonly=False, groups="hr_payroll.group_hr_payroll_user") + vordienstzeiten_monate = fields.Integer( + related="version_id.vordienstzeiten_monate", inherited=True, + readonly=False, groups="hr_payroll.group_hr_payroll_user") + bda_vorschlag = fields.Date( + related="version_id.bda_vorschlag", inherited=True, + 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") \ No newline at end of file + readonly=False, groups="hr_payroll.group_hr_payroll_user") + + def action_bda_uebernehmen(self): + """Delegation an die Version (Button am Mitarbeiterformular).""" + self.mapped('version_id').action_bda_uebernehmen() \ No newline at end of file diff --git a/addons/l10n_at_gemeinde_payroll/tests/test_entgelt.py b/addons/l10n_at_gemeinde_payroll/tests/test_entgelt.py index f26cac0..3ec4d3b 100644 --- a/addons/l10n_at_gemeinde_payroll/tests/test_entgelt.py +++ b/addons/l10n_at_gemeinde_payroll/tests/test_entgelt.py @@ -44,6 +44,7 @@ class EntgeltTest(TransactionCase): slip_month = month or 7 slip = self.env['hr.payslip'].create({ + 'name': 'GemBG-Test %d/2026' % slip_month, 'employee_id': employee.id, 'struct_id': self.structure.id, 'date_from': date(2026, slip_month, 1), diff --git a/addons/l10n_at_gemeinde_payroll/tests/test_katalog.py b/addons/l10n_at_gemeinde_payroll/tests/test_katalog.py index 352710c..83539e8 100644 --- a/addons/l10n_at_gemeinde_payroll/tests/test_katalog.py +++ b/addons/l10n_at_gemeinde_payroll/tests/test_katalog.py @@ -1,6 +1,8 @@ # Part of the gem360 onboarding. See LICENSE / project docs for details. from datetime import date +from freezegun import freeze_time + from odoo.tests import TransactionCase, tagged @@ -53,9 +55,14 @@ class KatalogTest(TransactionCase): if not version: version = self.env['hr.version'].create({ 'employee_id': employee.id, - 'date_start': date(2020, 1, 1), + 'contract_date_start': date(2020, 1, 1), }) version.write({ + # contract_date_start ins Suchfenster des Vorrückungs- + # Wizards rücken — hr.version.date_start ist ein nicht- + # gespeicherter Compute daraus (nie gelaufener Test, + # Odoo-19-Latenzkorrektur). + 'contract_date_start': date(2014, 1, 1), 'entlohnungsgruppe_id': self.env.ref('l10n_at_gemeinde_payroll.bgld_grp_av_av1').id, 'entlohnungsstufe': stufe, @@ -76,10 +83,13 @@ class KatalogTest(TransactionCase): """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)) + # Stichtag ab Katalog-Gültigkeit (Seed 2026-07-01) — vor dem + # 1. 7. 2026 gibt es keine Katalogwerte, der Wizard überspringt + # korrekt (nie gelaufener Test, Odoo-19-Latenzkorrektur). + version.vorrueckungstermin = date(2028, 4, 1) + max_stufe = version.entlohnungsgruppe_id._max_stufe(date(2028, 4, 1)) wizard = self.env['gemeinde.payroll.vorrueckung.wizard'].create({ - 'stichtag': date(2024, 4, 1), + 'stichtag': date(2028, 4, 1), }) self.assertIn(version, wizard.version_ids) wizard.action_apply() @@ -87,9 +97,29 @@ class KatalogTest(TransactionCase): version_top = self._version_mit_katalog(date(2014, 3, 15), stufe=max_stufe) - version_top.vorrueckungstermin = date(2024, 4, 1) + version_top.vorrueckungstermin = date(2028, 4, 1) wizard2 = self.env['gemeinde.payroll.vorrueckung.wizard'].create({ - 'stichtag': date(2024, 4, 1), + 'stichtag': date(2028, 4, 1), }) wizard2.action_apply() - self.assertEqual(version_top.entlohnungsstufe, max_stufe) \ No newline at end of file + self.assertEqual(version_top.entlohnungsstufe, max_stufe) + + def test_06_bda_vorschlag_aus_vordienstzeiten(self): + """AP9 — § 67 Abs 1 GemBG: BDA-Vorschlag = Vertragsbeginn + zurückverlegt um die anerkannten Vordienstzeiten; Übernahme + setzt das BDA und den Vorrückungstermin neu (§ 66 Abs 2).""" + version = self._version_mit_katalog(date(2014, 3, 15)) + version.write({'contract_date_start': date(2026, 7, 1)}) + self.assertFalse(version.bda_vorschlag) + version.vordienstzeiten_monate = 48 + self.assertEqual(version.bda_vorschlag, date(2022, 7, 1)) + # Ohne Übernahme bleibt das gepflegte BDA (Setup) unangetastet. + self.assertEqual(version.besoldungsdienstalter, date(2014, 3, 15)) + with freeze_time('2026-09-12'): + version.action_bda_uebernehmen() + self.assertEqual(version.besoldungsdienstalter, date(2022, 7, 1)) + # Vorrückungstermin nach § 66 Abs 2: Die 4-Jahres-Vollendung + # (1. 7. 2026) ist am 12. 9. 2026 bereits verstrichen → der + # Termin der NÄCHSTFÄLLIGEN Vorrückung: BDA + 8 Jahre = + # 1. 7. 2030 → 1. 8. 2030. + self.assertEqual(version.vorrueckungstermin, date(2030, 8, 1)) \ No newline at end of file diff --git a/addons/l10n_at_gemeinde_payroll/views/hr_version_bgld_views.xml b/addons/l10n_at_gemeinde_payroll/views/hr_version_bgld_views.xml index c101f22..dfa698e 100644 --- a/addons/l10n_at_gemeinde_payroll/views/hr_version_bgld_views.xml +++ b/addons/l10n_at_gemeinde_payroll/views/hr_version_bgld_views.xml @@ -10,8 +10,15 @@ groups="hr_payroll.group_hr_payroll_user"> + + +