Files
odoo-at-payroll/addons/l10n_at_hr_payroll_private/models/endabrechnung.py
T
fegger 4b86bcb790 [IMP] l10n_at_hr_payroll_private: add voluntary severance under § 67 Abs 6 (AP15-B)
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)
2026-09-15 11:34:09 +02:00

526 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 7N
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