mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
[IMP] l10n_at_hr_payroll: GP4 SV/DB-Kern (DG-Beiträge, Geringfügigkeit, § 49, Beitragsmonat, DAG)
AP4-Kernbau (D3-Vorfinanzierung, freigegebener GP4-Bauplan): Die Dienstgeberseite der Sozialversicherung wird sektorneutral im Kern gebaut, damit sowohl das General-AT-Produkt als auch die GemBG-Schiene sie konsumieren (keine Fork-Werte, payroll-Skill-Architekturregel). Neu in models/sozialversicherung.py: - SVDG laufend (ÖGK Normalbetriebe 2026: 20,98 %; Wien 21,23 %) und auf Sonderzahlungen (§ 54 ASVG: 20,48 % inkl. UV 1,10 und IESG 0,10); - Geringfügigkeit § 5 Abs 2 ASVG mit Hochrechnung bei untermonatiger Beschäftigung (SZ zählen nicht) und §-53a-Abs-1-UV als Monatspauschale; - beitragsfreie Bezüge § 49 Abs 3 ASVG als Jahres-Kumulative je DN (8 €/AT, 1.000/365/186/300/2.000 €/J, Homeoffice 3 €/Tag × 100); - altersabhängige Satzausschaltungen als Stichtags-Datumfelder am Vertrag: AlVG § 1 Abs 2 lit e (AV DN+DG und IESG), ASVG § 51 Abs 6 (UV ab 60/Lehrlinge), § 51 Abs 7 (PV in der Bonusphase je hälftig DG+DN), FLAG § 41 Abs 4 lit f (LNK-Freiheit ab 60); - ALV-DN-Staffel § 2a AMPFG, WF-Satzvektor Bundesland × Jahr (Wien 0,75/0,75 ab 1.1.2026, löst die KB-Protokollnotiz), Beitragsmonats- Allokation (Anspruchsprinzip: Sätze/HBG historisch je Gruppe) und DAG-Bewertung § 1 DAG (19,4 %, Schwelle 1,5 × GFG, ≥ 2 Geringfügige). Quellenverifikation (verbindlich): ÖGK-Beitragstarifsystem gültig ab 01.07.2026, TASY-LSWH-Export 2026-03-24, Beitragsrechtliche Werte 2026 und RIS-Normtexte. GFG 2026 = 551,10 € (Aufwertung ausgesetzt gem. § 810 Abs 3 ASVG — löst KB-Konflikt A7-Rest); §-19a-Selbstversicherung 2026 = 83,49 €/Monat, DG-UV daraus abgeleitet 6,50 €/Monat. Rechtskorrektur § 2a AMPFG: Die Staffel mindert nur den DN-Anteil (ÖGK-Abschläge 'Mind. AV auf 0/1/2 %') und gilt auch für SZ-Beiträge; bei 2.059,41 € BG sinkt DN-SV von pauschal 18,07 % auf 15,12 %. test_lohnsteuer wurde entsprechend angepasst (Rechenfälle RF-L1 ff.). Tests: test_sozialversicherung (14 Fälle); Suite 74/74 grün auf odoo_dev. Rechenfälle: l10n_at_hr_payroll_private/docs/rechenfaelle/ sozialversicherung.md.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
from . import tasy
|
||||
from . import lohnsteuer
|
||||
from . import lohnsteuer
|
||||
from . import sozialversicherung
|
||||
from . import sozialversicherung
|
||||
@@ -198,29 +198,15 @@ class HrPayslip(models.Model):
|
||||
return ergebnis
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# DN-SV (Bemessungsbestandteil § 62; Werte at_sv_* — GP4 baut
|
||||
# DG-Seite/Geringfügigkeit/beitragsfreie Kumulative darauf)
|
||||
# DN-SV (Bemessungsbestandteil § 62) — Basis/Sätze/Geringfügigkeit/
|
||||
# §-49-Kumulative/DG-Seite: models/sozialversicherung.py (GP4)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_svdn_basis_laufen(self, result_rules=None):
|
||||
"""SV-Beitragsgrundlage laufend (HBG-capped, at_sv_*)."""
|
||||
self.ensure_one()
|
||||
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich'))
|
||||
return min(max(self._l10n_at_brutto('laufend', result_rules), 0.0), hbg)
|
||||
|
||||
def _l10n_at_svdn(self, komponente, result_rules=None):
|
||||
"""DN-SV-Komponente auf laufender Basis: kv/pv/av/ak/wf —
|
||||
AK § 61 AKG und WF § 62 Z 5 nur laufend (nicht auf §-67-Bezüge);
|
||||
WF-Wien über Company-Flag (DN 0,75 statt 0,50)."""
|
||||
Wien-WF über Satzvektor (l10n_at_wf_wien/Bundesland, GP4)."""
|
||||
self.ensure_one()
|
||||
basis = self._l10n_at_svdn_basis_laufen(result_rules)
|
||||
codes = {'kv': 'at_sv_kv_dn', 'pv': 'at_sv_pv_dn',
|
||||
'av': 'at_sv_av_dn', 'ak': 'at_sv_ak_dn'}
|
||||
if komponente == 'wf':
|
||||
satz = 0.75 if self.company_id.l10n_at_wf_wien \
|
||||
else float(self._l10n_at_param('at_sv_wf_dn'))
|
||||
else:
|
||||
satz = float(self._l10n_at_param(codes[komponente]))
|
||||
return basis * satz / 100.0
|
||||
return self._l10n_at_sv_beitrag('dn', komponente, result_rules)
|
||||
|
||||
def _l10n_at_svdn_gesamt(self, result_rules=None):
|
||||
self.ensure_one()
|
||||
@@ -240,11 +226,15 @@ class HrPayslip(models.Model):
|
||||
|
||||
def _l10n_at_svdn_sz(self, result_rules=None):
|
||||
"""SV-DN auf sonstigen Bezügen (§ 67 Abs 12 — vor Fixsätzen
|
||||
abzuziehen): KV/PV/AV, ohne AK/WF (§ 62 Z 3/Z 5)."""
|
||||
abzuziehen): KV/PV/AV, ohne AK/WF (§ 62 Z 3/Z 5); Satzausschal-
|
||||
tungen/Steffel über _l10n_at_sv_satz (GP4)."""
|
||||
self.ensure_one()
|
||||
if self._l10n_at_geringfuegig(result_rules):
|
||||
return 0.0
|
||||
basis = self._l10n_at_svdn_basis_sz(result_rules)
|
||||
satz = sum(float(self._l10n_at_param(code)) for code in
|
||||
('at_sv_kv_dn', 'at_sv_pv_dn', 'at_sv_av_dn'))
|
||||
satz = sum(self._l10n_at_sv_satz(
|
||||
'dn', k, staffel_basis=self._l10n_at_sv_basis_roh(result_rules))
|
||||
for k in ('kv', 'pv', 'av'))
|
||||
return basis * satz / 100.0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""Sozialversicherungs-Kern (GP4/AP4): DG-Beiträge (SVDG), Geringfügigkeit
|
||||
(§ 5 Abs 2 ASVG iVm § 810 Abs 3 — 2026-Aufwertung ausgestellt), §-53a-
|
||||
UV der Geringfügigen, beitragsfreie Bezüge nach § 49 Abs 3 ASVG mit
|
||||
Jahres-Kumulativen, altersabhängige Satzausschaltungen (§ 1 Abs 2 lit e
|
||||
AlVG, § 51 Abs 6/7 ASVG, § 12 Abs 2 IESG), WF-Satzvektor Bundesland ×
|
||||
Jahr (Wien 1,5 % ab 1.1.2026), ALV-DN-Staffel (§ 2a AMPFG) und
|
||||
Beitragsmonats-Allokation (Anspruchsprinzip, historisch versionierte
|
||||
Sätze/HBG je Beitragsmonat).
|
||||
|
||||
Verifizierte Grundlagen (2026-09-11, s. RECHTSQUELLEN-Privat Abschn. 4
|
||||
und docs/rechenfaelle/sozialversicherung.md):
|
||||
- ÖGK-Beitragstarifsystem „Normalbetriebe Allgemeiner Teil", gültig ab
|
||||
01.07.2026 (DN 18,07 % · DG 20,98 % · Wien DN 18,32/DG 21,23 %);
|
||||
- TASY-LSWH-Export 2026-03-24 (GFG 551,10 · DAG-Grenze 826,65 ·
|
||||
§-19a-Pauschalbeitrag 83,49 €/Monat — Primärquellen-Entscheidung);
|
||||
- „Beitragsrechtliche Werte 2026" (sozialversicherung.at): GFG-Aufwertung
|
||||
2026 ausgesetzt (§ 810 Abs 3 ASVG); AV-Staffel bis 2.225/2.427/2.630 €
|
||||
0/1/2 % DN; WF Wien 0,75/0,75 ab 1.1.2026;
|
||||
- ASVG §§ 51 Abs 6 (UV-Entfall 60. LJ/Lehrlinge), 51 Abs 7 (PV in der
|
||||
Bonusphase hälftig, DG und DN), 53a Abs 1/3/5 (UV 1,1 % der allgemeinen
|
||||
Beitragsgrundlage; Pauschalbeitrag 14,12 %, auch von SZ);
|
||||
IESG § 12 Abs 1 Z 4 iVm Abs 2 (Entfall ab Folgemonat nach 63);
|
||||
AlVG § 1 Abs 2 lit e (AV-Entfall, ab 2027 inkl. Korridorpension).
|
||||
|
||||
Laufzeit-Datenfluss wie GP3: Regeln reichen Zwischensummen über
|
||||
``result_rules`` an die Compute-Methoden; abgeschlossene Läufe werden
|
||||
über line_ids gelesen. Der Beitragsmonat wird je Regel geführt
|
||||
(``l10n_at_beitragsmonat``) und auf die Lohnkontolinie durchgereicht.
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
class HrSalaryRule(models.Model):
|
||||
_inherit = 'hr.salary.rule'
|
||||
|
||||
l10n_at_sv49_frei = fields.Selection(
|
||||
[('keine', 'Keine §-49-Abs-3-Befreiung'),
|
||||
('mahlzeit', 'Mahlzeiten-Gutschein (§ 49 Abs 3 Z 12; 8 €/AT)'),
|
||||
('rabatt', 'Mitarbeiterrabatt (§ 49 Abs 3 Z 29; 1.000 €/J)'),
|
||||
('betriebsveranstaltung', 'Betriebsveranstaltung (Z 17; 365 €/J)'),
|
||||
('jubilaeum', 'Jubiläums-Sachzuwendung (Z 17; 186 €/J)'),
|
||||
('zukunftssicherung', 'Zukunftssicherung (Z 18 lit a; 300 €/J)'),
|
||||
('kinderbetreuung', 'Kinderbetreuungszuschuss (Z 11 lit d; 2.000 €/Kind/J)'),
|
||||
('homeoffice', 'Homeoffice-Pauschale (Z 31; 3 €/Tag, max 100 Tage)')],
|
||||
string='AT § 49 Abs 3 (beitragsfrei)',
|
||||
default='keine',
|
||||
help='Beitragsfreie Bezugsbestandteile: der beitragsfreie Anteil '
|
||||
'wird je Bezugsmonat von der SV-Beitragsgrundlage abgezogen; '
|
||||
'Grenzen als Jahres-Kumulative je Dienstnehmer '
|
||||
'(at_sv49_grenzen). Überschreiten = beitragspflichtiger '
|
||||
'Sachbezug (Umwidmung). Menge = Arbeitstage (Z 12/Z 31).')
|
||||
l10n_at_s67_abs = fields.Selection(
|
||||
[('keine', 'Keine §-67-Abs-3/6-Begünstigung'),
|
||||
('abs3', 'Abfertigung (§ 67 Abs 3 EStG)'),
|
||||
('abs6', 'Abfindung (§ 67 Abs 6 EStG)')],
|
||||
string='AT § 67 Abs 3/6',
|
||||
default='keine',
|
||||
help='Markiert Bezüge, die beim FLAF-Dienstgeberbeitrag (§ 41 '
|
||||
'Abs 4 FLAG), Zuschlag zum DG-Beitrag (WKG) und der '
|
||||
'Kommunalsteuer (§ 5 Abs 2 KommStG) aus der Bemessungsgrund'
|
||||
'lage ausgenommen sind.')
|
||||
l10n_at_beitragsmonat = fields.Date(
|
||||
string='AT Beitragsmonat (Anspruchsprinzip)',
|
||||
help='Monat, in dem der Anspruch auf diesen Bezug fällig wurde '
|
||||
'(§ 17 ASVG). Leer = Abrechnungsmonat. Für Nachzahlungs-/'
|
||||
'Korrekturlinien setzen: SV-Sätze, HBG und GFG werden '
|
||||
'historisch versioniert je Beitragsmonat aufgelöst '
|
||||
'(Lohnsteuer rollt kumulativ in den Zuflussmonat, GP3).')
|
||||
|
||||
|
||||
class HrPayslipLine(models.Model):
|
||||
_inherit = 'hr.payslip.line'
|
||||
|
||||
l10n_at_beitragsmonat = fields.Date(
|
||||
string='AT Beitragsmonat',
|
||||
compute='_compute_l10n_at_beitragsmonat',
|
||||
store=True,
|
||||
readonly=False,
|
||||
index=True,
|
||||
help='Beitragsmonat der Linie (Anspruchsprinzip); default '
|
||||
'Abrechnungsmonat, über die Regel übersteuerbar.')
|
||||
|
||||
@api.depends('salary_rule_id.l10n_at_beitragsmonat', 'date_from')
|
||||
def _compute_l10n_at_beitragsmonat(self):
|
||||
for line in self:
|
||||
line.l10n_at_beitragsmonat = (
|
||||
line.salary_rule_id.l10n_at_beitragsmonat or line.date_from)
|
||||
|
||||
|
||||
class HrVersion(models.Model):
|
||||
_inherit = 'hr.version'
|
||||
|
||||
l10n_at_lehrling = fields.Boolean(
|
||||
string='AT Lehrling',
|
||||
help='Keine AK-Umlage (§ 61 AKG) und kein UV-Beitrag (§ 51 Abs 6 '
|
||||
'ASVG — aus Mitteln der Unfallversicherung); GFG gilt nicht '
|
||||
'(§ 5 Abs 3 ASVG).')
|
||||
l10n_at_alv_entfall_ab = fields.Date(
|
||||
string='AT ALV-Entfall ab (§ 1 Abs 2 lit e AlVG)',
|
||||
help='AV-Beitrag DN+DG und IESG-Zuschlag entfallen ab diesem '
|
||||
'Beitragsmonat (63. Lebensjahr oder erfüllte Alterspensions-'
|
||||
'Anwartschaft; ab 1.1.2027 inkl. Korridorpension, '
|
||||
'NOR40280219; § 12 Abs 2 IESG). Stichtagsregel am Vertrag.')
|
||||
l10n_at_uv_entfall_ab = fields.Date(
|
||||
string='AT UV-Entfall ab (§ 51 Abs 6 ASVG)',
|
||||
help='Unfallversicherungsbeitrag entfällt ab diesem '
|
||||
'Beitragsmonat (Vollendung des 60. Lebensjahres); die '
|
||||
'Person bleibt unfallversichert.')
|
||||
l10n_at_pv_bonusphase_ab = fields.Date(
|
||||
string='AT PV-Bonusphase ab (§ 51 Abs 7 ASVG)',
|
||||
help='Pensionsversicherungsbeitrag je zur Hälfte reduziert für '
|
||||
'DG und DN (Bonusphase/Pensionsanspruch; ÖGK-Abschlag '
|
||||
'A 15; Pensionskonto-Gutschrift bleibt ungekürzt — Auswertung '
|
||||
'GP8).')
|
||||
l10n_at_lnkp_frei_ab = fields.Date(
|
||||
string='AT LNK-frei ab (§ 41 Abs 4 lit f FLAG)',
|
||||
help='Arbeitslöhne ab diesem Kalendermonat sind vom FLAF-'
|
||||
'Dienstgeberbeitrag und vom Zuschlag zum Dienstgeberbeitrag '
|
||||
'(WKG) ausgenommen (Folgemonat nach Vollendung des 60. '
|
||||
'Lebensjahres). Kommunalsteuer: keine Befreiung.')
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
l10n_at_bundesland = fields.Selection(
|
||||
[('burgenland', 'Burgenland'), ('kaernten', 'Kärnten'),
|
||||
('niederoesterreich', 'Niederösterreich'),
|
||||
('oberoesterreich', 'Oberösterreich'), ('salzburg', 'Salzburg'),
|
||||
('steiermark', 'Steiermark'), ('tirol', 'Tirol'),
|
||||
('vorarlberg', 'Vorarlberg'), ('wien', 'Wien')],
|
||||
string='AT Bundesland (Betriebsstätte)',
|
||||
help='Betriebsstätten-Bundesland für Bundesland-Satzvektoren '
|
||||
'(z. B. DZ-Landeskammeranteil; WF-Wien zusätzlich über das '
|
||||
'Company-Flag l10n_at_wf_wien steuerbar).')
|
||||
l10n_at_wk_mitglied = fields.Boolean(
|
||||
string='AT WKO-Mitgliedschaft (DZ-pflichtig)',
|
||||
help='Zuschlag zum Dienstgeberbeitrag (§§ 122/126 WKG) nur für '
|
||||
'Mitglieder der Wirtschaftskammerorganisation (§ 2 WKG).')
|
||||
|
||||
def _l10n_at_dag(self, monat_start):
|
||||
"""Dienstgeberabgabe geringfügig Beschäftigter (§ 1 DAG) für den
|
||||
Kalendermonat: 19,4 % der Lohnsumme (Beitragsgrundlagen ohne SZ)
|
||||
aller geringfügig Beschäftigten, wenn die Lohnsumme das 1,5-fache
|
||||
der monatlichen GFG überschreitet und mindestens zwei
|
||||
Geringfügige beschäftigt sind (lb-bes-06; TASY MINGRDAGP1 2026:
|
||||
826,65 €; Ausweis/Veranlagung GP8)."""
|
||||
monat_ende = (monat_start + timedelta(days=32)).replace(day=1) \
|
||||
- timedelta(days=1)
|
||||
slips = self.env['hr.payslip'].search([
|
||||
('company_id', '=', self.id),
|
||||
('date_from', '<=', monat_ende),
|
||||
('date_to', '>=', monat_start),
|
||||
('state', 'not in', ('draft', 'cancel')),
|
||||
('l10n_at_geringfuegig', '=', True),
|
||||
])
|
||||
lohnsumme = sum(
|
||||
slip._l10n_at_sv_basis_roh() for slip in slips)
|
||||
anzahl = len({s.employee_id.id for s in slips})
|
||||
params = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_dag', monat_start, raise_if_not_found=False) or {}
|
||||
gfg = float(self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_sv_gfg_monatlich', monat_start,
|
||||
raise_if_not_found=False) or 0.0)
|
||||
schwelle = float(params.get('schwelle_faktor', 0.0)) * gfg
|
||||
abgabe = 0.0
|
||||
if anzahl >= 2 and lohnsumme > schwelle:
|
||||
abgabe = lohnsumme * float(params.get('satz', 0.0)) / 100.0
|
||||
return {'anzahl': anzahl, 'lohnsumme': lohnsumme,
|
||||
'schwelle': schwelle, 'abgabe': abgabe}
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
l10n_at_geringfuegig = fields.Boolean(
|
||||
string='AT Geringfügig beschäftigt',
|
||||
compute='_compute_l10n_at_geringfuegig',
|
||||
store=True,
|
||||
help='Laufendes Entgelt (§-49-bereinigt, hochgerechnet bei '
|
||||
'untermonatiger Beschäftigung) überschreitet die GFG nicht '
|
||||
'(§ 5 Abs 2 ASVG). DAG-Monitoring-Grundlage (§ 1 DAG).')
|
||||
|
||||
@api.depends('line_ids', 'date_from', 'date_to', 'version_id')
|
||||
def _compute_l10n_at_geringfuegig(self):
|
||||
for slip in self:
|
||||
try:
|
||||
slip.l10n_at_geringfuegig = slip._l10n_at_geringfuegig()
|
||||
except Exception:
|
||||
# Alter Lauf ohne GFG-Parameterzeile: keine Bewertung.
|
||||
slip.l10n_at_geringfuegig = False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Geringfügigkeit (§ 5 Abs 2 ASVG; SZ zählen nicht — lb-bes-06)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_geringfuegig(self, result_rules=None):
|
||||
"""Hochgerechnetes laufendes Entgelt ≤ GFG (2026: 551,10 € —
|
||||
Aufwertung gem. § 810 Abs 3 ASVG ausgesetzt). Lehrlinge sind
|
||||
nie geringfügig (§ 5 Abs 3 ASVG). Verhältnisse kürzer als ein
|
||||
Naturalmonat: Gesamtentgelt direkt gegen die Monatsgrenze."""
|
||||
self.ensure_one()
|
||||
version = self.version_id
|
||||
if version.l10n_at_lehrling:
|
||||
return False
|
||||
roh = self._l10n_at_sv_basis_roh(result_rules)
|
||||
if roh <= 0.0:
|
||||
# Ohne laufenden Bezug keine Statusbewertung (z. B.
|
||||
# reiner SZ-Lauf): Normalbehandlung der SZ-Beiträge.
|
||||
return False
|
||||
if (version.contract_date_start and version.contract_date_end
|
||||
and (version.contract_date_end - version.contract_date_start)
|
||||
.days < 29):
|
||||
vergleich = roh
|
||||
else:
|
||||
tage = self._l10n_at_lohnsteuertage()
|
||||
vergleich = roh * 30.0 / max(tage, 1) if tage < 30 else roh
|
||||
gfg = float(self.env['hr.rule.parameter']
|
||||
._get_parameter_from_code(
|
||||
'at_sv_gfg_monatlich', self.date_to,
|
||||
raise_if_not_found=False) or 0.0)
|
||||
return vergleich <= gfg
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Beitragsfreie Bezüge § 49 Abs 3 ASVG (Jahres-Kumulative je DN)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_sv49_ytd(self, kategorie, menge=False):
|
||||
"""Jahresverbrauch einer §-49-Kategorie aus früheren validierten
|
||||
Läufen (refundbewusst über _l10n_at_ytd_slaeufe)."""
|
||||
self.ensure_one()
|
||||
total = 0.0
|
||||
for slip in self._l10n_at_ytd_slaeufe():
|
||||
linien = slip.line_ids.filtered(
|
||||
lambda l: l.salary_rule_id.l10n_at_sv49_frei == kategorie)
|
||||
if menge:
|
||||
total += sum(linien.mapped('quantity'))
|
||||
else:
|
||||
total += sum(linien.mapped('total'))
|
||||
return total
|
||||
|
||||
def _l10n_at_sv49_freie_laufen(self, result_rules=None):
|
||||
"""Beitragsfreie Anteile laufender Bezüge dieses LZP: je Kategorie
|
||||
Tages-/Jahresgrenzen (at_sv49_grenzen) minus Jahresverbrauch;
|
||||
Überschreiten = beitragspflichtig (Umwidmung, § 49 Abs 3)."""
|
||||
self.ensure_one()
|
||||
grenzen = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_sv49_grenzen', self.date_to, raise_if_not_found=False)
|
||||
if not grenzen:
|
||||
return 0.0
|
||||
frei_gesamt = 0.0
|
||||
for rule in self.struct_id.rule_ids:
|
||||
kategorie = rule.l10n_at_sv49_frei
|
||||
if (not kategorie or kategorie == 'keine'
|
||||
or rule.l10n_at_bemessung != 'laufend'):
|
||||
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')),
|
||||
'quantity': sum(linie.mapped('quantity'))}
|
||||
if not werte:
|
||||
continue
|
||||
total = werte['total']
|
||||
menge = werte.get('quantity', 0.0)
|
||||
if kategorie == 'mahlzeit':
|
||||
frei = min(
|
||||
total, float(grenzen.get('mahlzeit_tag')) * menge)
|
||||
elif kategorie == 'homeoffice':
|
||||
rest_tage = max(
|
||||
float(grenzen.get('homeoffice_tage'))
|
||||
- self._l10n_at_sv49_ytd('homeoffice', menge=True), 0.0)
|
||||
frei = min(
|
||||
total,
|
||||
float(grenzen.get('homeoffice_tag'))
|
||||
* min(menge, rest_tage))
|
||||
else:
|
||||
pool = {'rabatt': 'rabatt_jahr',
|
||||
'betriebsveranstaltung': 'betriebsveranstaltung_jahr',
|
||||
'jubilaeum': 'jubilaeum_jahr',
|
||||
'zukunftssicherung': 'zukunftssicherung_jahr',
|
||||
'kinderbetreuung': 'kinderbetreuung_jahr'}.get(
|
||||
kategorie)
|
||||
rest = max(
|
||||
float(grenzen.get(pool))
|
||||
- self._l10n_at_sv49_ytd(kategorie), 0.0)
|
||||
frei = min(total, rest)
|
||||
frei_gesamt += max(frei, 0.0)
|
||||
return frei_gesamt
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Laufende Beitragsgrundlage — je Beitragsmonat (Anspruchsprinzip)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_sv_basis_roh(self, result_rules=None):
|
||||
"""Laufender Arbeitslohn minus beitragsfreie §-49-Anteile
|
||||
(vor HBG-Cap; Geringfügigkeits-/Staffel-Basis)."""
|
||||
self.ensure_one()
|
||||
roh = self._l10n_at_brutto('laufend', result_rules) \
|
||||
- self._l10n_at_sv49_freie_laufen(result_rules)
|
||||
return max(roh, 0.0)
|
||||
|
||||
def _l10n_at_sv_basis_laufen_monate(self, result_rules=None):
|
||||
"""[(Beitragsmonat, HBG-gecappte Basis), …]: laufender Arbeitslohn
|
||||
(§-49-bereinigt), gruppiert nach Regel-Beitragsmonat; HBG je
|
||||
Beitragsmonat historisch aufgelöst (at_sv_hbg_monatlich)."""
|
||||
self.ensure_one()
|
||||
gruppen = {}
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if rule.l10n_at_bemessung != 'laufend':
|
||||
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) \
|
||||
.replace(day=1)
|
||||
gruppen[monat] = gruppen.get(monat, 0.0) + werte['total']
|
||||
standard = self.date_from.replace(day=1)
|
||||
if standard in gruppen:
|
||||
gruppen[standard] -= self._l10n_at_sv49_freie_laufen(
|
||||
result_rules)
|
||||
ergebnis = []
|
||||
for monat, basis in sorted(gruppen.items()):
|
||||
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich', monat))
|
||||
ergebnis.append((monat, min(max(basis, 0.0), hbg)))
|
||||
return ergebnis
|
||||
|
||||
def _l10n_at_svdn_basis_laufen(self, result_rules=None):
|
||||
"""SV-Beitragsgrundlage laufend: Summe der je Beitragsmonat
|
||||
HBG-gecappten, §-49-bereinigten Basen (at_sv_*)."""
|
||||
self.ensure_one()
|
||||
return sum(basis for _monat, basis
|
||||
in self._l10n_at_sv_basis_laufen_monate(result_rules))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Effektive Beitragssätze (Alter/Stichtäge, Wien-WF, ALV-DN-Staffel)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_sv_satz(self, seite, komponente, monat=None,
|
||||
staffel_basis=None):
|
||||
"""Effektiver Satz (vH) je Seite ('dn'/'dg') und Komponente
|
||||
(kv/pv/av/uv/ak/wf/iesg) zum Beitragsmonat, unter Berücksich-
|
||||
tigung der Stichtags-Regeln am Vertrag und der ALV-DN-Staffel
|
||||
(§ 2a AMPFG: reduziert nur den DN-Anteil; Grenzen nicht zu
|
||||
aliquotieren; gilt auch für SZ-Beiträge)."""
|
||||
self.ensure_one()
|
||||
version = self.version_id
|
||||
monat = monat or self.date_from
|
||||
if komponente == 'wf':
|
||||
vektor = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_sv_wf_vektor', monat, raise_if_not_found=False)
|
||||
satz = None
|
||||
if vektor:
|
||||
wien = (self.company_id.l10n_at_wf_wien
|
||||
or self.company_id.l10n_at_bundesland == 'wien')
|
||||
eintrag = vektor.get('wien' if wien else 'default') \
|
||||
or vektor.get('default')
|
||||
if eintrag:
|
||||
satz = float(eintrag.get(seite, 0.0))
|
||||
if satz is None:
|
||||
satz = float(self.env['hr.rule.parameter']
|
||||
._get_parameter_from_code(
|
||||
'at_sv_wf_%s' % seite, monat,
|
||||
raise_if_not_found=False) or 0.0)
|
||||
return satz
|
||||
satz = float(self._l10n_at_param(
|
||||
'at_sv_%s_%s' % (komponente, seite), monat))
|
||||
entfall_alv = (version.l10n_at_alv_entfall_ab
|
||||
and monat >= version.l10n_at_alv_entfall_ab)
|
||||
if komponente in ('av', 'iesg') and entfall_alv:
|
||||
# AV DN+DG entfallen (AlVG § 1 Abs 2 lit e) — der IESG-
|
||||
# Zuschlag folgt dem AV-Beitrag (§ 12 Abs 1 Z 4 iVm
|
||||
# § 12 Abs 2 IESG).
|
||||
return 0.0
|
||||
if komponente == 'uv':
|
||||
if version.l10n_at_lehrling \
|
||||
or (version.l10n_at_uv_entfall_ab
|
||||
and monat >= version.l10n_at_uv_entfall_ab):
|
||||
return 0.0
|
||||
if komponente == 'ak' and version.l10n_at_lehrling:
|
||||
return 0.0
|
||||
if komponente == 'iesg' and version.l10n_at_lehrling:
|
||||
# § 12 Abs 2 IESG: für Lehrlinge gesamte Lehrzeit kein Zuschlag
|
||||
return 0.0
|
||||
if komponente == 'pv' and (
|
||||
version.l10n_at_pv_bonusphase_ab
|
||||
and monat >= version.l10n_at_pv_bonusphase_ab):
|
||||
faktor = float(self.env['hr.rule.parameter']
|
||||
._get_parameter_from_code(
|
||||
'at_pv_bonus_faktor', monat,
|
||||
raise_if_not_found=False) or 0.5)
|
||||
satz *= faktor
|
||||
if komponente == 'av' and seite == 'dn':
|
||||
staffel = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_sv_av_staffel_ampfg', monat, raise_if_not_found=False)
|
||||
if staffel and staffel.get('tiers'):
|
||||
basis = staffel_basis if staffel_basis is not None \
|
||||
else self._l10n_at_sv_basis_roh()
|
||||
if basis > 0:
|
||||
for _von, bis, dn_satz in staffel['tiers']:
|
||||
if basis <= float(bis):
|
||||
return float(dn_satz)
|
||||
# über der obersten Stufe: voller DN-Satz
|
||||
return float(staffel.get('dn_voll', satz))
|
||||
return satz
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Beiträge laufend (DN- wie DG-seitig; geringfügig = 0)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_sv_beitrag(self, seite, komponente, result_rules=None):
|
||||
"""Beitragssumme je Beitragsmonat: Basis × historischen Satz;
|
||||
geringfügige Beschäftigung → 0 (stattdessen §-53a-UV, s. u.)."""
|
||||
self.ensure_one()
|
||||
if self._l10n_at_geringfuegig(result_rules):
|
||||
return 0.0
|
||||
total = 0.0
|
||||
for monat, basis in self._l10n_at_sv_basis_laufen_monate(
|
||||
result_rules):
|
||||
satz = self._l10n_at_sv_satz(seite, komponente, monat, basis)
|
||||
total += basis * satz / 100.0
|
||||
return total
|
||||
|
||||
def _l10n_at_svdn(self, komponente, result_rules=None):
|
||||
"""DN-SV-Komponente laufend: kv/pv/av/ak/wf — AK (§ 61 AKG) und
|
||||
WF nur laufend, HBGL-Cap; Wien über WF-Satzvektor."""
|
||||
self.ensure_one()
|
||||
return self._l10n_at_sv_beitrag('dn', komponente, result_rules)
|
||||
|
||||
def _l10n_at_svdg(self, komponente, result_rules=None):
|
||||
"""DG-SV-Komponente laufend: kv/pv/av/uv/wf/iesg (AK = 0);
|
||||
ÖGK-Tarif „Normalbetriebe" 2026: DG-Gesamt 20,98 % (Wien 21,23)."""
|
||||
self.ensure_one()
|
||||
return self._l10n_at_sv_beitrag('dg', komponente, result_rules)
|
||||
|
||||
def _l10n_at_svdg_gesamt(self, result_rules=None):
|
||||
self.ensure_one()
|
||||
return sum(self._l10n_at_svdg(k, result_rules)
|
||||
for k in ('kv', 'pv', 'av', 'uv', 'wf', 'iesg'))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Beiträge auf sonstigen Bezügen (§ 54 ASVG; 60faches HBG je Jahr)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_svdg_sz(self, komponente, result_rules=None):
|
||||
"""DG-Beitrag auf sonstigen Bezügen: KV/PV/AV/UV und IESG-Zuschlag
|
||||
(folgt dem AV-Beitrag) — ohne AK/WF (§ 62 Z 3/Z 5; lb-son-03:
|
||||
DN 17,07 %; DG 20,48 % inkl. UV 1,10 + IESG 0,10)."""
|
||||
self.ensure_one()
|
||||
if self._l10n_at_geringfuegig(result_rules):
|
||||
return 0.0
|
||||
basis = self._l10n_at_svdn_basis_sz(result_rules)
|
||||
satz = self._l10n_at_sv_satz(
|
||||
'dg', komponente, staffel_basis=self._l10n_at_sv_basis_roh(
|
||||
result_rules))
|
||||
return basis * satz / 100.0
|
||||
|
||||
def _l10n_at_svdg_sz_gesamt(self, result_rules=None):
|
||||
self.ensure_one()
|
||||
return sum(self._l10n_at_svdg_sz(k, result_rules)
|
||||
for k in ('kv', 'pv', 'av', 'uv', 'iesg'))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# § 53a ASVG — Geringfügig Beschäftigte
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_svdg_uv_geringfuegig(self, result_rules=None):
|
||||
"""DG-UV geringfügig Beschäftigter: 1,1 % der allgemeinen
|
||||
Beitragsgrundlage (§ 53a Abs 1 ASVG) als Monatspauschale
|
||||
(at_sv_geringfuegig['beitrag_uv']; aBG 2026 = 83,49 ÷ 14,12 % =
|
||||
591,29 → 6,50 € — abgeleitet, s. Rechenfälle). Entfällt bei
|
||||
UV-Stichtag (60. LJ) bzw. Lehrlingen (§ 51 Abs 6 ASVG)."""
|
||||
self.ensure_one()
|
||||
if not self._l10n_at_geringfuegig(result_rules):
|
||||
return 0.0
|
||||
if self._l10n_at_sv_satz('dg', 'uv') <= 0.0:
|
||||
return 0.0
|
||||
params = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_sv_geringfuegig', self.date_to, raise_if_not_found=False) \
|
||||
or {}
|
||||
return float(params.get('beitrag_uv', 0.0))
|
||||
Reference in New Issue
Block a user