[IMP] l10n_at_hr_payroll_private: GP5 Abfertigung Neu (BVG-Regel, eigene BV-Grundlage, Anwartschaft/Report/Wizard)

Laufender Beitrag (§ 6 BMSVG): Regel BVG (Kategorie ATP_DG, ohne Nettoeffekt) auf EIGENER, ungecappter BV-Grundlage — §-49-bereinigt, ohne HBG-/Jahresdeckel (§ 6 Abs 5 iVm § 49 ASVG) und ohne §-67-Abs-3/6-Zeilen (§ 49 Abs 3 Z 7 ASVG); bewusst kein Rückgriff auf die SV-SZ-Basis des Kerns (Nutzer-Entscheidung eigene BV-Grundlage; deren Z-7-Ausschluss fehlt — GP4-Beobachtung bleibt offen dokumentiert).

Beitragsfreier erster Monat taggenau: Fenster [Eintritt, Eintritt + 1 Monat), Beitragspflicht ab Monatsjubiläum (15. 06. → 15. 07.; KB-Beispiel lb-vor-03: 4.500/30 × 17 = 2.550 — die ab-Folgetag-Lesart war ein Off-by-one und wurde verworfen); 30-Tage-Aliquotierung über die Lohnsteuertage (Teilmonate ohne Doppelabzug); Monatsende-Klippe (Eintritt 31. 01.): Fenster bis Monatsultimo, Pflicht ab Folgemonatserstem (ABGB-Fristrechnung); AV ≤ 1 Monat zur Gänze beitragsfrei.

Wiederzulassung binnen 12 Monaten → Beitragspflicht ab dem 1. Tag; Vor-AV beliebiger Dauer (auch < 1 Monat, Nutzer-Klarstellung) und schlägt auch die Kurz-AV-Freiheit des laufenden AV (OGH 9 ObA 30/16a; Suche über frühere hr.version mit contract_date_end im 12-Monats-Fenster). Sonderzahlungen werden nicht um das Fenster gekürzt, bleiben frei nur bei Beitragsmonat (voller Datumstag, taggenau) im Fenster. Geringfügig Beschäftigte und Lehrlinge sind erfasst — BVG läuft auch im §-53a-Fall.

Anwartschaft (§ 15: gutgebrachte Beiträge + Erträge, keine Monatsentgelt-Staffel) on-the-fly aus validierten Läufen; Anwartschaftsreport (QWeb auf hr.employee) und Endabrechnungs-Wizard (§§ 14-17: §-14-Abs-2-Verfügungsmatrix, < 36 Beitragsmonate sperrt immer, §-16-Fälligkeit am Ende des zweitfolgenden Monats, 6-Monats-Erklärungsfrist, 5 Verfügungsoptionen) — die Auszahlung erfolgt durch die MV-Kasse, nicht über den Payslip.

Stammdaten: Vertrags-Flag l10n_at_abfertigung_neu (§ 46) + Modell l10n.at.bv.kasse (Leitzahl für eSV/mBGM; Seed der 7 Kassen-Namen noupdate, lb-gsv-11 — Leitzahlen aus Mandats-Unterlagen) + Mitarbeiter-Felder l10n_at_bv_ertraege/l10n_at_bv_monate_extern (Kassen-Auszug).

Bewusst nicht: §-7-BMSVG-Fiktivfälle (dokumentierte Option), Geringfügigen-Jahreszahlung + 2,5 % (GP7), BV-Provider (Option).

