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_private: Sachbezüge (AP12)
This commit is contained in:
@@ -52,6 +52,24 @@ class HrSalaryRule(models.Model):
|
||||
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):
|
||||
@@ -197,6 +215,76 @@ class HrPayslip(models.Model):
|
||||
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)
|
||||
@@ -217,11 +305,11 @@ class HrPayslip(models.Model):
|
||||
"""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_brutto('sonstig', result_rules), 0.0)
|
||||
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_brutto('sonstig'), 0.0)
|
||||
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):
|
||||
@@ -242,14 +330,16 @@ class HrPayslip(models.Model):
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_bemessung_laufen(self, result_rules=None):
|
||||
"""LSt-Bemessung laufend: Arbeitslohn − SV-DN (§ 62-Kette,
|
||||
inkl. AK/WF) − steuerfreie Bezüge § 68. Der volle Arbeitslohn
|
||||
ist lohnsteuerlich unbegrenzt (keine HBG-Cap für die LSt;
|
||||
die Cap gilt nur für die SV-Sätze)."""
|
||||
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'])
|
||||
- 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);
|
||||
|
||||
@@ -37,6 +37,18 @@ from odoo.tools.safe_eval import safe_eval
|
||||
class HrSalaryRule(models.Model):
|
||||
_inherit = 'hr.salary.rule'
|
||||
|
||||
l10n_at_sv_art = fields.Selection(
|
||||
[('folgt', 'Folgt AT LSt-Bemessungsart'),
|
||||
('laufend', 'Laufender SV-Bezug'),
|
||||
('sonstig', 'Sonstiger SV-Bezug'),
|
||||
('keine', 'Kein SV-Bezug')],
|
||||
string='AT SV-Bemessungsart',
|
||||
default='folgt',
|
||||
help='Steuert ausschließlich die SV-Zuordnung. Standard „Folgt“ '
|
||||
'übernimmt die AT LSt-Bemessungsart und ist damit '
|
||||
'rückwärtskompatibel. Abweichung für Drittlohn oder '
|
||||
'abweichende LSt-/SV-Zuordnung, z. B. Bonusmeilen oder '
|
||||
'Hardware-Verkauf.')
|
||||
l10n_at_sv49_frei = fields.Selection(
|
||||
[('keine', 'Keine §-49-Abs-3-Befreiung'),
|
||||
('mahlzeit', 'Mahlzeiten-Gutschein (§ 49 Abs 3 Z 12; 8 €/AT)'),
|
||||
@@ -254,7 +266,7 @@ class HrPayslip(models.Model):
|
||||
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'):
|
||||
or self._l10n_at_sv_art(rule) != 'laufend'):
|
||||
continue
|
||||
werte = None
|
||||
if result_rules is not None:
|
||||
@@ -297,11 +309,33 @@ class HrPayslip(models.Model):
|
||||
# ------------------------------------------------------------------
|
||||
# Laufende Beitragsgrundlage — je Beitragsmonat (Anspruchsprinzip)
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_sv_art(self, rule):
|
||||
"""Effektive SV-Art einer Regel; Standard folgt der LSt-Art."""
|
||||
self.ensure_one()
|
||||
return (rule.l10n_at_bemessung if rule.l10n_at_sv_art == 'folgt'
|
||||
else rule.l10n_at_sv_art)
|
||||
|
||||
def _l10n_at_sv_brutto(self, art, result_rules=None):
|
||||
"""SV-Brutto je Art; abweichende SV-Zuordnung bleibt von der
|
||||
LSt-Bemessungsart getrennt (Drittlohn/Sonderfälle)."""
|
||||
self.ensure_one()
|
||||
total = 0.0
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if self._l10n_at_sv_art(rule) != art:
|
||||
continue
|
||||
if result_rules is not None:
|
||||
if rule.code in result_rules:
|
||||
total += result_rules[rule.code].get('total', 0.0)
|
||||
else:
|
||||
total += sum(self.line_ids.filtered(
|
||||
lambda l: l.code == rule.code).mapped('total'))
|
||||
return total
|
||||
|
||||
def _l10n_at_sv_basis_roh(self, result_rules=None):
|
||||
"""Laufender Arbeitslohn minus beitragsfreie §-49-Anteile
|
||||
"""Laufendes SV-Brutto minus beitragsfreie §-49-Anteile
|
||||
(vor HBG-Cap; Geringfügigkeits-/Staffel-Basis)."""
|
||||
self.ensure_one()
|
||||
roh = self._l10n_at_brutto('laufend', result_rules) \
|
||||
roh = self._l10n_at_sv_brutto('laufend', result_rules) \
|
||||
- self._l10n_at_sv49_freie_laufen(result_rules)
|
||||
return max(roh, 0.0)
|
||||
|
||||
@@ -312,7 +346,7 @@ class HrPayslip(models.Model):
|
||||
self.ensure_one()
|
||||
gruppen = {}
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if rule.l10n_at_bemessung != 'laufend':
|
||||
if self._l10n_at_sv_art(rule) != 'laufend':
|
||||
continue
|
||||
werte = None
|
||||
if result_rules is not None:
|
||||
|
||||
Reference in New Issue
Block a user