[IMP] l10n_at_hr_payroll_private: add domestic travel expenses (AP13-A)

Use Odoo's payroll expense reconciliation flow so documented employee-paid travel expenses remain linked to their payslip and payable settlement. Classify only the excess over the verified domestic tax-free limits as running remuneration, avoiding both unpaid expense reimbursements and double payment.
This commit is contained in:
2026-09-14 13:42:06 +02:00
parent 91ac9e5fda
commit 3f307a0688
11 changed files with 607 additions and 2 deletions
@@ -50,4 +50,15 @@
<field name="name">Sachbezug Bonusmeilen — Einlösungswert (manuell)</field>
<field name="code">ATP_SB_BONUSMEILEN</field>
</record>
<!-- AP13-A: Diese Eingaben werden ausschließlich aus den mit dem
Payslip verknüpften hr.expense-Reisekosten erzeugt. -->
<record id="input_type_atp_reise_steuerpflichtig" model="hr.payslip.input.type">
<field name="name">Reisekosten — steuerpflichtiger Überhang</field>
<field name="code">ATP_REISE_STEUERPFLICHTIG</field>
</record>
<record id="input_type_atp_reise_nettoausgleich" model="hr.payslip.input.type">
<field name="name">Reisekosten — Nettoausgleich Überhang</field>
<field name="code">ATP_REISE_NETTOAUSGLEICH</field>
</record>
</odoo>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- AP13-A: verified domestic § 26 Z 4 / RGV reference values.
Parameters are deliberately date-versioned; foreign rates and
KV-specific § 3 Abs 1 Z 16b rates are outside this first scope. -->
<record id="hr_rule_parameter_at_reise_inland" model="hr.rule.parameter">
<field name="name">Reisekosten Inland</field>
<field name="code">at_reise_inland</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_reise_inland_2026"
model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_reise_inland"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">{'taggeld_tag': 30.0, 'naechtigung_pauschale': 17.0}</field>
</record>
<record id="hr_rule_parameter_at_km_geld" model="hr.rule.parameter">
<field name="name">Amtliches Kilometergeld</field>
<field name="code">at_km_geld</field>
<field name="country_id" ref="base.at"/>
</record>
<record id="hr_rule_parameter_value_at_km_geld_2026"
model="hr.rule.parameter.value">
<field name="rule_parameter_id" ref="hr_rule_parameter_at_km_geld"/>
<field name="date_from">2026-01-01</field>
<field name="parameter_value">{'pkw': 0.50, 'motorrad': 0.25, 'fahrrad': 0.25, 'dienstfahrrad': 0.0, 'fuss': 0.25, 'mitfahrer': 0.15, 'km_jahr_max': 30000.0, 'niedriger_satz_jahr_max': 12600.0}</field>
</record>
</odoo>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- The standard hr_payroll_expense link/reconciliation requires this
exact EXPENSES rule and a payable debit account. Its neutral AT
classification reimburses documented expenses without adding them to
LSt/SV/LNK bases. -->
<record id="rule_expenses" model="hr.salary.rule">
<field name="name">Spesenersatz (über Reisekostenabrechnung)</field>
<field name="code">EXPENSES</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 = 'EXPENSES' in inputs and inputs['EXPENSES'].amount</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="l10n_at_sv_art">keine</field>
<field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[
result = inputs['EXPENSES'].amount
]]></field>
</record>
<!-- Only the amount over the documented § 26 limit is ordinary
remuneration. EXPENSES still refunds the actual expense once;
REISE_NETTOAUSGLEICH prevents a second reimbursement through this
classification line while taxes remain withheld. -->
<record id="rule_reise_steuerpflichtig" model="hr.salary.rule">
<field name="name">Reisekosten — steuerpflichtiger Überhang</field>
<field name="code">REISE_STEUERPFLICHTIG</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_REISE_STEUERPFLICHTIG' in inputs and inputs['ATP_REISE_STEUERPFLICHTIG'].amount</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[
result = inputs['ATP_REISE_STEUERPFLICHTIG'].amount
]]></field>
</record>
<record id="rule_reise_nettoausgleich" model="hr.salary.rule">
<field name="name">Reisekosten — Nettoausgleich Überhang</field>
<field name="code">REISE_NETTOAUSGLEICH</field>
<field name="sequence" eval="45"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_abzug"/>
<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_REISE_NETTOAUSGLEICH' in inputs and inputs['ATP_REISE_NETTOAUSGLEICH'].amount</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="l10n_at_sv_art">keine</field>
<field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[
result = inputs['ATP_REISE_NETTOAUSGLEICH'].amount
]]></field>
</record>
</odoo>