Tests: test_bv_private (10 Fälle, RF-BV1-10); gesamt 84/84 grün (frischer -i-Lauf auf Wegwerf-DB). Rechenfälle docs/rechenfaelle/abfertigung.md. 19.0.5.0.0.
This commit is contained in:
2026-09-12 08:22:13 +02:00
parent 3aff46de07
commit 0ea7f54682
16 changed files with 1235 additions and 13 deletions
+36 -5
View File
@@ -67,13 +67,44 @@ Abs 6/7 ASVG, § 12 IESG), WF-Satzvektor Wien, Beitragsmonats-
Allokation (Anspruchsprinzip) und DAG-Bewertung (§ 1 DAG). Allokation (Anspruchsprinzip) und DAG-Bewertung (§ 1 DAG).
Rechenfälle `docs/rechenfaelle/sozialversicherung.md`. Rechenfälle `docs/rechenfaelle/sozialversicherung.md`.
**Offen (GP5+):** Abfertigung Neu (BMSVG), Meldewesen/Belege, **GP5 Abfertigung Neu (umgesetzt 2026-09-11):** BMSVG-Regel `BVG`
FI-Buchung UGB; All-in-UI-Bericht (GP8); B14 längster-Urlaubs-Teil- (§ 6 Abs 1: 1,53 % DG auf Entgelt **und** SZ; Kern-Parameter
Verfeinerung; DZ-WKO-Verifikation (8.11) vor Wien-Mandanten. `at_sv_bv_dg`) auf **EIGENER, ungecappter BV-Grundlage** (§ 6 Abs 5
iVm § 49 ASVG — kein HBG-/Jahresdeckel, ohne §-67-Abs-3/6-Zeilen gemäß
§ 49 Abs 3 Z 7 ASVG) — `models/abfertigung_private.py`:
- **Beitragsfreier erster Monat taggenau** (§ 6 Abs 1): Fenster
[Eintritt, Eintritt + 1 Monat), Pflicht ab Monatsjubiläum
(15. 06. → 15. 07.); 30-Tage-Aliquotierung (KB 4.500/30 × 17 =
2.550); Monatsende-Klippe (31. 01.) → frei bis Monatsultimo,
Pflicht ab Folgemonatserstem; AV ≤ 1 Monat zur Gänze beitragsfrei;
**Wiederzulassung binnen 12 Monaten → Pflicht ab dem 1. Tag**
(OGH 9 ObA 30/16a; Vor-AV beliebiger Dauer — auch < 1 Monat);
- **SZ ohne Fenster-Kürzung**; frei nur bei Beitragsmonat im Fenster
(taggenau); läuft **auch für geringfügig Beschäftigte und
Lehrlinge** (§ 46, § 1 Abs 2 Z 1);
- **Anwartschaft** (§ 15: gutgebrachte Beiträge + Erträge aus dem
Kassen-Auszug, KEINE Monatsentgelt-Staffel) aus validierten
Läufen; Vertragsfelder Abfertigung-Neu/BV-Kasse
(`l10n.at.bv.kasse` mit Leitzahl für eSV/mBGM) + Erträge/Vor-Beitrags-
monate am Mitarbeiter; **Anwartschaftsreport** (QWeb, am Mitarbeiter
druckbar) + **Endabrechnungs-Wizard** (§§ 14-17: §-14-Abs-2-
Verfügungsmatrix mit 36-Beitragsmonate-Kriterium, §-16-Fälligkeit,
6-Monats-Erklärungsfrist, 5 §-17-Optionen) — Auszahlung selbst
läuft über die MV-Kasse, nicht über den Payslip.
Rechenfälle `docs/rechenfaelle/abfertigung.md`.
**Offen (GP6+):** Meldewesen/Belege (GP7), FI-Buchung UGB (GP6),
Jahreswechsel (GP8); All-in-UI-Bericht (GP8); B14 längster-Urlaubs-Teil-
Verfeinerung; DZ-WKO-Verifikation (8.11) vor Wien-Mandanten;
§-7-BMSVG-Fiktivfälle (Präsenzdienst/Kranken-/Wochengeld) als
dokumentierte Option (Rechenfälle-Datei).
Tests: `test_kv_katalog` + `test_entgelt_private` (GP2) + Tests: `test_kv_katalog` + `test_entgelt_private` (GP2) +
`test_lst_private` (GP3) + `test_sv_private` (GP4) `test_lst_private` (GP3) + `test_sv_private` (GP4) +
`test_bv_private` (GP5) —
`.venv/bin/python -m odoo -d <testdb> -u l10n_at_hr_payroll,l10n_at_hr_payroll_private `.venv/bin/python -m odoo -d <testdb> -u l10n_at_hr_payroll,l10n_at_hr_payroll_private
--test-tags /l10n_at_hr_payroll,/l10n_at_hr_payroll_private --test-enable --stop-after-init --test-tags /l10n_at_hr_payroll,/l10n_at_hr_payroll_private --test-enable --stop-after-init
--addons-path=odoo_19.0+e.20260910/odoo-19.0+e.20260910/odoo/addons,addons` --addons-path=odoo_19.0+e.20260910/odoo-19.0+e.20260910/odoo/addons,addons`
(Lokal grün am 2026-09-11: **74/74 Tests**.) (Lokal grün am 2026-09-11: **84/84 Tests**.)
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3. # Part of the odoo-at-payroll project. License: LGPL-3.
{ {
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)', 'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
'version': '19.0.4.0.0', 'version': '19.0.5.0.0',
'author': 'ixsol / gem360', 'author': 'ixsol / gem360',
'website': 'https://gem360.at', 'website': 'https://gem360.at',
'category': 'Human Resources/Payroll', 'category': 'Human Resources/Payroll',
@@ -34,18 +34,30 @@ Grundlagen: ``personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md`` und
- **GP3 Lohnsteuer (umgesetzt):** dünne Regeln SVDN_KV/PV/AV/AK/WF + - **GP3 Lohnsteuer (umgesetzt):** dünne Regeln SVDN_KV/PV/AV/AK/WF +
LST_LFD/LST_SZ auf dem AP3-Kern (§ 66 kumulativ, § 67 Sechstel/ LST_LFD/LST_SZ auf dem AP3-Kern (§ 66 kumulativ, § 67 Sechstel/
Fixsätze, § 68-Steuerfreiheiten). Fixsätze, § 68-Steuerfreiheiten).
- **GP4 SV/DB (dieser Stand):** SVDN_SZ-Nachtrag (§ 67 Abs 12 — Netto- - **GP4 SV/DB (umgesetzt):** SVDN_SZ-Nachtrag (§ 67 Abs 12 — Netto-
Korrektur in §-67-Monaten), DG-Block SVDG_KV/PV/AV/UV/WF/IESG + Korrektur in §-67-Monaten), DG-Block SVDG_KV/PV/AV/UV/WF/IESG +
SVDG_SZ_* + §-53a-UV Geringfügiger (ATP_DG, ohne Nettoeffekt; SVDG_SZ_* + §-53a-UV Geringfügiger (ATP_DG, ohne Nettoeffekt;
ÖGK 2026: DG 20,98 % / Wien 21,23 %), Lohnnebenkosten FLAFDB ÖGK 2026: DG 20,98 % / Wien 21,23 %), Lohnnebenkosten FLAFDB
(§ 41 FLAG), KOMMST (§ 9 KommStG) und DZ (§§ 122/126 WKG, (§ 41 FLAG), KOMMST (§ 9 KommStG) und DZ (§§ 122/126 WKG,
company-flag-gated) mit Freibetragsstaffel 1.095/1.460 € — company-flag-gated) mit Freibetragsstaffel 1.095/1.460 € —
Logik/Werte im Bundes-Kern (D3-Vorfinanzierung). Logik/Werte im Bundes-Kern (D3-Vorfinanzierung).
- **GP5 Abfertigung Neu (dieser Stand):** BMSVG-Regel ``BVG``
(§ 6: 1,53 % DG auf Entgelt und SZ, Kern-Parameter
``at_sv_bv_dg``) auf EIGENER, ungecappter BV-Grundlage (§ 6 Abs 5
iVm § 49 ASVG — kein HBG-Cap, ohne §-67-Abs-3/6-Zeilen),
beitragsfreier erster Monat taggenau (30-Tage-Aliquotierung;
Monatsende-Klippe), Wiederzulassung binnen 12 Monaten ab Tag 1
(Vor-AV beliebiger Dauer), läuft auch für Geringfügige und
Lehrlinge; Vertragsfeld Abfertigung-Neu/BV-Kasse + Erträge/Vor-
Monate am Mitarbeiter; Anwartschaftsreport (QWeb) + Endabrechnungs-
Wizard (§§ 14-17: Verfügungsausschluss-Matrix, 36-Beitragsmonate,
§-16-Fälligkeit, §-17-Optionen).
- **Seed D2 (Referenz-KVs R1):** SI-2203 „Handwerk und Gewerbe" + - **Seed D2 (Referenz-KVs R1):** SI-2203 „Handwerk und Gewerbe" +
SI-2748 „Metallgewerbe" (Gehaltstabellen ab 1. 1. 2026, kuratiert aus SI-2748 „Metallgewerbe" (Gehaltstabellen ab 1. 1. 2026, kuratiert aus
den KV-Library-Volltexten). den KV-Library-Volltexten).
- **Folgen (GP5+):** Abfertigung Neu (BMSVG), Meldewesen/Belege, - **Folgen (GP6+):** FI-Buchung UGB (GP6), Meldewesen/Belege (GP7),
FI-Buchung UGB, Jahreswechsel. Jahreswechsel (GP8); §-7-BMSVG-Fiktivfälle (Präsenzdienst/
Kranken-/Wochengeld) als dokumentierte Option.
Kein VRV-Bezug (Gemeinde-Schiene bleibt ``l10n_at_gemeinde_payroll`` + Kein VRV-Bezug (Gemeinde-Schiene bleibt ``l10n_at_gemeinde_payroll`` +
gem360-Brücke). gem360-Brücke).
@@ -63,7 +75,11 @@ gem360-Brücke).
'data/salary_rules_private.xml', 'data/salary_rules_private.xml',
'data/salary_rules_lst_private.xml', 'data/salary_rules_lst_private.xml',
'data/salary_rules_sv_private.xml', 'data/salary_rules_sv_private.xml',
'data/salary_rules_bvg_private.xml',
'data/bv_kasse_seed.xml',
'views/kv_views.xml', 'views/kv_views.xml',
'views/bv_views.xml',
'report/bv_report.xml',
], ],
'countries': ['at'], 'countries': ['at'],
'license': 'LGPL-3', 'license': 'LGPL-3',
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Seed der am Markt bestehenden Mitarbeitervorsorgekassen — -->
<!-- NUR Namen (lb-gsv-11, Stand 2026-07: „derzeit sieben BV- -->
<!-- Kassen (Allianz, APK, BONUS, BUAK, NÖ, Valida Plus, VBV)"). -->
<!-- Leitzahlen und Kassen-Details bewusst NICHT gesetzt: aus den -->
<!-- Kassen-Unterlagen des Mandanten übernehmen (eSV/mBGM, GP7) — -->
<!-- niemals geraten (Verify-before-assume). noupdate, damit -->
<!-- Mandats-Pflege (Leitzahl) bei Modul-Updates erhalten bleibt. -->
<data noupdate="1">
<record id="bv_kasse_allianz" model="l10n.at.bv.kasse">
<field name="name">Allianz</field>
</record>
<record id="bv_kasse_apk" model="l10n.at.bv.kasse">
<field name="name">APK</field>
</record>
<record id="bv_kasse_bonus" model="l10n.at.bv.kasse">
<field name="name">BONUS</field>
</record>
<record id="bv_kasse_buak" model="l10n.at.bv.kasse">
<field name="name">BUAK</field>
</record>
<record id="bv_kasse_noe" model="l10n.at.bv.kasse">
<field name="name"></field>
</record>
<record id="bv_kasse_valida" model="l10n.at.bv.kasse">
<field name="name">Valida Plus</field>
</record>
<record id="bv_kasse_vbv" model="l10n.at.bv.kasse">
<field name="name">VBV</field>
</record>
</data>
</odoo>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- GP5: Abfertigung Neu (BMSVG) — dünne BVG-Regel für die -->
<!-- privatwirtschaftliche Structure. Logik/Werte im Kern -->
<!-- (Parameter at_sv_bv_dg, 1,53 %) und im Privatmodul -->
<!-- (models/abfertigung_private.py: eigene, ungecappte BV- -->
<!-- Grundlage § 6 Abs 5 iVm § 49 ASVG, beitragsfreier erster -->
<!-- Monat taggenau, Wiederzulassung, SZ ohne Fenster-Kürzung); -->
<!-- hier nur die 1-Zeilen-Bindung (kein Wertefork). -->
<!-- Kategorie ATP_DG: reine Dienstgeberkosten ohne Nettoeffekt; -->
<!-- läuft bewusst auch für geringfügig Beschäftigte und -->
<!-- Lehrlinge (§ 46 BMSVG). -->
<!-- ============================================================ -->
<record id="rule_bvg" model="hr.salary.rule">
<field name="name">BV-Beitrag Abfertigung Neu (§ 6 BMSVG; DG)</field>
<field name="code">BVG</field>
<field name="sequence" eval="95"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_dg"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = payslip._at_bv_beitrag(result_rules) > 0</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="amount_python_compute"><![CDATA[
result = payslip._at_bv_beitrag(result_rules)
]]></field>
</record>
</odoo>
@@ -0,0 +1,105 @@
# Rechenfälle Abfertigung Neu (GP5) — BMSVG
**Quellen (✅ verifiziert):** `RECHTSQUELLEN-Privat.md` Abschnitt 8
(konsolidierter BMSVG-Volltext, Stand 26. 07. 2025); Layer-2-Belege
lb-vor-03 (Beitragszahlung, Stand 2026-08), lb-vor-06 (Geltungs-
bereich), lb-end-03 (Verfügungsmöglichkeiten, Stand 2026-08),
lb-vor-10 (Übertritt). Umsetzung: Kern-Parameter `at_sv_bv_dg`
(`rule_parameters_sv_2026.xml`); privat `models/abfertigung_private.py`
+ Regel `BVG` (`salary_rules_bvg_private.xml`) + Endabrechnungs-Wizard
`l10n.at.bv.endabrechnung.wizard`. Tests: `test_bv_private.py`.
## Systematik (GP5, verifiziert)
- **Beitrag (§ 6 Abs 1):** DG zahlt **1,53 vH** des monatlichen Entgelts
**und** der Sonderzahlungen an die Mitarbeitervorsorgekasse (Abfuhr
über den KV-Träger, Fälligkeit 15. Folgemonat, Baubereich BUAK —
Zahlungs-/Meldeseite GP7).
- **Eigene BV-Grundlage (§ 6 Abs 5 iVm § 49 ASVG):** GFG und HBG
**unberücksichtigt** → kein HBG-Cap, kein SZ-Jahresdeckel; §-49-
bereinigt (laufend: `_l10n_at_sv_basis_roh`; sonstig: alle
§-67-Zeilen). **§-67-Abs-3/6-Zeilen (Abfertigung Alt/Abfindung)
scheiden aus** (§ 49 Abs 3 Z 7 ASVG, beitragsfrei) — bewusst eigene
Grundlage statt Rückgriff auf die SV-SZ-Basis des Kerns, deren
Z-7-Ausschluss nicht umgesetzt ist (GP4-Beobachtung, offen
dokumentiert).
- **Geltungsbereich (§ 46, § 1 Abs 2 Z 1):** private AV ab 1. 1. 2003
(Vertrags-Flag `l10n_at_abfertigung_neu`, default True; Alt-Fälle/
§ 47-Übertritte abwählen); Länder/Gemeinden ausgenommen (Sektor-
Trennung zum GemBG-Modul § 130). **Geringfügig Beschäftigte und
Lehrlinge sind erfasst** — BVG läuft auch ohne SV-Hauptbeiträge
(§-53a-Fall); Geringfügigen-Jahreszahlung + 2,5 % Zinsenzuschlag =
Zahlungsmodus (GP7/GP8).
- **Beitragsfreier erster Monat (§ 6 Abs 1), taggenau:**
- Fenster = [Eintritt, Eintritt + 1 Monat); **Beitragspflicht ab
Monatsjubiläum** (Eintritt 15. 06. → frei 15. 06.14. 07., Pflicht
ab 15. 07.).
- **Aliquotierung mit fixem 30-Tage-Monat** (Dachverband; KB-Beispiel
4.500/30 × 17 = 2.550): laufende BG × pflichtige Kalendertage ÷
Lohnsteuertage (Vollmonat = 30; Teilmonat = Kalendertage, damit das
für Teilmonate bereits aliquote Entgelt nicht doppelt gekürzt
wird).
- **Monatsende-Klippe** (Eintritt 31. 01., Jubiläum 31. 02. existiert
nicht): Fenster bis **Monatsultimo**, Pflicht ab **Folgemonats-
erstem** (ABGB-Fristrechnung).
- **AV ≤ 1 Monat → zur Gänze beitragsfrei** (auch bloße Urlaubs-
ersatzleistung; Ende < Jubiläum).
- **Wiederzulassung binnen 12 Monaten → Beitragspflicht ab dem
1. Tag** (OGH 9 ObA 30/16a); Vor-AV **beliebiger Dauer** (auch
< 1 Monat — Nutzer-Entscheidung 2026-09-11) und schlägt auch die
Kurz-AV-Freiheit des laufenden AV. Suche über frühere
`hr.version` desselben Mitarbeiters mit `contract_date_end` im
12-Monats-Fenster vor dem neuen Start.
- **Sonderzahlungen:** **keine** aliquote Kürzung um den beitragsfreien
Monat; beitragsfrei bleiben sie nur, wenn ihr **Beitragsmonat**
(Regelfeld `l10n_at_beitragsmonat`, sonst LZP-Beginn) **im Fenster**
liegt — taggenau verglichen (SZ fällig 20. 07. ist bei Fensterende
15. 07. pflichtig).
- **EOSB (§§ 14-17):** Anwartschaft = **gutgebrachte Beiträge +
Erträge** (§ 15 — KEINE Monatsentgelt-Staffel); Erträge aus dem
Kassen-Auszug am Mitarbeiter pflegen (`l10n_at_bv_ertraege`).
**Verfügungsausschluss (§ 14 Abs 2):** AN-Kündigung (außer
MSchG/VKG-Teilzeit-Kündigung), verschuldete Entlassung,
unberechtigter vorzeitiger Austritt — und **IMMER bei < 36
Beitragsmonaten** (alle Beitragszeiten über Arbeitgeber hinweg
zählen; Vor-Monate am Mitarbeiter: `l10n_at_bv_monate_extern`).
Fälligkeit am **Ende des zweitfolgenden Monats** nach
Geltendmachung (§ 16); **fünf** Verfügungsoptionen, ohne Erklärung
binnen **6 Monaten** Weiterveranlagung (§ 17). Auszahlung durch die
MV-Kasse — kein Payslip-Bestandteil (Wizard rein informativ).
## Rechenfälle (Fixtures test_bv_private.py)
| RF | Konstellation | Berechnung | Erwartung |
|---|---|---|---|
| RF-BV1 | Bestand (2020), November mit B13 (KV-Katalog 2.059,41) | (2.059,41 + 2.059,41) × 1,53 % | **BVG 63,02 €**; ATP_DG, NETTO unverändert |
| RF-BV2 | Entgelt 8.000 € (Jänner, kein B13) | SVDG_KV: HBG 6.930 × 3,78 %; BVG: 8.000 × 1,53 % | SVDG_KV 262,55; **BVG 122,40 €** (kein HBG-Cap) |
| RF-BV3 | Eintritt 15. 06. 2026, Entgelt 6.000 € | Juni: Fenster; Juli: 6.000/30 × 17; August: voll | Juni **0** · Juli 3.400 × 1,53 % = **52,02 €** · August **91,80 €** |
| RF-BV4 | Vor-AV 01.15. 06. 2026 (14 Tage), neuer AV ab 01. 07. | Wiederzulassung → Pflicht ab Tag 1 | Juli **91,80 €** (kein freier Monat trotz kurzem Vor-AV) |
| RF-BV5 | AV 01.30. 06. 2026 (genau 1 Monat) | Ende < Jubiläum 01. 07. | Juni **0** (zur Gänze beitragsfrei) |
| RF-BV6 | Geringfügig 500 € (Jänner) | §-53a-Fall: SVDG 0, aber BMSVG erfasst | **BVG 7,65 €** |
| RF-BV7 | Eintritt 15. 06.; Teststruktur LFD 2.000 / SZ 20. 06. / SZ 20. 07. / Abfertigung-Alt 10.000 (§ 67 Abs 3) | (2.000 × 17/30 + 1.000) × 1,53 %; SZ 20. 06. frei; Alt-Abfertigung ausgeschieden | **BVG 32,64 €** (1.133,33 + 1.000) |
| RF-BV8 | 3 validierte Läufe (Jän-März, je 2.059,41); Austritt 31. 08., Geltendmachung 11. 09. | Anwartschaft 3 × 31,51 = 94,53; §-14-Prüfung | Monate 3 → gesperrt; +33 extern → 36, AG-Kündigung verfügbar; AN-Kündigung gesperrt; Fälligkeit **30. 11.**, Erklärungsfrist **28. 02. 2027** |
| RF-BV9 | Alt-Verhältnis (Flag False), November | Regel aus | **BVG 0** |
| RF-BV10 | Eintritt 31. 01. 2026 | Klippe: Fenster bis 28. 02., Pflicht ab 01. 03. | Februar **0** · März **91,80 €** |
## Offene Punkte / bewusst nicht in GP5
- **§ 7 BMSVG-Fiktivfälle** (Präsenz-/Ausbildungs-/Zivildienst:
fiktive BG = Kinderbetreuungsgeld 435,90 €/Monat Stand 2026-08;
Krankengeld: 50 % der BG vor dem Versicherungsfall ohne SZ;
Wochengeld: 3-Monats-Ø inkl. anteiliger SZ): dokumentierte Option —
setzt die Modellierung der Entgeltersatzleistungen voraus, die das
Modul (noch) nicht hat; kein toter Parameter.
- **SV-SZ-Basis des Kerns** (`_l10n_at_svdn_basis_sz`) schließt
§-67-Abs-3/6-Zeilen nicht aus (beitragsfrei nach § 49 Abs 3 Z 7
ASVG) — GP4-Beobachtung, bewusst nicht in GP5 angefasst
(Nutzer-Entscheidung: eigene BV-Grundlage); möglicher Folgefix im
SV-Kern separat zu klären.
- **Abfuhr/Meldung** (mBGM: Leitzahl BV-Kasse, DG-Kontonummer;
Geringfügigen-Jahreszahlung + 2,5 %), **Fristenkalender**
(6-Monats-Erklärung, §-16-Fälligkeit als Aktivität), **BV-Provider-
Anbindung**: GP7/GP8 bzw. Option.
- Wiederzulassung wird je Mitarbeiter in der DB geprüft („gleicher
Arbeitgeber" = gleiches Unternehmen angenommen); Multi-Company-
Konstellationen sind dokumentierte Grenze.
@@ -1,4 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3. # Part of the odoo-at-payroll project. License: LGPL-3.
from . import kollektivvertrag from . import kollektivvertrag
from . import hr_version_private from . import hr_version_private
from . import payslip_private from . import payslip_private
from . import bv_kasse
from . import abfertigung_private
from . import bv_report
@@ -0,0 +1,257 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""Abfertigung Neu (BMSVG) — GP5: laufender BV-Beitrag (§ 6) mit
EIGENER BV-Grundlage, Anwartschafts-Fortschreibung (§ 15) und
Endabrechnung (§§ 14-17).
Verifizierte Grundlagen (RECHTSQUELLEN-Privat Abschn. 8 ✅; KB
lb-vor-03/lb-vor-06/lb-end-03, Stand 2026-08):
- § 6 Abs 1: DG-Beitrag 1,53 vH des monatlichen Entgelts und der
Sonderzahlungen an die Mitarbeitervorsorgekasse (Abfuhr über den
KV-Träger, Fälligkeit 15. Folgemonat, Baubereich BUAK — GP7).
- § 6 Abs 5 iVm § 49 ASVG: Bemessung ohne GFG und HBG → eigene,
ungecappte BV-Grundlage. Bewusst KEIN Rückgriff auf die SV-SZ-Basis
des Kerns: deren §-49-Abs-3-Z-7-Ausschluss für §-67-Abs-3/6-Bezüge
(Abfertigung/Abfindung) ist dort nicht umgesetzt; die BV-Grundlage
schließt diese Zeilen korrekt aus (Nutzer-Entscheidung 2026-09-11).
- Geltungsbereich § 46/§ 1 Abs 2 Z 1: private Arbeitsverhältnisse ab
1. 1. 2003 (Flag am Vertrag); geringfügig Beschäftigte und Lehrlinge
sind ERFASST — BVG läuft auch ohne SV-Hauptbeiträge.
- Beitragsfreier erster Monat (§ 6 Abs 1), taggenau: Fenster =
[Eintritt, Eintritt + 1 Monat), Beitragspflicht ab Monatsjubiläum
(Eintritt 15. 06. → frei bis 14. 07., Pflicht ab 15. 07.; KB-Beispiel
4.500/30 × 17 = 2.550). Monatsende-Klippe (Eintritt 31. 01. → kein
31. 02.): Fenster bis Monatsultimo, Pflicht ab Folgemonatserstem
(ABGB-Fristrechnung). AV ≤ 1 Monat → zur Gänze beitragsfrei (auch
Urlaubsersatzleistung). Wiederzulassung binnen 12 Monaten →
Beitragspflicht ab dem 1. TAG; Vor-AV beliebiger Dauer (auch
< 1 Monat; OGH 9 ObA 30/16a) — schlägt auch die Kurz-AV-Freiheit
des laufenden AV (Nutzer-Entscheidung 2026-09-11).
- Sonderzahlungen: keine aliquote Kürzung um den beitragsfreien
Monat; beitragsfrei nur, wenn ihr Beitragsmonat ins Fenster fällt.
- §§ 14-17: Anwartschaft = gutgebrachte Beiträge + Erträge (KEINE
Monatsentgelt-Staffel); Verfügungsausschluss bei AN-Kündigung,
verschuldeter Entlassung, unberechtigtem vorzeitigem Austritt oder
weniger als 36 Beitragsmonaten (alle Beitragszeiten zählen, daher
manuelle Vor-Arbeitgeber-Monate am Mitarbeiter); Fälligkeit am Ende
des zweitfolgenden Monats nach Geltendmachung (§ 16); fünf
Verfügungsoptionen, ohne Erklärung binnen 6 Monaten
Weiterveranlagung (§ 17).
"""
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import fields, models
class HrVersion(models.Model):
_inherit = 'hr.version'
l10n_at_abfertigung_neu = fields.Boolean(
string='AT Abfertigung Neu (BMSVG)',
default=True,
groups="hr_payroll.group_hr_payroll_user", tracking=True,
help='Dienstverhältnis unterliegt dem BMSVG (§ 46: AV ab '
'1. 1. 2003; § 1 Abs 2 Z 1: Länder/Gemeinden ausgenommen). '
'Für Alt-Verhältnisse (§§ 23 f AngG) und § 47-Übertritte '
'abwählen — dann läuft keine BVG-Regel. Geringfügig '
'Beschäftigte und Lehrlinge sind erfasst.')
l10n_at_bv_kasse_id = fields.Many2one(
'l10n.at.bv.kasse', string='AT Mitarbeitervorsorgekasse',
index=True, groups="hr_payroll.group_hr_payroll_user",
tracking=True,
help='Gewählte BV-Kasse dieses Dienstverhältnisses (lb-vor-02); '
'Anwartschaftskonto und §-17-Verfügungen laufen über diese '
'Kasse.')
class HrEmployee(models.Model):
_inherit = 'hr.employee'
l10n_at_abfertigung_neu = fields.Boolean(
related="version_id.l10n_at_abfertigung_neu", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
l10n_at_bv_kasse_id = fields.Many2one(
related="version_id.l10n_at_bv_kasse_id", inherited=True,
readonly=False, groups="hr_payroll.group_hr_payroll_user")
l10n_at_bv_ertraege = fields.Monetary(
string='AT BV-Erträge (Kassen-Auszug)',
currency_field='currency_id',
groups="hr_payroll.group_hr_payroll_user",
help='Veranlagungserträge der Mitarbeitervorsorgekasse laut '
'Kassen-Auszug (§ 15 BMSVG: Auszahlung der Anwartschaft = '
'gutgebrachte Beiträge + Erträge) — manuell pflegen.')
l10n_at_bv_monate_extern = fields.Integer(
string='AT BV-Beitragsmonate Vor-Arbeitgeber',
groups="hr_payroll.group_hr_payroll_user",
help='Beitragsmonate aus Vor-Beschäftigungen (§ 14 Abs 2 BMSVG: '
'alle Beitragszeiten über Arbeitgeber hinweg summierbar — '
'36-Monats-Kriterium) — laut Auszug der MV-Kasse pflegen.')
def _at_bv_anwartschaft(self):
"""Anwartschafts-Fortschreibung (§ 15 BMSVG): gutgebrachte
Beiträge aus validierten Läufen, je Beitragsmonat/Jahr
gruppiert (Refund-Läufe netten über negative Linien; Monate
zählen auch aus negativen Linien — der Monat ist belegt)."""
self.ensure_one()
monate = set()
total = 0.0
jahre = {}
slips = self.env['hr.payslip'].search([
('employee_id', '=', self.id),
('state', 'not in', ('draft', 'cancel')),
])
for line in slips.line_ids.filtered(
lambda l: l.code == 'BVG'):
if not line.total:
continue
monat = (line.l10n_at_beitragsmonat
or line.date_from).replace(day=1)
monate.add(monat)
total += line.total
eintrag = jahre.setdefault(
monat.year, {'monate': set(), 'summe': 0.0})
eintrag['monate'].add(monat)
eintrag['summe'] += line.total
return {
'monate': len(monate),
'beitraege': total,
'jahre': [(jahr, len(w['monate']), w['summe'])
for jahr, w in sorted(jahre.items())],
}
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
# ------------------------------------------------------------------
# Beitragsfreies Erstfenster (§ 6 Abs 1 BMSVG, taggenau)
# ------------------------------------------------------------------
def _at_bv_fenster(self):
"""(pflichtig_ab, kurz_av) des Arbeitsverhältnisses:
pflichtig_ab = erstes beitragspflichtiges Datum (None =
Beitragspflicht ab dem 1. Tag bzw. ohne bewertbare
Vertragsdaten); kurz_av = True → AV ≤ 1 Monat, zur Gänze
beitragsfrei. Reihenfolge: Wiederzulassung schlägt alles,
dann Kurz-AV-Freiheit, dann taggenaues Fenster."""
self.ensure_one()
version = self.version_id
start = version.contract_date_start
if not start:
# Ohne Vertragsdaten ist die Befreiung nicht bewertbar →
# konservativ Vollpflicht.
return None, False
# Wiederzulassung binnen 12 Monaten: Beitragspflicht ab dem
# 1. Tag; Vor-Dienstverhältnis beliebiger Dauer (auch
# < 1 Monat) löst die Ausnahme aus.
prior = self.env['hr.version'].search([
('employee_id', '=', self.employee_id.id),
('id', '!=', version.id),
('contract_date_end', '!=', False),
('contract_date_end', '>=',
start - relativedelta(months=12)),
('contract_date_end', '<', start),
], limit=1)
if prior:
return None, False
# Monatsjubiläum, taggenau; Monatsende-Klippe: existiert der
# Jubiläumstag im Folgemonat nicht (Eintritt 31. 01.), endet
# das Fenster am Monatsultimo, Pflicht ab Folgemonatserstem.
jubilaeum = start + relativedelta(months=1)
if jubilaeum.day < start.day:
jubilaeum += timedelta(days=1)
end = version.contract_date_end
if end and end < jubilaeum:
# AV ≤ 1 Monat → zur Gänze beitragsfrei (lb-vor-03, auch
# eine bloße Urlaubsersatzleistung).
return None, True
return jubilaeum, False
# ------------------------------------------------------------------
# Eigene BV-Grundlage (§ 6 Abs 5 iVm § 49 ASVG — ohne GFG/HBG)
# ------------------------------------------------------------------
def _at_bv_sonst_monate(self, result_rules=None):
"""[(Beitragsmonat, sonstige BV-Basis)]: sonstige Bezüge ohne
§-67-Abs-3/6-Zeilen (Abfertigung/Abfindung sind nach
§ 49 Abs 3 Z 7 ASVG beitragsfrei, damit auch nicht BV-
pflichtig), gruppiert nach Regel-Beitragsmonat mit voller
Tagesgenauigkeit (Anspruchsprinzip, Muster
_l10n_at_sv_basis_laufen_monate) — die Fenster-Prüfung braucht
die Fälligkeit taggenau (SZ fällig 20. 07. ist bei Fenster-
Ende 15. 07. pflichtig, der Monatsanfang wäre es nicht)."""
self.ensure_one()
gruppen = {}
for rule in self.struct_id.rule_ids:
if (rule.l10n_at_bemessung != 'sonstig'
or rule.l10n_at_s67_abs != 'keine'):
continue
werte = None
if result_rules is not None:
if rule.code in result_rules:
werte = result_rules[rule.code]
else:
linie = self.line_ids.filtered(
lambda l: l.code == rule.code)
if linie:
werte = {'total': sum(linie.mapped('total'))}
if not werte:
continue
monat = rule.l10n_at_beitragsmonat or self.date_from
gruppen[monat] = gruppen.get(monat, 0.0) + werte['total']
return sorted(gruppen.items())
def _at_bv_basis_laufen(self, result_rules=None):
"""Laufende BV-Grundlage: §-49-bereinigtes Entgelt ohne
HBG-Cap (§ 6 Abs 5), aliquotiert um das beitragsfreie
Erstfenster — 30-Tage-Monat (KB-Beispiel 4.500/30 × 17 =
2.550); Teilmonate über die Lohnsteuertage, um Doppelabzüge
zu vermeiden (das Entgelt ist für die Teilzeit bereits
aliquotiert)."""
self.ensure_one()
pflichtig_ab, kurz_av = self._at_bv_fenster()
if kurz_av:
return 0.0
basis = self._l10n_at_sv_basis_roh(result_rules)
if pflichtig_ab is None or pflichtig_ab <= self.date_from:
return basis
if pflichtig_ab > self.date_to:
# LZP liegt zur Gänze im beitragsfreien Erstfenster.
return 0.0
ende = min(self.date_to,
self.version_id.contract_date_end or self.date_to)
tage = (ende - pflichtig_ab).days + 1
if tage <= 0:
return 0.0
return basis * tage / max(self._l10n_at_lohnsteuertage(), 1)
def _at_bv_basis_sonst(self, result_rules=None):
"""BV-Grundlage sonstiger Bezüge (Sonderzahlungen & Co.):
ungecappt (kein HBG-/Jahresdeckel, § 6 Abs 5) und ohne
§-67-Abs-3/6-Zeilen; keine aliquote Kürzung um das freie
Fenster — frei bleibt nur, was im Fenster fällig wird
(Beitragsmonat im Fenster, lb-vor-03)."""
self.ensure_one()
pflichtig_ab, kurz_av = self._at_bv_fenster()
if kurz_av:
return 0.0
total = 0.0
for monat, basis in self._at_bv_sonst_monate(result_rules):
if pflichtig_ab is not None and monat < pflichtig_ab:
continue
total += basis
return total
def _at_bv_beitrag(self, result_rules=None):
"""BV-Beitrag (§ 6 Abs 1 BMSVG): 1,53 % DG (at_sv_bv_dg) auf
die eigene Grundlage laufend + sonstig; ATP_DG-Zeile ohne
Nettoeffekt (FI-Zuordnung über hr_payroll_account, GP6).
Läuft bewusst auch für geringfügig Beschäftigte und
Lehrlinge (§ 46, § 1 Abs 2 Z 1; Jahreszahlung + 2,5 %
Zinsenzuschlag der Geringfügigen = Zahlungsmodus GP7/GP8)."""
self.ensure_one()
if not self.version_id.l10n_at_abfertigung_neu:
return 0.0
basis = (self._at_bv_basis_laufen(result_rules)
+ self._at_bv_basis_sonst(result_rules))
satz = float(self._l10n_at_param('at_sv_bv_dg'))
return basis * satz / 100.0
@@ -0,0 +1,20 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""Betriebliche Vorsorgekasse (BMSVG): Auswahl je Arbeitsverhältnis
(lb-vor-02 — Voraussetzung der Beitragsabfuhr; die Kasse führt das
Anwartschaftskonto und nimmt die §-17-Verfügungen entgegen). Die
Leitzahl ist eSV-/mBGM-Angabe (GP7) und ist aus den Kassen-Unterlagen
des Mandanten zu übernehmen — nicht aus Vermutung."""
from odoo import fields, models
class L10nAtBvKasse(models.Model):
_name = 'l10n.at.bv.kasse'
_description = 'Betriebliche Vorsorgekasse (BMSVG)'
_order = 'name'
name = fields.Char(required=True)
leitzahl = fields.Char(
string='Leitzahl (eSV/mBGM)',
help='Leitzahl der Mitarbeitervorsorgekasse gemäß DM-ORG/'
'mBGM-Angaben (GP7); aus den Kassen-Unterlagen übernehmen.')
active = fields.Boolean(default=True)
@@ -0,0 +1,20 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""QWeb-Datenlieferant für den BV-Anwartschaftsreport (GP5): die
Anwartschaftswerte werden im Python-Teil aufbereitet (Muster
AbstractModel-Report), der QWeb-Teil bleibt frei von Methodenaufrufen."""
from odoo import models
class L10nAtBvAnwartschaftReport(models.AbstractModel):
_name = 'report.l10n_at_hr_payroll_private.report_bv_anwartschaft'
_description = 'BV-Anwartschaftsreport (Abfertigung Neu, § 15 BMSVG)'
def _get_report_values(self, docids, data=None):
docs = self.env['hr.employee'].browse(docids)
return {
'docs': docs,
'anwartschaft': {
employee.id: employee._at_bv_anwartschaft()
for employee in docs
},
}
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- GP5: BV-Anwartschaftsreport (§ 15 BMSVG: gutgebrachte -->
<!-- Beiträge + Erträge laut Kassen-Auszug — KEINE -->
<!-- Monatsentgelt-Staffel). Daten aus dem AbstractModel-Report -->
<!-- (models/bv_report.py); druckbar am Mitarbeiter und aus dem -->
<!-- Endabrechnungs-Wizard. -->
<!-- ============================================================ -->
<record id="action_report_bv_anwartschaft" model="ir.actions.report">
<field name="name">BV-Anwartschaftsreport (Abfertigung Neu)</field>
<field name="model">hr.employee</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">l10n_at_hr_payroll_private.report_bv_anwartschaft</field>
<field name="report_file">l10n_at_hr_payroll_private.report_bv_anwartschaft</field>
<field name="paperformat_id" ref="base.paperformat_euro"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="binding_type">report</field>
</record>
<template id="report_bv_anwartschaft">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="employee">
<t t-set="fort" t-value="anwartschaft[employee.id]"/>
<t t-call="web.external_layout">
<div class="page">
<h2>BV-Anwartschaftsreport (Abfertigung Neu)</h2>
<table class="table table-sm o_main_table">
<tbody>
<tr>
<td><strong>Dienstnehmer:in</strong></td>
<td><t t-out="employee.name"/></td>
<td><strong>Mitarbeitervorsorgekasse</strong></td>
<td><t t-out="employee.l10n_at_bv_kasse_id.name or '—'"/></td>
</tr>
<tr>
<td><strong>Beitragsmonate (dieser DG)</strong></td>
<td><t t-out="fort['monate']"/></td>
<td><strong>Beitragsmonate Vor-Arbeitgeber</strong></td>
<td><t t-out="employee.l10n_at_bv_monate_extern"/></td>
</tr>
</tbody>
</table>
<h3>Gutgebrachte Beiträge je Beitragsjahr (§ 6 BMSVG)</h3>
<table class="table table-sm o_main_table">
<thead>
<tr>
<th>Beitragsjahr</th>
<th class="text-end">Beitragsmonate</th>
<th class="text-end">Gutgebrachte Beiträge</th>
</tr>
</thead>
<tbody>
<tr t-foreach="fort['jahre']" t-as="jahr">
<td><t t-out="jahr[0]"/></td>
<td class="text-end"><t t-out="jahr[1]"/></td>
<td class="text-end">
<span t-out="jahr[2]"
t-options='{"widget": "monetary", "display_currency": employee.currency_id}'/>
</td>
</tr>
<tr>
<td><strong>Summe gutgebracht</strong></td>
<td/>
<td class="text-end">
<span t-out="fort['beitraege']"
t-options='{"widget": "monetary", "display_currency": employee.currency_id}'/>
</td>
</tr>
<tr>
<td>Erträge laut Kassen-Auszug (§ 15)</td>
<td/>
<td class="text-end">
<span t-out="employee.l10n_at_bv_ertraege"
t-options='{"widget": "monetary", "display_currency": employee.currency_id}'/>
</td>
</tr>
<tr>
<td><strong>Anwartschaft gesamt (Beiträge + Erträge)</strong></td>
<td/>
<td class="text-end">
<span t-out="fort['beitraege'] + employee.l10n_at_bv_ertraege"
t-options='{"widget": "monetary", "display_currency": employee.currency_id}'/>
</td>
</tr>
</tbody>
</table>
<p class="small text-muted">
Auszahlung der Abfertigungsanwartschaft durch die
Mitarbeitervorsorgekasse (§ 15 BMSVG — keine
Monatsentgelt-Staffel); Fälligkeit am Ende des
zweitfolgenden Monats nach Geltendmachung (§ 16).
Verfügungsausschluss nach § 14 Abs 2 BMSVG bei
Kündigung durch den AN, verschuldeter Entlassung,
unberechtigtem vorzeitigem Austritt oder weniger
als 36 Beitragsmonaten (Vor-Arbeitgeber-Monate
zählen mit — Mitarbeiter-Stamm pflegen).
</p>
</div>
</t>
</t>
</t>
</template>
</odoo>
@@ -8,4 +8,7 @@ access_l10n_at_payroll_kv_gruppe_manager,L10n AT Payroll KV-Gruppe Manager,model
access_l10n_at_payroll_kv_gruppe_stufe_user,L10n AT Payroll KV-Gruppe-Stufe User,model_l10n_at_payroll_kv_gruppe_stufe,hr_payroll.group_hr_payroll_user,1,0,0,0 access_l10n_at_payroll_kv_gruppe_stufe_user,L10n AT Payroll KV-Gruppe-Stufe User,model_l10n_at_payroll_kv_gruppe_stufe,hr_payroll.group_hr_payroll_user,1,0,0,0
access_l10n_at_payroll_kv_gruppe_stufe_manager,L10n AT Payroll KV-Gruppe-Stufe Manager,model_l10n_at_payroll_kv_gruppe_stufe,hr_payroll.group_hr_payroll_manager,1,1,1,1 access_l10n_at_payroll_kv_gruppe_stufe_manager,L10n AT Payroll KV-Gruppe-Stufe Manager,model_l10n_at_payroll_kv_gruppe_stufe,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_l10n_at_payroll_kv_import_wizard,L10n AT Payroll KV-Import Wizard,model_l10n_at_payroll_kv_import_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1 access_l10n_at_payroll_kv_import_wizard,L10n AT Payroll KV-Import Wizard,model_l10n_at_payroll_kv_import_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
access_l10n_at_payroll_kv_import_line,L10n AT Payroll KV-Import Line,model_l10n_at_payroll_kv_import_line,hr_payroll.group_hr_payroll_user,1,1,1,1 access_l10n_at_payroll_kv_import_line,L10n AT Payroll KV-Import Line,model_l10n_at_payroll_kv_import_line,hr_payroll.group_hr_payroll_user,1,1,1,1
access_l10n_at_bv_kasse_user,L10n AT BV-Kasse User,model_l10n_at_bv_kasse,hr_payroll.group_hr_payroll_user,1,0,0,0
access_l10n_at_bv_kasse_manager,L10n AT BV-Kasse Manager,model_l10n_at_bv_kasse,hr_payroll.group_hr_payroll_manager,1,1,1,1
access_l10n_at_bv_endabrechnung_wizard,L10n AT BV-Endabrechnung Wizard,model_l10n_at_bv_endabrechnung_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
8 access_l10n_at_payroll_kv_gruppe_stufe_user L10n AT Payroll KV-Gruppe-Stufe User model_l10n_at_payroll_kv_gruppe_stufe hr_payroll.group_hr_payroll_user 1 0 0 0
9 access_l10n_at_payroll_kv_gruppe_stufe_manager L10n AT Payroll KV-Gruppe-Stufe Manager model_l10n_at_payroll_kv_gruppe_stufe hr_payroll.group_hr_payroll_manager 1 1 1 1
10 access_l10n_at_payroll_kv_import_wizard L10n AT Payroll KV-Import Wizard model_l10n_at_payroll_kv_import_wizard hr_payroll.group_hr_payroll_user 1 1 1 1
11 access_l10n_at_payroll_kv_import_line L10n AT Payroll KV-Import Line model_l10n_at_payroll_kv_import_line hr_payroll.group_hr_payroll_user 1 1 1 1
12 access_l10n_at_bv_kasse_user L10n AT BV-Kasse User model_l10n_at_bv_kasse hr_payroll.group_hr_payroll_user 1 0 0 0
13 access_l10n_at_bv_kasse_manager L10n AT BV-Kasse Manager model_l10n_at_bv_kasse hr_payroll.group_hr_payroll_manager 1 1 1 1
14 access_l10n_at_bv_endabrechnung_wizard L10n AT BV-Endabrechnung Wizard model_l10n_at_bv_endabrechnung_wizard hr_payroll.group_hr_payroll_user 1 1 1 1
@@ -2,4 +2,5 @@
from . import test_kv_katalog from . import test_kv_katalog
from . import test_entgelt_private from . import test_entgelt_private
from . import test_lst_private from . import test_lst_private
from . import test_sv_private from . import test_sv_private
from . import test_bv_private
@@ -0,0 +1,297 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""GP5 Abfertigung Neu (BMSVG) auf der privatwirtschaftlichen
Structure: BVG-Regel (§ 6: 1,53 % DG auf Entgelt und SZ, EIGENE
ungecappte Grundlage, beitragsfreier erster Monat taggenau mit
Monatsende-Klippe, Wiederzulassung binnen 12 Monaten ab Tag 1 — auch
bei kurzem Vor-AV —, SZ-Fälligkeit taggenau gegen das Fenster,
§-67-Abs-3/6-Ausschluss, kein HBG-Cap, läuft auch bei Geringfügigkeit)
sowie Anwartschafts-Fortschreibung (§ 15) und Endabrechnungs-Wizard
(§§ 14-17: Verfügungsmatrix, 36-Beitragsmonate, §-16/§-17-Fristen).
Rechenfälle: docs/rechenfaelle/abfertigung.md."""
from datetime import date, timedelta
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install')
class BvPrivateTest(TransactionCase):
def setUp(self):
super().setUp()
self.structure = self.env.ref(
'l10n_at_hr_payroll_private.structure_at_privat')
self.kv2203 = self.env.ref(
'l10n_at_hr_payroll_private.kv_si_2203')
self.gruppe_ii = self.env.ref(
'l10n_at_hr_payroll_private.kv2203_grp_ii')
self.calendar = self.env['resource.calendar'].create({
'name': 'AT Test 40h Mo-Fr',
'tz': 'Europe/Vienna',
'hours_per_day': 8,
'attendance_ids': [
(0, 0, {'name': 'Tag %d' % wd, 'dayofweek': str(wd),
'hour_from': 8.0, 'hour_to': 16.0})
for wd in range(5)
],
})
self.env.company.resource_calendar_id = self.calendar
def _employee(self, version_vals=None):
employee = self.env['hr.employee'].create({
'name': 'Test BV privat',
})
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({
'contract_date_start': date(2020, 1, 1),
'kv_id': self.kv2203.id,
'kv_gruppe_id': self.gruppe_ii.id,
'erfahrungsstufe': 1,
'resource_calendar_id': self.calendar.id,
})
if version_vals:
version.write(version_vals)
return employee
def _slip(self, employee, monat, jahr=2026, confirm=False,
struct=None):
erster = date(jahr, monat, 1)
naechster = (erster + timedelta(days=32)).replace(day=1)
slip = self.env['hr.payslip'].create({
'name': 'Test-BV-Privat %d/%d' % (monat, jahr),
'employee_id': employee.id,
'struct_id': (struct or self.structure).id,
'date_from': erster,
'date_to': naechster - timedelta(days=1),
})
slip.compute_sheet()
if confirm:
slip.action_payslip_done()
return slip
def _total(self, slip, code):
return sum(slip.line_ids.filtered(
lambda l: l.code == code).mapped('total'))
# Teststruktur mit Fixbeträgen + eigener BVG-Regel (Muster
# _lnk_struktur), um Fenster-/SZ-/§-67-Eckfälle zielgenau zu
# treffen, ohne vom KV-Katalog-Entgelt abzuhängen.
def _bv_struktur(self, code, regeln):
struktur = self.env['hr.payroll.structure'].create({
'name': 'Test AT BV %s' % code,
'code': 'TEST_AT_BV_%s' % code,
'type_id': self.structure.type_id.id,
'country_id': self.env.ref('base.at').id,
})
brutto = self.env.ref(
'l10n_at_hr_payroll_private.category_atp_brutto')
dg = self.env.ref(
'l10n_at_hr_payroll_private.category_atp_dg')
for regel_code, betrag, bemessung, s67, beitragsmonat in regeln:
vals = {
'name': regel_code,
'code': regel_code,
'struct_id': struktur.id,
'category_id': brutto.id,
'condition_select': 'none',
'l10n_at_bemessung': bemessung,
'l10n_at_s67_abs': s67,
'amount_select': 'fix',
'amount_fix': betrag,
}
if beitragsmonat:
vals['l10n_at_beitragsmonat'] = beitragsmonat
self.env['hr.salary.rule'].create(vals)
self.env['hr.salary.rule'].create({
'name': 'BV-Beitrag Abfertigung Neu (Test)',
'code': 'BVG',
'struct_id': struktur.id,
'category_id': dg.id,
'condition_select': 'python',
'condition_python':
'result = payslip._at_bv_beitrag(result_rules) > 0',
'amount_select': 'code',
'l10n_at_bemessung': 'neutral',
'amount_python_compute':
'result = payslip._at_bv_beitrag(result_rules)',
})
return struktur
# ------------------------------------------------------------------
# RF-BV1/2: Standardlauf und kein HBG-Cap (§ 6 Abs 5)
# ------------------------------------------------------------------
def test_01_standard_b13(self):
"""November mit B13: BVG 1,53 % auf KVGEO + B13 (4.118,82 €
→ 63,02 €), Kategorie ATP_DG ohne Nettoeffekt."""
employee = self._employee()
slip = self._slip(employee, 11)
basis = (self._total(slip, 'KVGEO') + self._total(slip, 'B13'))
self.assertAlmostEqual(basis, 4118.82, places=2)
self.assertAlmostEqual(self._total(slip, 'BVG'),
4118.82 * 0.0153, places=2)
# Kein Nettoeffekt: ATP_DG bleibt außerhalb der NETTO-Rechnung.
abzug = sum(self._total(slip, code) for code in
('SVDN_KV', 'SVDN_PV', 'SVDN_AV', 'SVDN_AK',
'SVDN_WF', 'SVDN_SZ', 'LST_LFD', 'LST_SZ'))
self.assertAlmostEqual(self._total(slip, 'NETTO'),
basis - abzug, places=1)
def test_02_kein_hbg_cap(self):
"""Entgelt 8.000 € über der HBG 6.930 €: SVDG_KV cappt auf
6.930, BVG läuft auf die volle Grundlage (§ 6 Abs 5 iVm
§ 49 ASVG — GFG und HBG unberücksichtigt)."""
employee = self._employee({'ueberzahlung': 8000.0})
slip = self._slip(employee, 1)
self.assertAlmostEqual(self._total(slip, 'KVGEO'), 8000.0,
places=2)
self.assertAlmostEqual(self._total(slip, 'SVDG_KV'),
6930.0 * 0.0378, places=2)
self.assertAlmostEqual(self._total(slip, 'BVG'),
8000.0 * 0.0153, places=2)
# ------------------------------------------------------------------
# RF-BV3/4/5/10: Beitragsfreier erster Monat (§ 6 Abs 1)
# ------------------------------------------------------------------
def test_03_erster_monat_fenster(self):
"""Eintritt 15. 06.: Juni zur Gänze beitragsfrei, Juli aliquot
mit 30-Tage-Monat (KB-Beispiel: × 17/30), August voll."""
employee = self._employee(
{'ueberzahlung': 6000.0,
'contract_date_start': date(2026, 6, 15)})
slip_jun = self._slip(employee, 6)
self.assertAlmostEqual(self._total(slip_jun, 'BVG'), 0.0,
places=2)
slip_jul = self._slip(employee, 7)
self.assertAlmostEqual(self._total(slip_jul, 'KVGEO'), 6000.0,
places=2)
self.assertAlmostEqual(
self._total(slip_jul, 'BVG'),
6000.0 * 17 / 30 * 0.0153, places=2)
slip_aug = self._slip(employee, 8)
self.assertAlmostEqual(self._total(slip_aug, 'BVG'),
6000.0 * 0.0153, places=2)
def test_04_wiederzulassung(self):
"""Vor-AV nur 14 Tage, Ende binnen 12 Monaten → Wieder-
zulassung: neuer AV beitragspflichtig ab dem 1. Tag (auch
bei kurzem Vor-AV; OGH 9 ObA 30/16a)."""
employee = self._employee(
{'ueberzahlung': 6000.0,
'contract_date_start': date(2026, 7, 1)})
employee.version_id.write({'date_version': date(2026, 7, 1)})
self.env['hr.version'].create({
'employee_id': employee.id,
'date_version': date(2026, 6, 1),
'contract_date_start': date(2026, 6, 1),
'contract_date_end': date(2026, 6, 15),
})
slip = self._slip(employee, 7)
self.assertEqual(slip.version_id, employee.version_id)
self.assertAlmostEqual(self._total(slip, 'BVG'),
6000.0 * 0.0153, places=2)
def test_05_kurz_av(self):
"""AV genau 1 Monat (1.-30. 06.) → zur Gänze beitragsfrei
(lb-vor-03; auch bloße Urlaubsersatzleistungen)."""
employee = self._employee(
{'ueberzahlung': 6000.0,
'contract_date_start': date(2026, 6, 1),
'contract_date_end': date(2026, 6, 30)})
slip = self._slip(employee, 6)
self.assertAlmostEqual(self._total(slip, 'KVGEO'), 6000.0,
places=2)
self.assertAlmostEqual(self._total(slip, 'BVG'), 0.0, places=2)
def test_10_monatsende_klippe(self):
"""Eintritt 31. 01. — der Jubiläumstag 31. 02. existiert
nicht → Fenster bis Monatsultimo (28. 02.), Pflicht ab
01. 03. (ABGB-Fristrechnung)."""
employee = self._employee(
{'ueberzahlung': 6000.0,
'contract_date_start': date(2026, 1, 31)})
slip_feb = self._slip(employee, 2)
self.assertAlmostEqual(self._total(slip_feb, 'BVG'), 0.0,
places=2)
slip_mrz = self._slip(employee, 3)
self.assertAlmostEqual(self._total(slip_mrz, 'BVG'),
6000.0 * 0.0153, places=2)
# ------------------------------------------------------------------
# RF-BV6/7/9: Geringfügige, SZ-Fälligkeit/§-67-Ausschluss, Alt-Fall
# ------------------------------------------------------------------
def test_06_geringfuegig(self):
"""Geringfügig Beschäftigte sind BMSVG-erfasst: BVG läuft
(500 € → 7,65 €), obwohl der SVDG-Block auf 0 geht."""
employee = self._employee({'ueberzahlung': 500.0})
slip = self._slip(employee, 1)
self.assertTrue(slip.l10n_at_geringfuegig)
self.assertAlmostEqual(self._total(slip, 'SVDG_KV'), 0.0,
places=2)
self.assertAlmostEqual(self._total(slip, 'BVG'),
500.0 * 0.0153, places=2)
def test_07_sz_fenster_und_s67(self):
"""SZ mit Beitragsmonat im freien Fenster bleibt frei
(Fälligkeit taggenau: 20. 06. frei, 20. 07. pflichtig);
§-67-Abs-3-Abfertigung scheidet aus der BV-Grundlage aus
(§ 49 Abs 3 Z 7 ASVG); laufender Anteil 17/30."""
employee = self._employee(
{'contract_date_start': date(2026, 6, 15)})
struktur = self._bv_struktur('A', [
('LFD', 2000.0, 'laufend', 'keine', None),
('SZ_FENSTER', 1000.0, 'sonstig', 'keine',
date(2026, 6, 20)),
('SZ_PFLECHTIG', 1000.0, 'sonstig', 'keine',
date(2026, 7, 20)),
('ABF_ALT', 10000.0, 'sonstig', 'abs3', None),
])
slip = self._slip(employee, 7, struct=struktur)
basis = 2000.0 * 17 / 30 + 1000.0
self.assertAlmostEqual(self._total(slip, 'BVG'),
basis * 0.0153, places=2)
def test_09_alt_fall_flag(self):
"""Alt-Verhältnis (Flag abgewählt) läuft ohne BVG."""
employee = self._employee({'l10n_at_abfertigung_neu': False})
slip = self._slip(employee, 11)
self.assertAlmostEqual(self._total(slip, 'KVGEO'), 2059.41,
places=2)
self.assertAlmostEqual(self._total(slip, 'BVG'), 0.0, places=2)
# ------------------------------------------------------------------
# RF-BV8: Anwartschaft + Endabrechnungs-Wizard (§§ 14-17)
# ------------------------------------------------------------------
def test_08_wizard_verfuegbarkeit(self):
"""Anwartschaft aus 3 validierten Läufen (3 × 31,51 €);
§-14-Matrix: < 36 Beitragsmonate sperrt IMMER, unschädliche
AG-Kündigung + 36 Monate verfügbar, AN-Kündigung sperrt;
Fristen § 16 (Ende des zweitfolgenden Monats) und § 17
(6 Monate Erklärungsfrist)."""
employee = self._employee()
for monat in (1, 2, 3):
self._slip(employee, monat, confirm=True)
fort = employee._at_bv_anwartschaft()
self.assertEqual(fort['monate'], 3)
self.assertAlmostEqual(fort['beitraege'],
3 * 2059.41 * 0.0153, places=1)
self.assertEqual(fort['jahre'], [(2026, 3, fort['beitraege'])])
wizard = self.env['l10n.at.bv.endabrechnung.wizard'].create({
'employee_id': employee.id,
'austrittsdatum': date(2026, 8, 31),
'beendigungsart': 'ag_kuendigung',
'geltendmachung': date(2026, 9, 11),
})
self.assertFalse(wizard.verfuegbar) # 3 < 36 Beitragsmonate
self.assertIn('36', wizard.sperrgrund)
wizard.write({'monate_extern': 33})
self.assertEqual(wizard.monate_gesamt, 36)
self.assertTrue(wizard.verfuegbar) # unschädliche Beendigung
wizard.write({'beendigungsart': 'an_kuendigung'})
self.assertFalse(wizard.verfuegbar) # § 14 Abs 2-Gruppe
self.assertEqual(wizard.faelligkeit, date(2026, 11, 30))
self.assertEqual(wizard.erklaerungsfrist, date(2027, 2, 28))
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- GP5 Abfertigung Neu: BV-Kassen-Stamm, Vertragsfelder am -->
<!-- Mitarbeiter (Payroll-Gruppe, Muster KV-Einstufung), -->
<!-- Endabrechnungs-Wizard (§§ 14-17 BMSVG) und Menüs. -->
<!-- ============================================================ -->
<record id="bv_kasse_list_view" model="ir.ui.view">
<field name="name">l10n.at.bv.kasse.list</field>
<field name="model">l10n.at.bv.kasse</field>
<field name="arch" type="xml">
<list string="Mitarbeitervorsorgekassen">
<field name="name"/>
<field name="leitzahl"/>
<field name="active" column_invisible="1"/>
</list>
</field>
</record>
<record id="bv_kasse_form_view" model="ir.ui.view">
<field name="name">l10n.at.bv.kasse.form</field>
<field name="model">l10n.at.bv.kasse</field>
<field name="arch" type="xml">
<form string="Mitarbeitervorsorgekasse">
<sheet>
<group>
<group>
<field name="name"/>
<field name="leitzahl"/>
</group>
<group>
<field name="active" widget="boolean_toggle"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_bv_kasse" model="ir.actions.act_window">
<field name="name">Mitarbeitervorsorgekassen</field>
<field name="res_model">l10n.at.bv.kasse</field>
<field name="view_mode">list,form</field>
</record>
<!-- BMSVG-Stammdaten am Mitarbeitervertrag (Payroll-Gruppe, wie
die KV-Einstufung). -->
<record id="payroll_hr_employee_view_form_bv" model="ir.ui.view">
<field name="name">hr.employee.form.bv</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr_payroll.payroll_hr_employee_view_form"/>
<field name="arch" type="xml">
<group name="payroll_group" position="inside">
<group name="bv_einstufung" string="Abfertigung Neu (BMSVG)"
groups="hr_payroll.group_hr_payroll_user">
<field name="l10n_at_abfertigung_neu"/>
<field name="l10n_at_bv_kasse_id"/>
<field name="l10n_at_bv_monate_extern"/>
<field name="l10n_at_bv_ertraege"/>
</group>
</group>
</field>
</record>
<!-- Endabrechnungs-Wizard (Austritt): Anwartschaftsausweis +
§-14-Abs-2-Prüfung; Auszahlung läuft über die MV-Kasse. -->
<record id="bv_endabrechnung_wizard_form_view" model="ir.ui.view">
<field name="name">l10n.at.bv.endabrechnung.wizard.form</field>
<field name="model">l10n.at.bv.endabrechnung.wizard</field>
<field name="arch" type="xml">
<form string="BV-Endabrechnung (Abfertigung Neu, §§ 14-17 BMSVG)">
<group>
<group string="Dienstverhältnis">
<field name="employee_id"/>
<field name="austrittsdatum"/>
<field name="beendigungsart"/>
<field name="geltendmachung"/>
</group>
<group string="Anwartschaft (§ 15 BMSVG)">
<field name="kasse_id"/>
<field name="monate_lokal"/>
<field name="monate_extern"/>
<field name="monate_gesamt"/>
<field name="beitraege_lokal"/>
<field name="ertraege"/>
<field name="anwartschaft" class="oe_subtotal_footer_separator"/>
</group>
</group>
<group string="Verfügbarkeit (§ 14 Abs 2 BMSVG)">
<group>
<field name="verfuegbar" widget="badge"
decoration-success="verfuegbar"
decoration-danger="not verfuegbar"/>
<field name="sperrgrund" invisible="verfuegbar"
nolabel="1" placeholder="Kein Verfügungsausschluss"/>
</group>
<group string="Fristen">
<field name="erklaerungsfrist"/>
<field name="faelligkeit"/>
</group>
</group>
<div class="alert alert-info" role="alert">
Verfügung über die Anwartschaft (§ 17 BMSVG — innerhalb
von 6 Monaten nach Austritt erklären, sonst
Weiterveranlagung): 1) Kapitalauszahlung (6 % Lohnsteuer,
§ 67 Abs 3 EStG — Einbehalt durch die MV-Kasse),
2) Weiterveranlagung bei der MV-Kasse, 3) Übertragung auf
die MV-Kasse des neuen Arbeitgebers, 4) Überweisung an
einen Versicherer als Einmalprämie für eine
Pensionszusatzversicherung (§ 108b EStG — keine LSt),
5) Überweisung an eine Pensionskasse. Weitere
Auszahlungsfälle (§ 14 Abs 1; Einzelbeurteilung mit der
Kasse): Inanspruchnahme einer Alterspension (§ 4 Abs 3
APG), Auflösung ab Korridorpensions-Alter (62), fünf
Jahre kein BMSVG-pflichtiges Arbeitsverhältnis, Tod
(ungekürzt an unterhaltsberechtigte Erben).
</div>
<footer>
<button string="Anwartschaftsreport drucken"
name="action_report" type="object"
class="btn-primary"/>
<button string="Schließen" class="btn-secondary"
special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_bv_endabrechnung_wizard" model="ir.actions.act_window">
<field name="name">BV-Endabrechnung (Austritt)</field>
<field name="res_model">l10n.at.bv.endabrechnung.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_l10n_at_payroll_private_bv_kasse"
name="Mitarbeitervorsorgekassen"
parent="menu_l10n_at_payroll_private_root"
action="action_bv_kasse" sequence="50"/>
<menuitem id="menu_l10n_at_payroll_private_bv_end"
name="BV-Endabrechnung (Austritt)"
parent="menu_l10n_at_payroll_private_root"
action="action_bv_endabrechnung_wizard" sequence="60"/>
</odoo>
@@ -1,2 +1,3 @@
# Part of the odoo-at-payroll project. License: LGPL-3. # Part of the odoo-at-payroll project. License: LGPL-3.
from . import kv_katalog_import from . import kv_katalog_import
from . import bv_endabrechnung
@@ -0,0 +1,156 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""BV-Endabrechnungs-Wizard (GP5): Anwartschaftsausweis bei Austritt
mit §-14-Abs-2-Verfügbarkeitsprüfung und Fristenausweis (§§ 16 f.
BMSVG). Reiner Informations-/Dokumentationsprozess — die Auszahlung
der Anwartschaft erfolgt durch die MV-Kasse (§ 15: gutgebrachte
Beiträge + Erträge, KEINE Monatsentgelt-Staffel), nicht über die
Lohnverrechnung. Verfügungsmatrix (lb-end-03, Stand 2026-08):
Verfügungsausschluss bei AN-Kündigung (außer MSchG/VKG-Teilzeit-
Kündigung), verschuldeter Entlassung, unberechtigtem vorzeitigem
Austritt — und IMMER bei weniger als 36 Beitragsmonaten seit der
ersten Beitragszahlung bzw. letztmaligen Verfügung (alle
Beitragszeiten zählen, Vor-Arbeitgeber manuell erfassen)."""
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
ABS14_GRUPPEN = ('an_kuendigung', 'verschuldete_entlassung',
'unberechtigter_austritt')
class L10nAtBvEndabrechnungWizard(models.TransientModel):
_name = 'l10n.at.bv.endabrechnung.wizard'
_description = 'BV-Endabrechnung (Abfertigung Neu, §§ 14-17 BMSVG)'
employee_id = fields.Many2one(
'hr.employee', string='Dienstnehmer:in', required=True)
austrittsdatum = fields.Date(
string='Austritt (Ende des Arbeitsverhältnisses)',
help='Beginn der 6-Monats-Erklärungsfrist (§ 17 BMSVG): gibt der '
'Berechtigte binnen sechs Monaten keine '
'Verwendungserklärung ab, wird weiterveranlagt.')
beendigungsart = fields.Selection(
[('ag_kuendigung', 'Kündigung durch den Arbeitgeber (unschädlich)'),
('einvernehmlich', 'Einvernehmliche Auflösung (unschädlich)'),
('berechtigter_austritt',
'Berechtigter vorzeitiger Austritt (unschädlich)'),
('unverschuldete_entlassung',
'Unverschuldete/unrechtmäßige Entlassung (unschädlich)'),
('an_kuendigung', 'Kündigung durch den AN (§ 14 Abs 2 BMSVG)'),
('verschuldete_entlassung',
'Verschuldete Entlassung (§ 14 Abs 2 BMSVG)'),
('unberechtigter_austritt',
'Unberechtigter vorzeitiger Austritt (§ 14 Abs 2 BMSVG)'),
('tod', 'Tod (ungekürzt an unterhaltsberechtigte Erben)')],
string='Beendigungsart', required=True)
monate_extern = fields.Integer(
string='Beitragsmonate Vor-Arbeitgeber',
help='Beitragsmonate aus Vor-Beschäftigungen laut Auszug der '
'MV-Kasse (§ 14 Abs 2 BMSVG: alle Beitragszeiten über '
'Arbeitgeber hinweg summierbar). Vorbelegt aus dem '
'Mitarbeiter-Stamm.')
ertraege = fields.Monetary(
string='Erträge laut Kassen-Auszug',
currency_field='currency_id',
help='Veranlagungserträge laut Auszug der MV-Kasse; die '
'Anwartschaft = gutgebrachte Beiträge + Erträge (§ 15 '
'BMSVG).')
currency_id = fields.Many2one(
'res.currency',
default=lambda self: self.env.company.currency_id)
geltendmachung = fields.Date(
string='Geltendmachung',
default=fields.Date.today,
help='Datum der Geltendmachung gegenüber dem Arbeitgeber; die '
'Fälligkeit der Auszahlung tritt am Ende des '
'zweitfolgenden Monats ein (§ 16 BMSVG).')
kasse_id = fields.Many2one(
'l10n.at.bv.kasse', string='Mitarbeitervorsorgekasse',
compute='_compute_ergebnis')
monate_lokal = fields.Integer(
string='Beitragsmonate (dieser DG)',
compute='_compute_ergebnis',
help='Beitragsmonate aus validierten Lohnläufen mit BVG-Beitrag.')
beitraege_lokal = fields.Monetary(
string='Gutgebrachte Beiträge (dieser DG)',
currency_field='currency_id', compute='_compute_ergebnis')
monate_gesamt = fields.Integer(
string='Beitragsmonate gesamt', compute='_compute_ergebnis',
help='Beitragsmonate gesamt inkl. Vor-Arbeitgeber — '
'36-Monats-Kriterium des § 14 Abs 2 BMSVG.')
anwartschaft = fields.Monetary(
string='Anwartschaft (Beiträge + Erträge)',
currency_field='currency_id', compute='_compute_ergebnis')
verfuegbar = fields.Boolean(
string='Verfügbar (§ 14 Abs 2 BMSVG)', compute='_compute_ergebnis')
sperrgrund = fields.Text(
string='Verfügungsausschluss', compute='_compute_ergebnis')
faelligkeit = fields.Date(
string='Fälligkeit Auszahlung (§ 16 BMSVG)',
compute='_compute_ergebnis')
erklaerungsfrist = fields.Date(
string='Erklärungsfrist (§ 17 BMSVG)', compute='_compute_ergebnis')
@api.onchange('employee_id')
def _onchange_employee(self):
for wizard in self:
if not wizard.employee_id:
continue
wizard.austrittsdatum = (
wizard.employee_id.version_id.contract_date_end
or fields.Date.today())
wizard.monate_extern = \
wizard.employee_id.l10n_at_bv_monate_extern
wizard.ertraege = wizard.employee_id.l10n_at_bv_ertraege
@api.depends('employee_id', 'beendigungsart', 'monate_extern',
'ertraege', 'geltendmachung', 'austrittsdatum')
def _compute_ergebnis(self):
for wizard in self:
if wizard.employee_id:
fort = wizard.employee_id._at_bv_anwartschaft()
wizard.kasse_id = wizard.employee_id.l10n_at_bv_kasse_id
wizard.monate_lokal = fort['monate']
wizard.beitraege_lokal = fort['beitraege']
else:
wizard.kasse_id = False
wizard.monate_lokal = 0
wizard.beitraege_lokal = 0.0
wizard.monate_gesamt = wizard.monate_lokal \
+ wizard.monate_extern
wizard.anwartschaft = wizard.beitraege_lokal + wizard.ertraege
grund = False
if wizard.beendigungsart in ABS14_GRUPPEN:
grund = _('Verfügungsausschluss nach § 14 Abs 2 BMSVG '
'wegen der Beendigungsart (Kündigung durch '
'den AN, verschuldete Entlassung oder '
'unberechtigter vorzeitiger Austritt); die '
'Anwartschaft bleibt bei der MV-Kasse '
'weiterveranlagt.')
elif wizard.monate_gesamt < 36:
grund = _('Verfügungsausschluss nach § 14 Abs 2 BMSVG: '
'noch keine 36 Beitragsmonate (%(monaten)s von '
'36; alle Beitragszeiten zählen).',
monaten=wizard.monate_gesamt)
wizard.verfuegbar = not grund
wizard.sperrgrund = grund
if wizard.geltendmachung:
# § 16: Fälligkeit am Ende des zweitfolgenden Monats.
zweit = wizard.geltendmachung + relativedelta(months=2)
wizard.faelligkeit = (zweit.replace(day=1)
+ relativedelta(months=1)
- timedelta(days=1))
else:
wizard.faelligkeit = False
wizard.erklaerungsfrist = (
wizard.austrittsdatum + relativedelta(months=6)
if wizard.austrittsdatum else False)
def action_report(self):
self.ensure_one()
return self.env.ref(
'l10n_at_hr_payroll_private.action_report_bv_anwartschaft'
).report_action(self.employee_id)