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">
+
+
+