4b86bcb790
Split the voluntary severance into its privileged part and a tariff remainder on the exit run: the privileged part combines the quarter rule (1/4 of the last twelve months' running pay, capped at nine times the monthly HBG) with the twelfth rule (2/12 to 12/12 by proven service, capped at n × 3 × HBG), reduced per Z 3 by severance already received and the statutory claim due at this termination, all taxed at the flat 6 % rate. The excess runs as its own rule flagged l10n_at_s67_abs6_ueberhang: taxed with the running tariff of the payment month but excluded from the annual sixth, since Z 5 exempts only the Z 1/Z 2 amounts and the excess is a special payment, not running pay. The twelve-month basis aggregates validated prior runs plus own lines, excluding the flagged excess and § 26 Z 4 travel overhang via a dedicated rule flag. Free of SV (§ 49 Abs 3 Z 7 ASVG), payroll levies regardless of the 6 %/tarif split (§ 41 Abs 4 lit b FLAG, § 122 Abs 8 WKG, § 5 Abs 2 lit b KommStG) and BV contributions; the former BV-Anwartschaft exclusion Z 7 is repealed (BGBl I 118/2015). The wizard proposes the rolling basis and the statutory claim, previews Z 1/Z 2/reduction/privileged/excess/tax and requires explicit confirmation against claim-conversion risk. The § 291d EO termination pot nets both severance wage-tax rules; key 260 now includes LST_ABF_FREI. Verified against the original statute text in .ris/ (EStG § 67 Abs 6) and the knowledge-base sources lb-end-12/13/15; KZ positioning of Abs-6 amounts and the excess's § 291d pot allocation remain documented verification points against LStR Rz 1087 ff. Version 19.0.14.0.0 (private), 19.0.10.0.0 (core)
526 lines
25 KiB
Python
526 lines
25 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""AP14-A: Endabrechnungs-Zyklus — Urlaubsersatzleistung (UEL),
|
||
Abgangsentschädigung (§ 67 Abs 8 lit b), Aufrechnung offener Vorschüsse
|
||
und Zeitguthaben/-schulden (§ 19e AZG).
|
||
|
||
Am Originalwortlaut verifiziert (.ris/, Stand 2026-09-14):
|
||
- § 11 Abs 2 ASVG: die Pflichtversicherung besteht für die Zeit des
|
||
Bezuges einer Ersatzleistung für Urlaubsentgelt (und einer
|
||
Kündigungsentschädigung) weiter; gebühren beide, ist zur Bestimmung
|
||
des Zeitraumes ZUERST die Kündigungsentschädigung und im Anschluss
|
||
die Ersatzleistung heranzuziehen (sequentielle Verlängerungsfenster).
|
||
Die 6-WT/5-AT-Tage-Umrechnung ist Verwaltungspraxis (lb-url-15),
|
||
nicht Gesetzeswortlaut.
|
||
- § 67 Abs 8 lit d EStG: UEL „soweit sie laufenden Arbeitslohn
|
||
betreffen, als laufender Arbeitslohn, soweit sie sonstige Bezüge
|
||
betreffen, als sonstiger Bezug im Kalendermonat der Zahlung" — die
|
||
Zweikomponenten-Abbildung läuft nativ über l10n_at_bemessung.
|
||
- § 67 Abs 8 lit b EStG (Kündigungs-/Abgangsentschädigung): nach
|
||
Abzug der §-62-Beiträge ist ein Fünftel steuerfrei zu belassen,
|
||
höchstens ein Fünftel des Neunfachen der monatlichen HBG; der Rest
|
||
läuft tarifisch im Zuflussmonat (Abs 10), Abs 2 (Sechstel) ist
|
||
NICHT anzuwenden. LNK-pflichtig (FLAG § 41 Abs 4 nennt nur
|
||
§ 67 Abs 3 und 6) und SV-pflichtig (keine §-49-Abs-3-Z-7-Befreiung)
|
||
mit Umlegung auf den Kündigungsfrist-Zeitraum.
|
||
- § 19e Abs 2 AZG: Guthaben an Normalarbeitszeit + 50 % Zuschlag
|
||
(Entfall bei unberechtigtem vorzeitigem Austritt; KV kann
|
||
abweichend regeln).
|
||
"""
|
||
from datetime import timedelta
|
||
|
||
from dateutil.relativedelta import relativedelta
|
||
|
||
from odoo import fields, models
|
||
|
||
|
||
class HrSalaryRule(models.Model):
|
||
_inherit = 'hr.salary.rule'
|
||
|
||
l10n_at_sv_verlaengerung = fields.Selection(
|
||
[('keine', 'Keine SV-Verlängerung (§ 11 Abs 2 ASVG)'),
|
||
('urlaubsersatz', 'UEL-Tage-Schema (Urlaubsersatzleistung)'),
|
||
('abgang', 'Kündigungsfrist (Abgangsentschädigung)')],
|
||
string='AT SV-Verlängerung', default='keine',
|
||
help='Verteilt die laufende SV-Basis dieser Regel auf die '
|
||
'Pflichtversicherungs-Verlängerungsmonate nach § 11 Abs 2 '
|
||
'ASVG statt auf den Abrechnungsmonat: Abgangsentschädigung '
|
||
'zuerst (Kündigungsfrist), UEL anschließend (Tage-Schema '
|
||
'lb-url-15).')
|
||
l10n_at_s67_fuenftel = fields.Boolean(
|
||
string='AT § 67 Abs 8 lit a/b (Fünftelregelung)',
|
||
help='LSt-Pfad der Vergleichs-/Kündigungsentschädigungen: nach '
|
||
'Abzug der §-62-Beiträge ist ein Fünftel steuerfrei '
|
||
'(Deckel: ein Fünftel des Neunfachen der monatlichen HBG), '
|
||
'der Rest läuft tarifisch im Zuflussmonat — ohne '
|
||
'Jahressechstel-Erhöhung (Abs 2 nicht anzuwenden).')
|
||
l10n_at_s26_z4_reise = fields.Boolean(
|
||
string='AT § 26 Z 4 Reisevergütung',
|
||
help='§-26-Z-4-Reisevergütungen zählen nicht in die '
|
||
'12-Monats-Basis der §-67-Abs-6-Begünstigung (lb-end-13); '
|
||
'Sonderzahlungen sind ohnehin nicht bemessung „laufend“.')
|
||
|
||
|
||
class HrPayslip(models.Model):
|
||
_inherit = 'hr.payslip'
|
||
|
||
l10n_at_abf_alt_anspruchsart = fields.Selection(
|
||
[('normal', 'Normalanspruch (§ 23 AngG)'),
|
||
('eltern', 'Elternaustritt (§ 23a Abs 3/4 AngG)'),
|
||
('tod', 'Tod (§ 23 Abs 6 AngG)'),
|
||
('pension', 'Pension/Invalidität (§ 23a Abs 1 AngG)'),
|
||
('eingefroren', 'Eingefrorener Altanspruch (§ 47 BMSVG)'),
|
||
('manuell', 'Sonstiger nachgewiesener Anspruch')],
|
||
string='AT Abfertigung Alt — Anspruchsart', copy=False)
|
||
l10n_at_abf_alt_dienstzeit_monate = fields.Integer(
|
||
string='AT Abfertigung Alt — Dienstzeitmonate', copy=False)
|
||
l10n_at_abf_alt_monatsentgelt = fields.Float(
|
||
string='AT Abfertigung Alt — Monatsentgelt', copy=False,
|
||
help='Bestätigte arbeitsrechtliche Bemessungsgrundlage inklusive '
|
||
'regelmäßiger Bezugsbestandteile und aliquoter Sonderzahlungen.')
|
||
l10n_at_abf_alt_laufender_arbeitslohn = fields.Float(
|
||
string='AT Abfertigung Alt — laufender Arbeitslohn', copy=False,
|
||
help='Laufender Monatsarbeitslohn für den steuerlichen '
|
||
'Vervielfacher nach § 67 Abs 3 EStG.')
|
||
l10n_at_abf_alt_monatslohnsteuer = fields.Float(
|
||
string='AT Abfertigung Alt — tarifmäßige Monatslohnsteuer',
|
||
copy=False,
|
||
help='Auf den maßgeblichen laufenden Monatsarbeitslohn entfallende '
|
||
'tarifmäßige Lohnsteuer nach den persönlichen Verhältnissen.')
|
||
l10n_at_abf_alt_mehrfach = fields.Float(
|
||
string='AT Abfertigung Alt — Anspruchsfaktor', copy=False)
|
||
l10n_at_abf_alt_nachweis = fields.Text(
|
||
string='AT Abfertigung Alt — Nachweis/Begründung', copy=False)
|
||
l10n_at_abf_frei_basis_12 = fields.Float(
|
||
string='AT Freiwillige Abfertigung — 12-Monats-Basis', copy=False,
|
||
help='Laufende Bezüge der letzten zwölf Monate (§ 67 Abs 6 Z 1/2 '
|
||
'EStG): vom Wizard bestätigt — alle steuerbaren Bezugsteile '
|
||
'inklusive steuerfreier Zuschläge und Sachbezüge, ohne '
|
||
'Sonderzahlungen und ohne §-26-Z-4-Reisevergütungen; keine '
|
||
'Bezüge anderer Arbeitgeber, keine Hochrechnung kurzer DV.')
|
||
l10n_at_abf_frei_dienstzeit_monate = fields.Integer(
|
||
string='AT Freiwillige Abfertigung — nachgewiesene Dienstzeit '
|
||
'(Monate)', copy=False)
|
||
l10n_at_abf_frei_erhaltene_abfertigungen = fields.Float(
|
||
string='AT Freiwillige Abfertigung — erhaltene Abfertigungen',
|
||
copy=False,
|
||
help='Während der nachgewiesenen Dienstzeit bereits erhaltene '
|
||
'Abfertigungen nach § 67 Abs 3 oder Abs 6 EStG — kürzen das '
|
||
'Z-2-Ausmaß (§ 67 Abs 6 Z 3).')
|
||
l10n_at_abf_frei_gesetzlicher_anspruch = fields.Float(
|
||
string='AT Freiwillige Abfertigung — bestehender gesetzlicher '
|
||
'Anspruch', copy=False,
|
||
help='Bestehender Anspruch auf eine gesetzliche Abfertigung am '
|
||
'aktuellen Dienstverhältnis-Ende (§ 67 Abs 6 Z 3 EStG) — '
|
||
'kürzt das Z-2-Ausmaß.')
|
||
l10n_at_abf_frei_nachweis = fields.Text(
|
||
string='AT Freiwillige Abfertigung — Nachweis/Begründung',
|
||
copy=False)
|
||
|
||
# ------------------------------------------------------------------
|
||
# SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2 ASVG)
|
||
# ------------------------------------------------------------------
|
||
def _at_end_arbeitsrechtliches_ende(self):
|
||
"""Arbeitsrechtliches Ende = letzter Arbeitstag; das SV-Ende
|
||
(„Ende des Entgeltanspruches") liegt darüber hinaus, wenn ein
|
||
Entgeltanspruch über das Ende hinaus läuft."""
|
||
self.ensure_one()
|
||
return self.version_id.contract_date_end or self.date_to
|
||
|
||
def _at_end_arbeitstage_woche(self):
|
||
"""Arbeitstage pro Woche des Vertragskalenders (Schema-Grundlage
|
||
der SV-Tage-Umrechnung: 5-AT-Woche, 6-WT-Woche, Teilzeitwoche)."""
|
||
self.ensure_one()
|
||
calendar = self.version_id.resource_calendar_id
|
||
days = {int(att.dayofweek) for att in calendar.attendance_ids} \
|
||
if calendar else set()
|
||
return len(days) or 5
|
||
|
||
def _at_end_sv_tage(self, tage):
|
||
"""SV-Tage aus abzurechnenden Urlaubstagen (Verwaltungspraxis
|
||
lb-url-15): je volle Woche à N Arbeitstage werden 7−N
|
||
Kalendertage hinzugerechnet (5-AT-Woche +2, 6-WT-Woche +1,
|
||
2-Tage-Teilzeitwoche +5) — nur ganze Wochenblöcke."""
|
||
self.ensure_one()
|
||
at_woche = self._at_end_arbeitstage_woche()
|
||
blocks = int(tage // at_woche)
|
||
return tage + blocks * (7 - at_woche)
|
||
|
||
def _at_end_verlaengerungs_monate(self, sv_tage, start):
|
||
"""[(Monatsanfang, Tage im Monat)] eines Verlängerungsfensters
|
||
ab dem übergebenen Starttag (§ 11 Abs 2 ASVG)."""
|
||
self.ensure_one()
|
||
ergebnis = {}
|
||
rest = int(sv_tage)
|
||
tag = start
|
||
while rest > 0:
|
||
monatsende = (tag.replace(day=28) + timedelta(days=4)) \
|
||
.replace(day=1) - timedelta(days=1)
|
||
tage_dabei = min((monatsende - tag).days + 1, rest)
|
||
monat = tag.replace(day=1)
|
||
ergebnis[monat] = ergebnis.get(monat, 0) + tage_dabei
|
||
tag = monatsende + timedelta(days=1)
|
||
rest -= tage_dabei
|
||
return sorted(ergebnis.items())
|
||
|
||
def _at_end_rule_verlaengerung(self, rule, result_rules=None):
|
||
"""[(Monatsanfang, Tage, Anteil)] der Regel-Basis nach § 11
|
||
Abs 2: die Abgangsentschädigung belegt ZUERST die (fiktive)
|
||
Kündigungsfrist, die UEL SCHLIESSLICH die Urlaubstage — beide
|
||
Fenster laufen ab dem Tag nach dem arbeitsrechtlichen Ende
|
||
hintereinander (Gesetzeswortlaut: „zunächst die
|
||
Kündigungsentschädigung und im Anschluss die Ersatzleistung")."""
|
||
self.ensure_one()
|
||
start = self._at_end_arbeitsrechtliches_ende() + timedelta(days=1)
|
||
if rule.l10n_at_sv_verlaengerung == 'urlaubsersatz':
|
||
ffrist = self._at_input_amount('ATP_ABGANG_FFRIST_TAGE')
|
||
if ffrist:
|
||
start += timedelta(days=int(ffrist))
|
||
tage = self._at_end_sv_tage(self._at_uel_tage())
|
||
else:
|
||
ffrist = self._at_input_amount('ATP_ABGANG_FFRIST_TAGE')
|
||
tage = int(ffrist) if ffrist else 0
|
||
monate = self._at_end_verlaengerungs_monate(tage, start)
|
||
if not monate:
|
||
return []
|
||
gesamt = sum(tage for _monat, tage in monate)
|
||
return [(monat, tage, tage / float(gesamt))
|
||
for monat, tage in monate]
|
||
|
||
# ------------------------------------------------------------------
|
||
# Eingabe-Helfer
|
||
# ------------------------------------------------------------------
|
||
def _at_input_amount(self, code):
|
||
self.ensure_one()
|
||
linie = self.input_line_ids.filtered(
|
||
lambda i: i.input_type_id.code == code)
|
||
return linie[:1].amount or 0.0
|
||
|
||
# ------------------------------------------------------------------
|
||
# Abfertigung Alt — §§ 23/23a AngG, § 67 Abs 3 EStG
|
||
# ------------------------------------------------------------------
|
||
def _at_abf_alt_staffel(self, dienstzeit_monate=None):
|
||
"""Arbeitsrechtlicher Grundfaktor nach § 23 Abs 1 AngG."""
|
||
self.ensure_one()
|
||
monate = dienstzeit_monate
|
||
if monate is None:
|
||
monate = self.l10n_at_abf_alt_dienstzeit_monate
|
||
staffel = self._l10n_at_param('at_endabrechnung').get(
|
||
'abfertigung_alt_staffel', [])
|
||
mehrfach = 0.0
|
||
for grenze, faktor in staffel:
|
||
if monate >= int(grenze):
|
||
mehrfach = float(faktor)
|
||
return mehrfach
|
||
|
||
def _at_abf_alt_mehrfach_vorschlag(self, dienstzeit_monate=None,
|
||
anspruchsart=None):
|
||
"""§-23-Faktor mit Halb-/Deckelregeln für Elternaustritt/Tod."""
|
||
self.ensure_one()
|
||
art = anspruchsart or self.l10n_at_abf_alt_anspruchsart
|
||
monate = dienstzeit_monate
|
||
if monate is None:
|
||
monate = self.l10n_at_abf_alt_dienstzeit_monate
|
||
mehrfach = self._at_abf_alt_staffel(monate)
|
||
if art == 'eltern':
|
||
if monate < 60:
|
||
return 0.0
|
||
return min(mehrfach / 2.0, 3.0) if mehrfach else 0.0
|
||
if art == 'tod':
|
||
return mehrfach / 2.0
|
||
return mehrfach
|
||
|
||
def _at_abf_alt_betrag(self, result_rules=None):
|
||
"""Gesetzliche Alt-Abfertigung als eigene Bezugszeile."""
|
||
self.ensure_one()
|
||
return self._at_input_amount('ATP_ABF_ALT')
|
||
|
||
def _at_abf_alt_steuer_6(self, result_rules=None):
|
||
self.ensure_one()
|
||
satz = float(self._l10n_at_param(
|
||
'at_endabrechnung')['abfertigung_steuersatz'])
|
||
return max(self._at_abf_alt_betrag(result_rules), 0.0) * satz / 100.0
|
||
|
||
def _at_abf_alt_steuer_vervielfachung(self, result_rules=None):
|
||
"""§ 67 Abs 3: Monats-Tarif-LSt × steuerlicher Vervielfacher.
|
||
|
||
Der steuerliche Vervielfacher ist Abfertigungsbetrag dividiert
|
||
durch den laufenden Arbeitslohn. Er kann vom arbeitsrechtlichen
|
||
Staffel-Faktor abweichen, weil die Abfertigungsbasis etwa aliquote
|
||
Sonderzahlungen enthält (lb-end-01, RF 21 Dienstjahre: 10,5 statt 9).
|
||
"""
|
||
self.ensure_one()
|
||
betrag = self._at_abf_alt_betrag(result_rules)
|
||
laufend = self.l10n_at_abf_alt_laufender_arbeitslohn
|
||
if betrag <= 0 or laufend <= 0:
|
||
return self._at_abf_alt_steuer_6(result_rules)
|
||
vervielfacher = betrag / laufend
|
||
return self.l10n_at_abf_alt_monatslohnsteuer * vervielfacher
|
||
|
||
def _at_abf_alt_lohnsteuer(self, result_rules=None):
|
||
"""Zwingend günstigere Steuer aus Vervielfachung und 6 %."""
|
||
self.ensure_one()
|
||
if self._at_abf_alt_betrag(result_rules) <= 0:
|
||
return 0.0
|
||
return min(self._at_abf_alt_steuer_vervielfachung(result_rules),
|
||
self._at_abf_alt_steuer_6(result_rules))
|
||
|
||
# ------------------------------------------------------------------
|
||
# Freiwillige Abfertigung — § 67 Abs 6 EStG (Viertel-/Zwölftelregelung)
|
||
# ------------------------------------------------------------------
|
||
def _at_bezuege_12_monate(self):
|
||
"""Laufende Bezüge der letzten zwölf Monate (§ 67 Abs 6 Z 1/2):
|
||
validierte Vormonats-Läufe plus eigene Zeilen — ohne den als
|
||
Abs-6-Überhang gekennzeichneten Betrag (das ist ein sonstiger
|
||
Bezug, kein laufender) und ohne §-26-Z-4-Reisevergütungen;
|
||
Sonderzahlungen sind ohnehin nicht bemessung „laufend“
|
||
(lb-end-13)."""
|
||
self.ensure_one()
|
||
start = (self.date_from - relativedelta(months=11)).replace(day=1)
|
||
vor_slips = self.env['hr.payslip'].search([
|
||
('employee_id', '=', self.employee_id.id),
|
||
('id', '!=', self.id),
|
||
('date_from', '>=', start),
|
||
('date_from', '<', self.date_from.replace(day=1)),
|
||
]).filtered(lambda p: p.state not in ('draft', 'cancel'))
|
||
total = 0.0
|
||
for slip in list(vor_slips) + [self]:
|
||
for line in slip.line_ids:
|
||
rule = line.salary_rule_id
|
||
if rule.l10n_at_bemessung != 'laufend':
|
||
continue
|
||
if (rule.l10n_at_s67_abs6_ueberhang
|
||
or rule.l10n_at_s26_z4_reise):
|
||
continue
|
||
total += line.total
|
||
return total
|
||
|
||
def _at_abf_frei_zwoelftel(self):
|
||
"""Zwölftel-Anzahl nach § 67 Abs 6 Z 2: die Dienstzeitstaffel
|
||
entspricht den §-23-Grenzen (36→2/12 … 300→12/12)."""
|
||
self.ensure_one()
|
||
return self._at_abf_alt_staffel(
|
||
self.l10n_at_abf_frei_dienstzeit_monate)
|
||
|
||
def _at_abf_frei_split(self, result_rules=None):
|
||
"""§ 67 Abs 6: begünstigter Teil (Viertel- und Zwölftelregelung,
|
||
gedeckelt 9 × bzw. n × 3 × monatliche HBG, Z-3-Kürzung um
|
||
erhaltene Abfertigungen und den bestehenden gesetzlichen
|
||
Anspruch) und der tarifische Überhang (Z 6)."""
|
||
self.ensure_one()
|
||
brutto = max(self._at_input_amount('ATP_ABF_FREI'), 0.0)
|
||
basis = self.l10n_at_abf_frei_basis_12
|
||
if basis <= 0:
|
||
basis = self._at_bezuege_12_monate()
|
||
basis = max(basis, 0.0)
|
||
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich'))
|
||
z1 = min(basis / 4.0, 9.0 * hbg)
|
||
zwoelftel = self._at_abf_frei_zwoelftel()
|
||
z2 = min(basis * zwoelftel / 12.0, zwoelftel * 3.0 * hbg)
|
||
kuerzung = max(
|
||
self.l10n_at_abf_frei_erhaltene_abfertigungen
|
||
+ self.l10n_at_abf_frei_gesetzlicher_anspruch, 0.0)
|
||
z2 = max(z2 - kuerzung, 0.0)
|
||
beguenstigt = min(brutto, z1 + z2)
|
||
return {
|
||
'basis': basis,
|
||
'z1': z1,
|
||
'z2': z2,
|
||
'kuerzung': kuerzung,
|
||
'beguenstigt': beguenstigt,
|
||
'ueberhang': brutto - beguenstigt,
|
||
}
|
||
|
||
def _at_abf_frei_beguenstigt(self, result_rules=None):
|
||
self.ensure_one()
|
||
return self._at_abf_frei_split(result_rules)['beguenstigt']
|
||
|
||
def _at_abf_frei_ueberhang(self, result_rules=None):
|
||
self.ensure_one()
|
||
return self._at_abf_frei_split(result_rules)['ueberhang']
|
||
|
||
def _at_abf_frei_lst_6(self, result_rules=None):
|
||
"""6 % auf den begünstigten Teil (§ 67 Abs 6, fester Steuersatz)."""
|
||
self.ensure_one()
|
||
satz = float(self._l10n_at_param(
|
||
'at_endabrechnung')['abfertigung_steuersatz'])
|
||
return self._at_abf_frei_beguenstigt(result_rules) * satz / 100.0
|
||
|
||
# ------------------------------------------------------------------
|
||
# Urlaubsersatzleistung — § 67 Abs 8 lit d, lb-url-15/16
|
||
# ------------------------------------------------------------------
|
||
def _at_uel_tage(self):
|
||
"""Offene Urlaubstage (Arbeitstage; keine Rundung, lb-url-16)."""
|
||
self.ensure_one()
|
||
return self._at_input_amount('ATP_UEL_TAGE')
|
||
|
||
def _at_uel_divisor(self):
|
||
"""Tagewert-Divisor: /26 bei Werktags-, /22 bei 5-AT-Kalender
|
||
(lb-url-16)."""
|
||
self.ensure_one()
|
||
params = self._l10n_at_param('at_endabrechnung')
|
||
if self._at_end_arbeitstage_woche() >= 6:
|
||
return float(params['divisor_werktage'])
|
||
return float(params['divisor_arbeitstage'])
|
||
|
||
def _at_uel_lfd(self):
|
||
"""UEL-laufender Anteil: Tage × Monatsentgelt / Divisor —
|
||
läuft als laufender Bezug durch den Tarif des Austrittsmonats
|
||
und erhöht das Jahressechstel (nativ über bemessung='laufend')."""
|
||
self.ensure_one()
|
||
return self._at_uel_tage() * self._at_kv_gehalt_basis() \
|
||
/ self._at_uel_divisor()
|
||
|
||
def _at_uel_sz_tageswert(self):
|
||
"""SZ-Anteil je Urlaubstag: 2 Sonderzahlungen à Monatsentgelt
|
||
pro Jahr (KV § 11) → je Monat 2 × SZ-Basis/12, als Tageswert
|
||
(auch bei bereits überaliquot bezahlten SZ, lb-url-16)."""
|
||
self.ensure_one()
|
||
sz_monat = 2.0 * self._at_sz_basis(self.date_to) / 12.0
|
||
return sz_monat / self._at_uel_divisor()
|
||
|
||
def _at_uel_sz(self):
|
||
"""UEL-Sonderzahlungsanteil: Tage × SZ-Tageswert."""
|
||
self.ensure_one()
|
||
return self._at_uel_tage() * self._at_uel_sz_tageswert()
|
||
|
||
# ------------------------------------------------------------------
|
||
# Abgangsentschädigung — § 67 Abs 8 lit b (Fünftelregelung)
|
||
# ------------------------------------------------------------------
|
||
def _at_fuenftel_regeln(self):
|
||
self.ensure_one()
|
||
return self.struct_id.rule_ids.filtered(
|
||
lambda rule: rule.l10n_at_s67_fuenftel)
|
||
|
||
def _at_fuenftel_total(self, result_rules=None):
|
||
self.ensure_one()
|
||
total = 0.0
|
||
for rule in self._at_fuenftel_regeln():
|
||
if result_rules is not None:
|
||
if rule.code in result_rules:
|
||
total += result_rules[rule.code]['total']
|
||
else:
|
||
linie = self.line_ids.filtered(lambda l: l.code == rule.code)
|
||
total += sum(linie.mapped('total'))
|
||
return total
|
||
|
||
def _at_fuenftel_tarif_basis(self, result_rules=None):
|
||
"""Tarifischer Teil (4/5) der Fünftel-Bezüge: Bemessung nach
|
||
Abzug der darauf entfallenden §-62-Beiträge (approximiert über
|
||
den effektiven DN-Satz der laufenden Basis; die HBG-Caps der
|
||
Verlängerungsmonate werden im SV-Pfad separat geführt); ein
|
||
Fünftel ist steuerfrei, gedeckelt mit einem Fünftel des
|
||
Neunfachen der monatlichen HBG (lb-end-21: 2026 6.930 × 9/5
|
||
= 12.474)."""
|
||
self.ensure_one()
|
||
total = self._at_fuenftel_total(result_rules)
|
||
if total <= 0:
|
||
return 0.0
|
||
satz = sum(self._l10n_at_sv_satz(
|
||
'dn', komponente,
|
||
staffel_basis=self._l10n_at_sv_basis_roh(result_rules))
|
||
for komponente in ('kv', 'pv', 'av', 'ak', 'wf'))
|
||
bemessung = max(total * (1.0 - satz / 100.0), 0.0)
|
||
deckel = float(self._l10n_at_param('at_sv_hbg_monatlich')) \
|
||
* 9.0 / 5.0
|
||
frei = min(bemessung / 5.0, deckel)
|
||
return bemessung - frei
|
||
|
||
# ------------------------------------------------------------------
|
||
# Engine-Anbindung: Tarif-Teil ohne Sechstel, LNK-Pflicht, SV-Umlegung
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_bemessung_laufen(self, result_rules=None):
|
||
basis = super()._l10n_at_bemessung_laufen(result_rules)
|
||
# § 67 Abs 8 lit b: 4/5 tarifisch im Zuflussmonat; die Regel
|
||
# selbst ist neutral und erhöht brutto('laufend') nicht → kein
|
||
# Sechstel-Effekt (Abs 2 nicht anzuwenden).
|
||
return basis + self._at_fuenftel_tarif_basis(result_rules)
|
||
|
||
def _at_lnk_basis(self, steuer, result_rules=None):
|
||
"""Upstream-Überschreibung (Muster account_posting_private;
|
||
Vorlage payslip_private._at_lnk_basis — bei Änderungen dort
|
||
gegen diesen Pin neu abgleichen): Kündigungsentschädigungen
|
||
sind Arbeitslöhne iSd § 41 Abs 3 FLAG und unterliegen NICHT
|
||
den Abs-4-Ausschlüssen (lit b nennt nur § 67 Abs 3 und 6) —
|
||
sie sind VOR der Freibetragsstaffel in die LNK-Basis
|
||
aufzunehmen."""
|
||
self.ensure_one()
|
||
basis = (self._l10n_at_brutto('laufend', result_rules)
|
||
+ self._l10n_at_brutto('sonstig', result_rules)
|
||
- self._l10n_at_st_frei_freie_laufen(result_rules))
|
||
for rule in self.struct_id.rule_ids:
|
||
if (rule.l10n_at_bemessung in ('laufend', 'sonstig')
|
||
and 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 += self._at_fuenftel_total(result_rules)
|
||
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 _l10n_at_sv_basis_laufen_monate(self, result_rules=None):
|
||
"""Upstream-Überschreibung (Muster account_posting_private;
|
||
Vorlage l10n_at_hr_payroll.sozialversicherung — bei Odoo- oder
|
||
Kern-Upgrades gegen diesen Pin neu abgleichen): identischer
|
||
Ablauf wie der Kern, zusätzlich werden Regeln mit AT-SV-
|
||
Verlängerung tageproportional auf die Verlängerungsmonate
|
||
gelegt. Der Standardanteil jedes Monats bleibt auf die volle
|
||
monatliche HBG gedeckelt, der Verlängerungsanteil auf den
|
||
Tageswert (monatliche HBG × Verlängerungstage/30, lb-url-15);
|
||
die Fenster laufen sequenziell (Abgang zuerst, UEL danach)."""
|
||
self.ensure_one()
|
||
gruppen = {}
|
||
verlaengerung = {}
|
||
tage_pro_monat = {}
|
||
for rule in self.struct_id.rule_ids:
|
||
if self._l10n_at_sv_art(rule) != '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
|
||
if rule.l10n_at_sv_verlaengerung != 'keine':
|
||
for monat, tage, anteil in \
|
||
self._at_end_rule_verlaengerung(rule, result_rules):
|
||
verlaengerung[monat] = verlaengerung.get(monat, 0.0) \
|
||
+ werte['total'] * anteil
|
||
tage_pro_monat[monat] = \
|
||
tage_pro_monat.get(monat, 0) + tage
|
||
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 in sorted(set(gruppen) | set(verlaengerung)):
|
||
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich', monat))
|
||
basis = min(max(gruppen.get(monat, 0.0), 0.0), hbg)
|
||
if monat in verlaengerung:
|
||
tageswert = hbg * tage_pro_monat[monat] / 30.0
|
||
basis += min(max(verlaengerung[monat], 0.0), tageswert)
|
||
ergebnis.append((monat, basis))
|
||
return ergebnis |