[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
@@ -7,4 +7,47 @@
<field name="name">Prämie/Ausgleich (manuell)</field>
<field name="code">ATP_PRAMIE</field>
</record>
<!-- AP12a: steuerfreie Sachbezüge (§ 3/§ 26 EStG, § 49 Abs 3 ASVG) -->
<!-- als manuelle Eingaben. Menge (number_of_days) = Arbeitstage -->
<!-- (Mahlzeiten/Homeoffice), amount = Tages-/Pauschalwert; die -->
<!-- Freibetrags-/Kumulationslogik läuft über die SB_*-Regeln. -->
<record id="input_type_atp_sb_mahlzeit" model="hr.payslip.input.type">
<field name="name">Sachbezug Mahlzeiten — Anz. Gutschein-Arbeitstage (manuell)</field>
<field name="code">ATP_SB_MAHLZEIT</field>
<field name="is_quantity" eval="True"/>
</record>
<record id="input_type_atp_sb_betriebsveranstaltung" model="hr.payslip.input.type">
<field name="name">Sachbezug Betriebsveranstaltung — Gesamtvalue (manuell)</field>
<field name="code">ATP_SB_BETRIEBSVERANSTALTUNG</field>
</record>
<record id="input_type_atp_sb_sachzuwendung" model="hr.payslip.input.type">
<field name="name">Sachbezug Sachzuwendung/Geschenk — Gesamtvalue (manuell)</field>
<field name="code">ATP_SB_SACHZUWENDUNG</field>
</record>
<record id="input_type_atp_sb_rabatt" model="hr.payslip.input.type">
<field name="name">Sachbezug Mitarbeiterrabatt — Gesamtvalue (manuell)</field>
<field name="code">ATP_SB_RABATT</field>
</record>
<record id="input_type_atp_sb_homeoffice" model="hr.payslip.input.type">
<field name="name">Sachbezug Homeoffice — Anz. Tage (manuell)</field>
<field name="code">ATP_SB_HOMEOFFICE</field>
<field name="is_quantity" eval="True"/>
</record>
<!-- AP12b: ereignisbezogene Werte. Amount ist jeweils der bereits
ermittelte Vorteil (tatsächliche Mehrkosten bzw. Buchwert minus
Kaufpreis); Stammdaten steuern Wagen/Wohnung/Parkplatz. -->
<record id="input_type_atp_sb_handy" model="hr.payslip.input.type">
<field name="name">Sachbezug Handy — private Mehrkosten (manuell)</field>
<field name="code">ATP_SB_HANDY</field>
</record>
<record id="input_type_atp_sb_gebraucht" model="hr.payslip.input.type">
<field name="name">Sachbezug Arbeitsmittel-Verkauf unter Buchwert (manuell)</field>
<field name="code">ATP_SB_GEBRAUCHT</field>
</record>
<record id="input_type_atp_sb_bonusmeilen" model="hr.payslip.input.type">
<field name="name">Sachbezug Bonusmeilen — Einlösungswert (manuell)</field>
<field name="code">ATP_SB_BONUSMEILEN</field>
</record>
</odoo>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- AP12b: Sachbezugswerte-VO — Jahresversionen, keine Literale in
den Regeln. Rechtsanker: lb-sac-03/04/09/18. -->
<record id="hr_rule_parameter_at_sb_wagen" model="hr.rule.parameter">
<field name="name">Sachbezug Dienstwagen — Sätze und Höchstwerte</field>
<field name="code">at_sb_wagen</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_sb_wagen" model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_sb_wagen"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">{'voll_hoch_satz': 2.0, 'voll_hoch_cap': 960.0, 'voll_niedrig_satz': 1.5, 'voll_niedrig_cap': 720.0, 'halb_hoch_satz': 1.0, 'halb_hoch_cap': 480.0, 'halb_niedrig_satz': 0.75, 'halb_niedrig_cap': 360.0}</field>
</record>
<record id="hr_rule_parameter_at_sb_wagen_co2" model="hr.rule.parameter">
<field name="name">Sachbezug Dienstwagen — CO₂-Grenzwerte</field>
<field name="code">at_sb_wagen_co2</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_sb_wagen_co2" model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_sb_wagen_co2"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">{'vor_2016': 130.0, '2017': 127.0, '2018': 124.0, '2019': 121.0, '2020': 141.0, '2021': 138.0, '2022': 135.0, '2023': 132.0, '2024': 129.0, 'ab_2025': 126.0}</field>
</record>
<record id="hr_rule_parameter_at_sb_wohnung" model="hr.rule.parameter">
<field name="name">Sachbezug Dienstwohnung — m²-Werte und Abschläge</field>
<field name="code">at_sb_wohnung</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_sb_wohnung" model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_sb_wohnung"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">{'burgenland': 6.09, 'kaernten': 7.81, 'niederoesterreich': 6.85, 'oberoesterreich': 7.23, 'salzburg': 9.22, 'steiermark': 9.21, 'tirol': 8.14, 'vorarlberg': 10.25, 'wien': 6.67, 'nicht_norm_abschlag': 0.30, 'besorger_abschlag': 0.35, 'kombiniert_abschlag': 0.555, 'klein_frei_m2': 35.0, 'klein_abschlag_bis_m2': 45.0, 'klein_abschlag': 0.35}</field>
</record>
<record id="hr_rule_parameter_at_sb_parkplatz" model="hr.rule.parameter">
<field name="name">Sachbezug Parkplatz — Monatswert</field>
<field name="code">at_sb_parkplatz</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_sb_parkplatz" model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_sb_parkplatz"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">14.53</field>
</record>
</odoo>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- AP12a: Sachbezüge — tagesbasierte Werte (tatsächlicher -->
<!-- Wert pro Arbeitstag). Mahlzeiten: tatsächlicher Bonwert -->
<!-- (Default = 8 €/AT = Freibetrag, d. h. vollständig frei; -->
<!-- Company setzt den tatsächlichen Bonwert, z. B. 11,0 — der -->
<!-- Überhang (Wert8)×Tage ist steuer-/beitragspflichtig, -->
<!-- lb-sac-17). Homeoffice: gesetzlich fix 3 €/AT (lb-tel-04/05, -->
<!-- § 26 Z 9 EStG). Freibetrags-Caps selbst liegen im Kern -->
<!-- (at_st_grenzen / at_sv49_grenzen). -->
<!-- ============================================================ -->
<record id="hr_rule_parameter_at_sb_werte" model="hr.rule.parameter">
<field name="name">Sachbezüge — tagesbasierte Werte</field>
<field name="code">at_sb_werte</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_sb_werte"
model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_sb_werte"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">{'mahlzeit_wert': 8.0, 'homeoffice_wert': 3.0}</field>
</record>
</odoo>
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- AP12b: wertgebundene Sachbezüge. Wagen/Wohnung/Parkplatz sind
Version-Stammdaten; Handy/Verkauf/Bonusmeilen Ereignis-Eingaben.
Die Regeln stehen vor B13/B14 (50ff.) und SV/LSt (60ff.). -->
<record id="rule_sb_parkplatz" model="hr.salary.rule">
<field name="name">Sachbezug Parkplatz am Dienstort</field>
<field name="code">SB_PARKPLATZ</field>
<field name="sequence" eval="53"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = payslip.version_id._l10n_at_sb_parkplatz_wert(payslip.date_to) &gt; 0</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_python_compute">result = payslip.version_id._l10n_at_sb_parkplatz_wert(payslip.date_to)</field>
</record>
<record id="rule_sb_wohnung" model="hr.salary.rule">
<field name="name">Sachbezug Dienstwohnung</field>
<field name="code">SB_WOHNUNG</field>
<field name="sequence" eval="54"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = payslip.version_id._l10n_at_sb_wohnung_wert(payslip.date_to) &gt; 0</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_python_compute">result = payslip.version_id._l10n_at_sb_wohnung_wert(payslip.date_to)</field>
</record>
<record id="rule_sb_wagen" model="hr.salary.rule">
<field name="name">Sachbezug Dienstwagen Privatnutzung</field>
<field name="code">SB_WAGEN</field>
<field name="sequence" eval="55"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = payslip.version_id._l10n_at_sb_wagen_wert(payslip.date_to) &gt; 0</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_python_compute">result = payslip.version_id._l10n_at_sb_wagen_wert(payslip.date_to)</field>
</record>
<record id="rule_sb_gebraucht" model="hr.salary.rule">
<field name="name">Sachbezug Arbeitsmittel-Verkauf unter Buchwert</field>
<field name="code">SB_GEBRAUCHT</field>
<field name="sequence" eval="56"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_GEBRAUCHT' in inputs and inputs['ATP_SB_GEBRAUCHT'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">sonstig</field>
<field name="l10n_at_sv_art">laufend</field>
<field name="amount_python_compute">result = inputs['ATP_SB_GEBRAUCHT'].amount</field>
</record>
<record id="rule_sb_bonusmeilen" model="hr.salary.rule">
<field name="name">Sachbezug Bonusmeilen (Drittlohn, nur SV)</field>
<field name="code">SB_BONUSMEILEN</field>
<field name="sequence" eval="57"/>
<!-- Drittlohn erhöht nicht den Auszahlungsnettoanspruch. -->
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_dg"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_BONUSMEILEN' in inputs and inputs['ATP_SB_BONUSMEILEN'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="l10n_at_sv_art">laufend</field>
<field name="amount_python_compute">result = inputs['ATP_SB_BONUSMEILEN'].amount</field>
</record>
<record id="rule_sb_handy" model="hr.salary.rule">
<field name="name">Sachbezug Handy — private Mehrkosten</field>
<field name="code">SB_HANDY</field>
<field name="sequence" eval="58"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = payslip.version_id.l10n_at_sb_handy_profil == 'ueberwiegend_privat' and 'ATP_SB_HANDY' in inputs and inputs['ATP_SB_HANDY'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_python_compute">result = inputs['ATP_SB_HANDY'].amount</field>
</record>
</odoo>
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- AP12a: steuerfreie Sachbezüge — Freibeträge nach -->
<!-- § 3/§ 26 EStG, beitragsfrei § 49 Abs 3 ASVG, lohnneben- -->
<!-- kostenfrei (lb-sac-17/07/02, lb-ges-02). -->
<!-- Input-Semantik: -->
<!-- - tagesbasiert (Mahlzeiten/Homeoffice): amount = Anzahl -->
<!-- Arbeitstage; der Tageswert kommt aus dem versionierten -->
<!-- Parameter at_sb_werte (Kern-Mechanik: total = Wert×Tage, -->
<!-- Freibetrag = min(total, Cap×Tage)). -->
<!-- - Pool-basiert (BV/Rabatt/Geschenk): amount = Gesamtvalue -->
<!-- des Monats; Freibetrag = min(total, Jahrespool YTD). -->
<!-- Die Freibetrags-/Jahreskumulationslogik läuft im Kern über -->
<!-- l10n_at_sv49_frei (SV) und l10n_at_st_frei (LSt/LNK). -->
<!-- Nur der übersteigende Teil ist beitrags-/steuerpflichtig -->
<!-- (Freibeträge, keine Freigrenzen). -->
<!-- ============================================================ -->
<record id="rule_sb_mahlzeit" model="hr.salary.rule">
<field name="name">Sachbezug Mahlzeiten-Gutschein (manuell)</field>
<field name="code">SB_MAHLZEIT</field>
<field name="sequence" eval="41"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_MAHLZEIT' in inputs and inputs['ATP_SB_MAHLZEIT'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_sv49_frei">mahlzeit</field>
<field name="l10n_at_st_frei">mahlzeit</field>
<field name="amount_python_compute"><![CDATA[
# § 3 Abs 1 Z 17 EStG / § 49 Abs 3 Z 12 ASVG: Gutschein bis 8 €/
# Arbeitstag frei (Kern-Cap at_st_grenzen.mahlzeit_tag). Input =
# Anz. Arbeitstage; Tageswert = tatsächlicher Bonwert (at_sb_werte).
# total = Wert × Tage (Engine: amount×qty); Überhang (Wert8)×Tage
# pflichtig (lb-sac-17).
tage = inputs['ATP_SB_MAHLZEIT'].amount
wert = float(payslip._l10n_at_param('at_sb_werte')['mahlzeit_wert'])
result = wert
result_qty = tage
]]></field>
</record>
<record id="rule_sb_betriebsveranstaltung" model="hr.salary.rule">
<field name="name">Sachbezug Betriebsveranstaltung (manuell)</field>
<field name="code">SB_BETRIEBSVERANSTALTUNG</field>
<field name="sequence" eval="42"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_BETRIEBSVERANSTALTUNG' in inputs and inputs['ATP_SB_BETRIEBSVERANSTALTUNG'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_sv49_frei">betriebsveranstaltung</field>
<field name="l10n_at_st_frei">betriebsveranstaltung</field>
<field name="amount_python_compute"><![CDATA[
# § 3 Abs 1 Z 14 EStG / § 49 Abs 3 Z 17 ASVG: Teilnahme bis 365 €/
# AN/Kalenderjahr frei (Jahres-Kumulative je DN); Überhang
# lohnsteuer-/beitragspflichtig (Freibetrag, lb-sac-07).
result = inputs['ATP_SB_BETRIEBSVERANSTALTUNG'].amount
]]></field>
</record>
<record id="rule_sb_sachzuwendung" model="hr.salary.rule">
<field name="name">Sachbezug Sachzuwendung/Jubiläum/Geschenk (manuell)</field>
<field name="code">SB_SACHZUWENDUNG</field>
<field name="sequence" eval="43"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_SACHZUWENDUNG' in inputs and inputs['ATP_SB_SACHZUWENDUNG'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_sv49_frei">jubilaeum</field>
<field name="l10n_at_st_frei">jubilaeum</field>
<field name="amount_python_compute"><![CDATA[
# § 3 Abs 1 Z 14 EStG / § 49 Abs 3 Z 17 ASVG: Sachzuwendungen
# (Jubiläums-/Weihnachtsgeschenke) bis 186 €/AN/Kalenderjahr frei —
# eigener Pool neben der Betriebsveranstaltung (lb-ges-02, lb-sac-07);
# Barzuwendungen immer pflichtig (Regel nur für Sachwerte).
result = inputs['ATP_SB_SACHZUWENDUNG'].amount
]]></field>
</record>
<record id="rule_sb_rabatt" model="hr.salary.rule">
<field name="name">Sachbezug Mitarbeiterrabatt (manuell)</field>
<field name="code">SB_RABATT</field>
<field name="sequence" eval="44"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_RABATT' in inputs and inputs['ATP_SB_RABATT'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_sv49_frei">rabatt</field>
<field name="l10n_at_st_frei">rabatt</field>
<field name="amount_python_compute"><![CDATA[
# § 3 Abs 1 Z 21 EStG / § 49 Abs 3 Z 29 ASVG: Rabatte über der
# 20 %-Freigrenze bis 1.000 €/AN/Jahr frei (Jahres-Kumulative);
# darüber pflichtig (lb-sac-02). Aufzeichnungspflicht des
# Arbeitgebers bleibt außen (Lohnkonto-Prüffeld).
result = inputs['ATP_SB_RABATT'].amount
]]></field>
</record>
<record id="rule_sb_homeoffice" model="hr.salary.rule">
<field name="name">Sachbezug Homeoffice-Pauschale (manuell)</field>
<field name="code">SB_HOMEOFFICE</field>
<field name="sequence" eval="48"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = 'ATP_SB_HOMEOFFICE' in inputs and inputs['ATP_SB_HOMEOFFICE'].amount</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_sv49_frei">homeoffice</field>
<field name="l10n_at_st_frei">homeoffice</field>
<field name="amount_python_compute"><![CDATA[
# § 26 Z 9 EStG / § 49 Abs 3 Z 31 ASVG: Homeoffice-Pauschale 3 €/
# Arbeitstag, max 100 Tage/Kalenderjahr (Jahres-Kumulative). Input =
# Anz. Tage; Tageswert = 3 € (at_sb_werte.homeoffice_wert).
tage = inputs['ATP_SB_HOMEOFFICE'].amount
wert = float(payslip._l10n_at_param('at_sb_werte')['homeoffice_wert'])
result = wert
result_qty = tage
]]></field>
</record>
</odoo>