9add58fe83
Pay the pre-2003 AngG severance from the end-of-employment run as its own neutral benefit line: free of SV (§ 49 Abs 3 Z 7 ASVG) and payroll levies, outside the annual sixth, and collected into the § 291d EO termination pot net of its own wage tax. Taxation follows the binding better-of rule of § 67 Abs 3 EStG: multiple-of-method (tariff wage tax on the running monthly wage × the statutory multiple of the payout) versus the flat 6 % rate, applied mandatorily in favour of the employee. The tariff wage tax is captured as a separately confirmed value instead of deriving it from gross pay through the § 62 engine. The wizard enforces § 23a Abs 3 AngG (parental exit needs five uninterrupted years), blocks BMSVG-new contracts except frozen claims and validates AGRD 02/06 against the claim type. The LNK basis subtracts § 67 Abs 3/6 rules only when they actually run in the tax base, the L16 generator reports LST_ABF in key 260 outside 210/220, and result_rules access in the garnishment paths avoids DefaultDictPayroll materialisation. Verified against the original statute texts in .ris/ (AngG §§ 23/23a, EStG § 67 Abs 3). Version 19.0.13.0.0 (private), 19.0.9.0.0 (core)
499 lines
23 KiB
Python
499 lines
23 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""Lohnsteuer-Kern (GP3/AP3): § 66 Monatslohnsteuer kumulativ mit
|
||
Rollung (Zuflussprinzip), § 67 sonstige Bezüge mit Sechstel-
|
||
Aufrollung, § 68-Steuerfreiheiten, DN-SV als Bemessungsbestandteil.
|
||
|
||
Architektur (D4-verifiziert): hr.salary.rule.struct_id ist required und
|
||
rule_ids One2many — Kern-Regeln sind nicht teilbar. Der Kern liefert
|
||
Compute + Parameter + Flags; die Lokalisationen binden dünne Regeln,
|
||
die diese Methoden aufrufen (kein Wertefork, keine Logikfork).
|
||
|
||
Laufzeit-Datenfluss: compute_sheet legt die Zeilen erst NACH der
|
||
Regelschleife an — die Regeln reichen daher die Zwischensummen über
|
||
``result_rules`` (localdict, s. hr_payslip._compute_line L1225) an
|
||
die Compute-Methoden; frühere (validierte) Läufe werden über deren
|
||
line_ids gelesen (YTD/Rollung).
|
||
|
||
User-Entscheidungen 2026-09-11:
|
||
- Lohnsteuertage: 30 je vollem Monat; Teilmonat = Kalendertage der
|
||
Beschäftigung inkl. arbeitsfreier Tage (§ 66 Abs 3), max 30.
|
||
- Bemessung laufend = Arbeitslohn − SV-DN − steuerfreie Bezüge
|
||
(user-Formel „SV-Beitragsgrundlage − SV-DN").
|
||
- Bemessung SZ = sonstiger Bezug − SV-DN(SZ) (Abs 12) − 620 €
|
||
(jahreskumulativ); 620–Jahressechstel 6 %, darüber LSt-lfd (Abs 10).
|
||
- Aufrollung: Vormonatskorrekturen rollen über die kumulative Engine
|
||
in den Zuflussmonat; SV-Allokation auf Beitragsmonate = GP4,
|
||
Nachtrags-mBGM = GP7.
|
||
"""
|
||
from datetime import date, timedelta
|
||
|
||
from odoo import fields, models
|
||
|
||
|
||
class HrSalaryRule(models.Model):
|
||
_inherit = 'hr.salary.rule'
|
||
|
||
l10n_at_bemessung = fields.Selection(
|
||
[('neutral', 'Kein Arbeitslohn (Abzug/Netto/DG)'),
|
||
('laufend', 'Laufender Arbeitslohn (§ 25/§ 66)'),
|
||
('sonstig', 'Sonstiger Bezug (§ 67)')],
|
||
string='AT Bemessungsart',
|
||
default='neutral',
|
||
help='Laufend: §-66-Kette (Hochrechnung); sonstig: § 67 '
|
||
'(Jahressechstel/Fixsätze); neutral: kein Arbeitslohn.')
|
||
l10n_at_steuerbefreiung_68 = fields.Selection(
|
||
[('keine', 'Keine §-68-Steuerfreiheit'),
|
||
('szfn', '§ 68 Abs 1 (Sonn-/Feiertags-/Nachtzuschläge)'),
|
||
('ue_std', '§ 68 Abs 2 (ÜStd-Zuschläge bis 50 % Grundlohn)')],
|
||
string='AT § 68-Freibetrag',
|
||
default='keine')
|
||
l10n_at_zuschlagsanteil = fields.Float(
|
||
string='AT Zuschlagsanteil (%)',
|
||
help='Anteil des Zeilenbetrags, der Zuschlag (und damit '
|
||
'§-68-potentiell steuerfrei) ist — z. B. 50 %-Regel mit '
|
||
'Grundvergütung: 0,5/1,5 = 33,33 %.')
|
||
l10n_at_st_frei = fields.Selection(
|
||
[('keine', 'Keine § 3/§ 26-Steuerfreiheit'),
|
||
('mahlzeit', 'Mahlzeiten-Gutschein (§ 3 Abs 1 Z 17; 8 €/AT)'),
|
||
('rabatt', 'Mitarbeiterrabatt (§ 3 Abs 1 Z 21; 1.000 €/J)'),
|
||
('betriebsveranstaltung',
|
||
'Betriebsveranstaltung (§ 3 Abs 1 Z 14; 365 €/J)'),
|
||
('jubilaeum',
|
||
'Sachzuwendung/Jubiläum/Geschenk (§ 3 Abs 1 Z 14; 186 €/J)'),
|
||
('homeoffice',
|
||
'Homeoffice-Pauschale (§ 26 Z 9; 3 €/Tag, max 100 Tage/J)')],
|
||
string='AT § 3/§ 26 (steuerfrei)',
|
||
default='keine',
|
||
help='Steuerfreie Bezugsbestandteile (Freibeträge): der '
|
||
'steuerfreie Anteil wird je Bezugsmonat von der LSt-'
|
||
'Bemessungsgrundlage und von der LNK-Bemessungsgrundlage '
|
||
'abgezogen; Grenzen als Jahres-Kumulative je Dienstnehmer '
|
||
'(at_st_grenzen). Nur der übersteigende Teil ist '
|
||
'steuerpflichtig. Menge = Arbeitstage (Z 17/Z 9).')
|
||
|
||
|
||
class HrVersion(models.Model):
|
||
_inherit = 'hr.version'
|
||
|
||
l10n_at_familienabsetzbetrag = fields.Selection(
|
||
[('keine', 'Keine'), ('avb', 'Alleinverdienerabsetzbetrag'),
|
||
('avab', 'Alleinerzieherabsetzbetrag')],
|
||
string='AT Familienabsetzbetrag',
|
||
default='keine',
|
||
help='AVB nur bei Partnereinkommen ≤ Grenze (§ 33 Abs 4; '
|
||
'Nachweis mandantenseitig). Betrag nach Anzahl Kinder.')
|
||
l10n_at_ueberw_nachtarbeit = fields.Boolean(
|
||
string='AT Überwiegende Nachtarbeit (§ 68 Abs 6)',
|
||
help='Erhöht den SZFN-Freibetrag 400 → 600 € (Nachtarbeit '
|
||
'≥ 3 h zwischen 19 und 7 Uhr, überwiegend in der NAZ).')
|
||
|
||
|
||
class ResCompany(models.Model):
|
||
_inherit = 'res.company'
|
||
|
||
l10n_at_wf_wien = fields.Boolean(
|
||
string='AT Wien (WF-Zuschlag)',
|
||
help='Wiener WF-Zuschlag: WF gesamt 1,5 % (DN 0,75 %) ab '
|
||
'1. 1. 2026 statt 1,0 % (Wiener WF-Beitragstarif 2018 § 1).')
|
||
|
||
|
||
class HrPayslip(models.Model):
|
||
_inherit = 'hr.payslip'
|
||
|
||
# ------------------------------------------------------------------
|
||
# Basis-Helfer
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_param(self, code, datum=None):
|
||
return self.env['hr.rule.parameter']._get_parameter_from_code(
|
||
code, datum or self.date_to)
|
||
|
||
def _l10n_at_linien(self, art):
|
||
self.ensure_one()
|
||
return self.line_ids.filtered(
|
||
lambda l: l.salary_rule_id.l10n_at_bemessung == art)
|
||
|
||
def _l10n_at_brutto(self, art, result_rules=None):
|
||
"""Arbeitslohn je Bemessungsart (user-Formel-Basis): im
|
||
laufenden compute über result_rules (Zwischensummen), bei
|
||
abgeschlossenen Läufen über line_ids."""
|
||
self.ensure_one()
|
||
if result_rules is not None:
|
||
total = 0.0
|
||
for rule in self.struct_id.rule_ids:
|
||
if (rule.l10n_at_bemessung == art
|
||
and rule.code in result_rules):
|
||
total += result_rules[rule.code]['total']
|
||
return total
|
||
return sum(self._l10n_at_linien(art).mapped('total'))
|
||
|
||
def _l10n_at_lohnsteuertage(self):
|
||
"""Lohnsteuertage des eigenen LZP: 30 je vollem Monat
|
||
(user decision; § 66 Abs 3 360-Tage-Jahr); Teilmonat =
|
||
Kalendertage der Beschäftigung inkl. arbeitsfreier Tage,
|
||
max 30."""
|
||
self.ensure_one()
|
||
erster = self.date_from.replace(day=1)
|
||
naechster = (erster + timedelta(days=32)).replace(day=1)
|
||
vollen_monat = (self.date_from == erster
|
||
and self.date_to == naechster - timedelta(days=1))
|
||
version = self.version_id
|
||
von = max(self.date_from,
|
||
version.contract_date_start or self.date_from)
|
||
bis = min(self.date_to, version.contract_date_end or self.date_to)
|
||
if von > bis:
|
||
return 0
|
||
if vollen_monat and von == self.date_from and bis == self.date_to:
|
||
return 30
|
||
return min((bis - von).days + 1, 30)
|
||
|
||
def _l10n_at_ytd_slaeufe(self):
|
||
"""Validierte, nicht stornierte Läufe des Kalenderjahres vor
|
||
diesem LZP (Rollungs-/Aufrollungsbasis; refundbewusst:
|
||
Negativ-Linien des Refund-Laufs neutralisieren den Original)."""
|
||
self.ensure_one()
|
||
jahr = self.date_to.year
|
||
return self.env['hr.payslip'].search([
|
||
('employee_id', '=', self.employee_id.id),
|
||
('date_to', '>=', date(jahr, 1, 1)),
|
||
('date_to', '<', self.date_from),
|
||
]).filtered(lambda p: p.state not in ('draft', 'cancel'))
|
||
|
||
def _l10n_at_ytd_linien_total(self, code):
|
||
"""YTD-Summe einer Regel über frühere Läufe (refundbewusst)."""
|
||
self.ensure_one()
|
||
total = 0.0
|
||
for slip in self._l10n_at_ytd_slaeufe():
|
||
total += sum(
|
||
slip.line_ids.filtered(
|
||
lambda l: l.code == code).mapped('total'))
|
||
return total
|
||
|
||
def _l10n_at_lohnsteuertage_ytd(self):
|
||
self.ensure_one()
|
||
return sum(slip._l10n_at_lohnsteuertage()
|
||
for slip in self._l10n_at_ytd_slaeufe()) \
|
||
+ self._l10n_at_lohnsteuertage()
|
||
|
||
# ------------------------------------------------------------------
|
||
# § 68 — Steuerfreiheiten (Buckets je Regel-Flag, versionierte Caps)
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_freibetraege_68(self, result_rules=None):
|
||
"""Steuerfreie Bezüge je LZP: {'szfn': x, 'ue_std': y} —
|
||
Abs 1: SFN-Zuschläge bis 400 € (600 € überw. Nachtarbeit);
|
||
Abs 2 iVm § 124b Z 440: ÜStd-Zuschläge bis 50 % Grundlohn,
|
||
erste 15 ÜStd, max 170 € (2026; ab 2027 10/120 — versioniert)."""
|
||
self.ensure_one()
|
||
ergebnis = {'szfn': 0.0, 'ue_std': 0.0}
|
||
grenzen = self._l10n_at_param('at_68_szfn_grenze')
|
||
cap_szfn = (grenzen['grenze_ueberwiegend']
|
||
if self.version_id.l10n_at_ueberw_nachtarbeit
|
||
else grenzen['grenze'])
|
||
ue = self._l10n_at_param('at_68_ue_std')
|
||
szfn = 0.0
|
||
betrag_ue, stunden_ue = 0.0, 0.0
|
||
for rule in self.struct_id.rule_ids:
|
||
werte = None
|
||
if result_rules is not None:
|
||
if rule.code in result_rules:
|
||
werte = result_rules[rule.code]
|
||
elif rule.l10n_at_bemessung != 'neutral':
|
||
line = self.line_ids.filtered(lambda l: l.code == rule.code)
|
||
if line:
|
||
werte = {'total': sum(line.mapped('total')),
|
||
'quantity': sum(line.mapped('quantity'))}
|
||
if not werte:
|
||
continue
|
||
anteil = (rule.l10n_at_zuschlagsanteil or 0.0) / 100.0
|
||
if rule.l10n_at_steuerbefreiung_68 == 'szfn':
|
||
szfn += werte['total'] * anteil
|
||
elif rule.l10n_at_steuerbefreiung_68 == 'ue_std':
|
||
betrag_ue += werte['total'] * anteil
|
||
stunden_ue += werte.get('quantity', 0.0)
|
||
ergebnis['szfn'] = min(max(szfn, 0.0), cap_szfn)
|
||
if stunden_ue > float(ue['stunden']):
|
||
betrag_ue = betrag_ue * float(ue['stunden']) / stunden_ue
|
||
ergebnis['ue_std'] = min(max(betrag_ue, 0.0), float(ue['betrag']))
|
||
return ergebnis
|
||
|
||
# ------------------------------------------------------------------
|
||
# § 3/§ 26 — Steuerfreiheit (Buckets je Regel-Flag, versionierte
|
||
# Caps; Jahres-Kumulative — Spieglung der §-49-SV-Seite)
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_st_frei_ytd(self, kategorie, menge=False):
|
||
"""Jahresverbrauch einer §-3/§-26-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_st_frei == kategorie)
|
||
if menge:
|
||
total += sum(linien.mapped('quantity'))
|
||
else:
|
||
total += sum(linien.mapped('total'))
|
||
return total
|
||
|
||
def _l10n_at_st_frei_freie_laufen(self, result_rules=None):
|
||
"""Steuerfreie Anteile laufender Bezüge dieses LZP: je Kategorie
|
||
Tages-/Jahresgrenzen (at_st_grenzen) minus Jahresverbrauch;
|
||
Freibeträge — nur der übersteigende Teil steuerpflichtig
|
||
(lb-sac-07/17, lb-ges-02, lb-sac-02)."""
|
||
self.ensure_one()
|
||
grenzen = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||
'at_st_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_st_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_st_frei_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'}.get(kategorie)
|
||
rest = max(
|
||
float(grenzen.get(pool))
|
||
- self._l10n_at_st_frei_ytd(kategorie), 0.0)
|
||
frei = min(total, rest)
|
||
frei_gesamt += max(frei, 0.0)
|
||
return frei_gesamt
|
||
|
||
# ------------------------------------------------------------------
|
||
# DN-SV (Bemessungsbestandteil § 62) — Basis/Sätze/Geringfügigkeit/
|
||
# §-49-Kumulative/DG-Seite: models/sozialversicherung.py (GP4)
|
||
# ------------------------------------------------------------------
|
||
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);
|
||
Wien-WF über Satzvektor (l10n_at_wf_wien/Bundesland, GP4)."""
|
||
self.ensure_one()
|
||
return self._l10n_at_sv_beitrag('dn', komponente, result_rules)
|
||
|
||
def _l10n_at_svdn_gesamt(self, result_rules=None):
|
||
self.ensure_one()
|
||
return sum(self._l10n_at_svdn(k, result_rules)
|
||
for k in ('kv', 'pv', 'av', 'ak', 'wf'))
|
||
|
||
def _l10n_at_svdn_basis_sz(self, result_rules=None):
|
||
"""SV-BG sonstige Bezüge: HBG monatlich und SZ-Jahreshöchstbetrag
|
||
(13.860) minus YTD-SZ-Basen (Anspruchsprinzip-Vorlauf GP4)."""
|
||
self.ensure_one()
|
||
sz = max(self._l10n_at_sv_brutto('sonstig', result_rules), 0.0)
|
||
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich'))
|
||
jahreshbg = float(self._l10n_at_param('at_sv_hbg_sz_jaehrlich'))
|
||
for slip in self._l10n_at_ytd_slaeufe():
|
||
jahreshbg -= max(slip._l10n_at_sv_brutto('sonstig'), 0.0)
|
||
return min(sz, hbg, max(jahreshbg, 0.0))
|
||
|
||
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); 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(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
|
||
|
||
# ------------------------------------------------------------------
|
||
# Bemessung (user-Formeln) — laufend / sonstig
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_bemessung_laufen(self, result_rules=None):
|
||
"""LSt-Bemessung laufend: Arbeitslohn − SV-DN (§ 62-Kette,
|
||
inkl. AK/WF) − steuerfreie Bezüge § 68 − steuerfreie Sachbezüge
|
||
§ 3/§ 26 (Freibeträge, Jahres-Kumulative). Der volle
|
||
Arbeitslohn ist lohnsteuerlich unbegrenzt (keine HBG-Cap für
|
||
die LSt; die Cap gilt nur für die SV-Sätze)."""
|
||
self.ensure_one()
|
||
freibe = self._l10n_at_freibetraege_68(result_rules)
|
||
return (self._l10n_at_brutto('laufend', result_rules)
|
||
- self._l10n_at_svdn_gesamt(result_rules)
|
||
- freibe['szfn'] - freibe['ue_std']
|
||
- self._l10n_at_st_frei_freie_laufen(result_rules))
|
||
|
||
def _l10n_at_sz_bemessung(self, result_rules=None):
|
||
"""LSt-Bemessung sonstiger Bezug: Bezug − SV-DN (Abs 12);
|
||
Freibetrag/Sechstel-Portionierung erfolgt in
|
||
_l10n_at_lst_sonstige."""
|
||
self.ensure_one()
|
||
return (self._l10n_at_brutto('sonstig', result_rules)
|
||
- self._l10n_at_svdn_sz(result_rules))
|
||
|
||
def _l10n_at_brutto_laufen_ytd(self, result_rules=None):
|
||
"""Kumulierte laufende Bruttobezüge des Jahres (Sechstel-Basis
|
||
ist Brutto, § 67 Abs 2 — inkl. Auszahlungsmonat-Laufendem)."""
|
||
self.ensure_one()
|
||
total = self._l10n_at_brutto('laufend', result_rules)
|
||
for slip in self._l10n_at_ytd_slaeufe():
|
||
total += slip._l10n_at_brutto('laufend')
|
||
return total
|
||
|
||
def _l10n_at_sechstel(self, result_rules=None):
|
||
"""Jahressechstel = kumulierte laufende Brutto × 60 ÷
|
||
Lohnsteuertage (YTD inkl. aktueller LZP)."""
|
||
self.ensure_one()
|
||
tage = self._l10n_at_lohnsteuertage_ytd()
|
||
if not tage:
|
||
return 0.0
|
||
return self._l10n_at_brutto_laufen_ytd(result_rules) * 60.0 / tage
|
||
|
||
# ------------------------------------------------------------------
|
||
# § 33 Tarif + Absetzbeträge
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_tarif(self, jbg):
|
||
"""§ 33 Abs 1: Zonentarif auf die Jahresbemessungsgrundlage."""
|
||
self.ensure_one()
|
||
zonen = self._l10n_at_param('at_lst_tarif')['zonen']
|
||
steuer = 0.0
|
||
for index, (grenze, satz) in enumerate(zonen):
|
||
obere = zonen[index + 1][0] if index + 1 < len(zonen) else jbg
|
||
if jbg > grenze:
|
||
steuer += (min(jbg, obere) - grenze) * float(satz) / 100.0
|
||
return steuer
|
||
|
||
def _l10n_at_absetzbetraege(self, jbg):
|
||
"""§ 66 zitierte Absetzbeträge: Verkehrsabsetzbetrag (496 €)
|
||
+ AVB/AVAB je Vertrags-Feld und Kinderzahl (§ 33 Abs 4/5)."""
|
||
self.ensure_one()
|
||
absetz = self._l10n_at_param('at_lst_absetzbetraege')
|
||
betrag = float(absetz['verkehrsabsetzbetrag'])
|
||
fam = self.version_id.l10n_at_familienabsetzbetrag
|
||
kinder = self.version_id.children or 0
|
||
if fam in ('avb', 'avab') and kinder > 0:
|
||
if kinder == 1:
|
||
betrag += float(absetz['avb_kind1'])
|
||
else:
|
||
betrag += float(absetz['avb_kinder2']) \
|
||
+ (kinder - 2) * float(absetz['avb_weiteres_kind'])
|
||
return betrag
|
||
|
||
# ------------------------------------------------------------------
|
||
# § 66 Monatslohnsteuer — kumulativ mit Rollung
|
||
# ------------------------------------------------------------------
|
||
|
||
def _l10n_at_lst_laufend(self, result_rules=None):
|
||
"""LSt auf laufende Bezüge (§ 66): kumulative Hochrechnung
|
||
(360-Tage-Jahr) minus bereits einbehaltene LSt — Vormonats-
|
||
korrekturen rollen automatisch in den Zuflussmonat (Aufrollung,
|
||
user decision). §-67-Bezüge unter der Freigrenze sowie der
|
||
Sechstel-Überschuss (§ 67 Abs 10) laufen tarifisch hier mit."""
|
||
self.ensure_one()
|
||
bem_ytd = self._l10n_at_bemessung_laufen(result_rules)
|
||
sz_tarifisch_aktuell = self._l10n_at_sz_tarifisch_aktuell(result_rules)
|
||
for slip in self._l10n_at_ytd_slaeufe():
|
||
bem_ytd += (slip._l10n_at_bemessung_laufen()
|
||
+ slip._l10n_at_sz_tarifisch_aktuell())
|
||
bem_ytd += sz_tarifisch_aktuell
|
||
tage = self._l10n_at_lohnsteuertage_ytd()
|
||
if not tage or bem_ytd <= 0:
|
||
return 0.0
|
||
faktor = 360.0 / tage
|
||
pauschalen = self._l10n_at_param('at_lst_pauschalen')
|
||
jbg = (bem_ytd * faktor
|
||
- float(pauschalen['werbepauschbetrag'])
|
||
- float(pauschalen['sonderausgaben_pauschbetrag']))
|
||
steuer = max(0.0,
|
||
self._l10n_at_tarif(jbg) - self._l10n_at_absetzbetraege(jbg))
|
||
soll_kumuliert = steuer / faktor
|
||
bereits = self._l10n_at_ytd_linien_total('LST_LFD')
|
||
return max(0.0, soll_kumuliert - bereits)
|
||
|
||
def _l10n_at_sz_tarifisch_aktuell(self, result_rules=None):
|
||
"""Anteil des aktuellen §-67-Bezugs, der tarifisch (LSt-lfd)
|
||
läuft: (a) gesamter Bezug bei Sechstel ≤ Freigrenze (2.615 €,
|
||
Pauschalierung unterbleibt) oder (b) Überschuss über das
|
||
Jahressechstel (§ 67 Abs 10)."""
|
||
self.ensure_one()
|
||
sz_bem = self._l10n_at_sz_bemessung(result_rules)
|
||
if sz_bem <= 0:
|
||
return 0.0
|
||
sechstel = self._l10n_at_sechstel(result_rules)
|
||
freigrenze = float(self._l10n_at_param('at_lst_67_freigrenze'))
|
||
if sechstel <= freigrenze:
|
||
return sz_bem
|
||
ytd_sz_bem = self._l10n_at_ytd_sz_bemessung()
|
||
verbleibend = max(sechstel - ytd_sz_bem, 0.0)
|
||
return max(sz_bem - verbleibend, 0.0)
|
||
|
||
def _l10n_at_ytd_sz_bemessung(self):
|
||
"""Kumulierte §-67-Bemessungen früherer Läufe (für Sechstel-
|
||
Verbrauch und Aufrollung)."""
|
||
self.ensure_one()
|
||
total = 0.0
|
||
for slip in self._l10n_at_ytd_slaeufe():
|
||
total += slip._l10n_at_sz_bemessung()
|
||
return total
|
||
|
||
# ------------------------------------------------------------------
|
||
# § 67 — sonstige Bezüge (Fixsätze im Sechstel + Aufrollung)
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_lst_sonstige(self, result_rules=None):
|
||
"""LSt auf sonstige Bezüge: Fixsätze 0/6/27/35,75 % auf die
|
||
Bemessung (Abs 12-SV vorher, Freibetrag 620 € jahreskumulativ)
|
||
innerhalb des Jahressechstels; Neuberechnung des gesamten
|
||
Jahres-SZ-Steuer bei jeder SZ-Zahlung (Günstig-Aufrollung,
|
||
lb-son-04): Soll = f(Jahres-Bemessung, aktuelles Sechstel)
|
||
minus bereits gezahlte SZ-Steuer."""
|
||
self.ensure_one()
|
||
sz_bem = self._l10n_at_sz_bemessung(result_rules)
|
||
if sz_bem <= 0:
|
||
return 0.0
|
||
sechstel = self._l10n_at_sechstel(result_rules)
|
||
freigrenze = float(self._l10n_at_param('at_lst_67_freigrenze'))
|
||
if sechstel <= freigrenze:
|
||
return 0.0 # tarifisch — läuft über LST_LFD
|
||
ytd_bem = max(self._l10n_at_ytd_sz_bemessung(), 0.0)
|
||
total_in_sechstel = min(ytd_bem + sz_bem, sechstel)
|
||
fixsaetze = self._l10n_at_param('at_lst_67_fixsaetze')
|
||
freibetrag = float(fixsaetze['freibetrag'])
|
||
soll_steuer = 0.0
|
||
offen = max(total_in_sechstel - freibetrag, 0.0)
|
||
for breite, satz in fixsaetze['pakete']:
|
||
portion = min(offen, float(breite))
|
||
soll_steuer += portion * float(satz) / 100.0
|
||
offen -= portion
|
||
if offen <= 0:
|
||
break
|
||
bereits = self._l10n_at_ytd_linien_total('LST_SZ')
|
||
return max(soll_steuer - bereits, 0.0)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Nachzahlungsfälle (plan 4.3 — Rollung automatisch; Fünftelregelung
|
||
# § 67 Abs 8 lit c als Utility; 15.2.-Cutoff/Willkür→Tarif GP8)
|
||
# ------------------------------------------------------------------
|
||
def _l10n_at_fuenftelregelung(self, betrag):
|
||
"""§ 67 Abs 8 lit c: zwangsläufige Verschiebung eines sonstigen
|
||
Bezugs über den 15. 2. — 1/5 des begünstigten Teils steuerfrei,
|
||
4/5 mit Tarif (Utility für GP8-Nachzahlungskonfiguration)."""
|
||
self.ensure_one()
|
||
return betrag * 4.0 / 5.0 |