[IMP] l10n_at_hr_payroll_private: Sachbezüge (AP12)

This commit is contained in:
2026-09-14 12:22:15 +02:00
parent 018554b215
commit 8efea77b6c
20 changed files with 1606 additions and 23 deletions
@@ -27,7 +27,8 @@ class LohnsteuerTest(TransactionCase):
})
def _regel(self, code, betrag, bemessung='laufend', flag68='keine',
anteil=0.0, quantity='1.0', python=None):
anteil=0.0, quantity='1.0', python=None, st_frei='keine',
sv_art='folgt'):
vals = {
'name': code,
'code': code,
@@ -37,6 +38,8 @@ class LohnsteuerTest(TransactionCase):
'l10n_at_bemessung': bemessung,
'l10n_at_steuerbefreiung_68': flag68,
'l10n_at_zuschlagsanteil': anteil,
'l10n_at_st_frei': st_frei,
'l10n_at_sv_art': sv_art,
}
if python:
vals.update({'amount_select': 'code',
@@ -282,4 +285,114 @@ class LohnsteuerTest(TransactionCase):
self.assertAlmostEqual(
slip._l10n_at_sz_tarifisch_aktuell(), sz_bem - 4000.0, places=2)
self.assertAlmostEqual(
slip._l10n_at_lst_sonstige(), (4000.0 - 620.0) * 0.06, places=1)
slip._l10n_at_lst_sonstige(), (4000.0 - 620.0) * 0.06, places=1)
# ------------------------------------------------------------------
# § 3/§ 26 — steuerfreie Sachbezüge (Freibeträge, Jahres-Kumulative)
# ------------------------------------------------------------------
def test_13_st_frei_mahlzeit(self):
"""Mahlzeiten-Gutschein 11 €/AT × 10 AT = 110 €: 80 € steuerfrei
(8 €/AT, § 3 Abs 1 Z 17), 30 € steuerpflichtig; LSt-Bemessung
um 80 € reduziert (lb-sac-17)."""
employee = self._employee()
self._regel('LFD', 2000.0)
self._regel('MAH', 11.0, quantity='10.0', st_frei='mahlzeit')
slip = self._slip(employee, 1)
self.assertAlmostEqual(
slip._l10n_at_st_frei_freie_laufen(), 80.0, places=2)
svdn = slip._l10n_at_svdn_gesamt()
self.assertAlmostEqual(
slip._l10n_at_bemessung_laufen(),
2000.0 + 110.0 - svdn - 80.0, places=2)
def test_14_st_frei_jahrespools(self):
"""Jahres-Kumulative: Betriebsveranstaltung 365 €/J (Freibetrag)
und Sachzuwendung/Jubiläum 186 €/J als separate Pools
(§ 3 Abs 1 Z 14; lb-sac-07, lb-ges-02)."""
employee = self._employee()
self._regel('LFD', 2000.0)
self._regel('BV', 200.0, st_frei='betriebsveranstaltung')
slip_a = self._slip(employee, 1, confirm=True)
self.assertAlmostEqual(
slip_a._l10n_at_st_frei_freie_laufen(), 200.0, places=2)
# Februar: 165 € Rest (365 200) → 165 frei, 35 steuerpflichtig
slip_b = self._slip(employee, 2, confirm=True)
self.assertAlmostEqual(
slip_b._l10n_at_st_frei_freie_laufen(), 165.0, places=2)
# März: Pool verbraucht → 0 frei, 200 steuerpflichtig
slip_c = self._slip(employee, 3)
self.assertAlmostEqual(
slip_c._l10n_at_st_frei_freie_laufen(), 0.0, places=2)
# Sachzuwendung separater Pool: 300 € → 186 frei, 114 pflichtig
self._regel('GSC', 300.0, st_frei='jubilaeum')
slip_d = self._slip(employee, 4)
self.assertAlmostEqual(
slip_d._l10n_at_st_frei_freie_laufen(), 186.0, places=2)
def test_15_st_frei_homeoffice(self):
"""Homeoffice 3 €/Tag, max 100 Tage/Jahr (§ 26 Z 9): 70 Tage im
Jänner (210 frei), Februar nur 30 Tage frei (90), 40 Tage × 3 =
120 steuerpflichtig."""
employee = self._employee()
self._regel('LFD', 2000.0)
self._regel('HO', 3.0, quantity='70.0', st_frei='homeoffice')
slip_a = self._slip(employee, 1, confirm=True)
self.assertAlmostEqual(
slip_a._l10n_at_st_frei_freie_laufen(), 210.0, places=2)
# Februar: 30 Tage Rest (100 70) → 90 frei, 120 steuerpflichtig
slip_b = self._slip(employee, 2)
self.assertAlmostEqual(
slip_b._l10n_at_st_frei_freie_laufen(), 90.0, places=2)
def test_16_st_frei_neutral(self):
"""Ohne st_frei-Flag keine Wirkung auf die LSt-Bemessung
(Regression)."""
employee = self._employee()
self._regel('LFD', 2000.0)
slip = self._slip(employee, 1)
self.assertAlmostEqual(slip._l10n_at_st_frei_freie_laufen(), 0.0)
svdn = slip._l10n_at_svdn_gesamt()
self.assertAlmostEqual(
slip._l10n_at_bemessung_laufen(), 2000.0 - svdn, places=2)
def test_17_sv_art_entkoppelt_lst_art(self):
"""SV-Art folgt standardmäßig der LSt-Art; Drittlohn kann nur
SV-laufend sein, Hardware-Verkauf LSt-sonstig/SV-laufend."""
employee = self._employee()
self._regel('LFD', 100.0)
self._regel('DRITT', 300.0, bemessung='neutral', sv_art='laufend')
self._regel('HW', 200.0, bemessung='sonstig', sv_art='laufend')
slip = self._slip(employee, 1)
self.assertAlmostEqual(
slip._l10n_at_sv_brutto('laufend'), 600.0, places=2)
self.assertAlmostEqual(
slip._l10n_at_sv_brutto('sonstig'), 0.0, places=2)
self.assertAlmostEqual(slip._l10n_at_svdn_basis_sz(), 0.0, places=2)
self.assertAlmostEqual(
slip._l10n_at_brutto('laufend'), 100.0, places=2)
self.assertAlmostEqual(
slip._l10n_at_brutto('sonstig'), 200.0, places=2)
def test_18_sv_art_folgt_ist_neutral(self):
"""Der Default 'folgt' erhält bestehende laufende und sonstige
SV-Zuordnungen unverändert."""
employee = self._employee()
self._regel('LFD', 100.0)
self._regel('SZ', 200.0, bemessung='sonstig')
slip = self._slip(employee, 1)
self.assertAlmostEqual(
slip._l10n_at_sv_brutto('laufend'), 100.0, places=2)
self.assertAlmostEqual(
slip._l10n_at_sv_brutto('sonstig'), 200.0, places=2)
def test_19_sv_art_keine_nur_lst(self):
"""SV-Art 'keine' schließt eine Regel ausschließlich aus der
SV aus; die deklarierte LSt-Bemessung bleibt unverändert."""
employee = self._employee()
self._regel('LFD', 100.0)
self._regel('NUR_LST', 300.0, sv_art='keine')
slip = self._slip(employee, 1)
self.assertAlmostEqual(
slip._l10n_at_sv_brutto('laufend'), 100.0, places=2)
self.assertAlmostEqual(
slip._l10n_at_brutto('laufend'), 400.0, places=2)