From 04099fef39db7ef1dce43a99e3d2bcac372fcfdc Mon Sep 17 00:00:00 2001 From: fegger Date: Fri, 11 Sep 2026 23:22:13 +0200 Subject: [PATCH] [IMP] l10n_at_hr_payroll_private: GP4 SV/DB-Anbindung (SVDN_SZ-Nachtrag, DG-Regelblock, FLAF/KommSt/DZ) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Anbindung der privatwirtschaftlichen Structure an den GP4-Kern nach dem bewährten GP3-Muster (dünne 1-Zeilen-Regeln; Logik und Werte verbleiben im Kern — hr.salary.rule.struct_id ist required, geteilte Kern-Regeln sind mechanisch unmöglich). SVDN_SZ-Nachtrag (§ 67 Abs 12): Die SV-DN auf sonstigen Bezügen war in GP3 nur als Bemessungsbestandteil der §-67-Lohnsteuer wirksam, fehlte aber als eigene Abzugszeile — das Netto in §-67-Monaten war um den SV-DN(SZ) zu hoch (RF-L1: 290,79 € nach §-2a-Korrektur). Die Erwartungen in test_01/test_20 wurden entsprechend korrigiert. DG-Block (Kategorie ATP_DG, ohne Nettoeffekt): SVDG_KV/PV/AV/UV/WF/IESG laufend, SVDG_SZ_* auf Sonderzahlungen, SVDG_UV_GFG (§-53a-Pauschale geringfügig Beschäftigter) sowie die Lohnnebenkosten FLAFDB (§ 41 FLAG), KOMMST (§ 9 KommStG) und DZ (§§ 122/126 WKG, nur bei Company- Flag l10n_at_wk_mitglied; Satz je Betriebsstätten-Bundesland). Gemeinsame LNK-Basis in payslip_private.py (_at_lnk_basis): monatlich gezahlte Arbeitslöhne nach Zufluss, ohne §-67-Abs-3/6-Bezüge (Kern-Flag l10n_at_s67_abs), lit-f-Befreiung nur für FLAF und DZ (KommSt ohne), Freibetragsstaffel 1.095/1.460 €. Rechenfälle docs/rechenfaelle/sozialversicherung.md (RF-SV-1 bis -7) und §-2a-Korrekturvermerk in lohnsteuer.md. Tests: test_sv_private (8 Fälle); gesamt 74/74 grün auf odoo_dev. --- addons/l10n_at_hr_payroll_private/README.md | 33 ++- .../__manifest__.py | 18 +- .../data/salary_rules_sv_private.xml | 253 ++++++++++++++++++ .../docs/rechenfaelle/lohnsteuer.md | 48 ++-- .../docs/rechenfaelle/sozialversicherung.md | 172 ++++++++++++ .../models/payslip_private.py | 72 +++++ .../tests/__init__.py | 3 +- .../tests/test_entgelt_private.py | 8 +- .../tests/test_lst_private.py | 25 +- .../tests/test_sv_private.py | 245 +++++++++++++++++ 10 files changed, 833 insertions(+), 44 deletions(-) create mode 100644 addons/l10n_at_hr_payroll_private/data/salary_rules_sv_private.xml create mode 100644 addons/l10n_at_hr_payroll_private/docs/rechenfaelle/sozialversicherung.md create mode 100644 addons/l10n_at_hr_payroll_private/tests/test_sv_private.py diff --git a/addons/l10n_at_hr_payroll_private/README.md b/addons/l10n_at_hr_payroll_private/README.md index 5af4d11..eda7eeb 100644 --- a/addons/l10n_at_hr_payroll_private/README.md +++ b/addons/l10n_at_hr_payroll_private/README.md @@ -47,12 +47,33 @@ Privatwirtschaft (KV)", AZG-Fenster-WETs, Regelinventar KVGEO/NACHT_SV/UE_*/MEHR_*/URL_UE/B13/B14/B14_RUECK/NETTO — vollständig `kv.wert`-getrieben (Rechenfälle `docs/rechenfaelle/`). -**Offen (GP3+):** Lohnsteuer/SV im Bundes-Kern (D3-Vorfinanzierung), -Abfertigung Neu, Meldewesen/Belege, FI-Buchung UGB; All-in-UI-Bericht -(GP8); B14 längster-Urlaubs-Teil-Verfeinerung. +**GP3 Lohnsteuer (umgesetzt 2026-09-11):** dünne Regeln +SVDN_KV/PV/AV/AK/WF + LST_LFD/LST_SZ auf dem AP3-Kern (§ 66 +kumulativ mit Rollung, § 67 Sechstel/Fixsätze mit Günstig- +Aufrollung, § 68-Steuerfreiheiten; Rechenfälle `docs/rechenfaelle/ +lohnsteuer.md`). -Tests: `test_kv_katalog` + `test_entgelt_private` (GP2-Rechenfälle) — +**GP4 SV/DB (umgesetzt 2026-09-11):** SVDN_SZ-Nachtrag (§ 67 Abs 12 — +Netto-Korrektur in §-67-Monaten), DG-Block SVDG_KV/PV/AV/UV/WF/IESG + +SVDG_SZ_* + §-53a-UV Geringfügiger (Kategorie ATP_DG, ohne +Nettoeffekt; ÖGK 2026: DG 20,98 % / Wien 21,23 %), Lohnnebenkosten +FLAFDB (§ 41 FLAG), KOMMST (§ 9 KommStG) und DZ (§§ 122/126 WKG, +company-flag-gated) mit Freibetragsstaffel 1.095/1.460 € — Logik/Werte +im Bundes-Kern (`models/sozialversicherung.py`, D3-Vorfinanzierung): +§-2a-ALV-DN-Staffel, Geringfügigkeit (§ 5 Abs 2 iVm § 810 Abs 3 ASVG), +beitragsfreie Bezüge § 49 Abs 3 mit Jahres-Kumulativen, +altersabhängige Satzausschaltungen (§ 1 Abs 2 lit e AlVG, § 51 +Abs 6/7 ASVG, § 12 IESG), WF-Satzvektor Wien, Beitragsmonats- +Allokation (Anspruchsprinzip) und DAG-Bewertung (§ 1 DAG). +Rechenfälle `docs/rechenfaelle/sozialversicherung.md`. + +**Offen (GP5+):** Abfertigung Neu (BMSVG), Meldewesen/Belege, +FI-Buchung UGB; All-in-UI-Bericht (GP8); B14 längster-Urlaubs-Teil- +Verfeinerung; DZ-WKO-Verifikation (8.11) vor Wien-Mandanten. + +Tests: `test_kv_katalog` + `test_entgelt_private` (GP2) + +`test_lst_private` (GP3) + `test_sv_private` (GP4) — `.venv/bin/python -m odoo -d -u l10n_at_hr_payroll,l10n_at_hr_payroll_private ---test-tags /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` -(Lokal grün am 2026-09-11: 29/29 Tests.) \ No newline at end of file +(Lokal grün am 2026-09-11: **74/74 Tests**.) \ No newline at end of file diff --git a/addons/l10n_at_hr_payroll_private/__manifest__.py b/addons/l10n_at_hr_payroll_private/__manifest__.py index a16300b..4465b74 100644 --- a/addons/l10n_at_hr_payroll_private/__manifest__.py +++ b/addons/l10n_at_hr_payroll_private/__manifest__.py @@ -1,7 +1,7 @@ # Part of the odoo-at-payroll project. License: LGPL-3. { 'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)', - 'version': '19.0.3.0.0', + 'version': '19.0.4.0.0', 'author': 'ixsol / gem360', 'website': 'https://gem360.at', 'category': 'Human Resources/Payroll', @@ -31,12 +31,21 @@ Grundlagen: ``personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md`` und (Vorlauf 2026-09-11 verifiziert; Rechenfälle unter ``docs/rechenfaelle/``). All-in-Vertragsmodell (§ 2g AVRAG): Pauschale/Grundlohn-Ausweis am Vertrag + Deckungsrechnung. +- **GP3 Lohnsteuer (umgesetzt):** dünne Regeln SVDN_KV/PV/AV/AK/WF + + LST_LFD/LST_SZ auf dem AP3-Kern (§ 66 kumulativ, § 67 Sechstel/ + Fixsätze, § 68-Steuerfreiheiten). +- **GP4 SV/DB (dieser Stand):** SVDN_SZ-Nachtrag (§ 67 Abs 12 — Netto- + Korrektur in §-67-Monaten), DG-Block SVDG_KV/PV/AV/UV/WF/IESG + + SVDG_SZ_* + §-53a-UV Geringfügiger (ATP_DG, ohne Nettoeffekt; + ÖGK 2026: DG 20,98 % / Wien 21,23 %), Lohnnebenkosten FLAFDB + (§ 41 FLAG), KOMMST (§ 9 KommStG) und DZ (§§ 122/126 WKG, + company-flag-gated) mit Freibetragsstaffel 1.095/1.460 € — + Logik/Werte im Bundes-Kern (D3-Vorfinanzierung). - **Seed D2 (Referenz-KVs R1):** SI-2203 „Handwerk und Gewerbe" + SI-2748 „Metallgewerbe" (Gehaltstabellen ab 1. 1. 2026, kuratiert aus den KV-Library-Volltexten). -- **Folgen (GP2+):** Entgelt-Engine (KVGEO, AZG-Zuschläge, 13./14. - Bezüge), Lohnsteuer/SV im Bundes-Kern (D3-Vorfinanzierung), - Abfertigung Neu, Meldewesen/Belege, FI-Buchung UGB. +- **Folgen (GP5+):** Abfertigung Neu (BMSVG), Meldewesen/Belege, + FI-Buchung UGB, Jahreswechsel. Kein VRV-Bezug (Gemeinde-Schiene bleibt ``l10n_at_gemeinde_payroll`` + gem360-Brücke). @@ -53,6 +62,7 @@ gem360-Brücke). 'data/input_types_private.xml', 'data/salary_rules_private.xml', 'data/salary_rules_lst_private.xml', + 'data/salary_rules_sv_private.xml', 'views/kv_views.xml', ], 'countries': ['at'], diff --git a/addons/l10n_at_hr_payroll_private/data/salary_rules_sv_private.xml b/addons/l10n_at_hr_payroll_private/data/salary_rules_sv_private.xml new file mode 100644 index 0000000..03800e9 --- /dev/null +++ b/addons/l10n_at_hr_payroll_private/data/salary_rules_sv_private.xml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + SV-DN auf sonstigen Bezügen (§ 67 Abs 12; KV/PV/AV) + SVDN_SZ + + + + python + result = payslip._l10n_at_svdn_sz(result_rules) > 0 + code + neutral + + + + + + + SV-DG Krankenversicherung (§ 51 Abs 1 Z 1 ASVG) + SVDG_KV + + + + python + result = payslip._l10n_at_svdg('kv', result_rules) > 0 + code + neutral + + + + SV-DG Pensionsversicherung (§ 51 Abs 1 Z 3 ASVG) + SVDG_PV + + + + python + result = payslip._l10n_at_svdg('pv', result_rules) > 0 + code + neutral + + + + SV-DG Arbeitslosenversicherung (§ 2 AMPFG; entfällt § 1 Abs 2 lit e AlVG) + SVDG_AV + + + + python + result = payslip._l10n_at_svdg('av', result_rules) > 0 + code + neutral + + + + SV-DG Unfallversicherung (§ 51 Abs 1 Z 2 ASVG; entfällt ab 60) + SVDG_UV + + + + python + result = payslip._l10n_at_svdg('uv', result_rules) > 0 + code + neutral + + + + WF-Beitrag DG (Wohnbauförderung; Wien 0,75 % ab 1.1.2026) + SVDG_WF + + + + python + result = payslip._l10n_at_svdg('wf', result_rules) > 0 + code + neutral + + + + IESG-Zuschlag DG (§ 12 Abs 1 Z 4 IESG; entfällt ab 63) + SVDG_IESG + + + + python + result = payslip._l10n_at_svdg('iesg', result_rules) > 0 + code + neutral + + + + + + + SV-DG KV auf sonstigen Bezügen (§ 54 ASVG) + SVDG_SZ_KV + + + + python + result = payslip._l10n_at_svdg_sz('kv', result_rules) > 0 + code + neutral + + + + SV-DG PV auf sonstigen Bezügen (§ 54 ASVG) + SVDG_SZ_PV + + + + python + result = payslip._l10n_at_svdg_sz('pv', result_rules) > 0 + code + neutral + + + + SV-DG AV auf sonstigen Bezügen (§ 3 AMPFG) + SVDG_SZ_AV + + + + python + result = payslip._l10n_at_svdg_sz('av', result_rules) > 0 + code + neutral + + + + SV-DG UV auf sonstigen Bezügen (§ 51 iVm § 54 ASVG) + SVDG_SZ_UV + + + + python + result = payslip._l10n_at_svdg_sz('uv', result_rules) > 0 + code + neutral + + + + IESG-Zuschlag DG auf sonstigen Bezügen (folgt dem AV-Beitrag) + SVDG_SZ_IESG + + + + python + result = payslip._l10n_at_svdg_sz('iesg', result_rules) > 0 + code + neutral + + + + + + + + UV-Beitrag geringfügig Beschäftigter (§ 53a Abs 1 ASVG) + SVDG_UV_GFG + + + + python + result = payslip._l10n_at_svdg_uv_geringfuegig(result_rules) > 0 + code + neutral + + + + + + + FLAF-Dienstgeberbeitrag (§ 41 FLAG) + FLAFDB + + + + python + result = payslip._at_flaf_db(result_rules) > 0 + code + neutral + + + + Kommunalsteuer (§ 9 KommStG 1993) + KOMMST + + + + python + result = payslip._at_kommst(result_rules) > 0 + code + neutral + + + + Zuschlag zum Dienstgeberbeitrag (§§ 122/126 WKG; WKO-Mitglieder) + DZ + + + + python + result = payslip._at_dz(result_rules) > 0 + code + neutral + + + \ No newline at end of file diff --git a/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/lohnsteuer.md b/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/lohnsteuer.md index f909ae5..1e2b2ac 100644 --- a/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/lohnsteuer.md +++ b/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/lohnsteuer.md @@ -1,4 +1,4 @@ -# Rechenfälle Lohnsteuer (GP3) — Kern + privatwirtschaftliche Structure +## Rechenfälle Lohnsteuer (GP3) — Kern + privatwirtschaftliche Structure **Quellen (✅ P0-GP3 2026-09-11):** EStG-Volltext RIS-Fassung 10. 09. 2026 (lokal `.firecrawl/ris/`); RECHTSQUELLEN-Privat @@ -6,6 +6,13 @@ Abschnitt 5 (§-Anker je Wert). Kern: `l10n_at_hr_payroll` (`models/lohnsteuer.py`, `data/rule_parameters_lst_2026.xml`); privat: dünne Regeln `SVDN_*`/`LST_LFD`/`LST_SZ`. +> **GP4-Korrektur (2026-09-11):** Die ALV-DN-Staffel § 2a AMPFG +> (bis 2.225 € BG: 0 %) ist ab GP4 in der SV-Basis wirksam — für die +> RF-Bezüge 2.059,41/2.199,15 € entfällt der DN-AV auf laufenden +> Bezügen und SZ (14,12 % statt 17,07 %); DN-Gesamt 15,12 statt +> 18,07 %. Die GP3-Beispielwerte sind unten entsprechend korrigiert; +> Details `sozialversicherung.md` RF-SV-1. + ## Systematik (user-Entscheidungen 2026-09-11) - **Lohnsteuertage:** 30 je vollem Monat; Teilmonat = Kalendertage @@ -23,41 +30,44 @@ privat: dünne Regeln `SVDN_*`/`LST_LFD`/`LST_SZ`. ## RF-L1: Vollmonat November mit B13 (VG II Stufe 1) -- KVGEO 2.059,41 €; SVDN = 18,07 % × 2.059,41 = **372,91 €** - (KV 79,70 · PV 211,08 · ALV 60,75 · AK 10,30 · WF 10,30). -- Bemessung laufend = 2.059,41 − 372,91 = **1.686,50 €**; - JBG = 1.686,50 × 12 − 192 = 20.046,06 → Tarif 20 % × 6.507,06 = - 1.301,41 − 496 = 805,41 ÷ 12 → **LST_LFD 67,12 €**. -- B13: SV-SZ = 2.059,41 × 17,07 % = **351,54 €**; Bemessung - 1.707,87; Sechstel = 2.059,41 × 60/30 = 4.118,82 > 2.615 ✓ - pauschal → LST_SZ = (1.707,87 − 620) × 6 % = **65,27 €**. +- KVGEO 2.059,41 €; SVDN = 15,12 % × 2.059,41 = **311,38 €** + (KV 79,70 · PV 211,08 · ALV 0 (§ 2a: 2.059,41 ≤ 2.225) · AK 10,30 · + WF 10,30). +- Bemessung laufend = 2.059,41 − 311,38 = **1.748,03 €**; + JBG = 1.748,03 × 12 − 192 = 20.784,36 → Tarif 20 % × 7.245,36 = + 1.450,87 − 496 = 954,87 ÷ 12 → **LST_LFD 79,57 €**. +- B13: SV-SZ = 2.059,41 × 14,12 % = **290,79 €**; Bemessung + 1.768,62; Sechstel = 2.059,41 × 60/30 = 4.118,82 > 2.615 ✓ + pauschal → LST_SZ = (1.768,62 − 620) × 6 % = **68,92 €**. ## RF-L2: Kumulative Rollung (Zuflussprinzip) -- Januar LST_LFD 67,12; Februar: JBG unverändert → Soll kumuliert - 134,24 − bereits 67,12 = **67,12** — Korrekturen Vormonat +- Januar LST_LFD 79,57; Februar: JBG unverändert → Soll kumuliert + 159,14 − bereits 79,57 = **79,57** — Korrekturen Vormonat (Nachzahlung, Gehaltsänderung) rollen automatisch in den Folgelauf (§ 66 kumulativ = Rollungsprinzip § 77 Abs 3). ## RF-L3: Günstig-Aufrollung bei zweiter SZ -- Okt (nur laufend), Nov B13 (LST_SZ 65,27 bezahlt), Dez B14 +- Okt (nur laufend), Nov B13 (LST_SZ 68,92 bezahlt), Dez B14 (Dezember-Fallback ohne Urlaub): Jahres-SZ-Bemessung - 2 × 1.707,87 = 3.415,74 ≤ Sechstel 4.118,82 → Soll-Jahr - (3.415,74 − 620) × 6 % = 167,74 − 65,27 = **LST_SZ(Dez) 102,47 €** + 2 × 1.768,62 = 3.537,24 ≤ Sechstel 4.118,82 → Soll-Jahr + (3.537,24 − 620) × 6 % = 175,03 − 68,92 = **LST_SZ(Dez) 106,11 €** (Neuberechnung bei jeder SZ-Zahlung, lb-son-04). ## RF-L4: Sechstel-Freigrenze (2.615 €) — SZ tarifisch -- Laufend 500 €/Monat: Sechstel = 500 × 60/30 = 1.000 ≤ 2.615 → - B14 läuft tarifisch über LST_LFD (LST_SZ = 0). +- Laufend 500 €/Monat (GP4: geringfügig → keine SV-Beiträge): + Sechstel = 500 × 60/30 = 1.000 ≤ 2.615 → B14 läuft tarifisch + über LST_LFD (LST_SZ = 0) — Mechanik-Demonstration der + Freigrenze (geringfügige B14-Fälle in der Praxis unüblich). ## RF-L5: Sechstel-Überschuss (§ 67 Abs 10) -- Laufend 2.000 €/Monat (Sechstel 4.000), SZ 6.000 €: - SV-SZ = 6.000 × 17,07 % = 1.024,20 → Bemessung 4.975,80; +- Laufend 2.000 €/Monat (§ 2a: ALV-DN 0), Sechstel 4.000, SZ + 6.000 €: SV-SZ = 6.000 × 14,12 % = 847,20 → Bemessung 5.152,80; 4.000 im Sechstel → Fixsteuer (4.000 − 620) × 6 % = **202,80 €**; - Überschuss 975,80 läuft dem laufenden Arbeitslohn des + Überschuss 1.152,80 läuft dem laufenden Arbeitslohn des Auszahlungsmonats zu (LSt-lfd-Kette). ## RF-L6: § 68-Freibeträge (Buckets über Regel-Flags) diff --git a/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/sozialversicherung.md b/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/sozialversicherung.md new file mode 100644 index 0000000..7a48ff1 --- /dev/null +++ b/addons/l10n_at_hr_payroll_private/docs/rechenfaelle/sozialversicherung.md @@ -0,0 +1,172 @@ +# Rechenfälle Sozialversicherung/Dienstgeberabgaben (GP4) — Kern + privatwirtschaftliche Structure + +**Quellen (✅ verifiziert 2026-09-11):** ÖGK-Beitragstarifsystem +„Normalbetriebe Allgemeiner Teil", gültig ab 01.07.2026 +(`.additional_docs/oegk/`); TASY-LSWH-Export 2026-03-24 +(`personalverrechnung/quellen/`); „Beitragsrechtliche Werte 2026" +(sozialversicherung.at, `.firecrawl/ris/sv-at/`); RIS-Normtexte +ASVG §§ 5/45/49/51/53a/54/810, AKG § 61, AlVG § 1 Abs 2 lit e, +AMPFG § 2a, IESG § 12, FLAG § 41, KommStG §§ 5/9, WKG §§ 122/126, +DAG § 1; Layer-2-Belege lb-sva-03/04/06/12, lb-son-03, lb-lnk-01/02/ +06/07, lb-bes-06 (Stand 2026-07/08). Kern: +`l10n_at_hr_payroll` (`models/sozialversicherung.py`, +`rule_parameters_sv_2026.xml`); privat: dünne Regeln `SVDG_*`/ +`SVDN_SZ`/`FLAFDB`/`KOMMST`/`DZ` + LNK-Basis in `payslip_private.py`. + +## Systematik (GP4, verifiziert) + +- **Sätze 2026 (ÖGK):** DN 18,07 % gesamt = KV 3,87 · PV 10,25 · + ALV 2,95 · UV 0 + AK 0,50 + WF 0,50; **DG 20,98 %** = KV 3,78 · + PV 12,55 · ALV 2,95 · UV 1,10 + WF 0,50 + IESG 0,10. + **Wien: DN 18,32 / DG 21,23 %** (WF gesamt 1,5 % = 0,75/0,75 ab + 1.1.2026, Wiener WF-Beitragstarif 2018 § 1 — SV-Werte-2026 Fn. 5). +- **ALV-DN-Staffel § 2a AMPFG** (DN-seitig! ÖGK-Abschläge „Mind. AV + auf 0/1/2 %" = −2,95/−1,95/−0,95): BG bis **2.225 €: 0 %** · + 2.225–2.427 €: 1 % · 2.427–2.630 €: 2 % · darüber 2,95 % — DG-AV + bleibt stets 2,95 %; Grenzen **nicht zu aliquotieren**; gilt auch + für SZ-Beiträge (lb-son-03); § 2a mit 31.12.2026 aufgehoben + (BGBl I 62/2026) — ab 2027 voller DN-Satz. +- **SZ-Beiträge (§ 54 ASVG):** DN 14,12 % (KV+PV; AV nach Staffel); + DG **20,48 %** = KV 3,78 + PV 12,55 + AV 2,95 + UV 1,10 + IESG 0,10 + (IESG folgt dem AV-Beitrag; ⚠ §-5-AMPFG-Volltext offen); **ohne + AK/WF**; Deckel: HBG monatlich (2026: 6.930 €) und 60faches HBG je + Kalenderjahr (SZ-Jahreshöchstbetrag 13.860 €) minus YTD-SZ-Basen. +- **Geringfügigkeit (§ 5 Abs 2 ASVG):** 2026 **551,10 €** — die + Aufwertung ist gem. **§ 810 Abs 3 ASVG ausgesetzt** (deshalb kein + 2026-Eintrag in der RIS-Anmerkungsreihe; A7-Rest gelöst). SZ zählen + nicht zur Ermittlung; **untermonatiger Beginn/Ende → Hochrechnung** + (Entgelt × 30 ÷ Kalendertage); Verhältnis < 1 Naturalmonat → + Gesamtentgelt direkt gegen die Monatsgrenze (lb-bes-06); Lehrlinge + nie geringfügig (§ 5 Abs 3). +- **Geringfügig Beschäftigte:** nur UV-Pflicht; **DG-UV = 1,1 % der + allgemeinen Beitragsgrundlage** (§ 53a Abs 1 ASVG) als Monatspauschale + **6,50 €** 2026 (aBG = 83,49 ÷ 14,12 % = 591,29 € abgeleitet ⚠); + Selbstversicherung KV+PV **83,49 €/Monat** (§ 77 Abs 2a ASVG; TASY + KVPVBTG19A: 2024: 73,20 · 2025: 77,81 · 2026: 83,49) — **Direct- + Zahlung der Person an den Träger, kein Lohnabzug**; Pauschalbeitrag + auch von SZ (§ 53a Abs 5). +- **DAG (§ 1 DAG):** 19,4 % der Lohnsumme (Beitragsgrundlagen ohne SZ) + aller geringfügig Beschäftigten je Kalendermonat, wenn Lohnsumme > + **826,65 €** (= 1,5 × GFG; TASY MINGRDAGP1) und ≥ 2 Geringfügige + („mit UV 20,5 %" = Summensicht; UV läuft separat über § 53a Abs 1; + ab 60. LJ entfällt der UV-Anteil); Ausweis/Veranlagung GP8. +- **Altersabhängige Satzausschaltungen** (Stichtags-Datumfelder am + Vertrag, Wirken ab Beitragsmonat): ALV DN+DG **und** IESG entfallen + (§ 1 Abs 2 lit e AlVG; § 12 Abs 1 Z 4 iVm Abs 2 IESG; ab 2027 inkl. + Korridorpension, NOR40280219); UV entfällt (§ 51 Abs 6 ASVG); PV je + hälftig DG+DN in der Bonusphase (§ 51 Abs 7 ASVG, „A 15"; Konto- + gutschrift bleibt ungekürzt — mBGM GP7); FLAF+DZ ohne lit-f-AN ab + Folgemonat nach 60 (§ 41 Abs 4 lit f FLAG — KommSt ohne Befreiung). +- **Beitragsfreie Bezüge § 49 Abs 3 ASVG** (Grenzen `at_sv49_grenzen`, + Stand 2026-07): Mahlzeiten-Gutscheine 8 €/AT (Lebensmittel 2 €/AT), + Mitarbeiterrabatte ≤ 20 % je Fall ODER 1.000 €/Jahr (R1: Jahrespool), + Betriebsveranstaltungen 365 €/Jahr, Jubiläums-Sachzuwendungen 186 €/Jahr, + Zukunftssicherung 300 €/Jahr, Kinderbetreuungszuschuss 2.000 €/Kind/Jahr + (R1: Pool je DN), Homeoffice-Pauschale 3 €/Tag max 100 Tage — als + Jahres-Kumulative je DN mit Umwidmung des Überschusses. +- **LNK (FLAF/KommSt/DZ):** BG = monatlich gezahlte Arbeitslöhne + (laufend + im Monat gezahlte §-67-Bezüge, Zufluss; kein SV-Abzug) + ohne §-67-Abs-3/6-Bezüge; **FLAF 3,7 %** (§ 41 Abs 5 FLAG; 2,7 % ab + 2028), **KommSt 3 %** (§ 9 KommStG), **DZ** nur WKO-Mitglieder + (§§ 122/126 WKG; Landeskammersatz je BL inkl. Bundesanteil 0,12 %, + 2026: 0,31–0,40 %, ⚠ WKO-Verifikation 8.11 offen); Freibetragsstaffel + ident: ≤ 1.095 € keine Abgabe · bis 1.460 € → BG − 1.095 € · darüber + voll. +- **Beitragsmonat (Anspruchsprinzip):** Regel-Feld + `l10n_at_beitragsmonat` (default Abrechnungsmonat) — Sätze, HBG und + GFG werden je Beitragsmonat historisch versioniert aufgelöst + (Nachzahlungslinien); Lohnsteuer rollt kumulativ in den Zuflussmonat + (GP3). mBGM-Nachträge (Storno+Neuanmeldung): GP7. + +## RF-SV-1: Vollmonat November mit B13 (VG II Stufe 1, 2.059,41 €) + +Erweitert GP3-RF-L1 um DG-Seite und LNK; **GP4-Korrektur: §-2a-Staffel +mindert den DN-AV auf 0 %** (2.059,41 ≤ 2.225) — DN-SV 15,12 % statt +pauschal 18,07 %. + +| Posten | Betrag | +|---|---| +| DN-SV laufend (15,12 %: KV 79,70 · PV 211,08 · ALV 0 · AK 10,30 · WF 10,30) | **311,38 €** | +| SV-DN auf B13 (SVDN_SZ, 14,12 % = 290,79) | **290,79 €** | +| DG-SV laufend (20,98 % = 432,04) | **432,04 €** | +| DG-SV auf B13 (20,48 % = 421,77) | **421,77 €** | +| FLAF-DB (3,7 % × 4.118,82) | **152,40 €** | +| KommSt (3 % × 4.118,82) | **123,56 €** | +| DZ (kein WKO-Mitglied) | 0,00 € | + +- LSt-lfd: Bemessung 2.059,41 − 311,38 = 1.748,03 → JBG 20.784,36 → + Tarif 1.450,87 − 496 = 954,87 ÷ 12 → **79,57 €**; LSt-SZ: + (2.059,41 − 290,79 − 620) × 6 % = **68,92 €**. +- Netto = 4.118,82 − (311,38 + 290,79 + 79,57 + 68,92) = + **3.368,16 €** (SVDN_SZ-Nachtrag; GP3 hatte den SV-DN auf der B13 + nicht als Abzugszeile — Netto war um 290,79 € zu hoch). + +## RF-SV-2: Wien (WF-Zuschlag ab 1.1.2026) + +- SVDN_WF/SVDG_WF je 0,75 % (statt 0,50) → DN-SV 18,37 %, DG-SV + 21,23 %; vor 1.1.2026 gilt der Bundesstandard (Satzvektor-Zeilen + `at_sv_wf_vektor`: 2019 default; 2026 wien+default). + +## RF-SV-3: Geringfügige Beschäftigung (500 €/Monat) + +- Hochgerechnetes Entgelt 500 ≤ 551,10 → geringfügig: **keine** + SVDN-/SVDG-Beiträge (KV/PV/AV/AK/WF entfallen); stattdessen + **SVDG_UV_GFG 6,50 €** (§ 53a Abs 1); keine LSt (Tarifzone). + Selbstversicherung KV+PV 83,49 €/Monat zahlt die Person direkt an + den Träger (kein Lohnabzug; SV-Werte-2026 Fn. 9). +- Untermonatiger Beginn 16.1.: 400 € → 750 € hochgerechnet → **nicht** + geringfügig (Vollversicherung); 250 € → 468,75 → geringfügig. + +## RF-SV-4: DAG (§ 1 DAG) + +- 2 Geringfügige à 500/550 € im Jänner: Lohnsumme 1.050 > 826,65 + (Schwelle = 1,5 × 551,10) → DAG = 1.050 × 19,4 % = **204,30 €** + (Company-Bewertung `_l10n_at_dag`; Ausweis GP8). 2 × 400 € = 800 → + unter der Schwelle → keine DAG; nur 1 Geringfügige(r) → keine DAG. + +## RF-SV-5: Altersabhängige Satzausschaltungen + +- **63 + Pensionsanspruch** (`l10n_at_alv_entfall_ab`): ALV DN+DG und + IESG entfallen → DG 17,93 % statt 20,98 %; Nachzahlungslinie mit + Beitragsmonat vor dem Stichtag zahlt weiter ALV (Anspruchsprinzip). +- **60** (`l10n_at_uv_entfall_ab`): UV entfällt laufend und auf SZ + → DG 19,88 %; geringfügige 61-Jährige: keine UV-Pauschale. +- **Bonusphase** (`l10n_at_pv_bonusphase_ab`): PV hälftig → DN 5,125 / + DG 6,275 % (§ 51 Abs 7 ASVG); Pensionskonto-Gutschrift mit voller + BG (mBGM-A 15, GP7). +- **Lit f** (`l10n_at_lnkp_frei_ab`): FLAF und DZ entfallen ab + Folgemonat nach 60; KommSt bleibt. + +## RF-SV-6: § 49 Abs 3 Kumulative + +- Mahlzeiten: 110 € auf 10 Arbeitstagen → 8 € × 10 = 80 € beitragsfrei, + 30 € Umwidmung → SV-BG 2.059,41 + 110 − 80 = 2.089,41. +- Betriebsveranstaltung: März 300 € frei (Rest 65), April wieder 300 € + → nur 65 frei; Mai Pool verbraucht → 0. +- Homeoffice: April 70 Tage à 3 € frei; Mai Rest 30 Tage → 90 € frei. +- Lehrling (§ 61 AKG / § 51 Abs 6 / § 12 Abs 2 IESG): keine AK-, UV-, + IESG-Beiträge; nie geringfügig (§ 5 Abs 3); DN 14,62 % (AV 0 nach + Staffel bei 300 €), DG 19,78 %. + +## RF-SV-7: Beitragsmonats-Allokation (Nachzahlung) + +- Jänner 2026: laufend 7.000 € + Nachzahlungslinie 7.000 € mit + Beitragsmonat 6/2025 → zwei Gruppen mit je eigener HBG: 6.930 € + (2026) und **6.450 €** (2025 = 30 × 215 €/Kalendertag, KB A8); + SVDG_KV = (6.930 + 6.450) × 3,78 %. + +## Offen / Verifikationspunkte (GP7/GP8) + +- **UV-Pauschale 6,50 €** abgeleitet (aBG 591,29 = 83,49 ÷ 14,12 %; + § 53a Abs 1/3) — direkter Beleg (SV-Werte-Tabellenzug) bei der + GP8-Parameterpflege nachziehen. +- **IESG auf SZ** (0,10 %) aus der ÖGK-Tarifsystematik (IE-Spalte auf + allen Beitragsgrundlagen des Tarifs) — §-5-AMPFG/§-12-IESG-Volltext- + Verifikation offen. +- **DZ-Landeskammersätze** (lb-lnk-07, Stand 2026-08) — WKO/WKG-Volltext + vor Freischaltung von Wien-Mandanten (8.11). +- DAG-Zahlungsrhythmus („jährlich", lb-bes-06) aus dem §-1-DAG-Volltext + (RIS) für den GP8-Ausweis; Geringfügigkeits-Bewertung bei Nachzahlung + je Beitragsmonat (Stichtagslogik) als GP8-Verfeinerung. +- Formel 7 (A3): konservative GP3-Verteilung (§-68-Zuschläge verbleiben + in der laufenden SV-BG); Prämienoptimierung offener Designpunkt. \ No newline at end of file diff --git a/addons/l10n_at_hr_payroll_private/models/payslip_private.py b/addons/l10n_at_hr_payroll_private/models/payslip_private.py index 33199fd..619b561 100644 --- a/addons/l10n_at_hr_payroll_private/models/payslip_private.py +++ b/addons/l10n_at_hr_payroll_private/models/payslip_private.py @@ -305,6 +305,78 @@ class HrPayslip(models.Model): monate = {l.slip_id.id for l in linien} return len(monate) >= 7, sum(l.total for l in linien) / 12.0 + # ------------------------------------------------------------------ + # Lohnnebenkosten FLAF-DG-Beitrag / KommSt / DZ (GP4; § 41 FLAG, + # § 5+§ 9 KommStG, §§ 122/126 WKG — Werte/Staffeln im Kern) + # ------------------------------------------------------------------ + def _at_lnk_basis(self, steuer, result_rules=None): + """LNK-Bemessungsgrundlage nach Freibetragsstaffel: monatlich + gezahlte Arbeitslöhne (laufend + im Monat gezahlte §-67-Bezüge, + Zuflussprinzip; kein SV-Abzug), ohne §-67-Abs-3/6-Bezüge + (Abfertigung/Abfindung). 'flaf'/'dz' zusätzlich ohne lit-f-AN + (Arbeitslöhne ab Folgemonat nach Vollendung des 60. Lebensjahres, + § 41 Abs 4 lit f FLAG — die Kommunalsteuer hat KEINE lit-f- + Befreiung). Freibetrag: ≤ 1.095 € keine Abgabe, bis 1.460 € → + Freibetrag 1.095 €, darüber volle BG (at_lnk_freibetrag).""" + self.ensure_one() + basis = (self._l10n_at_brutto('laufend', result_rules) + + self._l10n_at_brutto('sonstig', result_rules)) + for rule in self.struct_id.rule_ids: + if rule.l10n_at_s67_abs in ('abs3', 'abs6'): + if result_rules is not None and rule.code in result_rules: + basis -= result_rules[rule.code]['total'] + elif result_rules is None: + linie = self.line_ids.filtered( + lambda l: l.code == rule.code) + basis -= sum(linie.mapped('total')) + basis = max(basis, 0.0) + if steuer in ('flaf', 'dz'): + version = self.version_id + if (version.l10n_at_lnkp_frei_ab + and self.date_from >= version.l10n_at_lnkp_frei_ab): + return 0.0 + freibetrag = self.env['hr.rule.parameter']._get_parameter_from_code( + 'at_lnk_freibetrag', self.date_to, raise_if_not_found=False) or {} + grenze = float(freibetrag.get('grenze', 0.0)) + if basis <= 0.0: + return 0.0 + if basis <= grenze: + basis -= float(freibetrag.get('freibetrag', 0.0)) + return max(basis, 0.0) + + def _at_flaf_db(self, result_rules=None): + """FLAF-Dienstgeberbeitrag § 41 FLAG (3,7 % ab 2025, 2,7 % ab + 2028 — at_flaf_db; BG s. _at_lnk_basis).""" + self.ensure_one() + basis = self._at_lnk_basis('flaf', result_rules) + satz = float(self.env['hr.rule.parameter']._get_parameter_from_code( + 'at_flaf_db', self.date_to, raise_if_not_found=False) or 0.0) + return basis * satz / 100.0 + + def _at_kommst(self, result_rules=None): + """Kommunalsteuer § 9 KommStG 1993 (3 % — at_kommst_satz; BG + § 5 Abs 2: ohne §-67-Abs-3/6-Bezüge, ohne lit-f-Befreiung).""" + self.ensure_one() + basis = self._at_lnk_basis('kommst', result_rules) + satz = float(self.env['hr.rule.parameter']._get_parameter_from_code( + 'at_kommst_satz', self.date_to, raise_if_not_found=False) or 0.0) + return basis * satz / 100.0 + + def _at_dz(self, result_rules=None): + """Zuschlag zum Dienstgeberbeitrag (§§ 122/126 WKG) für WKO- + Mitgliedsbetriebe: Landeskammersatz je Betriebsstätten-Bundesland + inkl. Bundeskammeranteil (at_dz_vektor; ⚠ WKO-Verifikation 8.11); + BG identisch FLAF-DB inkl. lit f (lb-lnk-07).""" + self.ensure_one() + if not self.company_id.l10n_at_wk_mitglied: + return 0.0 + basis = self._at_lnk_basis('dz', result_rules) + vektor = self.env['hr.rule.parameter']._get_parameter_from_code( + 'at_dz_vektor', self.date_to, raise_if_not_found=False) or {} + satz = float((vektor.get('laender') or {}).get( + self.company_id.l10n_at_bundesland or '', 0.0)) + return basis * satz / 100.0 + # ------------------------------------------------------------------ # All-in-Deckungsrechnung (§ 2g AVRAG) — minimale GP2-Umsetzung: # Jahresvergleich geschuldete ÜStd-Vergütung vs. erhaltene Pauschale diff --git a/addons/l10n_at_hr_payroll_private/tests/__init__.py b/addons/l10n_at_hr_payroll_private/tests/__init__.py index e8ae740..30a83f8 100644 --- a/addons/l10n_at_hr_payroll_private/tests/__init__.py +++ b/addons/l10n_at_hr_payroll_private/tests/__init__.py @@ -1,4 +1,5 @@ # Part of the odoo-at-payroll project. License: LGPL-3. from . import test_kv_katalog from . import test_entgelt_private -from . import test_lst_private \ No newline at end of file +from . import test_lst_private +from . import test_sv_private \ No newline at end of file diff --git a/addons/l10n_at_hr_payroll_private/tests/test_entgelt_private.py b/addons/l10n_at_hr_payroll_private/tests/test_entgelt_private.py index 82f846e..c566e56 100644 --- a/addons/l10n_at_hr_payroll_private/tests/test_entgelt_private.py +++ b/addons/l10n_at_hr_payroll_private/tests/test_entgelt_private.py @@ -369,8 +369,8 @@ class EntgeltPrivateTest(TransactionCase): # Netto / All-in # ------------------------------------------------------------------ def test_20_netto_mit_dn_abzuegen(self): - """GP3: Netto = ATP_BRUTTO − ATP_ABZUG — SVDN + LSt laufen - real mit (AP3-Kern angebunden).""" + """GP3/GP4: Netto = ATP_BRUTTO − ATP_ABZUG — SVDN (inkl. SVDN_SZ + auf der B13/B14, § 67 Abs 12) + LSt laufen real mit.""" employee = self._employee(stufe=1) slip = self._compute(employee, 11) brutto = sum( @@ -383,9 +383,9 @@ class EntgeltPrivateTest(TransactionCase): abzug = sum( self._total(slip, code) for code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV', 'SVDN_AK', - 'SVDN_WF', 'LST_LFD', 'LST_SZ')) + 'SVDN_WF', 'SVDN_SZ', 'LST_LFD', 'LST_SZ')) self.assertAlmostEqual( - self._total(slip, 'NETTO'), brutto - abzug, places=2) + self._total(slip, 'NETTO'), brutto - abzug, places=1) def test_21_allin_deckungsrechnung(self): """§ 2g AVRAG: Jahresdeckung = 12 × Pauschale vs. geschuldete diff --git a/addons/l10n_at_hr_payroll_private/tests/test_lst_private.py b/addons/l10n_at_hr_payroll_private/tests/test_lst_private.py index 3b4c0ea..51df5da 100644 --- a/addons/l10n_at_hr_payroll_private/tests/test_lst_private.py +++ b/addons/l10n_at_hr_payroll_private/tests/test_lst_private.py @@ -88,32 +88,35 @@ class LstPrivateTest(TransactionCase): self._total(slip, 'SVDN_KV'), 2059.41 * 0.0387, places=2) self.assertAlmostEqual( self._total(slip, 'SVDN_PV'), 2059.41 * 0.1025, places=2) + # § 2a AMPFG: DN-AV 0 % bei BG 2.059,41 ≤ 2.225 (GP4-Staffel; + # DG-AV bleibt 2,95 %) → DN-Gesamt 15,12 % self.assertAlmostEqual( - self._total(slip, 'SVDN_AV'), 2059.41 * 0.0295, places=2) + self._total(slip, 'SVDN_AV'), 0.0, places=2) self.assertAlmostEqual( self._total(slip, 'SVDN_AK'), 2059.41 * 0.005, places=2) self.assertAlmostEqual( self._total(slip, 'SVDN_WF'), 2059.41 * 0.005, places=2) self.assertAlmostEqual(self._total(slip, 'B13'), 2059.41, places=2) - # LSt-lfd: Bemessung 1686,50 → JBG 20.046,06 → (1301,41 − 496)/12 - bem = 2059.41 - 2059.41 * 0.1807 + # LSt-lfd: Bemessung 1.748,03 → JBG 20.784,36 → (1.450,87 − 496)/12 + bem = 2059.41 - 2059.41 * 0.1512 jbg = bem * 12 - 132 - 60 erwartet_lfd = (max(0.0, 0.20 * (jbg - 13539.0) - 496.0)) / 12 self.assertAlmostEqual( self._total(slip, 'LST_LFD'), erwartet_lfd, places=1) - # LSt-SZ (§ 67): 2059,41 − SV-SZ (17,07 %) − 620 → 6 % - sz_bem = 2059.41 - 2059.41 * 0.1707 + # LSt-SZ (§ 67): 2.059,41 − SV-SZ (14,12 %: KV+PV, AV 0) − 620 → 6 % + sz_bem = 2059.41 - 2059.41 * 0.1412 erwartet_sz = (sz_bem - 620.0) * 0.06 self.assertAlmostEqual( self._total(slip, 'LST_SZ'), erwartet_sz, places=1) - # Netto-Konsistenz: ATP_BRUTTO − ATP_ABZUG + # Netto-Konsistenz: ATP_BRUTTO − ATP_ABZUG (inkl. SVDN_SZ auf + # der B13 — § 67 Abs 12, GP4-Nachtrag) brutto = (self._total(slip, 'KVGEO') + self._total(slip, 'B13')) abzug = sum( self._total(slip, code) for code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV', 'SVDN_AK', 'SVDN_WF', - 'LST_LFD', 'LST_SZ')) + 'SVDN_SZ', 'LST_LFD', 'LST_SZ')) self.assertAlmostEqual( - self._total(slip, 'NETTO'), brutto - abzug, places=2) + self._total(slip, 'NETTO'), brutto - abzug, places=1) def test_02_rollung_zweiter_monat(self): """Kumulative §-66-Rollung: Februar zahlt die Differenz.""" @@ -131,7 +134,7 @@ class LstPrivateTest(TransactionCase): self._slip(employee, 10, confirm=True) slip_nov = self._slip(employee, 11, confirm=True) sz_nov = self._total(slip_nov, 'LST_SZ') - sz_bem = 2059.41 - 2059.41 * 0.1707 + sz_bem = 2059.41 - 2059.41 * 0.1412 self.assertAlmostEqual(sz_nov, (sz_bem - 620.0) * 0.06, places=1) slip_dez = self._slip(employee, 12) self.assertAlmostEqual( @@ -183,6 +186,8 @@ class LstPrivateTest(TransactionCase): self.assertAlmostEqual(self._total(slip, 'B13'), 2199.15, places=2) # B14-Dezember-Fallback (kein Urlaub im Jahr) — läuft mit self.assertAlmostEqual(self._total(slip, 'B14'), 2199.15, places=2) - sz_bem = 2 * 2199.15 - 2 * 2199.15 * 0.1707 + # B14-Dezember-Fallback (kein Urlaub im Jahr) — läuft mit; + # 2.199,15 ≤ 2.225 → DN-AV 0 (§ 2a) → SV-SZ 14,12 % + sz_bem = 2 * 2199.15 - 2 * 2199.15 * 0.1412 self.assertAlmostEqual( self._total(slip, 'LST_SZ'), (sz_bem - 620.0) * 0.06, places=1) \ No newline at end of file diff --git a/addons/l10n_at_hr_payroll_private/tests/test_sv_private.py b/addons/l10n_at_hr_payroll_private/tests/test_sv_private.py new file mode 100644 index 0000000..c6bdd1f --- /dev/null +++ b/addons/l10n_at_hr_payroll_private/tests/test_sv_private.py @@ -0,0 +1,245 @@ +# Part of the odoo-at-payroll project. License: LGPL-3. +"""GP4 SV/DB-Integration auf der privatwirtschaftlichen Structure: +DG-Beitragsblock (SVDG_*, SVDG_SZ_*, §-53a-UV), SVDN_SZ-Nachtrag +(§ 67 Abs 12 — Netto-Korrektur), FLAF-Dienstgeberbeitrag (§ 41 FLAG), +Kommunalsteuer (§ 9 KommStG) und Zuschlag zum DG-Beitrag (§§ 122/126 +WKG) mit Freibetragsstaffel 1.095/1.460 € und lit-f-Befreiung.""" +from datetime import date, timedelta + +from odoo.tests import TransactionCase, tagged + + +@tagged('post_install', '-at_install') +class SvPrivateTest(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 SV 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-SV-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')) + + # Hilfsstruktur für LNK-Staffel-/Ausnahmetests (Fixbeträge statt + # KV-Katalog, um die Freibetragszonen gezielt zu erreichen). + def _lnk_struktur(self, code, regeln): + struktur = self.env['hr.payroll.structure'].create({ + 'name': 'Test AT LNK %s' % code, + 'code': 'TEST_AT_LNK_%s' % code, + 'type_id': self.structure.type_id.id, + 'country_id': self.env.ref('base.at').id, + }) + category = self.env.ref( + 'l10n_at_hr_payroll_private.category_atp_brutto') + for regel_code, betrag, bemessung, s67 in regeln: + self.env['hr.salary.rule'].create({ + 'name': regel_code, + 'code': regel_code, + 'struct_id': struktur.id, + 'category_id': category.id, + 'condition_select': 'none', + 'l10n_at_bemessung': bemessung, + 'l10n_at_s67_abs': s67, + 'amount_select': 'fix', + 'amount_fix': betrag, + }) + return struktur + + # ------------------------------------------------------------------ + # RF-L1 erweitert: DG-Block + LNK auf dem November-Lauf mit B13 + # ------------------------------------------------------------------ + def test_01_dg_block_und_lnk(self): + """November mit B13: SVDG 20,98 % + SVDG_SZ 20,48 % (ATP_DG, + ohne Nettoeffekt); FLAF 3,7 % und KommSt 3 % auf der LNK-BG + (laufend + gezahlte SZ, > 1.460 € → volle BG).""" + employee = self._employee() + slip = self._slip(employee, 11) + self.assertAlmostEqual(self._total(slip, 'KVGEO'), 2059.41, places=2) + self.assertAlmostEqual(self._total(slip, 'B13'), 2059.41, places=2) + # DG laufend (ÖGK 2026: 20,98 %) + for code, satz in (('SVDG_KV', 0.0378), ('SVDG_PV', 0.1255), + ('SVDG_AV', 0.0295), ('SVDG_UV', 0.011), + ('SVDG_WF', 0.005), ('SVDG_IESG', 0.001)): + self.assertAlmostEqual( + self._total(slip, code), 2059.41 * satz, places=2) + # DG auf der B13 (§ 54 ASVG: KV/PV/AV/UV + IESG) + for code, satz in (('SVDG_SZ_KV', 0.0378), ('SVDG_SZ_PV', 0.1255), + ('SVDG_SZ_AV', 0.0295), ('SVDG_SZ_UV', 0.011), + ('SVDG_SZ_IESG', 0.001)): + self.assertAlmostEqual( + self._total(slip, code), 2059.41 * satz, places=2) + # LNK: BG = 2.059,41 + 2.059,41 = 4.118,82 > 1.460 → voll + flaf = 2 * 2059.41 * 0.037 + kommst = 2 * 2059.41 * 0.03 + self.assertAlmostEqual(self._total(slip, 'FLAFDB'), flaf, places=2) + self.assertAlmostEqual(self._total(slip, 'KOMMST'), kommst, places=2) + # DZ nur für WKO-Mitglieder (Default aus) + self.assertAlmostEqual(self._total(slip, 'DZ'), 0.0, places=2) + + def test_02_netto_mit_svdn_sz(self): + """SVDN_SZ-Nachtrag (§ 67 Abs 12): die SV-DN auf der B13 ist + eigene Abzugszeile — §-2a-Staffel: SV-SZ 14,12 % (AV-DN 0) + = 290,79 € statt der GP3-Pauschale 18,07 %.""" + employee = self._employee() + slip = self._slip(employee, 11) + self.assertAlmostEqual( + self._total(slip, 'SVDN_SZ'), 2059.41 * 0.1412, places=2) + brutto = (self._total(slip, 'KVGEO') + self._total(slip, 'B13')) + 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'), brutto - abzug, places=1) + + def test_03_wien_dg(self): + """Wien: SVDG_WF 0,75 % (Satzvektor, Wiener WF-Beitragstarif + 2018 § 1; DG-Gesamt 21,23 %).""" + employee = self._employee() + slip = self._slip(employee, 1) + slip.company_id.write({'l10n_at_wf_wien': True}) + slip.compute_sheet() + self.assertAlmostEqual( + self._total(slip, 'SVDG_WF'), 2059.41 * 0.0075, places=2) + + # ------------------------------------------------------------------ + # LNK-Mechanik: Freibetragsstaffel, §-67-Abs-3/6, lit f + # ------------------------------------------------------------------ + def test_04_lnk_freibetragsstaffel(self): + """Staffel § 41 Abs 4 FLAG (wortgleich § 9 KommStG): ≤ 1.095 € + keine Abgabe · 1.095–1.460 € → Freibetrag 1.095 € · darüber voll.""" + employee = self._employee() + struktur = self._lnk_struktur('A', [('LFD', 1000.0, 'laufend', 'keine')]) + slip_a = self._slip(employee, 1, struct=struktur) + self.assertAlmostEqual(slip_a._at_lnk_basis('flaf'), 0.0, places=2) + struktur_b = self._lnk_struktur( + 'B', [('LFD', 1000.0, 'laufend', 'keine'), + ('SZ', 300.0, 'sonstig', 'keine')]) + employee_b = self._employee() + slip_b = self._slip(employee_b, 2, struct=struktur_b) + # 1.300 € → Freibetrag: 1.300 − 1.095 = 205 € + self.assertAlmostEqual(slip_b._at_lnk_basis('kommst'), 205.0, places=2) + self.assertAlmostEqual( + slip_b._at_flaf_db(), 205.0 * 0.037, places=2) + self.assertAlmostEqual(slip_b._at_kommst(), 205.0 * 0.03, places=2) + struktur_c = self._lnk_struktur( + 'C', [('LFD', 5000.0, 'laufend', 'keine')]) + employee_c = self._employee() + slip_c = self._slip(employee_c, 3, struct=struktur_c) + self.assertAlmostEqual(slip_c._at_lnk_basis('flaf'), 5000.0, places=2) + + def test_05_lnk_s67_abs3(self): + """Abfertigungen (§ 67 Abs 3 EStG) und Abfindungen (Abs 6) sind + aus der FLAF-/KommSt-BG ausgenommen (§ 41 Abs 4 FLAG, § 5 Abs 2 + KommStG).""" + employee = self._employee() + struktur = self._lnk_struktur( + 'D', [('LFD', 2000.0, 'laufend', 'keine'), + ('ABF', 10000.0, 'sonstig', 'abs3')]) + slip = self._slip(employee, 1, struct=struktur) + self.assertAlmostEqual(slip._at_lnk_basis('flaf'), 2000.0, places=2) + self.assertAlmostEqual(slip._at_lnk_basis('kommst'), 2000.0, places=2) + + def test_06_lnk_lit_f(self): + """§ 41 Abs 4 lit f FLAG: Arbeitslöhne ab dem Folgemonat nach + Vollendung des 60. LJ entfallen aus FLAF- und DZ-BG — die + Kommunalsteuer bleibt pflichtig.""" + employee = self._employee( + {'l10n_at_lnkp_frei_ab': date(2026, 1, 1)}) + struktur = self._lnk_struktur( + 'E', [('LFD', 2000.0, 'laufend', 'keine')]) + slip = self._slip(employee, 1, struct=struktur) + self.assertAlmostEqual(slip._at_lnk_basis('flaf'), 0.0, places=2) + self.assertAlmostEqual(slip._at_lnk_basis('dz'), 0.0, places=2) + self.assertAlmostEqual( + slip._at_lnk_basis('kommst'), 2000.0, places=2) + + def test_07_dz_wko(self): + """DZ (§§ 122/126 WKG): 2026 Landeskammersatz inkl. Bundesanteil + je Betriebsstätten-Bundesland (Wien 0,36 %; lb-lnk-07).""" + employee = self._employee() + slip = self._slip(employee, 1) # Jänner: nur laufender Bezug + slip.company_id.write({ + 'l10n_at_wk_mitglied': True, 'l10n_at_bundesland': 'wien'}) + slip.compute_sheet() + self.assertAlmostEqual( + self._total(slip, 'DZ'), 2059.41 * 0.0036, places=2) + # Ohne WKO-Mitgliedschaft entfällt der DZ vollständig + slip.company_id.write({'l10n_at_wk_mitglied': False}) + slip.compute_sheet() + self.assertAlmostEqual(self._total(slip, 'DZ'), 0.0, places=2) + + # ------------------------------------------------------------------ + # Geringfügiger AN end-to-end (§ 53a Abs 1 ASVG) + # ------------------------------------------------------------------ + def test_08_geringfuegig_ende_zu_ende(self): + """Geringfügig Beschäftigter (Überzahlung 500 € ≤ GFG): keine + SVDN/SVDG-Zeilen, nur §-53a-UV 6,50 € (ATP_DG); kein SV-Abzug + und keine LSt → Netto = Brutto.""" + employee = self._employee({'ueberzahlung': 500.0}) + slip = self._slip(employee, 1) + self.assertAlmostEqual(self._total(slip, 'KVGEO'), 500.0, places=2) + self.assertTrue(slip.l10n_at_geringfuegig) + for code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV', 'SVDN_AK', + 'SVDN_WF', 'SVDG_KV', 'SVDG_PV', 'SVDG_AV', + 'SVDG_UV', 'SVDG_WF', 'SVDG_IESG'): + self.assertAlmostEqual(self._total(slip, code), 0.0, places=2) + self.assertAlmostEqual( + self._total(slip, 'SVDG_UV_GFG'), 6.50, places=2) + # LSt: Bemessung 500 − 0 → Tarif unterhalb der Belastungszone + self.assertAlmostEqual(self._total(slip, 'LST_LFD'), 0.0, places=1) + self.assertAlmostEqual( + self._total(slip, 'NETTO'), 500.0, places=2) \ No newline at end of file