[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:
2026-09-14 16:08:44 +02:00
parent 9ff977e0d4
commit ee94f4664f
14 changed files with 1189 additions and 4 deletions
@@ -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>