[IMP] l10n_at_hr_payroll_private: add end-of-employment payroll cycle (AP14-A)
Pay the untaken vacation, dismissal compensation, time balances and open advances from one exit run. The vacation replacement splits natively into a running and a special-payment component per § 67 Abs 8 lit d, only the running part extends the mandatory insurance under § 11 Abs 2 ASVG with sequential windows (dismissal compensation first) and daily-value ceilings. Dismissal compensation follows the one-fifth rule of § 67 Abs 8 lit b without raising the annual sixth, stays fully social-insurable by allocation over the notice period and fully subject to payroll taxes. Advances are offset against net pay only (§ 293 Abs 3 EO) and time credits carry the 50 % statutory surcharge without § 68 relief. Verified against the original statute texts in .ris/; § 67 Abs 6 lump-sum payments are deliberately deferred to AP15.
This commit is contained in:
+54
-2
@@ -2,9 +2,61 @@
|
||||
|
||||
## Current focus
|
||||
|
||||
**AP13-B Pendlerförderung umgesetzt — 127 Tests grün**
|
||||
**AP14-A Endabrechnungs-Zyklus umgesetzt — 133 Tests grün**
|
||||
(2026-09-14, uncommittet bis Nutzerfreigabe):
|
||||
|
||||
- **Rechtskorrektur gegen Originalwortlaut (.ris/, RIS-Gate erledigt):**
|
||||
§ 67 Abs 6 (freiwillige Abfertigung/Abfindung) = 6 %-Bänder
|
||||
(Z 1 ¼ der letzten-12-Monate-Bezüge, Deckel 9×HBG; Z 2 Dienstzeit-
|
||||
bänder), NICHT Fünftel → nach AP15 verschoben. Fünftelregelung gilt
|
||||
§ 67 Abs 8 lit b (Kündigungs-/Abgangsentschädigung) — diese sind
|
||||
SV-PFLICHTIG (Umlegung auf Kündigungsfrist, § 11 Abs 2 ASVG) und
|
||||
LNK-PFLICHTIG (FLAG § 41 Abs 4 lit b nennt nur Abs 3 und 6).
|
||||
§ 11 Abs 2 ASVG verifiziert: PV-Verlängerung für UEL-Bezug UND
|
||||
Kündigungsentschädigung, bei beiden ZUERST Kündigungsentschädigung,
|
||||
dann UEL (sequentielle Fenster); 6-WT/5-AT-Tage-Umrechnung ist
|
||||
Verwaltungspraxis (lb-url-15), nicht Gesetzeswortlaut. § 67 Abs 8
|
||||
lit d (UEL-Zweikomponenten) wortwörtlich bestätigt. § 19e Abs 2
|
||||
AZG (+50 % auf NAZ-Guthaben) verifiziert.
|
||||
- **Umsetzung (Privat 19.0.12.0.0):** `models/endabrechnung.py` mit
|
||||
Regel-Flags `l10n_at_sv_verlaengerung` (urlaubsersatz/abgang) und
|
||||
`l10n_at_s67_fuenftel`; UEL-Helfer (Divisor 26/22, SZ-Tageswert
|
||||
2×SZ-Basis/12); Upstream-Replicas mit Pin-Kommentar für
|
||||
`_at_lnk_basis` (Abgang VOR der Freibetragsstaffel) und
|
||||
`_l10n_at_sv_basis_laufen_monate` (Verlängerungsanteile mit
|
||||
Tageswert-Deckel HBG×Tage/30, Standardanteile volle Monats-HBG);
|
||||
`_l10n_at_bemessung_laufen` += Fünftel-Tarifteil (4/5, Deckel
|
||||
12.474 = 9×HBG/5) ohne Sechstel-Effekt.
|
||||
- **Regeln:** URL_ERSATZ_LFD (laufend, verlaengerung=urlaubsersatz),
|
||||
URL_ERSATZ_SZ (sonstig), ABGANG (neutral, sv_art=laufend,
|
||||
verlaengerung=abgang, fuenftel), ZEIT_GUTHABEN (laufend ×1,5,
|
||||
KEIN §-68), ZEIT_SCHULDEN (negativ), END_ABRECHNUNG_VORSCHUSS
|
||||
(neutral/keine — Netto-Abzug § 293 Abs 3 EO). Eingaben
|
||||
ATP_UEL_TAGE/ABGANG/ABGANG_FFRIST_TAGE/ZEIT_GUTHABEN/ZEIT_SCHULDEN/
|
||||
ABRECHNUNG_VORSCHUSS; Parameter at_endabrechnung (Divisoren).
|
||||
- **Wizard** `l10n.at.payroll.endabrechnung.wizard` (ACL, Views,
|
||||
Menü seq 42): sammelt UEL-Tage/Abgang/Frist/Zeit/Vorschuss, Vorschau
|
||||
über flüchtigen new()-Payslip, Button legt Endabrechnungs-Lauf im
|
||||
Austrittsmonat mit vorbefüllten Eingaben an. Achtung: hr.version-
|
||||
Strukturfeld heißt `structure_id` (nicht struct_id).
|
||||
- **Fund beim Testlauf:** Eingabetypen-Datei fehlte zunächst in der
|
||||
Manifest-Datenliste → NOT-NULL input_type_id; zwei Testassertionen
|
||||
mussten SV-DN-Differenzen bzw. den KVGEO-Standardanteil im März
|
||||
berücksichtigen.
|
||||
- **Tests** test_endabrechnung_private.py (6): UEL-Zweikomponenten
|
||||
(1.320/220 €, Sechstel +5.280), SV-Tage-Schema (23→31, 12→14,
|
||||
6→21) + Monatsverteilung, Abgang-Fünftel (Sechstel unverändert,
|
||||
LNK +9.000, Umlegung 16/26 Tage), Vorschuss-Netto (Abgaben neutral),
|
||||
Zeitguthaben ±, Wizard E2E. Suite **133/133** in ap13a_test.
|
||||
- **Offen AP14-B/AP15:** Vergleich § 67 Abs 8 lit a + freiwillige
|
||||
Abfertigung § 67 Abs 6 (beides mit Abf-Alt-Günstiger-Maschine,
|
||||
rollierende 12-Monats-Basis), Prämien-Beendigungs-Rollung,
|
||||
Meldewesen-Schnittstellen („Ende des Entgeltanspruchs“ in mBGM,
|
||||
gesonderter Lohnzettel LStR Rz 911a).
|
||||
|
||||
**AP13-B Pendlerförderung umgesetzt — 127 Tests grün**
|
||||
(2026-09-14, Commit **`9ff977e`** nach 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,
|
||||
@@ -39,7 +91,7 @@
|
||||
(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).
|
||||
- Version **19.0.11.0.0**.
|
||||
|
||||
**AP13-A Reisekosten Inland committed — 127-er Suite grün**
|
||||
(2026-09-14, Commit **`3f307a0`**):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
|
||||
'version': '19.0.11.0.0',
|
||||
'version': '19.0.12.0.0',
|
||||
'author': 'Florian Egger',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -104,6 +104,7 @@ gem360-Brücke).
|
||||
'data/work_entry_types_private.xml',
|
||||
'data/absence_private.xml',
|
||||
'data/input_types_private.xml',
|
||||
'data/input_types_endabrechnung_private.xml',
|
||||
'data/salary_rules_private.xml',
|
||||
'data/salary_rules_lst_private.xml',
|
||||
'data/salary_rules_efzg_private.xml',
|
||||
@@ -113,6 +114,7 @@ gem360-Brücke).
|
||||
'data/salary_rules_sb_private.xml',
|
||||
'data/salary_rules_sb2_private.xml',
|
||||
'data/salary_rules_reisekosten_private.xml',
|
||||
'data/salary_rules_endabrechnung_private.xml',
|
||||
'data/salary_rules_sv_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
@@ -126,6 +128,7 @@ gem360-Brücke).
|
||||
'data/rule_parameters_sb2_private.xml',
|
||||
'data/rule_parameters_reisekosten_private.xml',
|
||||
'data/rule_parameters_pendler_private.xml',
|
||||
'data/rule_parameters_endabrechnung_private.xml',
|
||||
'data/bv_kasse_seed.xml',
|
||||
'views/kv_views.xml',
|
||||
'views/bv_views.xml',
|
||||
@@ -137,6 +140,7 @@ gem360-Brücke).
|
||||
'views/sachbezuege_views.xml',
|
||||
'views/reisekosten_views.xml',
|
||||
'views/pendler_views.xml',
|
||||
'views/endabrechnung_views.xml',
|
||||
'report/gp8_reports.xml',
|
||||
'report/buchungsbeleg_report.xml',
|
||||
'report/bv_report.xml',
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- AP14-A: Endabrechnungs-Eingaben. Mengen-Eingaben (Tage/Stunden)
|
||||
tragen die Menge im amount-Feld (Muster ATP_SB_MAHLZEIT); die
|
||||
Regeln leiten daraus die Beträge ab. -->
|
||||
<record id="input_type_atp_uel_tage" model="hr.payslip.input.type">
|
||||
<field name="name">Urlaubsersatzleistung — offene Urlaubstage</field>
|
||||
<field name="code">ATP_UEL_TAGE</field>
|
||||
<field name="is_quantity" eval="True"/>
|
||||
</record>
|
||||
<record id="input_type_atp_abgang" model="hr.payslip.input.type">
|
||||
<field name="name">Abgangsentschädigung (Brutto)</field>
|
||||
<field name="code">ATP_ABGANG</field>
|
||||
</record>
|
||||
<record id="input_type_atp_abgang_ffrist_tage" model="hr.payslip.input.type">
|
||||
<field name="name">Abgangsentschädigung — Kündigungsfrist (Kalendertage)</field>
|
||||
<field name="code">ATP_ABGANG_FFRIST_TAGE</field>
|
||||
<field name="is_quantity" eval="True"/>
|
||||
</record>
|
||||
<record id="input_type_atp_zeit_guthaben" model="hr.payslip.input.type">
|
||||
<field name="name">Zeitguthaben Normalarbeitszeit (Stunden)</field>
|
||||
<field name="code">ATP_ZEIT_GUTHABEN</field>
|
||||
<field name="is_quantity" eval="True"/>
|
||||
</record>
|
||||
<record id="input_type_atp_zeit_schulden" model="hr.payslip.input.type">
|
||||
<field name="name">Zeitschulden (Stunden)</field>
|
||||
<field name="code">ATP_ZEIT_SCHULDEN</field>
|
||||
<field name="is_quantity" eval="True"/>
|
||||
</record>
|
||||
<record id="input_type_atp_abrechnung_vorschuss" model="hr.payslip.input.type">
|
||||
<field name="name">Aufrechnung offener Vorschuss (Netto)</field>
|
||||
<field name="code">ATP_ABRECHNUNG_VORSCHUSS</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- AP14-A: Endabrechnungs-Parameter (lb-url-16 Tagewert-Divisoren;
|
||||
§-10-Aliquotierung läuft über die Urlaubstage-Eingabe, der
|
||||
Fünftel-Deckel dynamisch aus 9 × at_sv_hbg_monatlich / 5). -->
|
||||
<record id="hr_rule_parameter_at_endabrechnung" model="hr.rule.parameter">
|
||||
<field name="name">Endabrechnung — UEL-Tagewert-Divisoren</field>
|
||||
<field name="code">at_endabrechnung</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
</record>
|
||||
<record id="hr_rule_parameter_value_at_endabrechnung"
|
||||
model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_at_endabrechnung"/>
|
||||
<field name="date_from">2026-01-01</field>
|
||||
<field name="parameter_value">{'divisor_werktage': 26.0, 'divisor_arbeitstage': 22.0}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- AP14-A: Endabrechnungs-Regeln. -->
|
||||
<!-- UEL: § 67 Abs 8 lit d — laufender Anteil als laufender -->
|
||||
<!-- Bezug (Tarif + Sechstel nativ), SZ-Anteil als sonstiger Bezug -->
|
||||
<!-- (6 %-Band, Freibetrag/Freigrenze im Kern). SV: nur der -->
|
||||
<!-- laufende Anteil verlängert die Pflichtversicherung (§ 11 -->
|
||||
<!-- Abs 2 ASVG, Tage-Umrechnung lb-url-15). -->
|
||||
<!-- Abgang: § 67 Abs 8 lit b — neutral (kein Sechstel), LSt über -->
|
||||
<!-- Fünftel-Override; SV-pflichtig mit Umlegung auf die -->
|
||||
<!-- Kündigungsfrist; LNK-pflichtig (FLAG § 41 Abs 4 nennt nur -->
|
||||
<!-- § 67 Abs 3 und 6). -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<!-- Urlaubsersatzleistung — laufender Anteil (§ 67 Abs 8 lit d). -->
|
||||
<record id="rule_url_ersatz_lfd" model="hr.salary.rule">
|
||||
<field name="name">Urlaubsersatzleistung — laufender Anteil (§ 67 Abs 8 lit d)</field>
|
||||
<field name="code">URL_ERSATZ_LFD</field>
|
||||
<field name="sequence" eval="25"/>
|
||||
<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_UEL_TAGE' in inputs and inputs['ATP_UEL_TAGE'].amount</field>
|
||||
<field name="l10n_at_bemessung">laufend</field>
|
||||
<field name="l10n_at_sv_verlaengerung">urlaubsersatz</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# § 10 UrlG (lb-url-16): Tageswert = Monatsentgelt / Divisor
|
||||
# (26 Werktage / 22 Arbeitstage); keine Rundung auf ganze Tage.
|
||||
result = payslip._at_kv_gehalt_basis() / payslip._at_uel_divisor()
|
||||
result_qty = inputs['ATP_UEL_TAGE'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Urlaubsersatzleistung — Sonderzahlungsanteil (§ 67 Abs 8 lit d). -->
|
||||
<record id="rule_url_ersatz_sz" model="hr.salary.rule">
|
||||
<field name="name">Urlaubsersatzleistung — Sonderzahlungsanteil (§ 67 Abs 8 lit d)</field>
|
||||
<field name="code">URL_ERSATZ_SZ</field>
|
||||
<field name="sequence" eval="26"/>
|
||||
<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_UEL_TAGE' in inputs and inputs['ATP_UEL_TAGE'].amount</field>
|
||||
<field name="l10n_at_bemessung">sonstig</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# SZ-Anteil der Urlaubsentgelt-Basis (lb-url-16): 2 Sonderzahlungen
|
||||
# à Monatsentgelt pro Jahr → je Monat 2 × SZ-Basis/12, als Tageswert
|
||||
# (auch bei bereits überaliquot bezahlten SZ).
|
||||
result = payslip._at_uel_sz_tageswert()
|
||||
result_qty = inputs['ATP_UEL_TAGE'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Abgangsentschädigung (§ 67 Abs 8 lit b — Kündigungsentschädigung). -->
|
||||
<record id="rule_abgang" model="hr.salary.rule">
|
||||
<field name="name">Abgangsentschädigung (§ 67 Abs 8 lit b)</field>
|
||||
<field name="code">ABGANG</field>
|
||||
<field name="sequence" eval="29"/>
|
||||
<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_ABGANG' in inputs and inputs['ATP_ABGANG'].amount</field>
|
||||
<field name="l10n_at_bemessung">neutral</field>
|
||||
<field name="l10n_at_sv_art">laufend</field>
|
||||
<field name="l10n_at_s67_fuenftel" eval="True"/>
|
||||
<field name="l10n_at_sv_verlaengerung">abgang</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = inputs['ATP_ABGANG'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Zeitguthaben an Normalarbeitszeit (§ 19e Abs 2 AZG: + 50 %). -->
|
||||
<record id="rule_zeit_guthaben" model="hr.salary.rule">
|
||||
<field name="name">Zeitguthaben Normalarbeitszeit + 50 % (§ 19e Abs 2 AZG)</field>
|
||||
<field name="code">ZEIT_GUTHABEN</field>
|
||||
<field name="sequence" eval="27"/>
|
||||
<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_ZEIT_GUTHABEN' in inputs and inputs['ATP_ZEIT_GUTHABEN'].amount</field>
|
||||
<field name="l10n_at_bemessung">laufend</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# § 19e Abs 2 AZG: Guthaben an Normalarbeitszeit + 50 % Zuschlag,
|
||||
# bemessen mit der Normalstunde (lb-end-10); der Zuschlag ist KEIN
|
||||
# Überstundenzuschlag iSd § 68 EStG (kein Freibetrags-Flag).
|
||||
result = payslip._at_grundstunde_norm() * 1.5
|
||||
result_qty = inputs['ATP_ZEIT_GUTHABEN'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Zeitschulden (Abrechnung mit der aktuellen Normalstunde). -->
|
||||
<record id="rule_zeit_schulden" model="hr.salary.rule">
|
||||
<field name="name">Zeitschulden-Abrechnung (negative Zeile)</field>
|
||||
<field name="code">ZEIT_SCHULDEN</field>
|
||||
<field name="sequence" eval="28"/>
|
||||
<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_ZEIT_SCHULDEN' in inputs and inputs['ATP_ZEIT_SCHULDEN'].amount</field>
|
||||
<field name="l10n_at_bemessung">laufend</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Abrechnung offener Zeitschulden mit der aktuellen Normalstunde
|
||||
# (lb-end-08); negative laufende Bezugszeile.
|
||||
result = -payslip._at_grundstunde_norm()
|
||||
result_qty = inputs['ATP_ZEIT_SCHULDEN'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Aufrechnung offener Vorschüsse: Rückverrechnung vom Netto
|
||||
NACH SV und Lohnsteuer (§ 293 Abs 3 EO, lb-end-06) — neutral,
|
||||
ohne SV-/LSt-/LNK-Wirkung. -->
|
||||
<record id="rule_end_abrechnung_vorschuss" model="hr.salary.rule">
|
||||
<field name="name">Aufrechnung offener Vorschuss (Netto)</field>
|
||||
<field name="code">END_ABRECHNUNG_VORSCHUSS</field>
|
||||
<field name="sequence" eval="80"/>
|
||||
<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_ABRECHNUNG_VORSCHUSS' in inputs and inputs['ATP_ABRECHNUNG_VORSCHUSS'].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_ABRECHNUNG_VORSCHUSS'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,95 @@
|
||||
# Endabrechnungs-Zyklus — Rechenfälle AP14-A
|
||||
|
||||
Stand: 2026-09. AP14-A bildet die Beendigungs-Abrechnung auf der
|
||||
privaten Structure ab: Urlaubsersatzleistung (UEL), Abgangsentschädigung,
|
||||
Aufrechnung offener Vorschüsse und Zeitguthaben/-schulden. Grundlagen
|
||||
sind am Originalwortlaut verifiziert (`.ris/`, Stand 2026-09-14):
|
||||
§ 11 Abs 2 ASVG, § 67 Abs 8 lit b und lit d EStG, § 19e Abs 2 AZG,
|
||||
§ 41 Abs 4 FLAG.
|
||||
|
||||
## Rechts- und Systemgrenzen
|
||||
|
||||
- **UEL (§ 10 UrlG, § 67 Abs 8 lit d EStG):** Zweikomponenten — der
|
||||
laufende Anteil läuft als laufender Bezug durch den Tarif des
|
||||
Austrittsmonats und erhöht das Jahressechstel (nativ über
|
||||
`bemessung='laufend'`); der Sonderzahlungsanteil läuft als sonstiger
|
||||
Bezug durch das 6 %-Band (Freibetrag 620 €, Freigrenze 2.615 € im
|
||||
Kern). Nur der laufende Anteil verlängert die Pflichtversicherung
|
||||
(§ 11 Abs 2 ASVG). Tagewert-Divisoren 26 (Werktage) / 22 (5-AT); die
|
||||
SV-Tage-Umrechnung (je volle Woche à N AT + 7−N KT, nur ganze
|
||||
Blöcke) ist Verwaltungspraxis (lb-url-15), nicht Gesetzeswortlaut.
|
||||
- **Abgangsentschädigung (§ 67 Abs 8 lit b):** nach Abzug der §-62-
|
||||
Beiträge ein Fünftel steuerfrei, gedeckelt mit einem Fünftel des
|
||||
Neunfachen der monatlichen HBG (2026: 6.930 × 9/5 = **12.474 €**);
|
||||
der Rest läuft tarifisch im Zuflussmonat — Abs 2 (Sechstel) ist nicht
|
||||
anzuwenden. SV-pflichtig mit Umlegung auf den Kündigungsfrist-
|
||||
Zeitraum; voll lohnnebenkostenpflichtig (FLAG § 41 Abs 4 nennt nur
|
||||
§ 67 Abs 3 und 6).
|
||||
- **Sequentielle Verlängerungsfenster:** Gebühren sowohl eine
|
||||
Kündigungsentschädigung als auch eine UEL, ist zur Bestimmung des
|
||||
Zeitraumes zunächst die Kündigungsentschädigung und im Anschluss die
|
||||
Ersatzleistung heranzuziehen (§ 11 Abs 2 ASVG) — die SV-Basen werden
|
||||
fensterweise auf die Monate verteilt, Standardanteile bleiben auf
|
||||
die volle monatliche HBG gedeckelt, Verlängerungsanteile auf den
|
||||
Tageswert (HBG × Tage/30).
|
||||
- **Aufrechnung offener Vorschüsse (§ 293 Abs 3 EO):** Rückverrechnung
|
||||
vom Netto nach SV und Lohnsteuer — zulässig auch gegen das
|
||||
Existenzminimum; keine Abgabenberührung (lb-end-06).
|
||||
- **Zeitguthaben (§ 19e Abs 2 AZG):** Guthaben an Normalarbeitszeit
|
||||
+ 50 % Zuschlag, bemessen mit der Normalstunde; der Zuschlag ist
|
||||
KEIN Überstundenzuschlag iSd § 68 EStG (lb-end-10). Entfall bei
|
||||
unberechtigtem vorzeitigem Austritt (Eingabe weglassen); KV kann
|
||||
abweichen.
|
||||
|
||||
## RF-END-1 — UEL-Zweikomponenten
|
||||
|
||||
Ende 15. 3., 11 offene AT à 120 € (2.640 € Gehalt / 22):
|
||||
|
||||
- `URL_ERSATZ_LFD` = 1.320 €: tarifisch im März, Sechstel +5.280 €
|
||||
(annualisiert 60/15);
|
||||
- `URL_ERSATZ_SZ` = 220 € (2 × 2.640/12 = 440/Monat → 20/Tag):
|
||||
6 %-Band;
|
||||
- SV: 1.320 € auf die Verlängerungsmonate (Tage-Schema), 220 € als
|
||||
sonstige Beitragsgrundlage gegen Monats- und Jahres-HBGl.
|
||||
|
||||
## RF-END-2 — SV-Tage-Umrechnung (§ 11 Abs 2)
|
||||
|
||||
- 23 AT (5-AT-Woche) → +4×2 KT = **31 SV-Tage**;
|
||||
- 12 WT (6-WT-Woche) → +2×1 KT = **14**;
|
||||
- 6 AT (2-Tage-Teilzeitwoche) → +3×5 KT = **21** (lb-url-15-Beispiel).
|
||||
- Ende 15. 3., 31 SV-Tage → Verlängerung 16. 3.–15. 4.; UEL-lfd-Basis
|
||||
tageproportional (März 16/31, April 15/31), Tageswert-Deckel je
|
||||
Verlängerungsmonat.
|
||||
|
||||
## RF-END-3 — Abgangsentschädigung (Fünftelregelung)
|
||||
|
||||
9.000 € Entschädigung, 42 Tage Kündigungsfrist, DN-Satz s:
|
||||
|
||||
- Bemessung 9.000 × (1 − s/100); davon 1/5 steuerfrei (≤ 12.474 €);
|
||||
4/5 tarifisch im Zuflussmonat, ohne Sechstelerhöhung;
|
||||
- SV: Umlegung 16. 3.–26. 4. (Kündigungsfrist zuerst);
|
||||
- LNK: volle 9.000 € in der Basis (§ 41 Abs 3 FLAG).
|
||||
|
||||
## RF-END-4 — Aufrechnung
|
||||
|
||||
2.000 € offener Vorschuss: `END_ABRECHNUNG_VORSCHUSS` mindert nur das
|
||||
Netto; LSt-, SV- und LNK-Basen unverändert.
|
||||
|
||||
## RF-END-5 — Zeitguthaben/-schulden
|
||||
|
||||
10 h Normalarbeitszeit-Guthaben × 1,5 + 5 h Zeitschulden × (−1):
|
||||
beides laufender Bezug, kein §-68-Freibetrag.
|
||||
|
||||
## Offene Punkte (dokumentiert, AP14-B/AP15)
|
||||
|
||||
- **Vergleich (§ 67 Abs 8 lit a)** und **freiwillige Abfertigung/
|
||||
Abfindung (§ 67 Abs 6 6 %-Bänder)** → AP15 mit der Abfertigung-Alt-
|
||||
Günstiger-Maschine (dort auch rollierende 12-Monats-Basis und
|
||||
Dienstzeit-Nachweise).
|
||||
- Prämien-Beendigung (§ 16 AngG-Aliquotierung, Rollung) — PRAM/SZ-Regeln
|
||||
vorhanden; Feinschliff AP14-B.
|
||||
- Meldewesen-Schnittstellen: „Ende des Entgeltanspruches" in mBGM/
|
||||
Abmeldung (GP7), gesonderter Lohnzettel bei Zahlung außerhalb des
|
||||
Beendigungsmonats (LStR 2002 Rz 911a).
|
||||
- Der §-62-Abzug des Fünftelpfades ist über den effektiven DN-Satz
|
||||
approximiert (HBG-Caps der Verlängerungsmonate laufen im SV-Pfad).
|
||||
@@ -20,3 +20,4 @@ from . import pfaendung_report
|
||||
from . import sachbezuege_private
|
||||
from . import reisekosten
|
||||
from . import pendler
|
||||
from . import endabrechnung
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP14-A: Endabrechnungs-Zyklus — Urlaubsersatzleistung (UEL),
|
||||
Abgangsentschädigung (§ 67 Abs 8 lit b), Aufrechnung offener Vorschüsse
|
||||
und Zeitguthaben/-schulden (§ 19e AZG).
|
||||
|
||||
Am Originalwortlaut verifiziert (.ris/, Stand 2026-09-14):
|
||||
- § 11 Abs 2 ASVG: die Pflichtversicherung besteht für die Zeit des
|
||||
Bezuges einer Ersatzleistung für Urlaubsentgelt (und einer
|
||||
Kündigungsentschädigung) weiter; gebühren beide, ist zur Bestimmung
|
||||
des Zeitraumes ZUERST die Kündigungsentschädigung und im Anschluss
|
||||
die Ersatzleistung heranzuziehen (sequentielle Verlängerungsfenster).
|
||||
Die 6-WT/5-AT-Tage-Umrechnung ist Verwaltungspraxis (lb-url-15),
|
||||
nicht Gesetzeswortlaut.
|
||||
- § 67 Abs 8 lit d EStG: UEL „soweit sie laufenden Arbeitslohn
|
||||
betreffen, als laufender Arbeitslohn, soweit sie sonstige Bezüge
|
||||
betreffen, als sonstiger Bezug im Kalendermonat der Zahlung" — die
|
||||
Zweikomponenten-Abbildung läuft nativ über l10n_at_bemessung.
|
||||
- § 67 Abs 8 lit b EStG (Kündigungs-/Abgangsentschädigung): nach
|
||||
Abzug der §-62-Beiträge ist ein Fünftel steuerfrei zu belassen,
|
||||
höchstens ein Fünftel des Neunfachen der monatlichen HBG; der Rest
|
||||
läuft tarifisch im Zuflussmonat (Abs 10), Abs 2 (Sechstel) ist
|
||||
NICHT anzuwenden. LNK-pflichtig (FLAG § 41 Abs 4 nennt nur
|
||||
§ 67 Abs 3 und 6) und SV-pflichtig (keine §-49-Abs-3-Z-7-Befreiung)
|
||||
mit Umlegung auf den Kündigungsfrist-Zeitraum.
|
||||
- § 19e Abs 2 AZG: Guthaben an Normalarbeitszeit + 50 % Zuschlag
|
||||
(Entfall bei unberechtigtem vorzeitigem Austritt; KV kann
|
||||
abweichend regeln).
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class HrSalaryRule(models.Model):
|
||||
_inherit = 'hr.salary.rule'
|
||||
|
||||
l10n_at_sv_verlaengerung = fields.Selection(
|
||||
[('keine', 'Keine SV-Verlängerung (§ 11 Abs 2 ASVG)'),
|
||||
('urlaubsersatz', 'UEL-Tage-Schema (Urlaubsersatzleistung)'),
|
||||
('abgang', 'Kündigungsfrist (Abgangsentschädigung)')],
|
||||
string='AT SV-Verlängerung', default='keine',
|
||||
help='Verteilt die laufende SV-Basis dieser Regel auf die '
|
||||
'Pflichtversicherungs-Verlängerungsmonate nach § 11 Abs 2 '
|
||||
'ASVG statt auf den Abrechnungsmonat: Abgangsentschädigung '
|
||||
'zuerst (Kündigungsfrist), UEL anschließend (Tage-Schema '
|
||||
'lb-url-15).')
|
||||
l10n_at_s67_fuenftel = fields.Boolean(
|
||||
string='AT § 67 Abs 8 lit a/b (Fünftelregelung)',
|
||||
help='LSt-Pfad der Vergleichs-/Kündigungsentschädigungen: nach '
|
||||
'Abzug der §-62-Beiträge ist ein Fünftel steuerfrei '
|
||||
'(Deckel: ein Fünftel des Neunfachen der monatlichen HBG), '
|
||||
'der Rest läuft tarifisch im Zuflussmonat — ohne '
|
||||
'Jahressechstel-Erhöhung (Abs 2 nicht anzuwenden).')
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2 ASVG)
|
||||
# ------------------------------------------------------------------
|
||||
def _at_end_arbeitsrechtliches_ende(self):
|
||||
"""Arbeitsrechtliches Ende = letzter Arbeitstag; das SV-Ende
|
||||
(„Ende des Entgeltanspruches") liegt darüber hinaus, wenn ein
|
||||
Entgeltanspruch über das Ende hinaus läuft."""
|
||||
self.ensure_one()
|
||||
return self.version_id.contract_date_end or self.date_to
|
||||
|
||||
def _at_end_arbeitstage_woche(self):
|
||||
"""Arbeitstage pro Woche des Vertragskalenders (Schema-Grundlage
|
||||
der SV-Tage-Umrechnung: 5-AT-Woche, 6-WT-Woche, Teilzeitwoche)."""
|
||||
self.ensure_one()
|
||||
calendar = self.version_id.resource_calendar_id
|
||||
days = {int(att.dayofweek) for att in calendar.attendance_ids} \
|
||||
if calendar else set()
|
||||
return len(days) or 5
|
||||
|
||||
def _at_end_sv_tage(self, tage):
|
||||
"""SV-Tage aus abzurechnenden Urlaubstagen (Verwaltungspraxis
|
||||
lb-url-15): je volle Woche à N Arbeitstage werden 7−N
|
||||
Kalendertage hinzugerechnet (5-AT-Woche +2, 6-WT-Woche +1,
|
||||
2-Tage-Teilzeitwoche +5) — nur ganze Wochenblöcke."""
|
||||
self.ensure_one()
|
||||
at_woche = self._at_end_arbeitstage_woche()
|
||||
blocks = int(tage // at_woche)
|
||||
return tage + blocks * (7 - at_woche)
|
||||
|
||||
def _at_end_verlaengerungs_monate(self, sv_tage, start):
|
||||
"""[(Monatsanfang, Tage im Monat)] eines Verlängerungsfensters
|
||||
ab dem übergebenen Starttag (§ 11 Abs 2 ASVG)."""
|
||||
self.ensure_one()
|
||||
ergebnis = {}
|
||||
rest = int(sv_tage)
|
||||
tag = start
|
||||
while rest > 0:
|
||||
monatsende = (tag.replace(day=28) + timedelta(days=4)) \
|
||||
.replace(day=1) - timedelta(days=1)
|
||||
tage_dabei = min((monatsende - tag).days + 1, rest)
|
||||
monat = tag.replace(day=1)
|
||||
ergebnis[monat] = ergebnis.get(monat, 0) + tage_dabei
|
||||
tag = monatsende + timedelta(days=1)
|
||||
rest -= tage_dabei
|
||||
return sorted(ergebnis.items())
|
||||
|
||||
def _at_end_rule_verlaengerung(self, rule, result_rules=None):
|
||||
"""[(Monatsanfang, Tage, Anteil)] der Regel-Basis nach § 11
|
||||
Abs 2: die Abgangsentschädigung belegt ZUERST die (fiktive)
|
||||
Kündigungsfrist, die UEL SCHLIESSLICH die Urlaubstage — beide
|
||||
Fenster laufen ab dem Tag nach dem arbeitsrechtlichen Ende
|
||||
hintereinander (Gesetzeswortlaut: „zunächst die
|
||||
Kündigungsentschädigung und im Anschluss die Ersatzleistung")."""
|
||||
self.ensure_one()
|
||||
start = self._at_end_arbeitsrechtliches_ende() + timedelta(days=1)
|
||||
if rule.l10n_at_sv_verlaengerung == 'urlaubsersatz':
|
||||
ffrist = self._at_input_amount('ATP_ABGANG_FFRIST_TAGE')
|
||||
if ffrist:
|
||||
start += timedelta(days=int(ffrist))
|
||||
tage = self._at_end_sv_tage(self._at_uel_tage())
|
||||
else:
|
||||
ffrist = self._at_input_amount('ATP_ABGANG_FFRIST_TAGE')
|
||||
tage = int(ffrist) if ffrist else 0
|
||||
monate = self._at_end_verlaengerungs_monate(tage, start)
|
||||
if not monate:
|
||||
return []
|
||||
gesamt = sum(tage for _monat, tage in monate)
|
||||
return [(monat, tage, tage / float(gesamt))
|
||||
for monat, tage in monate]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Eingabe-Helfer
|
||||
# ------------------------------------------------------------------
|
||||
def _at_input_amount(self, code):
|
||||
self.ensure_one()
|
||||
linie = self.input_line_ids.filtered(
|
||||
lambda i: i.input_type_id.code == code)
|
||||
return linie[:1].amount or 0.0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Urlaubsersatzleistung — § 67 Abs 8 lit d, lb-url-15/16
|
||||
# ------------------------------------------------------------------
|
||||
def _at_uel_tage(self):
|
||||
"""Offene Urlaubstage (Arbeitstage; keine Rundung, lb-url-16)."""
|
||||
self.ensure_one()
|
||||
return self._at_input_amount('ATP_UEL_TAGE')
|
||||
|
||||
def _at_uel_divisor(self):
|
||||
"""Tagewert-Divisor: /26 bei Werktags-, /22 bei 5-AT-Kalender
|
||||
(lb-url-16)."""
|
||||
self.ensure_one()
|
||||
params = self._l10n_at_param('at_endabrechnung')
|
||||
if self._at_end_arbeitstage_woche() >= 6:
|
||||
return float(params['divisor_werktage'])
|
||||
return float(params['divisor_arbeitstage'])
|
||||
|
||||
def _at_uel_lfd(self):
|
||||
"""UEL-laufender Anteil: Tage × Monatsentgelt / Divisor —
|
||||
läuft als laufender Bezug durch den Tarif des Austrittsmonats
|
||||
und erhöht das Jahressechstel (nativ über bemessung='laufend')."""
|
||||
self.ensure_one()
|
||||
return self._at_uel_tage() * self._at_kv_gehalt_basis() \
|
||||
/ self._at_uel_divisor()
|
||||
|
||||
def _at_uel_sz_tageswert(self):
|
||||
"""SZ-Anteil je Urlaubstag: 2 Sonderzahlungen à Monatsentgelt
|
||||
pro Jahr (KV § 11) → je Monat 2 × SZ-Basis/12, als Tageswert
|
||||
(auch bei bereits überaliquot bezahlten SZ, lb-url-16)."""
|
||||
self.ensure_one()
|
||||
sz_monat = 2.0 * self._at_sz_basis(self.date_to) / 12.0
|
||||
return sz_monat / self._at_uel_divisor()
|
||||
|
||||
def _at_uel_sz(self):
|
||||
"""UEL-Sonderzahlungsanteil: Tage × SZ-Tageswert."""
|
||||
self.ensure_one()
|
||||
return self._at_uel_tage() * self._at_uel_sz_tageswert()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Abgangsentschädigung — § 67 Abs 8 lit b (Fünftelregelung)
|
||||
# ------------------------------------------------------------------
|
||||
def _at_fuenftel_regeln(self):
|
||||
self.ensure_one()
|
||||
return self.struct_id.rule_ids.filtered(
|
||||
lambda rule: rule.l10n_at_s67_fuenftel)
|
||||
|
||||
def _at_fuenftel_total(self, result_rules=None):
|
||||
self.ensure_one()
|
||||
total = 0.0
|
||||
for rule in self._at_fuenftel_regeln():
|
||||
if result_rules is not None:
|
||||
if rule.code in result_rules:
|
||||
total += result_rules[rule.code]['total']
|
||||
else:
|
||||
linie = self.line_ids.filtered(lambda l: l.code == rule.code)
|
||||
total += sum(linie.mapped('total'))
|
||||
return total
|
||||
|
||||
def _at_fuenftel_tarif_basis(self, result_rules=None):
|
||||
"""Tarifischer Teil (4/5) der Fünftel-Bezüge: Bemessung nach
|
||||
Abzug der darauf entfallenden §-62-Beiträge (approximiert über
|
||||
den effektiven DN-Satz der laufenden Basis; die HBG-Caps der
|
||||
Verlängerungsmonate werden im SV-Pfad separat geführt); ein
|
||||
Fünftel ist steuerfrei, gedeckelt mit einem Fünftel des
|
||||
Neunfachen der monatlichen HBG (lb-end-21: 2026 6.930 × 9/5
|
||||
= 12.474)."""
|
||||
self.ensure_one()
|
||||
total = self._at_fuenftel_total(result_rules)
|
||||
if total <= 0:
|
||||
return 0.0
|
||||
satz = sum(self._l10n_at_sv_satz(
|
||||
'dn', komponente,
|
||||
staffel_basis=self._l10n_at_sv_basis_roh(result_rules))
|
||||
for komponente in ('kv', 'pv', 'av', 'ak', 'wf'))
|
||||
bemessung = max(total * (1.0 - satz / 100.0), 0.0)
|
||||
deckel = float(self._l10n_at_param('at_sv_hbg_monatlich')) \
|
||||
* 9.0 / 5.0
|
||||
frei = min(bemessung / 5.0, deckel)
|
||||
return bemessung - frei
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Engine-Anbindung: Tarif-Teil ohne Sechstel, LNK-Pflicht, SV-Umlegung
|
||||
# ------------------------------------------------------------------
|
||||
def _l10n_at_bemessung_laufen(self, result_rules=None):
|
||||
basis = super()._l10n_at_bemessung_laufen(result_rules)
|
||||
# § 67 Abs 8 lit b: 4/5 tarifisch im Zuflussmonat; die Regel
|
||||
# selbst ist neutral und erhöht brutto('laufend') nicht → kein
|
||||
# Sechstel-Effekt (Abs 2 nicht anzuwenden).
|
||||
return basis + self._at_fuenftel_tarif_basis(result_rules)
|
||||
|
||||
def _at_lnk_basis(self, steuer, result_rules=None):
|
||||
"""Upstream-Überschreibung (Muster account_posting_private;
|
||||
Vorlage payslip_private._at_lnk_basis — bei Änderungen dort
|
||||
gegen diesen Pin neu abgleichen): Kündigungsentschädigungen
|
||||
sind Arbeitslöhne iSd § 41 Abs 3 FLAG und unterliegen NICHT
|
||||
den Abs-4-Ausschlüssen (lit b nennt nur § 67 Abs 3 und 6) —
|
||||
sie sind VOR der Freibetragsstaffel in die LNK-Basis
|
||||
aufzunehmen."""
|
||||
self.ensure_one()
|
||||
basis = (self._l10n_at_brutto('laufend', result_rules)
|
||||
+ self._l10n_at_brutto('sonstig', result_rules)
|
||||
- self._l10n_at_st_frei_freie_laufen(result_rules))
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if rule.l10n_at_s67_abs in ('abs3', 'abs6'):
|
||||
if result_rules is not None and rule.code in result_rules:
|
||||
basis -= result_rules[rule.code]['total']
|
||||
elif result_rules is None:
|
||||
linie = self.line_ids.filtered(
|
||||
lambda l: l.code == rule.code)
|
||||
basis -= sum(linie.mapped('total'))
|
||||
basis += self._at_fuenftel_total(result_rules)
|
||||
basis = max(basis, 0.0)
|
||||
if steuer in ('flaf', 'dz'):
|
||||
version = self.version_id
|
||||
if (version.l10n_at_lnkp_frei_ab
|
||||
and self.date_from >= version.l10n_at_lnkp_frei_ab):
|
||||
return 0.0
|
||||
freibetrag = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_lnk_freibetrag', self.date_to, raise_if_not_found=False) or {}
|
||||
grenze = float(freibetrag.get('grenze', 0.0))
|
||||
if basis <= 0.0:
|
||||
return 0.0
|
||||
if basis <= grenze:
|
||||
basis -= float(freibetrag.get('freibetrag', 0.0))
|
||||
return max(basis, 0.0)
|
||||
|
||||
def _l10n_at_sv_basis_laufen_monate(self, result_rules=None):
|
||||
"""Upstream-Überschreibung (Muster account_posting_private;
|
||||
Vorlage l10n_at_hr_payroll.sozialversicherung — bei Odoo- oder
|
||||
Kern-Upgrades gegen diesen Pin neu abgleichen): identischer
|
||||
Ablauf wie der Kern, zusätzlich werden Regeln mit AT-SV-
|
||||
Verlängerung tageproportional auf die Verlängerungsmonate
|
||||
gelegt. Der Standardanteil jedes Monats bleibt auf die volle
|
||||
monatliche HBG gedeckelt, der Verlängerungsanteil auf den
|
||||
Tageswert (monatliche HBG × Verlängerungstage/30, lb-url-15);
|
||||
die Fenster laufen sequenziell (Abgang zuerst, UEL danach)."""
|
||||
self.ensure_one()
|
||||
gruppen = {}
|
||||
verlaengerung = {}
|
||||
tage_pro_monat = {}
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if self._l10n_at_sv_art(rule) != 'laufend':
|
||||
continue
|
||||
werte = None
|
||||
if result_rules is not None:
|
||||
if rule.code in result_rules:
|
||||
werte = result_rules[rule.code]
|
||||
else:
|
||||
linie = self.line_ids.filtered(
|
||||
lambda l: l.code == rule.code)
|
||||
if linie:
|
||||
werte = {'total': sum(linie.mapped('total'))}
|
||||
if not werte:
|
||||
continue
|
||||
if rule.l10n_at_sv_verlaengerung != 'keine':
|
||||
for monat, tage, anteil in \
|
||||
self._at_end_rule_verlaengerung(rule, result_rules):
|
||||
verlaengerung[monat] = verlaengerung.get(monat, 0.0) \
|
||||
+ werte['total'] * anteil
|
||||
tage_pro_monat[monat] = \
|
||||
tage_pro_monat.get(monat, 0) + tage
|
||||
continue
|
||||
monat = (rule.l10n_at_beitragsmonat or self.date_from) \
|
||||
.replace(day=1)
|
||||
gruppen[monat] = gruppen.get(monat, 0.0) + werte['total']
|
||||
standard = self.date_from.replace(day=1)
|
||||
if standard in gruppen:
|
||||
gruppen[standard] -= self._l10n_at_sv49_freie_laufen(
|
||||
result_rules)
|
||||
ergebnis = []
|
||||
for monat in sorted(set(gruppen) | set(verlaengerung)):
|
||||
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich', monat))
|
||||
basis = min(max(gruppen.get(monat, 0.0), 0.0), hbg)
|
||||
if monat in verlaengerung:
|
||||
tageswert = hbg * tage_pro_monat[monat] / 30.0
|
||||
basis += min(max(verlaengerung[monat], 0.0), tageswert)
|
||||
ergebnis.append((monat, basis))
|
||||
return ergebnis
|
||||
@@ -23,3 +23,4 @@ access_l10n_at_payroll_pfaendung_user,L10n AT Payroll Pfaendung User,model_l10n_
|
||||
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
|
||||
access_l10n_at_payroll_endabrechnung_wizard,L10n AT Payroll Endabrechnung Wizard,model_l10n_at_payroll_endabrechnung_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
|
||||
|
@@ -17,3 +17,4 @@ from . import test_sachbezuege_private
|
||||
from . import test_sachbezuege2_private
|
||||
from . import test_reisekosten_private
|
||||
from . import test_pendler_private
|
||||
from . import test_endabrechnung_private
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP14-A: Endabrechnungs-Zyklus — UEL-Zweikomponenten (§ 67 Abs 8
|
||||
lit d), SV-Verlängerung (§ 11 Abs 2 ASVG, sequentielle Fenster),
|
||||
Abgangsentschädigung (§ 67 Abs 8 lit b Fünftel), Aufrechnung offener
|
||||
Vorschüsse (§ 293 Abs 3 EO) und Zeitguthaben (§ 19e Abs 2 AZG)."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class EndabrechnungPrivateTest(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 End 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 _calendar(self, weekdays):
|
||||
return self.env['resource.calendar'].create({
|
||||
'name': 'AT Test End %dTage' % len(weekdays),
|
||||
'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 weekdays
|
||||
],
|
||||
})
|
||||
|
||||
def _employee(self, ueberzahlung=2640.0,
|
||||
ende=date(2026, 3, 15), calendar=None):
|
||||
employee = self.env['hr.employee'].create(
|
||||
{'name': 'Test End 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),
|
||||
'contract_date_end': ende,
|
||||
'ueberzahlung': ueberzahlung,
|
||||
'kv_id': self.kv2203.id,
|
||||
'kv_gruppe_id': self.gruppe_ii.id,
|
||||
'erfahrungsstufe': 1,
|
||||
'resource_calendar_id': (calendar or self.calendar).id,
|
||||
})
|
||||
return employee
|
||||
|
||||
def _slip(self, employee, monat=3, jahr=2026):
|
||||
erster = date(jahr, monat, 1)
|
||||
naechster = (erster + timedelta(days=32)).replace(day=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Test-End %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 _input(self, slip, code, amount):
|
||||
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()
|
||||
|
||||
def _total(self, slip, code):
|
||||
return sum(slip.line_ids.filtered(
|
||||
lambda line: line.code == code).mapped('total'))
|
||||
|
||||
def _basis_monate(self, slip):
|
||||
return dict(slip._l10n_at_sv_basis_laufen_monate())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-END-1: UEL-Zweikomponenten (§ 67 Abs 8 lit d)
|
||||
# ------------------------------------------------------------------
|
||||
def test_01_uel_zwei_komponenten(self):
|
||||
"""11 offene AT à 120 € (2.640/22): UEL-lfd 1.320 € läuft
|
||||
tarifisch und sechstelerhöhend; UEL-SZ 220 € (2×2.640/12/22)
|
||||
als sonstiger Bezug; nur der laufende Anteil verlängert die
|
||||
Pflichtversicherung."""
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
slip_mit = self._slip(emp_mit)
|
||||
slip_ohne = self._slip(emp_ohne)
|
||||
self._input(slip_mit, 'ATP_UEL_TAGE', 11)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip_mit, 'URL_ERSATZ_LFD'), 1320.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip_mit, 'URL_ERSATZ_SZ'), 220.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,
|
||||
1320.0, places=2)
|
||||
# Sechstel wächst um den annualisierten laufenden Anteil
|
||||
# (60/Lohnsteuertage × 1.320; Teilmonat März: 15 Tage).
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
|
||||
1320.0 * 60.0 / 15.0, places=2)
|
||||
# SZ-Bemessung enthält den UEL-SZ-Anteil (SV-DN nach § 67
|
||||
# Abs 12 abziehen, um den Brutto-Anteil zu isolieren)
|
||||
svdn_sz_diff = (slip_mit._l10n_at_svdn_sz()
|
||||
- slip_ohne._l10n_at_svdn_sz())
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sz_bemessung()
|
||||
- slip_ohne._l10n_at_sz_bemessung() + svdn_sz_diff,
|
||||
220.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-END-2: SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2)
|
||||
# ------------------------------------------------------------------
|
||||
def test_02_sv_tage_und_verlaengerung(self):
|
||||
"""Schema je voller Woche à N AT + (7−N) KT: 23 AT (5-Woche)
|
||||
→ 31 SV-Tage; 12 WT (6-Woche) → 14; 6 AT (2-Tage-Woche) → 21
|
||||
(lb-url-15). Ende 15. 3.: Verlängerung 16. 3.–15. 4. — die
|
||||
UEL-lfd-Basis liegt tageproportional auf den Monaten."""
|
||||
emp = self._employee()
|
||||
slip = self._slip(emp)
|
||||
self.assertEqual(slip._at_end_sv_tage(23), 31)
|
||||
self.assertEqual(slip._at_end_sv_tage(10), 14)
|
||||
|
||||
emp_6 = self._employee(calendar=self._calendar(range(6)))
|
||||
self.assertEqual(self._slip(emp_6)._at_end_sv_tage(12), 14)
|
||||
|
||||
emp_2 = self._employee(calendar=self._calendar([0, 3]))
|
||||
self.assertEqual(self._slip(emp_2)._at_end_sv_tage(6), 21)
|
||||
|
||||
self.assertEqual(
|
||||
slip._at_end_verlaengerungs_monate(
|
||||
31, date(2026, 3, 16)),
|
||||
[(date(2026, 3, 1), 16), (date(2026, 4, 1), 15)])
|
||||
|
||||
self._input(slip, 'ATP_UEL_TAGE', 23)
|
||||
basis = self._basis_monate(slip)
|
||||
self.assertAlmostEqual(
|
||||
basis[date(2026, 4, 1)], 2760.0 * 15.0 / 31.0, places=2)
|
||||
# März: KVGEO-Standardanteil + UEL-März-Anteil
|
||||
self.assertAlmostEqual(
|
||||
basis[date(2026, 3, 1)] - 2760.0 * 16.0 / 31.0,
|
||||
2640.0 * 10.0 / 22.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-END-3: Abgangsentschädigung (§ 67 Abs 8 lit b, Fünftel)
|
||||
# ------------------------------------------------------------------
|
||||
def test_03_abgang_fuenftel(self):
|
||||
"""9.000 € Abgangsentschädigung: Fünftel-LSt (1/5 steuerfrei
|
||||
nach §-62-Abzug, Deckel 12.474 €), 4/5 tarifisch OHNE
|
||||
Sechstelerhöhung; SV-Umlegung auf die 42 Tage Kündigungsfrist;
|
||||
voll LNK-pflichtig."""
|
||||
emp_mit = self._employee(ueberzahlung=3000.0)
|
||||
emp_ohne = self._employee(ueberzahlung=3000.0)
|
||||
slip_mit = self._slip(emp_mit)
|
||||
slip_ohne = self._slip(emp_ohne)
|
||||
self._input(slip_mit, 'ATP_ABGANG', 9000.0)
|
||||
self._input(slip_mit, 'ATP_ABGANG_FFRIST_TAGE', 42)
|
||||
|
||||
self.assertAlmostEqual(self._total(slip_mit, 'ABGANG'), 9000.0,
|
||||
places=2)
|
||||
# Sechstel unverändert (Abs 2 nicht anzuwenden)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
|
||||
0.0, places=2)
|
||||
# Tarifischer Teil: 9.000 minus §-62-Abzug, 1/5 frei
|
||||
satz = sum(slip_mit._l10n_at_sv_satz(
|
||||
'dn', k, staffel_basis=slip_mit._l10n_at_sv_basis_roh())
|
||||
for k in ('kv', 'pv', 'av', 'ak', 'wf'))
|
||||
bemessung = 9000.0 * (1.0 - satz / 100.0)
|
||||
deckel = float(slip_mit._l10n_at_param('at_sv_hbg_monatlich')) \
|
||||
* 9.0 / 5.0
|
||||
erwartet = bemessung - min(bemessung / 5.0, deckel)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_fuenftel_tarif_basis(), erwartet, 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,
|
||||
erwartet, places=2)
|
||||
# LNK-Basis um die volle Entschädigung erhöht
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_lnk_basis('flaf')
|
||||
- slip_ohne._at_lnk_basis('flaf'), 9000.0, places=2)
|
||||
# SV-Umlegung: 16. 3.–26. 4. (Kündigungsfrist zuerst); im März
|
||||
# liegt zusätzlich der aliquote KVGEO-Standardanteil
|
||||
basis = self._basis_monate(slip_mit)
|
||||
self.assertAlmostEqual(
|
||||
basis[date(2026, 4, 1)], 9000.0 * 26.0 / 42.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
basis[date(2026, 3, 1)] - 3000.0 * 10.0 / 22.0,
|
||||
9000.0 * 16.0 / 42.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-END-4: Aufrechnung offener Vorschüsse (§ 293 Abs 3 EO)
|
||||
# ------------------------------------------------------------------
|
||||
def test_04_aufrechnung_vorschuss(self):
|
||||
"""2.000 € offener Vorschuss wird vom Netto abgezogen — ohne
|
||||
jede Wirkung auf LSt-, SV- oder LNK-Basen (lb-end-06)."""
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
slip_mit = self._slip(emp_mit)
|
||||
slip_ohne = self._slip(emp_ohne)
|
||||
self._input(slip_mit, 'ATP_ABRECHNUNG_VORSCHUSS', 2000.0)
|
||||
|
||||
self.assertAlmostEqual(self._total(slip_mit, 'END_ABRECHNUNG_VORSCHUSS'),
|
||||
2000.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._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)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip_mit, 'NET') - self._total(slip_ohne, 'NET'),
|
||||
-2000.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-END-5: Zeitguthaben (§ 19e Abs 2 AZG) und Zeitschulden
|
||||
# ------------------------------------------------------------------
|
||||
def test_05_zeitguthaben_und_schulden(self):
|
||||
"""10 h Normalarbeitszeit-Guthaben + 50 % Zuschlag, 5 h
|
||||
Zeitschulden negativ — beides laufender Bezug, KEIN §-68-
|
||||
Freibetrag (lb-end-10)."""
|
||||
emp = self._employee(ueberzahlung=3000.0)
|
||||
slip = self._slip(emp)
|
||||
self._input(slip, 'ATP_ZEIT_GUTHABEN', 10.0)
|
||||
self._input(slip, 'ATP_ZEIT_SCHULDEN', 5.0)
|
||||
|
||||
stunde = slip._at_grundstunde_norm()
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip, 'ZEIT_GUTHABEN'), 10.0 * stunde * 1.5,
|
||||
places=2)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip, 'ZEIT_SCHULDEN'), -5.0 * stunde, places=2)
|
||||
regel = slip.struct_id.rule_ids.filtered(
|
||||
lambda rule: rule.code == 'ZEIT_GUTHABEN')
|
||||
self.assertEqual(regel.l10n_at_steuerbefreiung_68, 'keine')
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-END-6: Wizard legt den Endabrechnungs-Lauf vorbefüllt an
|
||||
# ------------------------------------------------------------------
|
||||
def test_06_wizard(self):
|
||||
"""Der Wizard sammelt UEL, Abgang, Zeitguthaben und Vorschuss
|
||||
und erzeugt den Payslip mit vorbefüllten Eingaben."""
|
||||
emp = self._employee()
|
||||
# Version auf die private Structure-Type binden, damit der
|
||||
# Wizard über version.struct_id die richtige Struktur findet.
|
||||
emp.version_id.write({'structure_type_id': self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_type_at_privat').id})
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': emp.id,
|
||||
'austrittsdatum': date(2026, 3, 15),
|
||||
'beendigungsart': 'einvernehmlich',
|
||||
'uel_tage': 11.0,
|
||||
'abgang_betrag': 9000.0,
|
||||
'abgang_ffrist_tage': 42.0,
|
||||
'zeit_guthaben_stunden': 10.0,
|
||||
'vorschuss_saldo': 500.0,
|
||||
})
|
||||
ergebnis = wizard.action_payslip()
|
||||
slip = self.env['hr.payslip'].browse(ergebnis['res_id'])
|
||||
codes = set(slip.input_line_ids.mapped(
|
||||
lambda line: line.input_type_id.code))
|
||||
self.assertIn('ATP_UEL_TAGE', codes)
|
||||
self.assertIn('ATP_ABGANG', codes)
|
||||
self.assertIn('ATP_ABRECHNUNG_VORSCHUSS', codes)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip, 'URL_ERSATZ_LFD'), 1320.0, places=2)
|
||||
self.assertAlmostEqual(self._total(slip, 'ABGANG'), 9000.0,
|
||||
places=2)
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- AP14-A: Endabrechnungs-Wizard (Beendigung) — sammelt UEL, -->
|
||||
<!-- Abgangsentschädigung, Zeitguthaben/-schulden und offene -->
|
||||
<!-- Vorschüsse und legt den Endabrechnungs-Lauf vorbefüllt an. -->
|
||||
<record id="endabrechnung_wizard_form_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.endabrechnung.wizard.form</field>
|
||||
<field name="model">l10n.at.payroll.endabrechnung.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Endabrechnung (Beendigung)">
|
||||
<group>
|
||||
<group string="Beendigung">
|
||||
<field name="employee_id"/>
|
||||
<field name="austrittsdatum"/>
|
||||
<field name="beendigungsart"/>
|
||||
</group>
|
||||
<group string="Offene Posten">
|
||||
<field name="uel_tage"/>
|
||||
<field name="zeit_guthaben_stunden"/>
|
||||
<field name="zeit_schulden_stunden"/>
|
||||
<field name="abgang_betrag"/>
|
||||
<field name="abgang_ffrist_tage"
|
||||
invisible="not abgang_betrag"/>
|
||||
<field name="vorschuss_saldo"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Vorschau (§ 10 UrlG, § 11 Abs 2 ASVG)">
|
||||
<group>
|
||||
<field name="uel_lfd_vorschau"/>
|
||||
<field name="uel_sz_vorschau"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="sv_tage_vorschau"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_payslip" type="object"
|
||||
string="Endabrechnungs-Lauf anlegen"
|
||||
class="btn-primary"/>
|
||||
<button string="Abbrechen" special="cancel"
|
||||
class="btn-secondary"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_endabrechnung_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Endabrechnung (Beendigung)</field>
|
||||
<field name="res_model">l10n.at.payroll.endabrechnung.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_endabrechnung"
|
||||
name="Endabrechnung (Beendigung)"
|
||||
parent="hr_work_entry_enterprise.menu_hr_payroll_root"
|
||||
action="action_endabrechnung_wizard" sequence="42"/>
|
||||
</odoo>
|
||||
@@ -1,3 +1,4 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
from . import kv_katalog_import
|
||||
from . import bv_endabrechnung
|
||||
from . import endabrechnung
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP14-A: Endabrechnungs-Wizard — sammelt die offenen Posten der
|
||||
Beendigungs-Abrechnung (Urlaubsersatzleistung, Abgangsentschädigung,
|
||||
Zeitguthaben/-schulden, offene Vorschüsse) und legt den
|
||||
Endabrechnungs-Lauf mit vorbefüllten Eingaben an. Reiner
|
||||
Sammel-/Anlageprozess; die BV-Anwartschaft läuft über den eigenen
|
||||
BV-Wizard (GP5), die Pfändung über die AP11-Maschine."""
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
_name = 'l10n.at.payroll.endabrechnung.wizard'
|
||||
_description = 'Endabrechnung (Beendigung des Dienstverhältnisses)'
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee', string='Dienstnehmer:in', required=True)
|
||||
austrittsdatum = fields.Date(
|
||||
string='Austritt (Ende des Arbeitsverhältnisses)', required=True)
|
||||
beendigungsart = fields.Selection(
|
||||
[('ag_kuendigung', 'Kündigung durch den Arbeitgeber'),
|
||||
('an_kuendigung', 'Kündigung durch den Dienstnehmer'),
|
||||
('einvernehmlich', 'Einvernehmliche Auflösung'),
|
||||
('berechtigter_austritt', 'Berechtigter vorzeitiger Austritt'),
|
||||
('unberechtigter_austritt',
|
||||
'Unberechtigter vorzeitiger Austritt '
|
||||
'(keine UEL für die 5./6. Woche, § 10 Abs 2 UrlG; '
|
||||
'kein Zeitguthaben-Zuschlag, § 19e Abs 2 AZG)'),
|
||||
('verschuldete_entlassung', 'Verschuldete Entlassung'),
|
||||
('unverschuldete_entlassung', 'Unverschuldete Entlassung'),
|
||||
('befristung', 'Zeitablauf (befristetes Dienstverhältnis)'),
|
||||
('tod', 'Tod (UEL an die Erben, § 10 Abs 5 UrlG)')],
|
||||
string='Beendigungsart', required=True)
|
||||
uel_tage = fields.Float(
|
||||
string='Offene Urlaubstage (Arbeitstage)',
|
||||
help='Resturlaub früherer Urlaubsjahre zur Gänze plus aliquoter '
|
||||
'Anspruch des laufenden Urlaubsjahres abzüglich verbrauchter '
|
||||
'Tage, ohne Rundung (§ 10 UrlG, lb-url-16). Für '
|
||||
'unberechtigten vorzeitigen Austritt sind die Tage der 5. '
|
||||
'und 6. Urlaubswoche des laufenden Jahres herauszurechnen.')
|
||||
abgang_betrag = fields.Monetary(
|
||||
string='Abgangsentschädigung (Brutto, § 67 Abs 8 lit b EStG)',
|
||||
currency_field='currency_id',
|
||||
help='Entschädigung für den Verzicht auf die Arbeitsleistung '
|
||||
'während der (fiktiven) Kündigungsfrist — LSt: Fünftelregelung; '
|
||||
'SV: Umlegung auf den Kündigungsfrist-Zeitraum (§ 11 Abs 2 '
|
||||
'ASVG); voll lohnnebenkostenpflichtig.')
|
||||
abgang_ffrist_tage = fields.Float(
|
||||
string='Kündigungsfrist (Kalendertage)',
|
||||
help='Kalendertage der (fiktiven) Kündigungsfrist, auf die die '
|
||||
'pauschalierte Abgangsentschädigung sv-rechtlich umzulegen '
|
||||
'ist (§ 11 Abs 2 ASVG).')
|
||||
zeit_guthaben_stunden = fields.Float(
|
||||
string='Zeitguthaben an Normalarbeitszeit (Stunden)',
|
||||
help='Offenes Guthaben an Normalarbeitszeit aus durchrechenbarem '
|
||||
'Arbeitszeitmodell — Abgeltung mit 50 % Zuschlag (§ 19e '
|
||||
'Abs 2 AZG); bei unberechtigtem vorzeitigem Austritt kein '
|
||||
'Zuschlag.')
|
||||
zeit_schulden_stunden = fields.Float(
|
||||
string='Zeitschulden (Stunden)',
|
||||
help='Offene Zeitschulden (negative Zeitguthaben), abgerechnet '
|
||||
'mit der aktuellen Normalstunde (lb-end-08).')
|
||||
vorschuss_saldo = fields.Monetary(
|
||||
string='Offener Vorschuss-/Darlehenssaldo',
|
||||
currency_field='currency_id',
|
||||
help='Bei Beendigung zur Gänze fälliger, noch offener '
|
||||
'Vorschusssaldo (lb-end-06) — Rückverrechnung vom Netto '
|
||||
'nach SV und Lohnsteuer, ohne Abgabenberührung '
|
||||
'(§ 293 Abs 3 EO).')
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', default=lambda self: self.env.company.currency_id)
|
||||
|
||||
uel_lfd_vorschau = fields.Monetary(
|
||||
string='UEL laufender Anteil (Vorschau)',
|
||||
currency_field='currency_id', compute='_compute_vorschau')
|
||||
uel_sz_vorschau = fields.Monetary(
|
||||
string='UEL Sonderzahlungsanteil (Vorschau)',
|
||||
currency_field='currency_id', compute='_compute_vorschau')
|
||||
sv_tage_vorschau = fields.Integer(
|
||||
string='SV-Verlängerungstage (§ 11 Abs 2)',
|
||||
compute='_compute_vorschau')
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee(self):
|
||||
for wizard in self:
|
||||
if wizard.employee_id:
|
||||
wizard.austrittsdatum = (
|
||||
wizard.employee_id.version_id.contract_date_end
|
||||
or fields.Date.today())
|
||||
|
||||
@api.depends('employee_id', 'austrittsdatum', 'uel_tage')
|
||||
def _compute_vorschau(self):
|
||||
for wizard in self:
|
||||
wizard.uel_lfd_vorschau = 0.0
|
||||
wizard.uel_sz_vorschau = 0.0
|
||||
wizard.sv_tage_vorschau = 0
|
||||
if not wizard.employee_id or not wizard.austrittsdatum:
|
||||
continue
|
||||
slip = wizard._vorschau_payslip()
|
||||
wizard.uel_lfd_vorschau = slip._at_uel_lfd()
|
||||
wizard.uel_sz_vorschau = slip._at_uel_sz()
|
||||
wizard.sv_tage_vorschau = slip._at_end_sv_tage(
|
||||
wizard.uel_tage)
|
||||
|
||||
def _vorschau_payslip(self):
|
||||
"""Flüchtiger Payslip-Kontext für die Vorschau-Berechnung (kein
|
||||
Speichern): trägt Austrittsmonat, Struktur und die Eingaben."""
|
||||
self.ensure_one()
|
||||
erster = self.austrittsdatum.replace(day=1)
|
||||
letzter = (erster + timedelta(days=32)).replace(day=1) \
|
||||
- timedelta(days=1)
|
||||
struct = self.employee_id.version_id.structure_id
|
||||
if not struct:
|
||||
struct = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_at_privat')
|
||||
slip = self.env['hr.payslip'].new({
|
||||
'employee_id': self.employee_id.id,
|
||||
'struct_id': struct.id,
|
||||
'date_from': erster,
|
||||
'date_to': letzter,
|
||||
})
|
||||
eingaben = [('ATP_UEL_TAGE', self.uel_tage)]
|
||||
slip.input_line_ids = [
|
||||
(0, 0, {
|
||||
'input_type_id': self.env['hr.payslip.input.type']
|
||||
.search([('code', '=', code)], limit=1).id,
|
||||
'amount': betrag,
|
||||
})
|
||||
for code, betrag in eingaben if betrag
|
||||
]
|
||||
return slip
|
||||
|
||||
def _eingaben(self):
|
||||
self.ensure_one()
|
||||
return [
|
||||
('ATP_UEL_TAGE', self.uel_tage),
|
||||
('ATP_ABGANG', self.abgang_betrag),
|
||||
('ATP_ABGANG_FFRIST_TAGE', self.abgang_ffrist_tage),
|
||||
('ATP_ZEIT_GUTHABEN', self.zeit_guthaben_stunden),
|
||||
('ATP_ZEIT_SCHULDEN', self.zeit_schulden_stunden),
|
||||
('ATP_ABRECHNUNG_VORSCHUSS', self.vorschuss_saldo),
|
||||
]
|
||||
|
||||
def action_payslip(self):
|
||||
"""Endabrechnungs-Lauf im Austrittsmonat anlegen und die
|
||||
gesammelten offenen Posten als Eingaben vorbefüllen."""
|
||||
self.ensure_one()
|
||||
erster = self.austrittsdatum.replace(day=1)
|
||||
letzter = (erster + timedelta(days=32)).replace(day=1) \
|
||||
- timedelta(days=1)
|
||||
struct = self.employee_id.version_id.structure_id \
|
||||
or self.env.ref('l10n_at_hr_payroll_private.structure_at_privat')
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': _('Endabrechnung %s') % self.employee_id.name,
|
||||
'employee_id': self.employee_id.id,
|
||||
'struct_id': struct.id,
|
||||
'date_from': erster,
|
||||
'date_to': letzter,
|
||||
})
|
||||
typen = {code: self.env['hr.payslip.input.type'].search(
|
||||
[('code', '=', code)], limit=1)
|
||||
for code, _betrag in self._eingaben()}
|
||||
self.env['hr.payslip.input'].create([{
|
||||
'payslip_id': slip.id,
|
||||
'input_type_id': typen[code].id,
|
||||
'amount': betrag,
|
||||
} for code, betrag in self._eingaben() if betrag])
|
||||
slip.compute_sheet()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Endabrechnung'),
|
||||
'res_model': 'hr.payslip',
|
||||
'view_mode': 'form',
|
||||
'res_id': slip.id,
|
||||
}
|
||||
Reference in New Issue
Block a user