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:
@@ -13,3 +13,5 @@ from . import test_efzg_private
|
||||
from . import test_mutterschutz_private
|
||||
from . import test_aeb_private
|
||||
from . import test_pfaendung_private
|
||||
from . import test_sachbezuege_private
|
||||
from . import test_sachbezuege2_private
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP12b: wertgebundene Sachbezüge — Dienstwagen, Dienstwohnung,
|
||||
Parkplatz, Handy und Bonusmeilen (lb-sac-03/04/09/18/15/08)."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class Sachbezug2PrivateTest(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.structure = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_at_privat')
|
||||
self.kv = self.env.ref('l10n_at_hr_payroll_private.kv_si_2203')
|
||||
self.gruppe = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv2203_grp_ii')
|
||||
|
||||
def _employee(self, values=None):
|
||||
employee = self.env['hr.employee'].create({'name': 'Test AP12b'})
|
||||
version = self.env['hr.version'].search(
|
||||
[('employee_id', '=', employee.id)], limit=1)
|
||||
version.write({
|
||||
'contract_date_start': date(2020, 1, 1),
|
||||
'kv_id': self.kv.id,
|
||||
'kv_gruppe_id': self.gruppe.id,
|
||||
'erfahrungsstufe': 1,
|
||||
**(values or {}),
|
||||
})
|
||||
return employee
|
||||
|
||||
def _slip(self, employee, monat=1):
|
||||
erster = date(2026, monat, 1)
|
||||
naechster = (erster + timedelta(days=32)).replace(day=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Test-AP12b %d' % monat,
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': erster,
|
||||
'date_to': naechster - timedelta(days=1),
|
||||
})
|
||||
slip.compute_sheet()
|
||||
return slip
|
||||
|
||||
def _input(self, slip, code, amount):
|
||||
input_type = self.env['hr.payslip.input.type'].search(
|
||||
[('code', '=', code)], limit=1)
|
||||
self.env['hr.payslip.input'].create({
|
||||
'payslip_id': slip.id,
|
||||
'input_type_id': input_type.id,
|
||||
'amount': amount,
|
||||
})
|
||||
slip.compute_sheet()
|
||||
|
||||
@staticmethod
|
||||
def _total(slip, code):
|
||||
return sum(slip.line_ids.filtered(
|
||||
lambda line: line.code == code).mapped('total'))
|
||||
|
||||
def test_01_dienstwagen_co2_halb_und_elektro(self):
|
||||
"""§ 4 SBWVO: 36.700 €, EZ 2025, 112 g/km → 1,5 % = 550,50;
|
||||
halber SB 275,25; CO₂ 130 → 2 % = 734; 0-g-Fahrzeug = 0."""
|
||||
basis = {
|
||||
'l10n_at_sb_wagen_aktiv': True,
|
||||
'l10n_at_sb_wagen_ak': 36700.0,
|
||||
'l10n_at_sb_wagen_erstzulassung': date(2025, 1, 1),
|
||||
}
|
||||
emp_niedrig = self._employee({**basis, 'l10n_at_sb_wagen_co2': 112})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(emp_niedrig), 'SB_WAGEN'), 550.50, places=2)
|
||||
emp_halb = self._employee({**basis, 'l10n_at_sb_wagen_co2': 112,
|
||||
'l10n_at_sb_wagen_halb': True})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(emp_halb), 'SB_WAGEN'), 275.25, places=2)
|
||||
emp_hoch = self._employee({**basis, 'l10n_at_sb_wagen_co2': 130})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(emp_hoch), 'SB_WAGEN'), 734.0, places=2)
|
||||
emp_elektrisch = self._employee({**basis, 'l10n_at_sb_wagen_co2': 0})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(emp_elektrisch), 'SB_WAGEN'), 0.0, places=2)
|
||||
|
||||
def test_02_dienstwagen_kostenbeitrag(self):
|
||||
"""Laufender Kostenbeitrag mindert nach § 4 Abs 7 SBWVO:
|
||||
550,50 − 300 = 250,50 € (lb-sac-04)."""
|
||||
employee = self._employee({
|
||||
'l10n_at_sb_wagen_aktiv': True,
|
||||
'l10n_at_sb_wagen_ak': 36700.0,
|
||||
'l10n_at_sb_wagen_erstzulassung': date(2025, 1, 1),
|
||||
'l10n_at_sb_wagen_co2': 112.0,
|
||||
'l10n_at_sb_wagen_kostenbeitrag': 300.0,
|
||||
})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(employee), 'SB_WAGEN'), 250.50, places=2)
|
||||
|
||||
def test_03_dienstwohnung_und_parkplatz(self):
|
||||
"""§ 2/§ 4a SBWVO: Wien 72,4 m² = 482,91; Kleinwohnung 30 m²
|
||||
= 0, 40 m² = 173,42; Parkplatz 14,53 abzüglich Kostenbeitrag."""
|
||||
common = {
|
||||
'l10n_at_sb_wohnung_aktiv': True,
|
||||
'l10n_at_sb_wohnung_bundesland': 'wien',
|
||||
'l10n_at_sb_wohnung_arbeitsplatznah': True,
|
||||
'l10n_at_sb_wohnung_kein_mittelpunkt': True,
|
||||
'l10n_at_sb_wohnung_ueberlassung_ab': date(2026, 1, 1),
|
||||
}
|
||||
employee = self._employee({**common, 'l10n_at_sb_wohnung_flaeche': 72.4})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(employee), 'SB_WOHNUNG'), 482.91, places=2)
|
||||
employee_klein = self._employee({**common, 'l10n_at_sb_wohnung_flaeche': 30})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(employee_klein), 'SB_WOHNUNG'), 0.0, places=2)
|
||||
employee_kombiniert = self._employee({
|
||||
'l10n_at_sb_wohnung_aktiv': True,
|
||||
'l10n_at_sb_wohnung_bundesland': 'wien',
|
||||
'l10n_at_sb_wohnung_flaeche': 100,
|
||||
'l10n_at_sb_wohnung_norm': False,
|
||||
'l10n_at_sb_wohnung_besorger': True,
|
||||
})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(employee_kombiniert), 'SB_WOHNUNG'),
|
||||
296.815, places=2)
|
||||
employee_abschlag = self._employee({**common, 'l10n_at_sb_wohnung_flaeche': 40})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(employee_abschlag), 'SB_WOHNUNG'), 173.42,
|
||||
places=2)
|
||||
employee_parkplatz = self._employee({
|
||||
'l10n_at_sb_parkplatz_aktiv': True,
|
||||
'l10n_at_sb_parkplatz_kostenbeitrag': 5.0,
|
||||
})
|
||||
self.assertAlmostEqual(
|
||||
self._total(self._slip(employee_parkplatz), 'SB_PARKPLATZ'), 9.53,
|
||||
places=2)
|
||||
|
||||
def test_04_handy_und_gebrauchtverkauf(self):
|
||||
"""Handy-Mehrkosten sind nur bei überwiegend privater Nutzung
|
||||
laufend; Verkauf unter Buchwert ist SV-laufend/LSt-sonstig."""
|
||||
emp_handy = self._employee({
|
||||
'l10n_at_sb_handy_profil': 'ueberwiegend_privat'})
|
||||
slip_handy = self._slip(emp_handy)
|
||||
self._input(slip_handy, 'ATP_SB_HANDY', 80.0)
|
||||
self.assertAlmostEqual(self._total(slip_handy, 'SB_HANDY'), 80.0)
|
||||
emp_beruflich = self._employee()
|
||||
slip_beruflich = self._slip(emp_beruflich)
|
||||
self._input(slip_beruflich, 'ATP_SB_HANDY', 80.0)
|
||||
self.assertAlmostEqual(self._total(slip_beruflich, 'SB_HANDY'), 0.0)
|
||||
|
||||
emp_verkauf = self._employee()
|
||||
slip_verkauf = self._slip(emp_verkauf)
|
||||
slip_basis = self._slip(self._employee())
|
||||
self._input(slip_verkauf, 'ATP_SB_GEBRAUCHT', 700.0)
|
||||
self.assertAlmostEqual(self._total(slip_verkauf, 'SB_GEBRAUCHT'), 700.0)
|
||||
self.assertAlmostEqual(
|
||||
slip_verkauf._l10n_at_sv_basis_roh()
|
||||
- slip_basis._l10n_at_sv_basis_roh(), 700.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_verkauf._l10n_at_svdn_basis_sz(), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_verkauf._l10n_at_brutto('sonstig')
|
||||
- slip_basis._l10n_at_brutto('sonstig'), 700.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_verkauf._at_lnk_basis('flaf')
|
||||
- slip_basis._at_lnk_basis('flaf'), 700.0, places=2)
|
||||
|
||||
def test_05_bonusmeilen_nur_sv(self):
|
||||
"""Drittlohn aus privat eingelösten Bonusmeilen: ausschließlich
|
||||
laufende SV, kein LSt-/LNK- oder Auszahlungsnettoanspruch."""
|
||||
slip = self._slip(self._employee())
|
||||
basis = self._slip(self._employee())
|
||||
self._input(slip, 'ATP_SB_BONUSMEILEN', 300.0)
|
||||
self.assertAlmostEqual(self._total(slip, 'SB_BONUSMEILEN'), 300.0)
|
||||
self.assertAlmostEqual(
|
||||
slip._l10n_at_sv_basis_roh() - basis._l10n_at_sv_basis_roh(),
|
||||
300.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip._at_lnk_basis('flaf') - basis._at_lnk_basis('flaf'),
|
||||
0.0, places=2)
|
||||
svdn_diff = (slip._l10n_at_svdn_gesamt()
|
||||
- basis._l10n_at_svdn_gesamt())
|
||||
# Der Drittlohn selbst ist nicht LSt-pflichtig; seine DN-SV ist
|
||||
# jedoch nach der bestehenden §-62-Kette LSt-BG-mindernd.
|
||||
self.assertAlmostEqual(
|
||||
slip._l10n_at_bemessung_laufen()
|
||||
- basis._l10n_at_bemessung_laufen(), -svdn_diff, places=2)
|
||||
lst_diff = self._total(slip, 'LST_LFD') - self._total(basis, 'LST_LFD')
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip, 'NET') - self._total(basis, 'NET'),
|
||||
-svdn_diff - lst_diff, places=1)
|
||||
@@ -0,0 +1,279 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP12a: steuerfreie Sachbezüge auf der privatwirtschaftlichen
|
||||
Structure end-to-end — manuelle SB-Eingaben (ATP_SB_*) → SB_*-Regeln
|
||||
→ §-49-Abs-3-SV- und §-3/§-26-LSt-Freibeträge (Freibeträge, Jahres-
|
||||
Kumulative je DN, lohnnebenkostenfrei): Mahlzeiten (lb-sac-17),
|
||||
Betriebsveranstaltungen (lb-sac-07), Sachzuwendungen (lb-ges-02),
|
||||
Mitarbeiterrabatte (lb-sac-02), Homeoffice/Telearbeit (lb-tel-04/05)."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class SachbezugPrivateTest(TransactionCase):
|
||||
|
||||
SB_CODES = ('SB_MAHLZEIT', 'SB_BETRIEBSVERANSTALTUNG',
|
||||
'SB_SACHZUWENDUNG', 'SB_RABATT', 'SB_HOMEOFFICE')
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.structure = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_at_privat')
|
||||
self.kv2203 = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv_si_2203')
|
||||
self.gruppe_ii = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv2203_grp_ii')
|
||||
self.calendar = self.env['resource.calendar'].create({
|
||||
'name': 'AT Test SB 40h Mo-Fr',
|
||||
'tz': 'Europe/Vienna',
|
||||
'hours_per_day': 8,
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Tag %d' % wd, 'dayofweek': str(wd),
|
||||
'hour_from': 8.0, 'hour_to': 16.0})
|
||||
for wd in range(5)
|
||||
],
|
||||
})
|
||||
self.env.company.resource_calendar_id = self.calendar
|
||||
|
||||
def _employee(self):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Test SB privat',
|
||||
})
|
||||
version = self.env['hr.version'].search(
|
||||
[('employee_id', '=', employee.id)], limit=1)
|
||||
if not version:
|
||||
version = self.env['hr.version'].create({
|
||||
'employee_id': employee.id,
|
||||
'date_start': date(2020, 1, 1)})
|
||||
version.write({
|
||||
'contract_date_start': date(2020, 1, 1),
|
||||
'kv_id': self.kv2203.id,
|
||||
'kv_gruppe_id': self.gruppe_ii.id,
|
||||
'erfahrungsstufe': 1,
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
})
|
||||
return employee
|
||||
|
||||
def _slip(self, employee, monat, jahr=2026):
|
||||
erster = date(jahr, monat, 1)
|
||||
naechster = (erster + timedelta(days=32)).replace(day=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Test-SB-Privat %d/%d' % (monat, jahr),
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': erster,
|
||||
'date_to': naechster - timedelta(days=1),
|
||||
})
|
||||
slip.compute_sheet()
|
||||
return slip
|
||||
|
||||
def _sb_input(self, slip, code, amount):
|
||||
"""SB-Eingabe anhängen und Lauf neu berechnen; liefert die
|
||||
zugehörige SB-Zeile (Regel-Code = SB_ + Eingabe-Code-Suffix)."""
|
||||
typ = self.env['hr.payslip.input.type'].search(
|
||||
[('code', '=', code)], limit=1)
|
||||
self.env['hr.payslip.input'].create({
|
||||
'payslip_id': slip.id,
|
||||
'input_type_id': typ.id,
|
||||
'amount': amount,
|
||||
})
|
||||
slip.compute_sheet()
|
||||
rule_code = 'SB_%s' % code.removeprefix('ATP_SB_')
|
||||
return slip.line_ids.filtered(lambda l: l.code == rule_code)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-SB-1/2: Mahlzeiten-Gutscheine (8 €/AT, lb-sac-17)
|
||||
# ------------------------------------------------------------------
|
||||
def test_01_mahlzeit_voll_frei(self):
|
||||
"""10 Gutschein-Arbeitstage à 8 € (at_sb_werte.mahlzeit_wert):
|
||||
SB-Zeile 80 €, SV- und LSt-Basis wie ohne SB, LNK frei
|
||||
(§ 3 Z 17 / § 49 Z 12, lb-sac-17)."""
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
slip_mit = self._slip(emp_mit, 1)
|
||||
slip_ohne = self._slip(emp_ohne, 1)
|
||||
linie = self._sb_input(slip_mit, 'ATP_SB_MAHLZEIT', 10)
|
||||
self.assertAlmostEqual(linie.total, 80.0, places=2)
|
||||
self.assertAlmostEqual(linie.quantity, 10.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sv49_freie_laufen(), 80.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_st_frei_freie_laufen(), 80.0, places=2)
|
||||
# Voll im Freibetrag: keine Wirkung auf SV-, LSt- und LNK-Basis
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sv_basis_roh()
|
||||
- slip_ohne._l10n_at_sv_basis_roh(), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_bemessung_laufen()
|
||||
- slip_ohne._l10n_at_bemessung_laufen(), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_lnk_basis('flaf')
|
||||
- slip_ohne._at_lnk_basis('flaf'), 0.0, places=2)
|
||||
|
||||
def test_02_mahlzeit_ueberhang(self):
|
||||
"""Tatsächlicher Bonwert 11 €/AT (at_sb_werte ab 6/2026):
|
||||
10 AT → SB-Zeile 110 €, frei 80 €, Überhang 30 € SV-/LSt-/
|
||||
LNK-pflichtig (lb-sac-17)."""
|
||||
self.env['hr.rule.parameter.value'].create({
|
||||
'rule_parameter_id': self.env.ref(
|
||||
'l10n_at_hr_payroll_private.hr_rule_parameter_at_sb_werte'
|
||||
).id,
|
||||
'date_from': date(2026, 6, 1),
|
||||
'parameter_value':
|
||||
"{'mahlzeit_wert': 11.0, 'homeoffice_wert': 3.0}",
|
||||
})
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
slip_mit = self._slip(emp_mit, 6)
|
||||
slip_ohne = self._slip(emp_ohne, 6)
|
||||
linie = self._sb_input(slip_mit, 'ATP_SB_MAHLZEIT', 10)
|
||||
self.assertAlmostEqual(linie.total, 110.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sv49_freie_laufen(), 80.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_st_frei_freie_laufen(), 80.0, places=2)
|
||||
# Überhang 30 € in SV- und LNK-Bemessung; LSt-Bemessung:
|
||||
# Überhang abzüglich der darauf entfallenden SV-DN (SV ist aus
|
||||
# der LSt-BG abzuziehen — §-62-Kette)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sv_basis_roh()
|
||||
- slip_ohne._l10n_at_sv_basis_roh(), 30.0, places=2)
|
||||
svdn_diff = (slip_mit._l10n_at_svdn_gesamt()
|
||||
- slip_ohne._l10n_at_svdn_gesamt())
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_bemessung_laufen()
|
||||
- slip_ohne._l10n_at_bemessung_laufen() + svdn_diff,
|
||||
30.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_lnk_basis('flaf')
|
||||
- slip_ohne._at_lnk_basis('flaf'), 30.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-SB-3/4: Jahrespools (§ 3 Z 14/Z 21, § 49 Z 17/Z 29)
|
||||
# ------------------------------------------------------------------
|
||||
def test_03_betriebsveranstaltung_pool(self):
|
||||
"""365 €/J-Pool je DN (lb-sac-07): Jänner 300 € voll frei
|
||||
(validiert), Februar 100 € → frei 65 €, Überhang 35 €
|
||||
pflichtig."""
|
||||
emp = self._employee()
|
||||
slip_jan = self._slip(emp, 1)
|
||||
linie_jan = self._sb_input(
|
||||
slip_jan, 'ATP_SB_BETRIEBSVERANSTALTUNG', 300)
|
||||
self.assertAlmostEqual(linie_jan.total, 300.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_jan._l10n_at_st_frei_freie_laufen(), 300.0, places=2)
|
||||
slip_jan.action_payslip_done()
|
||||
|
||||
emp_ohne = self._employee()
|
||||
slip_feb = self._slip(emp, 2)
|
||||
linie_feb = self._sb_input(
|
||||
slip_feb, 'ATP_SB_BETRIEBSVERANSTALTUNG', 100)
|
||||
self.assertAlmostEqual(linie_feb.total, 100.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_st_frei_ytd('betriebsveranstaltung'),
|
||||
300.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_st_frei_freie_laufen(), 65.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_sv49_freie_laufen(), 65.0, places=2)
|
||||
slip_ohne = self._slip(emp_ohne, 2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_sv_basis_roh()
|
||||
- slip_ohne._l10n_at_sv_basis_roh(), 35.0, places=2)
|
||||
svdn_diff = (slip_feb._l10n_at_svdn_gesamt()
|
||||
- slip_ohne._l10n_at_svdn_gesamt())
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_bemessung_laufen()
|
||||
- slip_ohne._l10n_at_bemessung_laufen() + svdn_diff,
|
||||
35.0, places=2)
|
||||
|
||||
def test_04_sachzuwendung_und_rabatt_pools(self):
|
||||
"""Eigene Pools neben der Betriebsveranstaltung:
|
||||
Sachzuwendung 186 €/J (lb-ges-02), Rabatt 1.000 €/J
|
||||
(lb-sac-02) — Jänner-Verbrauch mindert den Februar-Freibetrag,
|
||||
der Überhang ist pflichtig."""
|
||||
emp_g = self._employee()
|
||||
slip_g_jan = self._slip(emp_g, 1)
|
||||
self._sb_input(slip_g_jan, 'ATP_SB_SACHZUWENDUNG', 100)
|
||||
self.assertAlmostEqual(
|
||||
slip_g_jan._l10n_at_st_frei_freie_laufen(), 100.0, places=2)
|
||||
slip_g_jan.action_payslip_done()
|
||||
emp_g_ohne = self._employee()
|
||||
slip_g_feb = self._slip(emp_g, 2)
|
||||
self._sb_input(slip_g_feb, 'ATP_SB_SACHZUWENDUNG', 100)
|
||||
# frei: 186 − 100 = 86 → Überhang 14
|
||||
self.assertAlmostEqual(
|
||||
slip_g_feb._l10n_at_st_frei_freie_laufen(), 86.0, places=2)
|
||||
slip_g_ohne = self._slip(emp_g_ohne, 2)
|
||||
self.assertAlmostEqual(
|
||||
slip_g_feb._l10n_at_sv_basis_roh()
|
||||
- slip_g_ohne._l10n_at_sv_basis_roh(), 14.0, places=2)
|
||||
|
||||
emp_r = self._employee()
|
||||
slip_r_jan = self._slip(emp_r, 1)
|
||||
self._sb_input(slip_r_jan, 'ATP_SB_RABATT', 700)
|
||||
self.assertAlmostEqual(
|
||||
slip_r_jan._l10n_at_st_frei_freie_laufen(), 700.0, places=2)
|
||||
slip_r_jan.action_payslip_done()
|
||||
emp_r_ohne = self._employee()
|
||||
slip_r_feb = self._slip(emp_r, 2)
|
||||
self._sb_input(slip_r_feb, 'ATP_SB_RABATT', 500)
|
||||
# frei: 1.000 − 700 = 300 → Überhang 200
|
||||
self.assertAlmostEqual(
|
||||
slip_r_feb._l10n_at_st_frei_freie_laufen(), 300.0, places=2)
|
||||
slip_r_ohne = self._slip(emp_r_ohne, 2)
|
||||
self.assertAlmostEqual(
|
||||
slip_r_feb._l10n_at_sv_basis_roh()
|
||||
- slip_r_ohne._l10n_at_sv_basis_roh(), 200.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-SB-5: Homeoffice/Telearbeit (3 €/AT, max 100 Tage/J)
|
||||
# ------------------------------------------------------------------
|
||||
def test_05_homeoffice_jahreslimit(self):
|
||||
"""§ 26 Z 9: 3 €/AT, max 100 Tage/J (lb-tel-04/05) — 70 Tage
|
||||
(210 €) frei (validiert), dann 80 Tage → nur 30 Tage frei
|
||||
(90 €), Überhang 150 € pflichtig."""
|
||||
emp = self._employee()
|
||||
slip_jan = self._slip(emp, 1)
|
||||
linie_jan = self._sb_input(slip_jan, 'ATP_SB_HOMEOFFICE', 70)
|
||||
self.assertAlmostEqual(linie_jan.total, 210.0, places=2)
|
||||
self.assertAlmostEqual(linie_jan.quantity, 70.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_jan._l10n_at_st_frei_freie_laufen(), 210.0, places=2)
|
||||
slip_jan.action_payslip_done()
|
||||
|
||||
emp_ohne = self._employee()
|
||||
slip_feb = self._slip(emp, 2)
|
||||
linie_feb = self._sb_input(slip_feb, 'ATP_SB_HOMEOFFICE', 80)
|
||||
self.assertAlmostEqual(linie_feb.total, 240.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_st_frei_ytd('homeoffice', menge=True),
|
||||
70.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_st_frei_freie_laufen(), 90.0, places=2)
|
||||
slip_ohne = self._slip(emp_ohne, 2)
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_sv_basis_roh()
|
||||
- slip_ohne._l10n_at_sv_basis_roh(), 150.0, places=2)
|
||||
svdn_diff = (slip_feb._l10n_at_svdn_gesamt()
|
||||
- slip_ohne._l10n_at_svdn_gesamt())
|
||||
self.assertAlmostEqual(
|
||||
slip_feb._l10n_at_bemessung_laufen()
|
||||
- slip_ohne._l10n_at_bemessung_laufen() + svdn_diff,
|
||||
150.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-SB-6: ohne SB-Eingaben neutral
|
||||
# ------------------------------------------------------------------
|
||||
def test_06_ohne_sb_eingaben_neutral(self):
|
||||
"""Ohne SB-Eingaben: keine SB-Zeilen und keine Freibeträge —
|
||||
Bemessungen wie ohne AP12a (flag 'keine' ist wirkungslos)."""
|
||||
emp = self._employee()
|
||||
slip = self._slip(emp, 1)
|
||||
self.assertFalse(
|
||||
slip.line_ids.filtered(lambda l: l.code in self.SB_CODES))
|
||||
self.assertAlmostEqual(
|
||||
slip._l10n_at_st_frei_freie_laufen(), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip._l10n_at_sv49_freie_laufen(), 0.0, places=2)
|
||||
Reference in New Issue
Block a user