Compare commits
2 Commits
91ac9e5fda
...
9ff977e0d4
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ff977e0d4 | |||
| 3f307a0688 |
@@ -2,6 +2,73 @@
|
||||
|
||||
## Current focus
|
||||
|
||||
**AP13-B Pendlerförderung umgesetzt — 127 Tests grün**
|
||||
(2026-09-14, uncommittet bis Nutzerfreigabe):
|
||||
|
||||
- Neu `models/pendler.py`: Modell `l10n.at.payroll.pendler.erklaerung`
|
||||
(effektiv datiert, L-34-/Pendlerrechner-Nachweis als Attachment,
|
||||
pauschale_art klein/gross, fahrten_stufe drittel/zwei_drittel/voll,
|
||||
arbeitgeber_kfz, ticket_art keines/werkverkehr/oeffi,
|
||||
ticket_kosten_monatlich, bezugsumwandlung). Company-Related OHNE
|
||||
store=True (Muster pfaendung — store+required bricht den Insert mit
|
||||
NotNullViolation), ACL + Company-Record-Rule + Views/Menu.
|
||||
- Parameter `at_pendler` 2026: klein [[20,696],[41,1356],[61,2016]],
|
||||
gross [[2,372],[21,1476],[41,2568],[61,3672]], pendlereuro_km_jahr 6.0.
|
||||
Staffel „nächst-höhere Stufe erst bei MEHR als dem unteren km-Wert“ →
|
||||
ganzzahlige Grenzen 20/41/61 bzw. 2/21/41/61.
|
||||
- Engine-Anbindung (Privat-Overrides):
|
||||
`_l10n_at_bemessung_laufen` − PP (LZP = Jahreswert × Stufe/360 ×
|
||||
Lohnsteuertage, lb-pen-09); `_l10n_at_absetzbetraege` + PE als
|
||||
**Jahreswert × Stufe** (km × 6 € × Stufe) — kumulative Engine verbraucht
|
||||
Absetzbeträge über die Rollung, NICHT pe × 12. PP/PE ohne SV-/LNK-
|
||||
Wirkung (getestet).
|
||||
- §-26-Z5-Regeln: oeffi-Ticketkosten mindern PP (§ 16 Abs 1 Z 6 lit i);
|
||||
PP → 0 ⇒ PE = 0 (Fixkopplung lb-pen-04). Werkverkehr datengetrieben
|
||||
über die L-34-Reststrecke. Bezugsumwandlung sperrt PP/PE NICHT (Ticket =
|
||||
steuerpflichtiger laufender Bezug). Kfz sperrt beide.
|
||||
- Tests test_pendler_private.py (10): voller Monat 58/150 €, Staffeln,
|
||||
1/3-Stufe, untermonatig 3,87 € (lb-pen-09-Beispiel), Kfz-Sperre,
|
||||
Ticket-Reduktion/PE-Entfall, Bezugsumwandlung, effektiv datierte
|
||||
Auswahl, E2E-LSt-Wirkung. Zwei Testfixes beim ersten echten Lauf:
|
||||
`unit_amount` existiert nicht auf hr.expense (→ quantity),
|
||||
PE-Erwartungswert 40 € (20 km × 6 × 1/3) statt 50 €.
|
||||
- **Fund:** die AP13-A-Tests waren im AP13-A-Commit NICHT registriert
|
||||
(tests/__init__.py fehlte) — die „114 Tests grün“ liefen ohne die
|
||||
Reisekosten-Tests. Jetzt registriert; Suite 127/127 grün. Beim ersten
|
||||
echten Reisekosten-Testlauf: `hr.expense` hat kein `unit_amount`-Feld
|
||||
(Odoo 19: quantity/price_unit/total_amount) — Test-Helfer korrigiert.
|
||||
- Doku docs/rechenfaelle/pendler.md (RF-PEN-1…5 + offene Punkte: erhöhter
|
||||
VAB 853 €/Zuschlag 804 € als Quer-LSt-Rest, L-34-1-Monats-Meldeprozess).
|
||||
- Version **19.0.11.0.0**. Commit ausstehend (Nutzerfreigabe).
|
||||
|
||||
**AP13-A Reisekosten Inland committed — 127-er Suite grün**
|
||||
(2026-09-14, Commit **`3f307a0`**):
|
||||
|
||||
- Privatmodul **19.0.10.0.0** verwendet das Odoo-19-Standardmodul
|
||||
`hr_payroll_expense`: genehmigte, eigenfinanzierte Spesen werden über
|
||||
`EXPENSES` mit dem Payslip verbunden und gegen das Verbindlichkeitskonto
|
||||
abgestimmt. `rule_expenses` bucht auf 3850; die AT-Buchungsmatrix ergänzt
|
||||
diese Zuordnung.
|
||||
- Neu `models/reisekosten.py`, Parameter `at_reise_inland`/`at_km_geld`,
|
||||
Regeln `EXPENSES`/`REISE_STEUERPFLICHTIG`/`REISE_NETTOAUSGLEICH`,
|
||||
Spesen-View und Rechenfälle `docs/rechenfaelle/reisekosten.md`.
|
||||
Abgedeckt: dokumentiertes Inland-Taggeld, Nächtigung (belegt oder
|
||||
Pauschale), Kilometergeld, ÖV-Dienstreise und Familienheimfahrten.
|
||||
Kilometergrenzen: 30.000 km über Dienst-/Heimfahrten sowie 12.600 € bei
|
||||
niedrigem Satz; Familienheimfahrt: eine je Woche und keine Kumulation mit
|
||||
steuerfreiem Taggeld.
|
||||
- Der vollständige Spesenbetrag wird genau einmal über `EXPENSES` ersetzt.
|
||||
Nur der §-26-Überhang wird als laufender Bezug mit LSt/SV/LNK behandelt;
|
||||
die neutrale Nettoausgleichsregel verhindert eine Doppelersetzung.
|
||||
- Tests `test_reisekosten_private.py`: Taggeld-Cap, Kilometergrenze,
|
||||
abgabenpflichtiger Überhang/Nettoausgleich. Vollständiger Odoo-Lauf in
|
||||
`ap13a_test`: **114 Tests, 0 Fehler**; Python/XML-Checks grün.
|
||||
- Ausdrücklich noch offen in AP13-B: Pendlerpauschale, Pendlereuro und
|
||||
Jobticket. Nicht im AP13-A-Scope: Auslandssätze, §3 Abs1 Z16b-KV-Sätze
|
||||
und die detaillierte Einsatzortdauer-Verfolgung.
|
||||
- Vorheriger Dokumentations-/Ignore-Commit: **`91ac9e5`**. AP13-A ist
|
||||
derzeit **noch uncommittet**.
|
||||
|
||||
**AP 12b wertgebundene Sachbezüge umgesetzt — 166/166 Tests grün**
|
||||
(2026-09-14, freigegeben):
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
|
||||
'version': '19.0.9.7.0',
|
||||
'version': '19.0.11.0.0',
|
||||
'author': 'Florian Egger',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -91,7 +91,7 @@ Kein VRV-Bezug (Gemeinde-Schiene bleibt ``l10n_at_gemeinde_payroll`` +
|
||||
gem360-Brücke).
|
||||
""",
|
||||
'depends': ['l10n_at_hr_payroll', 'hr_payroll_account',
|
||||
'l10n_at', 'hr_payroll_account_iso20022',
|
||||
'hr_payroll_expense', 'l10n_at', 'hr_payroll_account_iso20022',
|
||||
'hr_holidays', 'hr_work_entry_holidays'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
@@ -112,6 +112,7 @@ gem360-Brücke).
|
||||
'data/salary_rules_pfaendung_private.xml',
|
||||
'data/salary_rules_sb_private.xml',
|
||||
'data/salary_rules_sb2_private.xml',
|
||||
'data/salary_rules_reisekosten_private.xml',
|
||||
'data/salary_rules_sv_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
@@ -123,6 +124,8 @@ gem360-Brücke).
|
||||
'data/rule_parameters_pfaendung_private.xml',
|
||||
'data/rule_parameters_sb_private.xml',
|
||||
'data/rule_parameters_sb2_private.xml',
|
||||
'data/rule_parameters_reisekosten_private.xml',
|
||||
'data/rule_parameters_pendler_private.xml',
|
||||
'data/bv_kasse_seed.xml',
|
||||
'views/kv_views.xml',
|
||||
'views/bv_views.xml',
|
||||
@@ -132,6 +135,8 @@ gem360-Brücke).
|
||||
'views/aeb_views.xml',
|
||||
'views/pfaendung_views.xml',
|
||||
'views/sachbezuege_views.xml',
|
||||
'views/reisekosten_views.xml',
|
||||
'views/pendler_views.xml',
|
||||
'report/gp8_reports.xml',
|
||||
'report/buchungsbeleg_report.xml',
|
||||
'report/bv_report.xml',
|
||||
|
||||
@@ -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,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- AP13-B: verifizierte Pendlerwerte 2026 (lb-pen-01/02/04/09, -->
|
||||
<!-- Budgetbegleitgesetz 2025). Staffel: nächsthöhere Stufe erst bei -->
|
||||
<!-- MEHR als dem unteren km-Wert (klein: 20/41/61, gross 2/21/41/61 -->
|
||||
<!-- — km ganzzahlig aus dem Pendlerrechner/L 34). -->
|
||||
<record id="hr_rule_parameter_at_pendler" model="hr.rule.parameter">
|
||||
<field name="name">Pendlerpauschale / Pendlereuro</field>
|
||||
<field name="code">at_pendler</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_at_pendler_2026"
|
||||
model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_at_pendler"/>
|
||||
<field name="date_from">2026-01-01</field>
|
||||
<field name="parameter_value">{'klein': [[20, 696.0], [41, 1356.0], [61, 2016.0]], 'gross': [[2, 372.0], [21, 1476.0], [41, 2568.0], [61, 3672.0]], 'pendlereuro_km_jahr': 6.0}</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>
|
||||
@@ -0,0 +1,69 @@
|
||||
# Pendlerförderung — Rechenfälle AP13-B
|
||||
|
||||
Stand: 2026-09. AP13-B umsetzt Pendlerpauschale (PP) und Pendlereuro (PE)
|
||||
sowie die L-34-Verhältnisdaten inkl. §-26-Z5-Tickets auf der privaten
|
||||
Structure. Grundlagen: lb-pen-01/02/04/06/08/09 und § 16 Abs 1 Z 6 EStG.
|
||||
|
||||
## Rechtsanker
|
||||
|
||||
- PP = Freibetrag der laufenden LSt-Bemessungsgrundlage; Staffeln 2026
|
||||
klein 696/1.356/2.016 € (ab 20/>40/>60 km), groß 372/1.476/2.568/3.672 €
|
||||
(ab 2/>20/>40/>60 km); nächsthöhere Stufe erst bei **mehr** als dem
|
||||
unteren km-Wert.
|
||||
- Fahrten-Stufe: 4–7 Fahrten → 1/3, 8–10 → 2/3, ab 11 → voll.
|
||||
- Untermonatige Beschäftigung: Jahreswert × Stufe / 360 × Lohnsteuertage
|
||||
des LZP (lb-pen-09).
|
||||
- PE 2026: 6 €/km/Jahr (Budgetbegleitgesetz 2025), aliquotiert mit der
|
||||
Fahrten-Stufe, direkter LSt-Absetzbetrag, Fixkopplung zum PP-Anspruch.
|
||||
- Arbeitgebereigenes Kfz für die Pendelstrecke sperrt PP und PE; ein
|
||||
arbeitgebereigenes Dienstfahrrad sperrt nicht.
|
||||
- § 26 Z 5 lit a Werkverkehr: PP nur für die nicht abgedeckte Reststrecke
|
||||
— datengetrieben über die L-34-Reststrecke.
|
||||
- § 26 Z 5 lit b Öffi-Ticket: AG-Ticketkosten mindern das PP anteilig
|
||||
(§ 16 Abs 1 Z 6 lit i); Bezugsumwandlung sperrt PP/PE nicht (Ticket =
|
||||
steuerpflichtiger laufender Bezug).
|
||||
- PP/PE erfordern die L 34 EDV mit Pendlerrechner-Ausdruck; Änderungen der
|
||||
Verhältnisse sind binnen eines Monats zu melden — im Modell als neue
|
||||
Gültigkeitsperiode (`l10n.at.payroll.pendler.erklaerung`).
|
||||
|
||||
## RF-PEN-1 — kleines PP voll, 25 km
|
||||
|
||||
PP = 696/12 = 58,00 €/Monat Freibetrag; PE = 25 × 6 = 150 €/Jahr.
|
||||
Kein SV- und kein LNK-Effekt.
|
||||
|
||||
## RF-PEN-2 — Fahrten-Stufe 1/3
|
||||
|
||||
20 km, 4 Fahrten: PP = 696 × 1/3 / 12 = 19,33 €; PE = (20 × 6) × 1/3 = 50
|
||||
€/Jahr.
|
||||
|
||||
## RF-PEN-3 — untermonatig (lb-pen-09)
|
||||
|
||||
Beginn 25. 4., 5 Pendeltage, 25 km, klein 1/3: PP = 696 × 1/3 / 360 × 6
|
||||
= **3,87 €**.
|
||||
|
||||
## RF-PEN-4 — Öffi-Ticket
|
||||
|
||||
AG-Ticketkosten 30 €/Monat bei kleinem PP 58 €: PP = 28 €; PE bleibt
|
||||
(Fixkopplung zum Restanspruch). Kosten ≥ PP → PP = 0 → PE = 0.
|
||||
|
||||
## RF-PEN-5 — Werkverkehr
|
||||
|
||||
PP auf der Reststrecke aus der L-34 (Datenpflege); PE folgt der Reststrecke.
|
||||
Überwiegende Werkverkehr-Beförderung mit voll abgedeckter Strecke →
|
||||
keine Erklärung mit Reststrecke → kein PP/PE.
|
||||
|
||||
## Engine-Bindung
|
||||
|
||||
- PP: Abzug in `_l10n_at_bemessung_laufen` (Privat-Override) — wirkt auf
|
||||
die §-66-Hochrechnung, nicht auf SV/LNK.
|
||||
- PE: Ergänzung in `_l10n_at_absetzbetraege` — Jahreswert × Stufe, wie der
|
||||
Verkehrsabsetzbetrag über die kumulative Rollung verbraucht.
|
||||
- Lohnkonto-Ausweise (zustehender PE, Kfz-Monate, Werkverkehrsmonate) sind
|
||||
Report-Thema (lb-pen-06) und nicht Bestandteil dieses MVP.
|
||||
|
||||
## Offene Punkte (dokumentiert, nicht geblockt)
|
||||
|
||||
- Erhöhter Verkehrsabsetzbetrag (853 €) und Pendler-Zuschlag (804 €) sind
|
||||
einkommensabhängige Jahreskomponenten — Quer-LSt-Rest (siehe Plan).
|
||||
- L 34-Änderungsmeldung binnen Monatsfrist ist Prozessregel; das Modell
|
||||
stützt die monatsgenaue Historie, prüft die Frist nicht.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Reisekosten — Rechenfälle AP13-A
|
||||
|
||||
Stand: 2026-09. AP13-A verarbeitet belegte **inländische**
|
||||
Reisekosten über Odoos `hr_payroll_expense`-Ablauf. Die Reisekostenabrechnung
|
||||
wird dem nächsten Payslip zugeordnet; die Standardregel `EXPENSES` trägt den
|
||||
gesamten Ersatz und die Verbindlichkeitsabstimmung.
|
||||
|
||||
## Rechts- und Systemgrenze
|
||||
|
||||
- § 26 Z 4 EStG: Inland-Taggeld maximal 30,00 € je 24 Stunden,
|
||||
bei mehr als drei Stunden 1/12 je angefangener Stunde; Nächtigungspauschale
|
||||
17,00 € oder belegte tatsächliche Kosten.
|
||||
- Amtliches Kilometergeld: Pkw/Kombi 0,50 €, Motorrad 0,25 €,
|
||||
Fahrrad/Fußweg 0,25 €, notwendiger Mitfahrer 0,15 €; insgesamt maximal
|
||||
30.000 km im Kalenderjahr einschließlich Familienheimfahrten.
|
||||
- Übersteigende oder nicht dokumentierte Beträge sind laufender Arbeitslohn
|
||||
und damit LSt-, SV- und lohnnebenkostenpflichtig.
|
||||
- Auslandssätze, § 3 Abs. 1 Z 16b EStG und die detaillierte Einsatzortdauer
|
||||
sind nicht Bestandteil von AP13-A.
|
||||
|
||||
## RF-REI-1 — Taggeld im Höchstausmaß
|
||||
|
||||
Dienstreise 10 Stunden, Auslagenersatz 25,00 €:
|
||||
|
||||
- Höchstausmaß: 10 × 30/12 = 25,00 €;
|
||||
- `EXPENSES`: 25,00 € netto ersetzt;
|
||||
- keine LSt-, SV- oder LNK-Wirkung.
|
||||
|
||||
## RF-REI-2 — Taggeld-Überhang
|
||||
|
||||
Dienstreise 10 Stunden, Auslagenersatz 40,00 €:
|
||||
|
||||
- steuerfrei: 25,00 €;
|
||||
- Überhang: 15,00 €;
|
||||
- `EXPENSES` ersetzt 40,00 €, `REISE_STEUERPFLICHTIG` erhöht die laufende
|
||||
Bemessungsgrundlage um 15,00 €, `REISE_NETTOAUSGLEICH` verhindert einen
|
||||
zweiten Ersatz dieser 15,00 €;
|
||||
- die darauf entfallende DN-SV und LSt vermindern die Nettauszahlung.
|
||||
|
||||
## RF-REI-3 — Kilometergeld und Jahresgrenze
|
||||
|
||||
500 km mit Pkw, Anspruch 250,00 €:
|
||||
|
||||
- 500 × 0,50 € = 250,00 € steuerfrei bei Reisekostenaufzeichnung;
|
||||
- nach 29.800 bestätigten Jahreskilometern sind nur 200 km bzw. 100,00 €
|
||||
steuerfrei, 150,00 € sind laufender Arbeitslohn.
|
||||
|
||||
## RF-REI-4 — Familienheimfahrt
|
||||
|
||||
Eine dokumentierte Heimfahrt am arbeitsfreien Tag wird als eigene Reiseart
|
||||
geführt und zählt in dieselbe 30.000-km-Jahresgrenze. Eine zweite erstattete
|
||||
Heimfahrt in derselben Kalenderwoche wird vor Einreichung abgewiesen.
|
||||
Für denselben arbeitsfreien Tag darf kein steuerfreies Taggeld kumulieren.
|
||||
|
||||
## Buchungsanker
|
||||
|
||||
`EXPENSES` muss auf ein Verbindlichkeitskonto buchen. Die AT-
|
||||
Buchungsmatrix ordnet es dem abstimmbaren Lohnverbindlichkeitskonto 3850 zu;
|
||||
`hr_payroll_expense` gleicht dieses gegen die aus der Spesenabrechnung
|
||||
entstandene Mitarbeiterverbindlichkeit ab.
|
||||
@@ -18,3 +18,5 @@ from . import aeb
|
||||
from . import pfaendung
|
||||
from . import pfaendung_report
|
||||
from . import sachbezuege_private
|
||||
from . import reisekosten
|
||||
from . import pendler
|
||||
|
||||
@@ -52,6 +52,10 @@ L10N_AT_BUCHUNGSMATRIX = {
|
||||
'rule_ue_sofei': {'debit': ('6310',)},
|
||||
'rule_ue_sofei_nacht': {'debit': ('6310',)},
|
||||
'rule_pram': {'debit': ('6230',)},
|
||||
# hr_payroll_expense requires a payable debit account on EXPENSES;
|
||||
# use the reconcilable payroll payable account for its automatic
|
||||
# expense-to-payslip reconciliation.
|
||||
'rule_expenses': {'debit': ('3850',)},
|
||||
'rule_b13': {'debit': ('6240',)},
|
||||
'rule_b14': {'debit': ('6240',)},
|
||||
'rule_b14_rueck': {'debit': ('6240',)},
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP13-B: L-34-backed commuter allowance and commuter tax credit."""
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class L10nAtPayrollPendlerErklaerung(models.Model):
|
||||
_name = 'l10n.at.payroll.pendler.erklaerung'
|
||||
_description = 'AT Pendlererklärung (L 34 EDV)'
|
||||
_order = 'date_start desc, id desc'
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', required=True, index=True)
|
||||
company_id = fields.Many2one(
|
||||
related='employee_id.company_id', readonly=True, required=True)
|
||||
date_start = fields.Date(string='Gültig ab', required=True, index=True)
|
||||
date_end = fields.Date(string='Gültig bis')
|
||||
strecke_km = fields.Integer(string='Einfache Wegstrecke (km)', required=True)
|
||||
pauschale_art = fields.Selection(
|
||||
[('klein', 'Kleines Pendlerpauschale'),
|
||||
('gross', 'Großes Pendlerpauschale')], required=True)
|
||||
fahrten_stufe = fields.Selection(
|
||||
[('drittel', '4–7 Fahrten (1/3)'),
|
||||
('zwei_drittel', '8–10 Fahrten (2/3)'),
|
||||
('voll', 'Ab 11 Fahrten (voll)')], required=True, default='voll')
|
||||
pendlerrechner_datum = fields.Date(string='Pendlerrechner-Abfragedatum', required=True)
|
||||
l34_attachment_ids = fields.Many2many('ir.attachment', string='L 34 EDV / Pendlerrechner')
|
||||
arbeitgeber_kfz = fields.Boolean(string='Arbeitgebereigenes Kfz für Pendelstrecke')
|
||||
ticket_art = fields.Selection(
|
||||
[('keines', 'Kein Werkverkehr/Öffi-Ticket'),
|
||||
('werkverkehr', '§ 26 Z 5 lit a Werkverkehr'),
|
||||
('oeffi', '§ 26 Z 5 lit b Öffi-Ticket')], default='keines', required=True)
|
||||
ticket_kosten_monatlich = fields.Monetary(string='AG-Ticketkosten pro Monat', currency_field='currency_id')
|
||||
ticket_bezugsumwandlung = fields.Boolean(string='Ticket durch Bezugsumwandlung')
|
||||
currency_id = fields.Many2one(related='company_id.currency_id', readonly=True)
|
||||
note = fields.Text(string='Bemerkung')
|
||||
|
||||
@api.constrains('date_start', 'date_end', 'strecke_km', 'ticket_art', 'ticket_kosten_monatlich', 'ticket_bezugsumwandlung')
|
||||
def _check_values(self):
|
||||
for rec in self:
|
||||
if rec.date_end and rec.date_end < rec.date_start:
|
||||
raise UserError(_('The commuter declaration end date cannot precede its start date.'))
|
||||
if rec.strecke_km < 0:
|
||||
raise UserError(_('The commuter distance cannot be negative.'))
|
||||
if rec.ticket_kosten_monatlich and rec.ticket_art == 'keines':
|
||||
raise UserError(_('Ticket costs require a ticket classification.'))
|
||||
if rec.ticket_bezugsumwandlung and rec.ticket_art == 'keines':
|
||||
raise UserError(_('Salary conversion requires a ticket classification.'))
|
||||
|
||||
def _l10n_at_pendler_werte(self, payslip):
|
||||
"""(PP-LZP-Freibetrag, PE-Jahreswert mit Stufenfaktor).
|
||||
|
||||
PP ist ein §-66-Abs-3-aliquotierter Freibetrag: Jahreswert ×
|
||||
Fahrten-Stufe / 360 × Lohnsteuertage des LZP (lb-pen-09); die
|
||||
kumulative Engine annualisiert ihn über 360/tage.
|
||||
|
||||
Der Pendlereuro ist ein Absetzbetrag auf der Steuer (lb-pen-04)
|
||||
und muss daher — wie der Verkehrsabsetzbetrag — auf der
|
||||
annualisierten Basis als Jahreswert einfließen: km × €/J ×
|
||||
Fahrten-Stufe.
|
||||
|
||||
§ 26 Z 5 lit b: die AG-Ticketkosten mindern das PP anteilig
|
||||
(§ 16 Abs 1 Z 6 lit i); § 26 Z 5 lit a (Werkverkehr) ist
|
||||
datengetrieben über die L-34-Reststrecke abgebildet. Eine
|
||||
Bezugsumwandlung sperrt PP/PE nicht (Ticket = steuerpflichtiger
|
||||
laufender Bezug, der DN trägt die Kosten). PE ist mit dem
|
||||
PP-Anspruch Fixkopplung (lb-pen-04): ohne PP-Anspruch kein PE.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if self.arbeitgeber_kfz:
|
||||
return 0.0, 0.0
|
||||
params = payslip._l10n_at_param('at_pendler')
|
||||
km = self.strecke_km
|
||||
grenzen = params['gross' if self.pauschale_art == 'gross' else 'klein']
|
||||
annual = 0.0
|
||||
for minimum, value in grenzen:
|
||||
if km >= minimum:
|
||||
annual = float(value)
|
||||
factor = {'drittel': 1.0 / 3.0, 'zwei_drittel': 2.0 / 3.0,
|
||||
'voll': 1.0}[self.fahrten_stufe]
|
||||
pp = annual / 12.0 * factor \
|
||||
* payslip._l10n_at_lohnsteuertage() / 30.0
|
||||
if self.ticket_art == 'oeffi':
|
||||
pp = max(pp - self.ticket_kosten_monatlich, 0.0)
|
||||
pe = km * float(params['pendlereuro_km_jahr']) * factor
|
||||
if pp <= 0.0:
|
||||
pe = 0.0
|
||||
return pp, pe
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
def _l10n_at_pendler_erklaerung(self):
|
||||
self.ensure_one()
|
||||
return self.env['l10n.at.payroll.pendler.erklaerung'].search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('date_start', '<=', self.date_to),
|
||||
'|', ('date_end', '=', False), ('date_end', '>=', self.date_from),
|
||||
], order='date_start desc, id desc', limit=1)
|
||||
|
||||
def _l10n_at_pendler_werte(self):
|
||||
self.ensure_one()
|
||||
erklaerung = self._l10n_at_pendler_erklaerung()
|
||||
return erklaerung._l10n_at_pendler_werte(self) if erklaerung else (0.0, 0.0)
|
||||
|
||||
def _l10n_at_bemessung_laufen(self, result_rules=None):
|
||||
basis = super()._l10n_at_bemessung_laufen(result_rules)
|
||||
pp, _pe = self._l10n_at_pendler_werte()
|
||||
return basis - pp
|
||||
|
||||
def _l10n_at_absetzbetraege(self, jbg):
|
||||
betrag = super()._l10n_at_absetzbetraege(jbg)
|
||||
_pp, pe = self._l10n_at_pendler_werte()
|
||||
# Pendlereuro = direkter LSt-Absetzbetrag (§ 33 Abs 5 Z 4):
|
||||
# Jahreswert mit Fahrten-Stufe; der Jahresverbrauch läuft über
|
||||
# die kumulative Engine (wie der Verkehrsabsetzbetrag).
|
||||
return betrag + pe
|
||||
@@ -0,0 +1,227 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP13-A: documented domestic travel reimbursements.
|
||||
|
||||
The standard ``hr_payroll_expense`` module keeps the employee expense and
|
||||
payslip payable reconciliation intact. It pays the full documented expense
|
||||
through ``EXPENSES``. Only the portion above the Austrian tax-free limit is
|
||||
added as running remuneration; a neutral payroll deduction prevents this
|
||||
classification line from reimbursing the expense a second time.
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
from math import ceil
|
||||
|
||||
from odoo import _, Command, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class HrExpense(models.Model):
|
||||
_inherit = 'hr.expense'
|
||||
|
||||
l10n_at_reise_art = fields.Selection(
|
||||
[('keine', 'Keine AT-Reisekostenklassifikation'),
|
||||
('taggeld', 'Taggeld Inland'),
|
||||
('nachtigung', 'Nächtigung Inland'),
|
||||
('kilometergeld', 'Kilometergeld Dienstfahrt'),
|
||||
('oeffentlich', 'Öffentliche Verkehrsmittel Dienstreise'),
|
||||
('familienheimfahrt', 'Familienheimfahrt')],
|
||||
string='AT Reisekostenart', default='keine')
|
||||
l10n_at_reise_datum = fields.Date(string='AT Reisetag')
|
||||
l10n_at_reise_nachweis = fields.Boolean(
|
||||
string='AT Reisekostenaufzeichnung/Nachweis vorhanden',
|
||||
help='Beleg bzw. zeitnah geführte Reisekostenaufzeichnung als '
|
||||
'Voraussetzung der abgabenfreien Behandlung.')
|
||||
l10n_at_reise_stunden = fields.Float(string='AT Reisedauer (Stunden)')
|
||||
l10n_at_reise_naechtigungen = fields.Float(string='AT Nächtigungen')
|
||||
l10n_at_reise_naechtigung_pauschale = fields.Boolean(
|
||||
string='AT Nächtigungspauschale',
|
||||
help='Ohne Aktivierung wird ein belegter tatsächlicher Nächtigungs-'
|
||||
'aufwand erstattet; mit Aktivierung gilt die Pauschale.')
|
||||
l10n_at_reise_kilometer = fields.Float(string='AT Dienstkilometer')
|
||||
l10n_at_reise_fahrzeug = fields.Selection(
|
||||
[('pkw', 'Pkw/Kombi'), ('motorrad', 'Motorrad'),
|
||||
('fahrrad', 'Eigenes Fahrrad/E-Bike'),
|
||||
('dienstfahrrad', 'Dienstfahrrad'), ('fuss', 'Zu Fuß')],
|
||||
string='AT Fahrzeugart')
|
||||
l10n_at_reise_mitfahrer = fields.Integer(
|
||||
string='AT Dienstlich notwendige Mitfahrer')
|
||||
l10n_at_reise_familienheimfahrt = fields.Boolean(
|
||||
string='AT Familienheimfahrt an arbeitsfreiem Tag',
|
||||
help='Nur für die eigene Reisekostenart Familienheimfahrt: eine '
|
||||
'Fahrt je Woche und bei unzumutbarer täglicher Rückkehr.')
|
||||
l10n_at_reise_kein_taggeld = fields.Boolean(
|
||||
string='AT Kein steuerfreies Taggeld für diesen arbeitsfreien Tag',
|
||||
help='Familienheimfahrten dürfen nicht mit steuerfreiem Taggeld für '
|
||||
'denselben arbeitsfreien Tag kumulieren.')
|
||||
|
||||
def _l10n_at_reise_validate(self):
|
||||
for expense in self:
|
||||
art = expense.l10n_at_reise_art
|
||||
if art == 'keine':
|
||||
continue
|
||||
if not expense.l10n_at_reise_datum:
|
||||
raise UserError(_('AT travel expenses require a travel date.'))
|
||||
if not expense.l10n_at_reise_nachweis:
|
||||
raise UserError(_(
|
||||
'AT travel expenses require a receipt or travel expense record.'))
|
||||
if art == 'taggeld' and expense.l10n_at_reise_stunden <= 0:
|
||||
raise UserError(_('Domestic daily allowance requires a travel duration.'))
|
||||
if art == 'nachtigung' and expense.l10n_at_reise_naechtigungen <= 0:
|
||||
raise UserError(_('Overnight reimbursement requires the number of overnights.'))
|
||||
if (art in ('kilometergeld', 'familienheimfahrt')
|
||||
and (expense.l10n_at_reise_kilometer <= 0
|
||||
or not expense.l10n_at_reise_fahrzeug)):
|
||||
raise UserError(_(
|
||||
'Mileage reimbursement requires kilometres and a vehicle type.'))
|
||||
if art == 'familienheimfahrt':
|
||||
if not expense.l10n_at_reise_familienheimfahrt:
|
||||
raise UserError(_(
|
||||
'Family home travel must be recorded as a trip on a non-working day.'))
|
||||
if not expense.l10n_at_reise_kein_taggeld:
|
||||
raise UserError(_(
|
||||
'Family home travel cannot be combined with tax-free daily allowance for the same non-working day.'))
|
||||
monday = expense.l10n_at_reise_datum - timedelta(
|
||||
days=expense.l10n_at_reise_datum.weekday())
|
||||
sunday = monday + timedelta(days=6)
|
||||
weekly_trips = self.search_count([
|
||||
('id', '!=', expense.id),
|
||||
('employee_id', '=', expense.employee_id.id),
|
||||
('l10n_at_reise_art', '=', 'familienheimfahrt'),
|
||||
('l10n_at_reise_datum', '>=', monday),
|
||||
('l10n_at_reise_datum', '<=', sunday),
|
||||
('state', 'not in', ('draft', 'refused', 'cancel')),
|
||||
])
|
||||
if weekly_trips:
|
||||
raise UserError(_(
|
||||
'Only one reimbursed family home trip per calendar week is tax-free.'))
|
||||
|
||||
def action_submit(self):
|
||||
self._l10n_at_reise_validate()
|
||||
return super().action_submit()
|
||||
|
||||
def _l10n_at_reise_km_rate(self, datum):
|
||||
self.ensure_one()
|
||||
werte = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_km_geld', datum)
|
||||
rate = float(werte.get(self.l10n_at_reise_fahrzeug, 0.0))
|
||||
passenger = (float(werte.get('mitfahrer', 0.0))
|
||||
* max(self.l10n_at_reise_mitfahrer, 0))
|
||||
return rate, passenger, werte
|
||||
|
||||
def _l10n_at_reise_split(self, km_ytd=0.0, niedriger_satz_ytd=0.0):
|
||||
"""Return the tax-free and taxable portions of one expense.
|
||||
|
||||
``km_ytd`` is the confirmed annual mileage before this expense. The
|
||||
caller processes current payslip expenses chronologically, so the
|
||||
annual 30,000-km cap is applied deterministically.
|
||||
"""
|
||||
self.ensure_one()
|
||||
amount = max(self.total_amount or 0.0, 0.0)
|
||||
art = self.l10n_at_reise_art
|
||||
if not art:
|
||||
art = 'keine'
|
||||
if art == 'keine':
|
||||
return amount, 0.0, km_ytd, niedriger_satz_ytd
|
||||
datum = self.l10n_at_reise_datum
|
||||
if not datum:
|
||||
raise UserError(_('AT travel expenses require a travel date.'))
|
||||
if art in ('oeffentlich',):
|
||||
return amount, 0.0, km_ytd, niedriger_satz_ytd
|
||||
if art == 'taggeld':
|
||||
werte = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_reise_inland', datum)
|
||||
hours = self.l10n_at_reise_stunden
|
||||
if hours <= 3.0:
|
||||
cap = 0.0
|
||||
else:
|
||||
full_days, rest_hours = divmod(hours, 24.0)
|
||||
cap = full_days * float(werte['taggeld_tag'])
|
||||
if rest_hours:
|
||||
cap += ceil(rest_hours) * float(werte['taggeld_tag']) / 12.0
|
||||
frei = min(amount, cap)
|
||||
return frei, amount - frei, km_ytd, niedriger_satz_ytd
|
||||
if art == 'nachtigung':
|
||||
if not self.l10n_at_reise_naechtigung_pauschale:
|
||||
return amount, 0.0, km_ytd, niedriger_satz_ytd
|
||||
werte = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_reise_inland', datum)
|
||||
frei = min(amount, self.l10n_at_reise_naechtigungen
|
||||
* float(werte['naechtigung_pauschale']))
|
||||
return frei, amount - frei, km_ytd, niedriger_satz_ytd
|
||||
rate, passenger, werte = self._l10n_at_reise_km_rate(datum)
|
||||
remaining_km = max(float(werte['km_jahr_max']) - km_ytd, 0.0)
|
||||
eligible_km = min(self.l10n_at_reise_kilometer, remaining_km)
|
||||
# The annual kilometre cap covers all vehicles and family home trips.
|
||||
# A service bicycle has no official kilometre allowance.
|
||||
cap = eligible_km * (rate + passenger)
|
||||
# For a lower official vehicle rate, § 26 Z 4 also limits the
|
||||
# annual base allowance to €12,600; passenger supplements do not
|
||||
# enlarge that amount.
|
||||
niedriger = rate < float(werte['pkw'])
|
||||
if niedriger:
|
||||
rest_betrag = max(
|
||||
float(werte['niedriger_satz_jahr_max']) - niedriger_satz_ytd,
|
||||
0.0)
|
||||
cap = min(cap, rest_betrag + eligible_km * passenger)
|
||||
frei = min(amount, cap)
|
||||
return (frei, amount - frei,
|
||||
km_ytd + self.l10n_at_reise_kilometer,
|
||||
niedriger_satz_ytd + (eligible_km * rate if niedriger else 0.0))
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
def _l10n_at_reise_km_ytd(self):
|
||||
self.ensure_one()
|
||||
jahr_start = date(self.date_from.year, 1, 1)
|
||||
expenses = self.env['hr.expense'].search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('l10n_at_reise_art', 'in', ('kilometergeld', 'familienheimfahrt')),
|
||||
('l10n_at_reise_datum', '>=', jahr_start),
|
||||
('l10n_at_reise_datum', '<', self.date_from),
|
||||
('payslip_id.state', 'not in', ('draft', 'cancel')),
|
||||
])
|
||||
kilometres = sum(expenses.mapped('l10n_at_reise_kilometer'))
|
||||
niedriger_betrag = 0.0
|
||||
for expense in expenses:
|
||||
rate, _passenger, werte = expense._l10n_at_reise_km_rate(
|
||||
expense.l10n_at_reise_datum)
|
||||
if rate < float(werte['pkw']):
|
||||
niedriger_betrag += expense.l10n_at_reise_kilometer * rate
|
||||
return kilometres, niedriger_betrag
|
||||
|
||||
def _l10n_at_reise_steuerpflichtig(self):
|
||||
self.ensure_one()
|
||||
km_ytd, niedriger_satz_ytd = self._l10n_at_reise_km_ytd()
|
||||
taxable = 0.0
|
||||
expenses = self.expense_ids.filtered(
|
||||
lambda expense: expense.l10n_at_reise_art != 'keine').sorted(
|
||||
key=lambda expense: (expense.l10n_at_reise_datum or date.min,
|
||||
expense.id))
|
||||
for expense in expenses:
|
||||
_frei, ueberhang, km_ytd, niedriger_satz_ytd = \
|
||||
expense._l10n_at_reise_split(km_ytd, niedriger_satz_ytd)
|
||||
taxable += ueberhang
|
||||
return taxable
|
||||
|
||||
def _update_expense_input_line_ids(self, search_new_valid_expenses=False):
|
||||
super()._update_expense_input_line_ids(
|
||||
search_new_valid_expenses=search_new_valid_expenses)
|
||||
taxable_input = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.input_type_atp_reise_steuerpflichtig')
|
||||
offset_input = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.input_type_atp_reise_nettoausgleich')
|
||||
for payslip in self:
|
||||
obsolete = payslip.input_line_ids.filtered(
|
||||
lambda line: line.input_type_id in (taxable_input, offset_input))
|
||||
commands = [Command.delete(line.id) for line in obsolete]
|
||||
taxable = payslip._l10n_at_reise_steuerpflichtig()
|
||||
if taxable:
|
||||
commands += [
|
||||
Command.create({'amount': taxable,
|
||||
'input_type_id': taxable_input.id}),
|
||||
Command.create({'amount': taxable,
|
||||
'input_type_id': offset_input.id}),
|
||||
]
|
||||
if commands:
|
||||
payslip.input_line_ids = commands
|
||||
@@ -21,3 +21,5 @@ access_l10n_at_payroll_aeb_user,L10n AT Payroll AEB User,model_l10n_at_payroll_a
|
||||
access_l10n_at_payroll_aeb_manager,L10n AT Payroll AEB Manager,model_l10n_at_payroll_aeb,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_pfaendung_user,L10n AT Payroll Pfaendung User,model_l10n_at_payroll_pfaendung,hr_payroll.group_hr_payroll_user,1,1,1,0
|
||||
access_l10n_at_payroll_pfaendung_manager,L10n AT Payroll Pfaendung Manager,model_l10n_at_payroll_pfaendung,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_pendler_erklaerung_user,L10n AT Payroll Pendlererklaerung User,model_l10n_at_payroll_pendler_erklaerung,hr_payroll.group_hr_payroll_user,1,1,1,0
|
||||
access_l10n_at_payroll_pendler_erklaerung_manager,L10n AT Payroll Pendlererklaerung Manager,model_l10n_at_payroll_pendler_erklaerung,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
|
||||
|
@@ -37,4 +37,11 @@
|
||||
<field name="groups"
|
||||
eval="[(4, ref('hr_payroll.group_hr_payroll_user'))]"/>
|
||||
</record>
|
||||
<record id="rule_pendler_erklaerung_company" model="ir.rule">
|
||||
<field name="name">Pendlererklärung: eigene Company</field>
|
||||
<field name="model_id" ref="model_l10n_at_payroll_pendler_erklaerung"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
|
||||
<field name="groups"
|
||||
eval="[(4, ref('hr_payroll.group_hr_payroll_user'))]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -15,3 +15,5 @@ from . import test_aeb_private
|
||||
from . import test_pfaendung_private
|
||||
from . import test_sachbezuege_private
|
||||
from . import test_sachbezuege2_private
|
||||
from . import test_reisekosten_private
|
||||
from . import test_pendler_private
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP13-B: Pendlerpauschale (Freibetrag) und Pendlereuro (Absetzbetrag)
|
||||
auf der kumulativen LSt-Engine — L-34-gestützte, effektiv datierte
|
||||
Erklärungen (lb-pen-01/02/04/06/09)."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class PendlerPrivateTest(TransactionCase):
|
||||
|
||||
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 Pendler 40h Mo-Fr',
|
||||
'tz': 'Europe/Vienna',
|
||||
'hours_per_day': 8,
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Tag %d' % weekday,
|
||||
'dayofweek': str(weekday),
|
||||
'hour_from': 8.0, 'hour_to': 16.0})
|
||||
for weekday in range(5)
|
||||
],
|
||||
})
|
||||
self.env.company.resource_calendar_id = self.calendar
|
||||
|
||||
def _employee(self, contract_start=date(2020, 1, 1)):
|
||||
employee = self.env['hr.employee'].create({'name': 'Test Pendler 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': contract_start})
|
||||
version.write({
|
||||
'contract_date_start': contract_start,
|
||||
'kv_id': self.kv2203.id,
|
||||
'kv_gruppe_id': self.gruppe_ii.id,
|
||||
'erfahrungsstufe': 1,
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
})
|
||||
return employee
|
||||
|
||||
def _erklaerung(self, employee, **vals):
|
||||
werte = {
|
||||
'employee_id': employee.id,
|
||||
'date_start': date(2026, 1, 1),
|
||||
'strecke_km': 25,
|
||||
'pauschale_art': 'klein',
|
||||
'fahrten_stufe': 'voll',
|
||||
'pendlerrechner_datum': date(2025, 12, 15),
|
||||
}
|
||||
werte.update(vals)
|
||||
return self.env['l10n.at.payroll.pendler.erklaerung'].create(werte)
|
||||
|
||||
def _slip(self, employee, month, year=2026):
|
||||
first = date(year, month, 1)
|
||||
next_month = (first + timedelta(days=32)).replace(day=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Test-Pendler %d/%d' % (month, year),
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': first,
|
||||
'date_to': next_month - timedelta(days=1),
|
||||
})
|
||||
slip.compute_sheet()
|
||||
return slip
|
||||
|
||||
def test_01_pp_freibetrag_voller_monat(self):
|
||||
"""Kleines PP 25 km voll: 696/12 = 58 € als Freibetrag der
|
||||
laufenden LSt-Bemessung; kein SV- und kein LNK-Effekt."""
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
self._erklaerung(emp_mit)
|
||||
slip_mit = self._slip(emp_mit, 1)
|
||||
slip_ohne = self._slip(emp_ohne, 1)
|
||||
pp, pe = slip_mit._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp, 58.0, places=2)
|
||||
self.assertAlmostEqual(pe, 150.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_ohne._l10n_at_bemessung_laufen()
|
||||
- slip_mit._l10n_at_bemessung_laufen(), 58.0, places=2)
|
||||
# PP = LSt-Freibetrag: SV- und LNK-Basis unverändert.
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sv_basis_roh()
|
||||
- slip_ohne._l10n_at_sv_basis_roh(), 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_pe_absetzbetrag(self):
|
||||
"""Pendlereuro 2026: 6 €/km/Jahr → 25 km × 6 = 150 €/Jahr
|
||||
zusätzlich zum Verkehrsabsetzbetrag (496 €)."""
|
||||
emp = self._employee()
|
||||
self._erklaerung(emp)
|
||||
slip = self._slip(emp, 1)
|
||||
absetz = self._l10n_at_vab()
|
||||
self.assertAlmostEqual(
|
||||
slip._l10n_at_absetzbetraege(100000.0), absetz + 150.0, places=2)
|
||||
|
||||
def _l10n_at_vab(self):
|
||||
absetz = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_lst_absetzbetraege', date(2026, 1, 31))
|
||||
return float(absetz['verkehrsabsetzbetrag'])
|
||||
|
||||
def test_03_stufen_und_grosses_pp(self):
|
||||
"""Staffel „mehr als": 40 km → klein 696 (nicht 1.356);
|
||||
großes PP 2 km → 372/12 = 31 €, PE 2 km × 6 = 12 €/J."""
|
||||
emp_40 = self._employee()
|
||||
self._erklaerung(emp_40, strecke_km=40)
|
||||
pp_40, pe_40 = self._slip(emp_40, 1)._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp_40, 58.0, places=2)
|
||||
self.assertAlmostEqual(pe_40, 240.0, places=2)
|
||||
|
||||
emp_gross = self._employee()
|
||||
self._erklaerung(emp_gross, strecke_km=2,
|
||||
pauschale_art='gross')
|
||||
pp, pe = self._slip(emp_gross, 1)._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp, 31.0, places=2)
|
||||
self.assertAlmostEqual(pe, 12.0, places=2)
|
||||
|
||||
def test_04_fahrten_stufen(self):
|
||||
"""lb-pen-09: 20 km klein, 4 Fahrten (1/3): PP 19,33 €. PE
|
||||
wird als Jahreswert mit Stufenfaktor zurückgegeben: (20 × 6) ×
|
||||
1/3 = 40 €/Jahr (Monatswert 3,33 €, lb-pen-04)."""
|
||||
emp = self._employee()
|
||||
self._erklaerung(emp, strecke_km=20, fahrten_stufe='drittel')
|
||||
pp, pe = self._slip(emp, 1)._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp, 19.33, places=2)
|
||||
self.assertAlmostEqual(pe, 120.0 * (1.0 / 3.0), places=2)
|
||||
|
||||
def test_05_untermonatig_lb_pen_09(self):
|
||||
"""lb-pen-09-Quellenbeispiel: Beginn 25.4., 5 Pendeltage, 25 km,
|
||||
klein 1/3 → 696 × 1/3 / 360 × 6 = 3,87 €."""
|
||||
emp = self._employee(contract_start=date(2026, 4, 25))
|
||||
self._erklaerung(emp, date_start=date(2026, 4, 1),
|
||||
fahrten_stufe='drittel')
|
||||
slip = self._slip(emp, 4)
|
||||
pp, _pe = slip._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp, 3.87, places=2)
|
||||
|
||||
def test_06_arbeitgeber_kfz_sperre(self):
|
||||
"""Arbeitgebereigenes Kfz für die Pendelstrecke: PP und PE
|
||||
entfallen (§ 16 Abs 1 Z 6 lit b)."""
|
||||
emp = self._employee()
|
||||
self._erklaerung(emp, arbeitgeber_kfz=True)
|
||||
slip = self._slip(emp, 1)
|
||||
self.assertEqual(slip._l10n_at_pendler_werte(), (0.0, 0.0))
|
||||
self.assertAlmostEqual(
|
||||
slip._l10n_at_absetzbetraege(100000.0),
|
||||
self._l10n_at_vab(), places=2)
|
||||
|
||||
def test_07_oeffi_ticket_kostenreduktion(self):
|
||||
"""§ 26 Z 5 lit b: AG-Ticketkosten mindern das PP anteilig;
|
||||
fällt das PP auf 0, entfällt auch der Fixkopplungs-PE."""
|
||||
emp = self._employee()
|
||||
self._erklaerung(emp, ticket_art='oeffi',
|
||||
ticket_kosten_monatlich=30.0)
|
||||
pp, pe = self._slip(emp, 1)._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp, 28.0, places=2)
|
||||
self.assertAlmostEqual(pe, 150.0, places=2)
|
||||
|
||||
emp_null = self._employee()
|
||||
self._erklaerung(emp_null, ticket_art='oeffi',
|
||||
ticket_kosten_monatlich=100.0)
|
||||
pp_null, pe_null = self._slip(emp_null, 1)._l10n_at_pendler_werte()
|
||||
self.assertEqual(pp_null, 0.0)
|
||||
self.assertEqual(pe_null, 0.0)
|
||||
|
||||
def test_08_bezugsumwandlung_sperrt_nicht(self):
|
||||
"""Bezugsumwandlung: Ticket ist steuerpflichtiger laufender
|
||||
Bezug (DN trägt die Kosten) — PP/PE laufen normal."""
|
||||
emp = self._employee()
|
||||
self._erklaerung(emp, ticket_art='oeffi',
|
||||
ticket_bezugsumwandlung=True)
|
||||
pp, pe = self._slip(emp, 1)._l10n_at_pendler_werte()
|
||||
self.assertAlmostEqual(pp, 58.0, places=2)
|
||||
self.assertAlmostEqual(pe, 150.0, places=2)
|
||||
|
||||
def test_09_effektive_datierte_auswahl(self):
|
||||
"""Neuere Erklärung überschreibt die ältere im Gültigkeits-
|
||||
fenster; historische Läufe behalten die damals gültige."""
|
||||
emp = self._employee()
|
||||
self._erklaerung(emp, strecke_km=25)
|
||||
neu = self._erklaerung(
|
||||
emp, date_start=date(2026, 3, 1), strecke_km=61)
|
||||
m2 = self._slip(emp, 2)
|
||||
m4 = self._slip(emp, 4)
|
||||
self.assertAlmostEqual(m2._l10n_at_pendler_werte()[0], 58.0, places=2)
|
||||
self.assertAlmostEqual(m4._l10n_at_pendler_werte()[0], 168.0, places=2)
|
||||
self.assertEqual(
|
||||
m4._l10n_at_pendler_erklaerung(), neu)
|
||||
|
||||
def test_10_e2e_lst_wirkung(self):
|
||||
"""E2E: PP mindert die LSt-Bemessung und damit die LSt, PE die
|
||||
Steuer direkt — LST_LFD sinkt gegen den Basellauf."""
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
self._erklaerung(emp_mit)
|
||||
slip_mit = self._slip(emp_mit, 1)
|
||||
slip_ohne = self._slip(emp_ohne, 1)
|
||||
lst_mit = sum(slip_mit.line_ids.filtered(
|
||||
lambda line: line.code == 'LST_LFD').mapped('total'))
|
||||
lst_ohne = sum(slip_ohne.line_ids.filtered(
|
||||
lambda line: line.code == 'LST_LFD').mapped('total'))
|
||||
self.assertLess(lst_mit, lst_ohne)
|
||||
@@ -0,0 +1,146 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP13-A: domestic travel expenses and taxable-overhang payroll split."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class ReisekostenPrivateTest(TransactionCase):
|
||||
|
||||
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 Reise 40h Mo-Fr',
|
||||
'tz': 'Europe/Vienna',
|
||||
'hours_per_day': 8,
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Tag %d' % weekday,
|
||||
'dayofweek': str(weekday),
|
||||
'hour_from': 8.0, 'hour_to': 16.0})
|
||||
for weekday in range(5)
|
||||
],
|
||||
})
|
||||
self.env.company.resource_calendar_id = self.calendar
|
||||
|
||||
def _employee(self):
|
||||
employee = self.env['hr.employee'].create({'name': 'Test Reise 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, month, year=2026):
|
||||
first = date(year, month, 1)
|
||||
next_month = (first + timedelta(days=32)).replace(day=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Test-Reise-Privat %d/%d' % (month, year),
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': first,
|
||||
'date_to': next_month - timedelta(days=1),
|
||||
})
|
||||
slip.compute_sheet()
|
||||
return slip
|
||||
|
||||
def _expense(self, values):
|
||||
"""An in-memory expense is sufficient for the legal split helper."""
|
||||
return self.env['hr.expense'].new({
|
||||
'name': 'Testreise',
|
||||
'employee_id': self._employee().id,
|
||||
'total_amount_currency': 0.0,
|
||||
'quantity': 1.0,
|
||||
**values,
|
||||
})
|
||||
|
||||
def _input(self, slip, xmlid, amount):
|
||||
input_type = self.env.ref(xmlid)
|
||||
self.env['hr.payslip.input'].create({
|
||||
'payslip_id': slip.id,
|
||||
'input_type_id': input_type.id,
|
||||
'amount': amount,
|
||||
})
|
||||
|
||||
def _total(self, slip, code):
|
||||
return sum(slip.line_ids.filtered(lambda line: line.code == code).mapped('total'))
|
||||
|
||||
def test_01_inland_taggeld_cap(self):
|
||||
"""10 h domestic travel: 10 × 30/12 = 25 €; the 15 € excess is
|
||||
ordinary running remuneration."""
|
||||
expense = self._expense({
|
||||
'total_amount': 40.0,
|
||||
'l10n_at_reise_art': 'taggeld',
|
||||
'l10n_at_reise_datum': date(2026, 1, 15),
|
||||
'l10n_at_reise_stunden': 10.0,
|
||||
'l10n_at_reise_nachweis': True,
|
||||
})
|
||||
frei, taxable, km_ytd, niedriger_satz_ytd = expense._l10n_at_reise_split()
|
||||
self.assertAlmostEqual(frei, 25.0, places=2)
|
||||
self.assertAlmostEqual(taxable, 15.0, places=2)
|
||||
self.assertEqual(km_ytd, 0.0)
|
||||
self.assertEqual(niedriger_satz_ytd, 0.0)
|
||||
|
||||
def test_02_kilometergeld_annual_cap(self):
|
||||
"""After 29,800 confirmed annual kilometres, 500 private-car
|
||||
kilometres yield only 100 € tax-free; 150 € are taxable."""
|
||||
expense = self._expense({
|
||||
'total_amount': 250.0,
|
||||
'l10n_at_reise_art': 'kilometergeld',
|
||||
'l10n_at_reise_datum': date(2026, 11, 15),
|
||||
'l10n_at_reise_kilometer': 500.0,
|
||||
'l10n_at_reise_fahrzeug': 'pkw',
|
||||
'l10n_at_reise_nachweis': True,
|
||||
})
|
||||
frei, taxable, km_ytd, niedriger_satz_ytd = expense._l10n_at_reise_split(29800.0)
|
||||
self.assertAlmostEqual(frei, 100.0, places=2)
|
||||
self.assertAlmostEqual(taxable, 150.0, places=2)
|
||||
self.assertAlmostEqual(km_ytd, 30300.0, places=2)
|
||||
self.assertEqual(niedriger_satz_ytd, 0.0)
|
||||
|
||||
def test_03_taxable_overhang_is_not_reimbursed_twice(self):
|
||||
"""EXPENSES refunds the actual 40 €. The 15 € classification line
|
||||
is offset in the net while its SV and LSt remain deductible."""
|
||||
employee_with = self._employee()
|
||||
employee_without = self._employee()
|
||||
with_expense = self._slip(employee_with, 1)
|
||||
without_expense = self._slip(employee_without, 1)
|
||||
self._input(with_expense, 'hr_payroll_expense.expense_other_input', 40.0)
|
||||
self._input(with_expense,
|
||||
'l10n_at_hr_payroll_private.input_type_atp_reise_steuerpflichtig',
|
||||
15.0)
|
||||
self._input(with_expense,
|
||||
'l10n_at_hr_payroll_private.input_type_atp_reise_nettoausgleich',
|
||||
15.0)
|
||||
with_expense.compute_sheet()
|
||||
|
||||
self.assertAlmostEqual(self._total(with_expense, 'EXPENSES'), 40.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
self._total(with_expense, 'REISE_STEUERPFLICHTIG'), 15.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
self._total(with_expense, 'REISE_NETTOAUSGLEICH'), 15.0, places=2)
|
||||
svdn_difference = (with_expense._l10n_at_svdn_gesamt()
|
||||
- without_expense._l10n_at_svdn_gesamt())
|
||||
self.assertAlmostEqual(
|
||||
with_expense._l10n_at_bemessung_laufen()
|
||||
- without_expense._l10n_at_bemessung_laufen()
|
||||
+ svdn_difference, 15.0, places=2)
|
||||
net_difference = (self._total(with_expense, 'NET')
|
||||
- self._total(without_expense, 'NET'))
|
||||
tax_difference = self._total(with_expense, 'LST_LFD') \
|
||||
- self._total(without_expense, 'LST_LFD')
|
||||
self.assertAlmostEqual(net_difference, 40.0 - svdn_difference - tax_difference,
|
||||
places=2)
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- AP13-B: Pendlererklärung (L 34 EDV / Pendlerrechner) — -->
|
||||
<!-- effektiv datierte, company-gebundene Stammdaten (lb-pen-06/08). -->
|
||||
<record id="pendler_erklaerung_list_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.pendler.erklaerung.list</field>
|
||||
<field name="model">l10n.at.payroll.pendler.erklaerung</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Pendlererklärungen">
|
||||
<field name="employee_id"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end" optional="show"/>
|
||||
<field name="strecke_km"/>
|
||||
<field name="pauschale_art"/>
|
||||
<field name="fahrten_stufe" optional="show"/>
|
||||
<field name="ticket_art" optional="show"/>
|
||||
<field name="arbeitgeber_kfz" optional="show"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="pendler_erklaerung_form_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.pendler.erklaerung.form</field>
|
||||
<field name="model">l10n.at.payroll.pendler.erklaerung</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Pendlererklärung (L 34 EDV)">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Erklärung (L 34 EDV)">
|
||||
<field name="employee_id"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="pendlerrechner_datum"/>
|
||||
<field name="l34_attachment_ids" widget="many2many_binary"/>
|
||||
</group>
|
||||
<group string="Werte (aus Pendlerrechner, nicht nachrechnen)">
|
||||
<field name="strecke_km"/>
|
||||
<field name="pauschale_art"/>
|
||||
<field name="fahrten_stufe"/>
|
||||
<field name="arbeitgeber_kfz"/>
|
||||
<field name="ticket_art"/>
|
||||
<field name="ticket_kosten_monatlich"
|
||||
invisible="ticket_art == 'keines'"/>
|
||||
<field name="ticket_bezugsumwandlung"
|
||||
invisible="ticket_art == 'keines'"/>
|
||||
<field name="note"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_pendler_erklaerung" model="ir.actions.act_window">
|
||||
<field name="name">Pendlererklärungen (L 34 EDV)</field>
|
||||
<field name="res_model">l10n.at.payroll.pendler.erklaerung</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Erste Pendlererklärung erfassen
|
||||
</p>
|
||||
<p>
|
||||
L 34 EDV mit Pendlerrechner-Ausdruck: Wegstrecke, Pauschalenart,
|
||||
Fahrtenkategorie und Werkverkehrs-/Öffi-Ticket-Verhältnisse.
|
||||
Änderungen der Verhältnisse werden als neue Gültigkeitsperiode
|
||||
erfasst (binnen eines Monats meldepflichtig, lb-pen-06).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_pendler"
|
||||
name="Pendlerförderung (L 34)"
|
||||
parent="hr_work_entry_enterprise.menu_hr_payroll_root"
|
||||
action="action_pendler_erklaerung" sequence="41"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="hr_expense_view_form_reisekosten" model="ir.ui.view">
|
||||
<field name="name">hr.expense.form.l10n.at.reisekosten</field>
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="inherit_id" ref="hr_payroll_expense.hr_expense_view_form_inherit_payroll"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<group string="AT Reisekosten (§ 26 EStG)"
|
||||
groups="hr_payroll.group_hr_payroll_user">
|
||||
<group>
|
||||
<field name="l10n_at_reise_art"/>
|
||||
<field name="l10n_at_reise_datum"
|
||||
invisible="l10n_at_reise_art == 'keine'"/>
|
||||
<field name="l10n_at_reise_nachweis"
|
||||
invisible="l10n_at_reise_art == 'keine'"/>
|
||||
</group>
|
||||
<group invisible="l10n_at_reise_art == 'keine'">
|
||||
<field name="l10n_at_reise_stunden"
|
||||
invisible="l10n_at_reise_art != 'taggeld'"/>
|
||||
<field name="l10n_at_reise_naechtigungen"
|
||||
invisible="l10n_at_reise_art != 'nachtigung'"/>
|
||||
<field name="l10n_at_reise_naechtigung_pauschale"
|
||||
invisible="l10n_at_reise_art != 'nachtigung'"/>
|
||||
<field name="l10n_at_reise_kilometer"
|
||||
invisible="l10n_at_reise_art not in ('kilometergeld', 'familienheimfahrt')"/>
|
||||
<field name="l10n_at_reise_fahrzeug"
|
||||
invisible="l10n_at_reise_art not in ('kilometergeld', 'familienheimfahrt')"/>
|
||||
<field name="l10n_at_reise_mitfahrer"
|
||||
invisible="l10n_at_reise_art != 'kilometergeld'"/>
|
||||
<field name="l10n_at_reise_familienheimfahrt"
|
||||
invisible="l10n_at_reise_art != 'familienheimfahrt'"/>
|
||||
<field name="l10n_at_reise_kein_taggeld"
|
||||
invisible="l10n_at_reise_art != 'familienheimfahrt'"/>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user