[IMP] l10n_at_hr_payroll_private: add voluntary severance under § 67 Abs 6 (AP15-B)

Split the voluntary severance into its privileged part and a tariff remainder on the exit run: the privileged part combines the quarter rule (1/4 of the last twelve months' running pay, capped at nine times the monthly HBG) with the twelfth rule (2/12 to 12/12 by proven service, capped at n × 3 × HBG), reduced per Z 3 by severance already received and the statutory claim due at this termination, all taxed at the flat 6 % rate. The excess runs as its own rule flagged l10n_at_s67_abs6_ueberhang: taxed with the running tariff of the payment month but excluded from the annual sixth, since Z 5 exempts only the Z 1/Z 2 amounts and the excess is a special payment, not running pay. The twelve-month basis aggregates validated prior runs plus own lines, excluding the flagged excess and § 26 Z 4 travel overhang via a dedicated rule flag. Free of SV (§ 49 Abs 3 Z 7 ASVG), payroll levies regardless of the 6 %/tarif split (§ 41 Abs 4 lit b FLAG, § 122 Abs 8 WKG, § 5 Abs 2 lit b KommStG) and BV contributions; the former BV-Anwartschaft exclusion Z 7 is repealed (BGBl I 118/2015). The wizard proposes the rolling basis and the statutory claim, previews Z 1/Z 2/reduction/privileged/excess/tax and requires explicit confirmation against claim-conversion risk. The § 291d EO termination pot nets both severance wage-tax rules; key 260 now includes LST_ABF_FREI. Verified against the original statute text in .ris/ (EStG § 67 Abs 6) and the knowledge-base sources lb-end-12/13/15; KZ positioning of Abs-6 amounts and the excess's § 291d pot allocation remain documented verification points against LStR Rz 1087 ff.

Version 19.0.14.0.0 (private), 19.0.10.0.0 (core)
This commit is contained in:
2026-09-15 11:34:09 +02:00
parent 9add58fe83
commit 4b86bcb790
19 changed files with 692 additions and 16 deletions
+46 -1
View File
@@ -2,8 +2,53 @@
## Current focus ## Current focus
**AP15-B freiwillige Abfertigung (§ 67 Abs 6) umgesetzt — Kern 55/55, Privat 146/146 Tests grün**
(2026-09-15, uncommittet bis Nutzerfreigabe; Privat **19.0.14.0.0**, Kern **19.0.10.0.0**):
- **Recht (RIS-Wortlaut § 67 Abs 6 verifiziert, lb-end-12/13/15):**
Z 1 Viertelregelung (¼ der letzten-12-Monats-Bezüge, Deckel 9×HBG)
+ Z 2 Zwölftelregelung (Staffel 3→2/12 … 25→12/12 = §-23-Grenzen,
Deckel n × 3 × HBG), Z 3 Kürzung um erhaltene Abfertigungen (Abs 3
ODER 6) und bestehenden gesetzlichen Anspruch am aktuellen Ende,
Z 5 Sechstel-Exklusion nur für die 6 %-Beträge, Z 6 Überhang
„wie ein laufender Bezug“ zum Zuflussmonat-Tarif, Z 7 (BV-Ausschluss)
seit BGBl I 118/2015 AUFGEHOBEN. SV-frei § 49 Abs 3 Z 7, LNK-frei
(§ 41 Abs 4 lit b FLAG, § 122 Abs 8 WKG, § 5 Abs 2 lit b KommStG)
UNABHÄNGIG von 6 % vs Tarif, BV-frei (§ 6 Abs 5 BMSVG).
- **Kern (19.0.10.0.0):** neuer Regel-Flag `l10n_at_s67_abs6_ueberhang`
(lohnsteuer.py); `_l10n_at_brutto_laufen_ytd` zieht Flag-Regeln aus
der Sechstel-Basis ab (Helfer `_l10n_at_abs6_ueberhang_total`, auch
je YTD-Lauf) — der Überhang läuft über bemessung='laufend' in die
laufende LSt-Bemessung und KZ 210, aber NICHT ins Sechstel;
`LST_ABF_FREI` in KZ-260-Salden. Test test_20 + L16-Test erweitert.
- **Privat:** Regeln `ABF_FREI` (seq 31, neutral/keine/abs6/
pfandung_beendigung — begünstigter Teil), `ABF_FREI_TARIF` (seq 32,
laufend/keine/abs6 + Überhang-Flag — Tarifrest), `LST_ABF_FREI`
(seq 73, 6 %); Eingabe ATP_ABF_FREI; Helfer in endabrechnung.py:
`_at_bezuege_12_monate` (validierte Vormonats-Läufe + eigene Zeilen,
ohne Überhang-/§-26-Z-4-Regeln — neues privates Regel-Flag
`l10n_at_s26_z4_reise` an REISE_STEUERPFLICHTIG), `_at_abf_frei_split`
(Z 1/Z 2/Deckel aus at_sv_hbg_monatlich/Z-3-Kürzung),
`_at_abf_frei_lst_6`. Wizard-Abschnitt mit auto 12-Monats-Basis +
gesetzlicher Anspruch aus ABF_ALT, Vorschau Z 1/Z 2/Kürzung/
begünstigt/Überhang/6 %/Netto, Bestätigungspflicht (Anspruchs-
umwandlungs-Hinweis lb-end-15). Pfändungs-Beendigungstopf zieht
LST_ABF + LST_ABF_FREI ab (test_09c). Konten: rule_abf_frei(_tarif)
6200/6000 vorläufig, rule_lst_abf_frei 3540. RF-ABF-4…6 + offene
Punkte in docs/rechenfaelle/endabrechnung.md.
- **Tests:** endabrechnung 17 (Z 1/Z 2, Überhang ohne Sechstel mit
SV/LNK/BV/LST_LFD-Asserts, Kürzung+Deckel, Wizard-E2E), pfändung 13,
Kern-Suite 55/55, Privat-Suite 146/146 (ap13a_test); ein Testfehler
(zweite `_input`-Eingabezeile statt Update) korrigiert.
- **Verifikationsreste (dokumentiert, nicht geblockt):** KZ-Positionierung
der Abs-6-Bezüge und §-291d-Zuordnung des Überhangs gegen LStR
Rz 1087 ff; geminderte Bezüge in der 12-Monats-Basis (LStR vs VwGH)
bewusst ohne Automatik.
- **Offen:** AP15-C (§ 67 Abs 8 lit a Vergleichssummen). Nicht
committen ohne Nutzeranforderung.
**AP15-A gesetzliche Abfertigung Alt umgesetzt — Privat 141/141, Kern 54/54 Tests grün** **AP15-A gesetzliche Abfertigung Alt umgesetzt — Privat 141/141, Kern 54/54 Tests grün**
(2026-09-15, uncommittet bis Nutzerfreigabe; Privat **19.0.13.0.0**, Kern **19.0.9.0.0**): (2026-09-15, Commit **`9add58f`** nach Nutzerfreigabe):
- **Recht am Originalwortlaut verifiziert (.ris/):** § 23a Abs 3 AngG - **Recht am Originalwortlaut verifiziert (.ris/):** § 23a Abs 3 AngG
verlangt für den Elternaustritt ein **ununterbrochen fünfjähriges** verlangt für den Elternaustritt ein **ununterbrochen fünfjähriges**
+1 -1
View File
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3. # Part of the odoo-at-payroll project. License: LGPL-3.
{ {
'name': 'Österreich - Personalverrechnung Kern (AT Payroll Core)', 'name': 'Österreich - Personalverrechnung Kern (AT Payroll Core)',
'version': '19.0.9.0.0', 'version': '19.0.10.0.0',
'author': 'Florian Egger', 'author': 'Florian Egger',
'website': 'https://gem360.at', 'website': 'https://gem360.at',
'category': 'Human Resources/Payroll', 'category': 'Human Resources/Payroll',
+33 -3
View File
@@ -70,6 +70,15 @@ class HrSalaryRule(models.Model):
'abgezogen; Grenzen als Jahres-Kumulative je Dienstnehmer ' 'abgezogen; Grenzen als Jahres-Kumulative je Dienstnehmer '
'(at_st_grenzen). Nur der übersteigende Teil ist ' '(at_st_grenzen). Nur der übersteigende Teil ist '
'steuerpflichtig. Menge = Arbeitstage (Z 17/Z 9).') 'steuerpflichtig. Menge = Arbeitstage (Z 17/Z 9).')
l10n_at_s67_abs6_ueberhang = fields.Boolean(
string='AT § 67 Abs 6 Z 6 — Überhang',
help='Übersteigender Teil einer freiwilligen Abfertigung/Abfindung '
'(§ 67 Abs 6 Z 6 EStG): wird wie ein laufender Bezug zum '
'Tarif des Zuflussmonats versteuert, erhöht aber das '
'Jahressechstel nicht — Abs 2 ist nur auf die mit 6 % '
'versteuerten Z-1/Z-2-Beträge nicht anzuwendbar (Z 5), der '
'Überhang ist ein sonstiger Bezug, kein laufender. LNK-frei '
'über § 67 Abs 6 (lb-end-12), SV-frei über § 49 Abs 3 Z 7.')
class HrVersion(models.Model): class HrVersion(models.Model):
@@ -351,11 +360,32 @@ class HrPayslip(models.Model):
def _l10n_at_brutto_laufen_ytd(self, result_rules=None): def _l10n_at_brutto_laufen_ytd(self, result_rules=None):
"""Kumulierte laufende Bruttobezüge des Jahres (Sechstel-Basis """Kumulierte laufende Bruttobezüge des Jahres (Sechstel-Basis
ist Brutto, § 67 Abs 2 — inkl. Auszahlungsmonat-Laufendem).""" ist Brutto, § 67 Abs 2 — inkl. Auszahlungsmonat-Laufendem).
Der §-67-Abs-6-Überhang (Flag) läuft tarifisch im Zuflussmonat,
ist aber ein sonstiger Bezug und erhöht das Jahressechstel
nicht."""
self.ensure_one() self.ensure_one()
total = self._l10n_at_brutto('laufend', result_rules) total = self._l10n_at_brutto('laufend', result_rules) \
- self._l10n_at_abs6_ueberhang_total(result_rules)
for slip in self._l10n_at_ytd_slaeufe(): for slip in self._l10n_at_ytd_slaeufe():
total += slip._l10n_at_brutto('laufend') total += slip._l10n_at_brutto('laufend') \
- slip._l10n_at_abs6_ueberhang_total()
return total
def _l10n_at_abs6_ueberhang_total(self, result_rules=None):
"""Summe der als §-67-Abs-6-Z-6-Überhang gekennzeichneten
Regel-Zeilen (tarifisch im Zuflussmonat, ohne Sechstel-Effekt)."""
self.ensure_one()
total = 0.0
for rule in self.struct_id.rule_ids:
if not rule.l10n_at_s67_abs6_ueberhang:
continue
if result_rules is not None:
if rule.code in result_rules:
total += result_rules[rule.code]['total']
else:
total += sum(self.line_ids.filtered(
lambda l, c=rule.code: l.code == c).mapped('total'))
return total return total
def _l10n_at_sechstel(self, result_rules=None): def _l10n_at_sechstel(self, result_rules=None):
@@ -390,7 +390,7 @@ class L10nAtPayrollL16Line(models.Model):
GP3-Kerns (Monats-Caps 400/600 und ÜStd 15/170 eingerechnet); GP3-Kerns (Monats-Caps 400/600 und ÜStd 15/170 eingerechnet);
220 = sonstige § 67 Abs 1/2/5 ohne Abs-3/6; 225 = SV-DN auf 220 = sonstige § 67 Abs 1/2/5 ohne Abs-3/6; 225 = SV-DN auf
KZ 220; 230 = laufende SV-DN + AK + WF; VIEB = 230 + 225 + KZ 220; 230 = laufende SV-DN + AK + WF; VIEB = 230 + 225 +
226; 260/BIEL = LST_LFD + LST_SZ + LST_ABF.""" 226; 260/BIEL = LST_LFD + LST_SZ + LST_ABF + LST_ABF_FREI."""
self.ensure_one() self.ensure_one()
salden = dict.fromkeys( salden = dict.fromkeys(
('kz210', 'kz215', 'kz220', 'kz225', 'kz226', 'kz230', ('kz210', 'kz215', 'kz220', 'kz225', 'kz226', 'kz230',
@@ -411,7 +411,8 @@ class L10nAtPayrollL16Line(models.Model):
elif line.code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV', elif line.code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV',
'SVDN_AK', 'SVDN_WF'): 'SVDN_AK', 'SVDN_WF'):
salden['kz230'] += line.total salden['kz230'] += line.total
elif line.code in ('LST_LFD', 'LST_SZ', 'LST_ABF'): elif line.code in ('LST_LFD', 'LST_SZ', 'LST_ABF',
'LST_ABF_FREI'):
salden['lohnsteuer'] += line.total salden['lohnsteuer'] += line.total
freibe = slip._l10n_at_freibetraege_68() freibe = slip._l10n_at_freibetraege_68()
salden['kz215'] += freibe['szfn'] + freibe['ue_std'] salden['kz215'] += freibe['szfn'] + freibe['ue_std']
@@ -235,6 +235,9 @@ class L16GeneratorTest(TransactionCase):
salary_rule_id=abs3_rule, code='ABF_ALT', total=12000.0), salary_rule_id=abs3_rule, code='ABF_ALT', total=12000.0),
types.SimpleNamespace( types.SimpleNamespace(
salary_rule_id=lst_rule, code='LST_ABF', total=720.0), salary_rule_id=lst_rule, code='LST_ABF', total=720.0),
types.SimpleNamespace(
salary_rule_id=lst_rule, code='LST_ABF_FREI',
total=300.0),
], ],
_l10n_at_freibetraege_68=lambda: {'szfn': 0.0, 'ue_std': 0.0}, _l10n_at_freibetraege_68=lambda: {'szfn': 0.0, 'ue_std': 0.0},
) )
@@ -243,7 +246,7 @@ class L16GeneratorTest(TransactionCase):
self.assertEqual(salden['kz210'], 0.0) self.assertEqual(salden['kz210'], 0.0)
self.assertEqual(salden['kz220'], 0.0) self.assertEqual(salden['kz220'], 0.0)
self.assertEqual(salden['lohnsteuer'], 720.0) self.assertEqual(salden['lohnsteuer'], 1020.0)
def test_02_arbeitsstaette(self): def test_02_arbeitsstaette(self):
"""§ 34 Abs 6 ASVG: je AN ein Satz 45 (Länge 300) mit BKNR, """§ 34 Abs 6 ASVG: je AN ein Satz 45 (Länge 300) mit BKNR,
@@ -125,6 +125,27 @@ class LohnsteuerTest(TransactionCase):
'contract_date_end': date(2026, 1, 10)}) 'contract_date_end': date(2026, 1, 10)})
self.assertEqual(slip._l10n_at_lohnsteuertage(), 10) self.assertEqual(slip._l10n_at_lohnsteuertage(), 10)
# ------------------------------------------------------------------
# § 67 Abs 6 Z 6 — Überhang (tarifisch, ohne Sechstel-Effekt)
# ------------------------------------------------------------------
def test_20_abs6_ueberhang_ohne_sechstel(self):
"""Der gekennzeichnete Überhang läuft tarifisch in der laufenden
Bemessung, erhöht aber das Jahressechstel nicht."""
employee = self._employee()
slip_ohne = self._slip(employee, 1)
regel = self._regel('T_ABS6_UEB', 1000.0, sv_art='keine')
regel.l10n_at_s67_abs6_ueberhang = True
slip_mit = self._slip(employee, 2)
self.assertAlmostEqual(
slip_mit._l10n_at_abs6_ueberhang_total(), 1000.0, places=2)
self.assertAlmostEqual(
slip_mit._l10n_at_bemessung_laufen()
- slip_ohne._l10n_at_bemessung_laufen(), 1000.0, places=2)
self.assertAlmostEqual(
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
0.0, places=2)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# § 68 Freibeträge (Buckets über Regel-Flags) # § 68 Freibeträge (Buckets über Regel-Flags)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3. # Part of the odoo-at-payroll project. License: LGPL-3.
{ {
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)', 'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
'version': '19.0.13.0.0', 'version': '19.0.14.0.0',
'author': 'Florian Egger', 'author': 'Florian Egger',
'website': 'https://gem360.at', 'website': 'https://gem360.at',
'category': 'Human Resources/Payroll', 'category': 'Human Resources/Payroll',
@@ -35,4 +35,8 @@
<field name="name">Gesetzliche Abfertigung Alt (Brutto)</field> <field name="name">Gesetzliche Abfertigung Alt (Brutto)</field>
<field name="code">ATP_ABF_ALT</field> <field name="code">ATP_ABF_ALT</field>
</record> </record>
<record id="input_type_atp_abf_frei" model="hr.payslip.input.type">
<field name="name">Freiwillige Abfertigung (Brutto)</field>
<field name="code">ATP_ABF_FREI</field>
</record>
</odoo> </odoo>
@@ -91,6 +91,45 @@ result = inputs['ATP_ABF_ALT'].amount
]]></field> ]]></field>
</record> </record>
<!-- Freiwillige Abfertigung (§ 67 Abs 6 EStG): begünstigter Teil
(Viertel-/Zwölftelregelung mit 6 %) und tarifischer Überhang
(Z 6) als getrennte Zeilen. -->
<record id="rule_abf_frei" model="hr.salary.rule">
<field name="name">Freiwillige Abfertigung — begünstigter Teil (§ 67 Abs 6)</field>
<field name="code">ABF_FREI</field>
<field name="sequence" eval="31"/>
<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_ABF_FREI' in inputs and inputs['ATP_ABF_FREI'].amount</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="l10n_at_sv_art">keine</field>
<field name="l10n_at_s67_abs">abs6</field>
<field name="l10n_at_pfandung_beendigung" eval="True"/>
<field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[
result = payslip._at_abf_frei_beguenstigt(result_rules)
]]></field>
</record>
<record id="rule_abf_frei_tarif" model="hr.salary.rule">
<field name="name">Freiwillige Abfertigung — Überhang (§ 67 Abs 6 Z 6)</field>
<field name="code">ABF_FREI_TARIF</field>
<field name="sequence" eval="32"/>
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
<field name="condition_select">python</field>
<field name="condition_python">result = payslip._at_abf_frei_ueberhang(result_rules) > 0</field>
<field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_sv_art">keine</field>
<field name="l10n_at_s67_abs">abs6</field>
<field name="l10n_at_s67_abs6_ueberhang" eval="True"/>
<field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[
result = payslip._at_abf_frei_ueberhang(result_rules)
]]></field>
</record>
<!-- Zeitguthaben an Normalarbeitszeit (§ 19e Abs 2 AZG: + 50 %). --> <!-- Zeitguthaben an Normalarbeitszeit (§ 19e Abs 2 AZG: + 50 %). -->
<record id="rule_zeit_guthaben" model="hr.salary.rule"> <record id="rule_zeit_guthaben" model="hr.salary.rule">
<field name="name">Zeitguthaben Normalarbeitszeit + 50 % (§ 19e Abs 2 AZG)</field> <field name="name">Zeitguthaben Normalarbeitszeit + 50 % (§ 19e Abs 2 AZG)</field>
@@ -119,4 +119,19 @@ result = payslip._l10n_at_lst_sonstige(result_rules)
result = payslip._at_abf_alt_lohnsteuer(result_rules) result = payslip._at_abf_alt_lohnsteuer(result_rules)
]]></field> ]]></field>
</record> </record>
<record id="rule_lst_abf_frei" model="hr.salary.rule">
<field name="name">Lohnsteuer freiwillige Abfertigung (§ 67 Abs 6)</field>
<field name="code">LST_ABF_FREI</field>
<field name="sequence" eval="73"/>
<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 = payslip._at_abf_frei_lst_6(result_rules) > 0</field>
<field name="amount_select">code</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="l10n_at_sv_art">keine</field>
<field name="amount_python_compute"><![CDATA[
result = payslip._at_abf_frei_lst_6(result_rules)
]]></field>
</record>
</odoo> </odoo>
@@ -33,6 +33,7 @@ result = inputs['EXPENSES'].amount
<field name="condition_select">python</field> <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="condition_python">result = 'ATP_REISE_STEUERPFLICHTIG' in inputs and inputs['ATP_REISE_STEUERPFLICHTIG'].amount</field>
<field name="l10n_at_bemessung">laufend</field> <field name="l10n_at_bemessung">laufend</field>
<field name="l10n_at_s26_z4_reise" eval="True"/>
<field name="amount_select">code</field> <field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[ <field name="amount_python_compute"><![CDATA[
result = inputs['ATP_REISE_STEUERPFLICHTIG'].amount result = inputs['ATP_REISE_STEUERPFLICHTIG'].amount
@@ -123,11 +123,62 @@ Durchschnittsbezügen, Lehrzeiten, Teilübertritten und §-23a-Sonderfällen
müssen Anspruch, Basis und Nachweis vor Anlage des Laufs bestätigt werden. müssen Anspruch, Basis und Nachweis vor Anlage des Laufs bestätigt werden.
Die geprüften Angaben werden am Payslip gespeichert. Die geprüften Angaben werden am Payslip gespeichert.
## Offene Punkte (AP15-B/AP15-C) ## RF-ABF-4 — Freiwillige Abfertigung, Viertel-/Zwölftelregelung
- **Vergleich (§ 67 Abs 8 lit a)** und **freiwillige Abfertigung/ 15 nachgewiesene Dienstjahre (6/12), 12-Monats-Basis 36.000 € (alle
Abfindung (§ 67 Abs 6 6 %-Bänder)** → AP15-B/C mit rollierender steuerbaren Bezugsteile inklusive steuerfreier Zuschläge und Sachbezüge,
12-Monats-Basis, Dienstzeit-Nachweisen und Alt-/Neu-Aufteilung. ohne Sonderzahlungen und ohne §-26-Z-4-Reisevergütungen), Brutto
40.000 €:
- Z 1 Viertelregelung: 36.000 / 4 = **9.000 €** (Deckel 9 × HBG =
62.370 € nicht greifend);
- Z 2 Zwölftelregelung: 36.000 × 6/12 = **18.000 €** (Deckel
6 × 3 × HBG = 124.740 € nicht greifend);
- begünstigter Teil: min(40.000, 9.000 + 18.000) = **27.000 €** mit
6 % = **1.620 €** Lohnsteuer (`ABF_FREI`/`LST_ABF_FREI`);
- Überhang **13.000 €** als `ABF_FREI_TARIF`: tarifisch in der
LSt-Bemessung des Zuflussmonats (LST_LFD), ohne Jahressechstel-Erhöhung
(Kern-Flag `l10n_at_s67_abs6_ueberhang`);
- SV-frei (§ 49 Abs 3 Z 7 ASVG), LNK-frei unabhängig von der
Besteuerungsart (§ 41 Abs 4 lit b FLAG, § 122 Abs 8 WKG,
§ 5 Abs 2 lit b KommStG), BV-frei (§ 6 Abs 5 BMSVG); Z 7 (BV-
Anwartschafts-Ausschluss) ist seit BGBl I 118/2015 aufgehoben.
## RF-ABF-5 — Z-3-Kürzung und Deckel
4.000 € bereits erhaltene Abfertigungen plus 9.000 € gesetzlicher
Anspruch am aktuellen Ende kürzen das Z-2-Ausmaß von 18.000 € auf
5.000 €; begünstigt sind 9.000 + 5.000 = 14.000 € von 20.000 € Brutto,
der Rest läuft zum Tarif. Bei einer 12-Monats-Basis von 12.000.000 €
begrenzen die Deckel auf 62.370 € (Z 1) plus 124.740 € (Z 2, 6 × 3 ×
HBG), begünstigt sind also höchstens 187.110 €.
## RF-ABF-6 — Pfändung und Wizard
- `ABF_FREI` (begünstigter Teil) läuft über den §-291d-Beendigungstopf,
vermindert um `LST_ABF` und `LST_ABF_FREI`; der Überhang
(`ABF_FREI_TARIF`) bleibt über die laufende Bemessung pfändbar, seine
Lohnsteuer steckt bereits in `LST_LFD`.
- Der Wizard schlägt die 12-Monats-Basis aus den letzten zwölf
validierten Läufen vor (ohne Überhang- und Reise-Regeln, geminderte
Bezüge nicht automatisch zurückgerechnet) und den gesetzlichen
Anspruch aus der Alt-Abfertigung; Prüfung und Nachweise werden
bestätigt (lückenlose Vordienstzeiten zum Lohnkonto, § 76 EStG,
keine Anspruchsumwandlung, lb-end-15).
## Offene Punkte (AP15-C und Verifikationsreste)
- **Vergleich (§ 67 Abs 8 lit a)** → AP15-C mit rollierender
12-Monats-Basis und Alt-/Neu-Aufteilung (freiwillige Abfertigung
§ 67 Abs 6 ist mit AP15-B umgesetzt).
- KZ-Positionierung der Abs-6-Bezüge (außerhalb KZ 210/220, LSt in
KZ 260 — GP3-Konvention wie Abs 3) und die Zuordnung des Z-6-Überhangs
zum §-291d-EO-Topf sind gegen LStR Rz 1087 ff / § 291d EO zu
verifizieren, bevor der MVP freigegeben wird.
- Geminderte Bezüge in der 12-Monats-Basis: die LStR-Rückrechnungs-
praxis (zwölf Monate voller Bezüge) widerspricht dem VwGH
(Krankengeld 6/7 einbeziehen) — bewusst ohne Automatik, Basis ist
bestätigbar (lb-end-13 ⚠).
- Prämien-Beendigung (§ 16 AngG-Aliquotierung, Rollung) — PRAM/SZ-Regeln - Prämien-Beendigung (§ 16 AngG-Aliquotierung, Rollung) — PRAM/SZ-Regeln
vorhanden; Feinschliff AP14-B. vorhanden; Feinschliff AP14-B.
- Meldewesen-Schnittstellen: „Ende des Entgeltanspruches" in mBGM/ - Meldewesen-Schnittstellen: „Ende des Entgeltanspruches" in mBGM/
@@ -69,10 +69,15 @@ L10N_AT_BUCHUNGSMATRIX = {
'rule_lst_lfd': {'credit': ('3540',)}, 'rule_lst_lfd': {'credit': ('3540',)},
'rule_lst_sz': {'credit': ('3540',)}, 'rule_lst_sz': {'credit': ('3540',)},
'rule_lst_abf': {'credit': ('3540',)}, 'rule_lst_abf': {'credit': ('3540',)},
'rule_lst_abf_frei': {'credit': ('3540',)},
# Gesetzliche Abfertigung Alt: bis zur Referenzbuchhaltungs- # Gesetzliche Abfertigung Alt: bis zur Referenzbuchhaltungs-
# Fixierung allgemeiner Gehalts-/Lohnaufwand, company-dependent # Fixierung allgemeiner Gehalts-/Lohnaufwand, company-dependent
# Regelkonto bleibt übersteuerbar. # Regelkonto bleibt übersteuerbar.
'rule_abf_alt': {'debit': ('6200',), 'debit_arbeiter': ('6000',)}, 'rule_abf_alt': {'debit': ('6200',), 'debit_arbeiter': ('6000',)},
# Freiwillige Abfertigung (§ 67 Abs 6): begünstigter Teil und
# tarifischer Überhang — gleiche vorläufige Aufwandskonten.
'rule_abf_frei': {'debit': ('6200',), 'debit_arbeiter': ('6000',)},
'rule_abf_frei_tarif': {'debit': ('6200',), 'debit_arbeiter': ('6000',)},
# Nettoauszahlung (reconcilable, s. o.) # Nettoauszahlung (reconcilable, s. o.)
'rule_netto': {'credit': ('3850',)}, 'rule_netto': {'credit': ('3850',)},
# DG-Beiträge (650655 SV Arbeiter · 656659 SV Angestellte) # DG-Beiträge (650655 SV Arbeiter · 656659 SV Angestellte)
@@ -28,6 +28,8 @@ Am Originalwortlaut verifiziert (.ris/, Stand 2026-09-14):
""" """
from datetime import timedelta from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import fields, models from odoo import fields, models
@@ -51,6 +53,11 @@ class HrSalaryRule(models.Model):
'(Deckel: ein Fünftel des Neunfachen der monatlichen HBG), ' '(Deckel: ein Fünftel des Neunfachen der monatlichen HBG), '
'der Rest läuft tarifisch im Zuflussmonat — ohne ' 'der Rest läuft tarifisch im Zuflussmonat — ohne '
'Jahressechstel-Erhöhung (Abs 2 nicht anzuwenden).') 'Jahressechstel-Erhöhung (Abs 2 nicht anzuwenden).')
l10n_at_s26_z4_reise = fields.Boolean(
string='AT § 26 Z 4 Reisevergütung',
help='§-26-Z-4-Reisevergütungen zählen nicht in die '
'12-Monats-Basis der §-67-Abs-6-Begünstigung (lb-end-13); '
'Sonderzahlungen sind ohnehin nicht bemessung „laufend“.')
class HrPayslip(models.Model): class HrPayslip(models.Model):
@@ -83,6 +90,31 @@ class HrPayslip(models.Model):
string='AT Abfertigung Alt — Anspruchsfaktor', copy=False) string='AT Abfertigung Alt — Anspruchsfaktor', copy=False)
l10n_at_abf_alt_nachweis = fields.Text( l10n_at_abf_alt_nachweis = fields.Text(
string='AT Abfertigung Alt — Nachweis/Begründung', copy=False) string='AT Abfertigung Alt — Nachweis/Begründung', copy=False)
l10n_at_abf_frei_basis_12 = fields.Float(
string='AT Freiwillige Abfertigung — 12-Monats-Basis', copy=False,
help='Laufende Bezüge der letzten zwölf Monate (§ 67 Abs 6 Z 1/2 '
'EStG): vom Wizard bestätigt — alle steuerbaren Bezugsteile '
'inklusive steuerfreier Zuschläge und Sachbezüge, ohne '
'Sonderzahlungen und ohne §-26-Z-4-Reisevergütungen; keine '
'Bezüge anderer Arbeitgeber, keine Hochrechnung kurzer DV.')
l10n_at_abf_frei_dienstzeit_monate = fields.Integer(
string='AT Freiwillige Abfertigung — nachgewiesene Dienstzeit '
'(Monate)', copy=False)
l10n_at_abf_frei_erhaltene_abfertigungen = fields.Float(
string='AT Freiwillige Abfertigung — erhaltene Abfertigungen',
copy=False,
help='Während der nachgewiesenen Dienstzeit bereits erhaltene '
'Abfertigungen nach § 67 Abs 3 oder Abs 6 EStG — kürzen das '
'Z-2-Ausmaß (§ 67 Abs 6 Z 3).')
l10n_at_abf_frei_gesetzlicher_anspruch = fields.Float(
string='AT Freiwillige Abfertigung — bestehender gesetzlicher '
'Anspruch', copy=False,
help='Bestehender Anspruch auf eine gesetzliche Abfertigung am '
'aktuellen Dienstverhältnis-Ende (§ 67 Abs 6 Z 3 EStG) — '
'kürzt das Z-2-Ausmaß.')
l10n_at_abf_frei_nachweis = fields.Text(
string='AT Freiwillige Abfertigung — Nachweis/Begründung',
copy=False)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2 ASVG) # SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2 ASVG)
@@ -232,6 +264,87 @@ class HrPayslip(models.Model):
return min(self._at_abf_alt_steuer_vervielfachung(result_rules), return min(self._at_abf_alt_steuer_vervielfachung(result_rules),
self._at_abf_alt_steuer_6(result_rules)) self._at_abf_alt_steuer_6(result_rules))
# ------------------------------------------------------------------
# Freiwillige Abfertigung — § 67 Abs 6 EStG (Viertel-/Zwölftelregelung)
# ------------------------------------------------------------------
def _at_bezuege_12_monate(self):
"""Laufende Bezüge der letzten zwölf Monate (§ 67 Abs 6 Z 1/2):
validierte Vormonats-Läufe plus eigene Zeilen — ohne den als
Abs-6-Überhang gekennzeichneten Betrag (das ist ein sonstiger
Bezug, kein laufender) und ohne §-26-Z-4-Reisevergütungen;
Sonderzahlungen sind ohnehin nicht bemessung „laufend“
(lb-end-13)."""
self.ensure_one()
start = (self.date_from - relativedelta(months=11)).replace(day=1)
vor_slips = self.env['hr.payslip'].search([
('employee_id', '=', self.employee_id.id),
('id', '!=', self.id),
('date_from', '>=', start),
('date_from', '<', self.date_from.replace(day=1)),
]).filtered(lambda p: p.state not in ('draft', 'cancel'))
total = 0.0
for slip in list(vor_slips) + [self]:
for line in slip.line_ids:
rule = line.salary_rule_id
if rule.l10n_at_bemessung != 'laufend':
continue
if (rule.l10n_at_s67_abs6_ueberhang
or rule.l10n_at_s26_z4_reise):
continue
total += line.total
return total
def _at_abf_frei_zwoelftel(self):
"""Zwölftel-Anzahl nach § 67 Abs 6 Z 2: die Dienstzeitstaffel
entspricht den §-23-Grenzen (36→2/12 … 300→12/12)."""
self.ensure_one()
return self._at_abf_alt_staffel(
self.l10n_at_abf_frei_dienstzeit_monate)
def _at_abf_frei_split(self, result_rules=None):
"""§ 67 Abs 6: begünstigter Teil (Viertel- und Zwölftelregelung,
gedeckelt 9 × bzw. n × 3 × monatliche HBG, Z-3-Kürzung um
erhaltene Abfertigungen und den bestehenden gesetzlichen
Anspruch) und der tarifische Überhang (Z 6)."""
self.ensure_one()
brutto = max(self._at_input_amount('ATP_ABF_FREI'), 0.0)
basis = self.l10n_at_abf_frei_basis_12
if basis <= 0:
basis = self._at_bezuege_12_monate()
basis = max(basis, 0.0)
hbg = float(self._l10n_at_param('at_sv_hbg_monatlich'))
z1 = min(basis / 4.0, 9.0 * hbg)
zwoelftel = self._at_abf_frei_zwoelftel()
z2 = min(basis * zwoelftel / 12.0, zwoelftel * 3.0 * hbg)
kuerzung = max(
self.l10n_at_abf_frei_erhaltene_abfertigungen
+ self.l10n_at_abf_frei_gesetzlicher_anspruch, 0.0)
z2 = max(z2 - kuerzung, 0.0)
beguenstigt = min(brutto, z1 + z2)
return {
'basis': basis,
'z1': z1,
'z2': z2,
'kuerzung': kuerzung,
'beguenstigt': beguenstigt,
'ueberhang': brutto - beguenstigt,
}
def _at_abf_frei_beguenstigt(self, result_rules=None):
self.ensure_one()
return self._at_abf_frei_split(result_rules)['beguenstigt']
def _at_abf_frei_ueberhang(self, result_rules=None):
self.ensure_one()
return self._at_abf_frei_split(result_rules)['ueberhang']
def _at_abf_frei_lst_6(self, result_rules=None):
"""6 % auf den begünstigten Teil (§ 67 Abs 6, fester Steuersatz)."""
self.ensure_one()
satz = float(self._l10n_at_param(
'at_endabrechnung')['abfertigung_steuersatz'])
return self._at_abf_frei_beguenstigt(result_rules) * satz / 100.0
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Urlaubsersatzleistung — § 67 Abs 8 lit d, lb-url-15/16 # Urlaubsersatzleistung — § 67 Abs 8 lit d, lb-url-15/16
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@@ -386,7 +386,8 @@ class HrPayslip(models.Model):
linie = self.line_ids.filtered( linie = self.line_ids.filtered(
lambda l, c=rule.code: l.code == c) lambda l, c=rule.code: l.code == c)
total += sum(linie.mapped('total')) total += sum(linie.mapped('total'))
steuer = self._at_pfa_rule_amount(result_rules, 'LST_ABF') steuer = sum(self._at_pfa_rule_amount(result_rules, c)
for c in ('LST_ABF', 'LST_ABF_FREI'))
return round(max(0.0, total - steuer), 2) return round(max(0.0, total - steuer), 2)
raise UserError('Unbekannter Pfändungstopf: %s' % topf) raise UserError('Unbekannter Pfändungstopf: %s' % topf)
@@ -479,3 +479,135 @@ class EndabrechnungPrivateTest(TransactionCase):
}) })
with self.assertRaises(UserError): with self.assertRaises(UserError):
wizard.action_payslip() wizard.action_payslip()
# ------------------------------------------------------------------
# AP15-B: freiwillige Abfertigung (§ 67 Abs 6)
# ------------------------------------------------------------------
def test_14_abf_frei_viertel_und_zwoelftel(self):
"""15 Jahre (6/12), Basis 36.000: Z 1 9.000 + Z 2 18.000; Brutto
12.000 → voll begünstigt, 6 % = 720; kein Überhang."""
emp = self._employee()
slip = self._slip(emp)
slip.write({
'l10n_at_abf_frei_basis_12': 36000.0,
'l10n_at_abf_frei_dienstzeit_monate': 180,
})
self._input(slip, 'ATP_ABF_FREI', 12000.0)
split = slip._at_abf_frei_split()
self.assertAlmostEqual(split['z1'], 9000.0, places=2)
self.assertAlmostEqual(split['z2'], 18000.0, places=2)
self.assertAlmostEqual(split['beguenstigt'], 12000.0, places=2)
self.assertAlmostEqual(split['ueberhang'], 0.0, places=2)
self.assertAlmostEqual(
self._total(slip, 'ABF_FREI'), 12000.0, places=2)
self.assertAlmostEqual(
self._total(slip, 'LST_ABF_FREI'), 720.0, places=2)
self.assertFalse(self._total(slip, 'ABF_FREI_TARIF'))
def test_15_abf_frei_ueberhang_tarif_ohne_sechstel(self):
"""Brutto 40.000: begünstigt 27.000 (Z 1 + Z 2), Überhang 13.000
läuft tarifisch über die LSt-Bemessung, ohne das Jahressechstel
zu erhöhen; SV, LNK und BV bleiben unberührt."""
emp_mit = self._employee()
emp_ohne = self._employee()
slip_mit = self._slip(emp_mit)
slip_ohne = self._slip(emp_ohne)
slip_mit.write({
'l10n_at_abf_frei_basis_12': 36000.0,
'l10n_at_abf_frei_dienstzeit_monate': 180,
})
self._input(slip_mit, 'ATP_ABF_FREI', 40000.0)
self.assertAlmostEqual(
self._total(slip_mit, 'ABF_FREI'), 27000.0, places=2)
self.assertAlmostEqual(
self._total(slip_mit, 'ABF_FREI_TARIF'), 13000.0, places=2)
lst6 = self.env.company.currency_id.round(27000.0 * 0.06)
self.assertAlmostEqual(
self._total(slip_mit, 'LST_ABF_FREI'), lst6, places=2)
self.assertAlmostEqual(
slip_mit._l10n_at_bemessung_laufen()
- slip_ohne._l10n_at_bemessung_laufen(), 13000.0, places=2)
self.assertGreater(
self._total(slip_mit, 'LST_LFD'), self._total(slip_ohne, 'LST_LFD'))
self.assertAlmostEqual(
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
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(
slip_mit._at_bv_basis_sonst()
- slip_ohne._at_bv_basis_sonst(), 0.0, places=2)
self.assertAlmostEqual(
slip_mit._at_pfaendung_basis(None, 'beendigung'),
27000.0 - lst6, places=2)
def test_16_abf_frei_kuerzung_und_deckel(self):
"""Z 3: erhaltene Abfertigungen plus gesetzlicher Anspruch kürzen
das Z-2-Ausmaß; bei großer Basis greifen die Deckel 9 × HBG
(Z 1) und n × 3 × HBG (Z 2)."""
emp = self._employee()
slip = self._slip(emp)
slip.write({
'l10n_at_abf_frei_basis_12': 36000.0,
'l10n_at_abf_frei_dienstzeit_monate': 180,
'l10n_at_abf_frei_erhaltene_abfertigungen': 4000.0,
'l10n_at_abf_frei_gesetzlicher_anspruch': 9000.0,
})
self._input(slip, 'ATP_ABF_FREI', 20000.0)
split = slip._at_abf_frei_split()
self.assertAlmostEqual(split['kuerzung'], 13000.0, places=2)
self.assertAlmostEqual(split['z2'], 5000.0, places=2)
self.assertAlmostEqual(split['beguenstigt'], 14000.0, places=2)
self.assertAlmostEqual(split['ueberhang'], 6000.0, places=2)
slip.write({
'l10n_at_abf_frei_basis_12': 12000000.0,
'l10n_at_abf_frei_erhaltene_abfertigungen': 0.0,
'l10n_at_abf_frei_gesetzlicher_anspruch': 0.0,
})
slip.input_line_ids.filtered(
lambda i: i.input_type_id.code == 'ATP_ABF_FREI').write(
{'amount': 300000.0})
slip.compute_sheet()
split = slip._at_abf_frei_split()
self.assertAlmostEqual(split['z1'], 62370.0, places=2)
self.assertAlmostEqual(split['z2'], 124740.0, places=2)
self.assertAlmostEqual(split['beguenstigt'], 187110.0, places=2)
self.assertAlmostEqual(split['ueberhang'], 112890.0, places=2)
def test_17_abf_frei_wizard(self):
emp = self._employee()
emp.version_id.write({
'structure_type_id': self.env.ref(
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
'l10n_at_abfertigung_neu': False,
})
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
'employee_id': emp.id,
'austrittsdatum': date(2026, 3, 15),
'departure_reason_id': self.env.ref(
'l10n_at_hr_payroll.departure_reason_einvernehmlich').id,
'abf_frei_aktiv': True,
'abf_frei_anspruch_bestaetigt': True,
'abf_frei_betrag': 20000.0,
'abf_frei_basis_12': 36000.0,
'abf_frei_dienstzeit_monate': 180,
'abf_frei_nachweis': 'Vordienstzeiten zum Lohnkonto nachgewiesen',
})
ergebnis = wizard.action_payslip()
slip = self.env['hr.payslip'].browse(ergebnis['res_id'])
self.assertAlmostEqual(self._total(slip, 'ABF_FREI'), 20000.0,
places=2)
self.assertAlmostEqual(self._total(slip, 'LST_ABF_FREI'), 1200.0,
places=2)
self.assertEqual(slip.l10n_at_abf_frei_dienstzeit_monate, 180)
self.assertEqual(slip.l10n_at_abf_frei_nachweis,
'Vordienstzeiten zum Lohnkonto nachgewiesen')
@@ -209,6 +209,35 @@ class PfaendungPrivateTest(TransactionCase):
self.assertAlmostEqual( self.assertAlmostEqual(
self._total(slip, 'PFAENDUNG_END'), masse, places=2) self._total(slip, 'PFAENDUNG_END'), masse, places=2)
def test_09c_pfaendung_abf_frei_topf(self):
"""AP15-B: begünstigter Teil der freiwilligen Abfertigung läuft
in den §-291d-Topf, abzüglich beider Abfertigungs-LSt-Arten; der
Überhang bleibt über die laufende Bemessung pfändbar."""
employee = self._employee(datetime.date(2020, 1, 1), unterhalt=0)
self._pfaendung(employee, datetime.date(2026, 2, 1))
slip = self._slip(employee, 3)
slip.write({
'l10n_at_abf_frei_basis_12': 36000.0,
'l10n_at_abf_frei_dienstzeit_monate': 180,
})
input_type = self.env.ref(
'l10n_at_hr_payroll_private.input_type_atp_abf_frei')
self.env['hr.payslip.input'].create({
'payslip_id': slip.id,
'input_type_id': input_type.id,
'amount': 40000.0,
})
slip.compute_sheet()
lst6 = self.env.company.currency_id.round(27000.0 * 0.06)
self.assertAlmostEqual(
self._total(slip, 'ABF_FREI'), 27000.0, places=2)
self.assertAlmostEqual(
self._total(slip, 'ABF_FREI_TARIF'), 13000.0, places=2)
self.assertAlmostEqual(
slip._at_pfaendung_basis(None, 'beendigung'),
27000.0 - lst6, places=2)
def test_10_rang(self): def test_10_rang(self):
"""Zwei Pfändungen → Rang 1 (früheres Einlangen) und Rang 2.""" """Zwei Pfändungen → Rang 1 (früheres Einlangen) und Rang 2."""
employee = self._employee(datetime.date(2026, 1, 1)) employee = self._employee(datetime.date(2026, 1, 1))
@@ -44,6 +44,27 @@
<field name="abf_alt_netto_vorschau"/> <field name="abf_alt_netto_vorschau"/>
</group> </group>
</group> </group>
<group string="Freiwillige Abfertigung (§ 67 Abs 6 EStG)">
<field name="abf_frei_aktiv"/>
<group invisible="not abf_frei_aktiv">
<field name="abf_frei_betrag"/>
<field name="abf_frei_basis_12"/>
<field name="abf_frei_dienstzeit_monate"/>
<field name="abf_frei_erhaltene_abfertigungen"/>
<field name="abf_frei_gesetzlicher_anspruch"/>
<field name="abf_frei_nachweis" placeholder="Vordienstzeiten zum Lohnkonto (§ 76 EStG), erhaltene Abfertigungen, keine Anspruchsumwandlung"/>
<field name="abf_frei_anspruch_bestaetigt"/>
</group>
<group invisible="not abf_frei_aktiv">
<field name="abf_frei_z1_vorschau"/>
<field name="abf_frei_z2_vorschau"/>
<field name="abf_frei_kuerzung_vorschau"/>
<field name="abf_frei_beguenstigt_vorschau"/>
<field name="abf_frei_ueberhang_vorschau"/>
<field name="abf_frei_lst_vorschau"/>
<field name="abf_frei_netto_vorschau"/>
</group>
</group>
<group string="Vorschau (§ 10 UrlG, § 11 Abs 2 ASVG)"> <group string="Vorschau (§ 10 UrlG, § 11 Abs 2 ASVG)">
<group> <group>
<field name="uel_lfd_vorschau"/> <field name="uel_lfd_vorschau"/>
@@ -90,6 +111,30 @@
</field> </field>
</record> </record>
<record id="hr_payslip_view_form_abf_frei" model="ir.ui.view">
<field name="name">hr.payslip.form.abfertigung.frei</field>
<field name="model">hr.payslip</field>
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook" position="inside">
<page string="Freiwillige Abfertigung" name="l10n_at_abfertigung_frei"
invisible="not l10n_at_abf_frei_basis_12 and not l10n_at_abf_frei_dienstzeit_monate and not l10n_at_abf_frei_erhaltene_abfertigungen and not l10n_at_abf_frei_gesetzlicher_anspruch">
<group>
<group>
<field name="l10n_at_abf_frei_basis_12" readonly="state != 'draft'"/>
<field name="l10n_at_abf_frei_dienstzeit_monate" readonly="state != 'draft'"/>
<field name="l10n_at_abf_frei_erhaltene_abfertigungen" readonly="state != 'draft'"/>
</group>
<group>
<field name="l10n_at_abf_frei_gesetzlicher_anspruch" readonly="state != 'draft'"/>
<field name="l10n_at_abf_frei_nachweis" readonly="state != 'draft'"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
<record id="action_endabrechnung_wizard" model="ir.actions.act_window"> <record id="action_endabrechnung_wizard" model="ir.actions.act_window">
<field name="name">Endabrechnung (Beendigung)</field> <field name="name">Endabrechnung (Beendigung)</field>
<field name="res_model">l10n.at.payroll.endabrechnung.wizard</field> <field name="res_model">l10n.at.payroll.endabrechnung.wizard</field>
@@ -101,6 +101,43 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
string='Nachweis/Begründung', string='Nachweis/Begründung',
help='Quelle der Dienstzeiten, Anspruchsprüfung, Alt-/Neu-Status ' help='Quelle der Dienstzeiten, Anspruchsprüfung, Alt-/Neu-Status '
'und allfällige manuelle Abweichung dokumentieren.') 'und allfällige manuelle Abweichung dokumentieren.')
abf_frei_aktiv = fields.Boolean(
string='Freiwillige Abfertigung (§ 67 Abs 6)')
abf_frei_anspruch_bestaetigt = fields.Boolean(
string='Prüfung und Nachweise bestätigt',
help='Bestätigung, dass es sich um eine Beendigungsleistung ohne '
'schädliche Anspruchsumwandlung handelt (KE/UE/SZ/Zeitguthaben '
'müssen getrennt abrechnet werden — GPLB-Risiko, lb-end-15), '
'dass die (Vor-)Dienstzeiten lückenlos zum Lohnkonto '
'(§ 76 EStG) nachgewiesen sind und erhaltene Abfertigungen '
'angegeben wurden.')
abf_frei_betrag = fields.Monetary(
string='Bruttobetrag', currency_field='currency_id')
abf_frei_basis_12 = fields.Monetary(
string='Laufende Bezüge letzte 12 Monate',
currency_field='currency_id',
help='Basis der Viertel-/Zwölftelregelung: alle steuerbaren '
'Bezugsteile (auch steuerfreie Zuschläge, Sachbezüge), '
'ohne Sonderzahlungen und ohne §-26-Z-4-Reisevergütungen; '
'wird aus den letzten zwölf validierten Läufen vorgeschlagen '
'und ist bestätigbar (lb-end-13).')
abf_frei_dienstzeit_monate = fields.Integer(
string='Nachgewiesene Dienstzeit (Monate)')
abf_frei_erhaltene_abfertigungen = fields.Monetary(
string='Bereits erhaltene Abfertigungen', currency_field='currency_id',
help='In den nachgewiesenen Dienstzeiten erhaltene Abfertigungen '
'(§ 67 Abs 3 oder Abs 6 EStG) — Kürzung nach Z 3.')
abf_frei_gesetzlicher_anspruch = fields.Monetary(
string='Bestehender gesetzlicher Anspruch',
currency_field='currency_id',
help='Anspruch auf eine gesetzliche Abfertigung am aktuellen '
'Dienstverhältnis-Ende (Z 3); bei aktiver Alt-Abfertigung '
'automatisch aus dem Alt-Anspruch vorgeschlagen.')
abf_frei_nachweis = fields.Text(
string='Nachweis/Begründung',
help='Vordienstzeiten-Nachweis (Lohnkonto § 76 EStG), erhaltene '
'Abfertigungen, Basis-Abweichungen und Anspruchsgrundlage '
'dokumentieren.')
currency_id = fields.Many2one( currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id) 'res.currency', default=lambda self: self.env.company.currency_id)
@@ -128,6 +165,27 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
abf_alt_netto_vorschau = fields.Monetary( abf_alt_netto_vorschau = fields.Monetary(
string='Abfertigung Alt netto', currency_field='currency_id', string='Abfertigung Alt netto', currency_field='currency_id',
compute='_compute_vorschau') compute='_compute_vorschau')
abf_frei_z1_vorschau = fields.Monetary(
string='Z 1 Viertelregelung (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
abf_frei_z2_vorschau = fields.Monetary(
string='Z 2 Zwölftelregelung (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
abf_frei_kuerzung_vorschau = fields.Monetary(
string='Z 3 Kürzung (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
abf_frei_beguenstigt_vorschau = fields.Monetary(
string='Begünstigter Teil 6 % (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
abf_frei_ueberhang_vorschau = fields.Monetary(
string='Überhang zum Tarif (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
abf_frei_lst_vorschau = fields.Monetary(
string='LSt 6 % (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
abf_frei_netto_vorschau = fields.Monetary(
string='Begünstigter Teil netto (Vorschau)',
currency_field='currency_id', compute='_compute_vorschau')
@api.onchange('employee_id') @api.onchange('employee_id')
def _onchange_employee(self): def _onchange_employee(self):
@@ -139,12 +197,16 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
version.contract_date_end or fields.Date.today()) version.contract_date_end or fields.Date.today())
wizard.departure_reason_id = version.departure_reason_id wizard.departure_reason_id = version.departure_reason_id
wizard._at_abf_alt_vorschlag_setzen() wizard._at_abf_alt_vorschlag_setzen()
wizard._at_abf_frei_vorschlag_setzen()
@api.onchange('austrittsdatum', 'abf_alt_anspruchsart', @api.onchange('austrittsdatum', 'abf_alt_anspruchsart',
'abf_alt_dienstzeit_monate') 'abf_alt_dienstzeit_monate', 'abf_alt_aktiv',
'abf_alt_monatsentgelt', 'abf_alt_mehrfach',
'abf_frei_aktiv', 'abf_frei_dienstzeit_monate')
def _onchange_abf_alt(self): def _onchange_abf_alt(self):
for wizard in self: for wizard in self:
wizard._at_abf_alt_vorschlag_setzen() wizard._at_abf_alt_vorschlag_setzen()
wizard._at_abf_frei_vorschlag_setzen()
def _at_abf_alt_vorschlag_setzen(self): def _at_abf_alt_vorschlag_setzen(self):
self.ensure_one() self.ensure_one()
@@ -168,11 +230,27 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
if vorgeschlagen or not self.abf_alt_mehrfach: if vorgeschlagen or not self.abf_alt_mehrfach:
self.abf_alt_mehrfach = vorgeschlagen self.abf_alt_mehrfach = vorgeschlagen
def _at_abf_frei_vorschlag_setzen(self):
self.ensure_one()
if not self.employee_id or not self.austrittsdatum:
return
slip = self._vorschau_payslip()
if self.abf_frei_aktiv and not self.abf_frei_basis_12:
self.abf_frei_basis_12 = slip._at_bezuege_12_monate()
if (self.abf_frei_aktiv and self.abf_alt_aktiv
and not self.abf_frei_gesetzlicher_anspruch):
self.abf_frei_gesetzlicher_anspruch = (
self.abf_alt_monatsentgelt * self.abf_alt_mehrfach)
@api.depends('employee_id', 'austrittsdatum', 'uel_tage', @api.depends('employee_id', 'austrittsdatum', 'uel_tage',
'abf_alt_aktiv', 'abf_alt_monatsentgelt', 'abf_alt_aktiv', 'abf_alt_monatsentgelt',
'abf_alt_laufender_arbeitslohn', 'abf_alt_monatslohnsteuer', 'abf_alt_laufender_arbeitslohn', 'abf_alt_monatslohnsteuer',
'abf_alt_mehrfach', 'abf_alt_mehrfach',
'abf_alt_dienstzeit_monate', 'abf_alt_anspruchsart') 'abf_alt_dienstzeit_monate', 'abf_alt_anspruchsart',
'abf_frei_aktiv', 'abf_frei_betrag', 'abf_frei_basis_12',
'abf_frei_dienstzeit_monate',
'abf_frei_erhaltene_abfertigungen',
'abf_frei_gesetzlicher_anspruch')
def _compute_vorschau(self): def _compute_vorschau(self):
for wizard in self: for wizard in self:
wizard.uel_lfd_vorschau = 0.0 wizard.uel_lfd_vorschau = 0.0
@@ -183,6 +261,13 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
wizard.abf_alt_steuer_6_vorschau = 0.0 wizard.abf_alt_steuer_6_vorschau = 0.0
wizard.abf_alt_lohnsteuer_vorschau = 0.0 wizard.abf_alt_lohnsteuer_vorschau = 0.0
wizard.abf_alt_netto_vorschau = 0.0 wizard.abf_alt_netto_vorschau = 0.0
wizard.abf_frei_z1_vorschau = 0.0
wizard.abf_frei_z2_vorschau = 0.0
wizard.abf_frei_kuerzung_vorschau = 0.0
wizard.abf_frei_beguenstigt_vorschau = 0.0
wizard.abf_frei_ueberhang_vorschau = 0.0
wizard.abf_frei_lst_vorschau = 0.0
wizard.abf_frei_netto_vorschau = 0.0
if not wizard.employee_id or not wizard.austrittsdatum: if not wizard.employee_id or not wizard.austrittsdatum:
continue continue
slip = wizard._vorschau_payslip() slip = wizard._vorschau_payslip()
@@ -202,6 +287,19 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
wizard.abf_alt_steuer_6_vorschau = steuer_6 wizard.abf_alt_steuer_6_vorschau = steuer_6
wizard.abf_alt_lohnsteuer_vorschau = steuer wizard.abf_alt_lohnsteuer_vorschau = steuer
wizard.abf_alt_netto_vorschau = betrag - steuer wizard.abf_alt_netto_vorschau = betrag - steuer
if wizard.abf_frei_aktiv:
split = slip._at_abf_frei_split()
satz = float(slip._l10n_at_param(
'at_endabrechnung')['abfertigung_steuersatz'])
wizard.abf_frei_z1_vorschau = split['z1']
wizard.abf_frei_z2_vorschau = split['z2']
wizard.abf_frei_kuerzung_vorschau = split['kuerzung']
wizard.abf_frei_beguenstigt_vorschau = split['beguenstigt']
wizard.abf_frei_ueberhang_vorschau = split['ueberhang']
wizard.abf_frei_lst_vorschau = \
split['beguenstigt'] * satz / 100.0
wizard.abf_frei_netto_vorschau = \
split['beguenstigt'] - wizard.abf_frei_lst_vorschau
def _vorschau_payslip(self): def _vorschau_payslip(self):
"""Flüchtiger Payslip-Kontext für die Vorschau-Berechnung (kein """Flüchtiger Payslip-Kontext für die Vorschau-Berechnung (kein
@@ -229,11 +327,21 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
self.abf_alt_monatslohnsteuer, self.abf_alt_monatslohnsteuer,
'l10n_at_abf_alt_mehrfach': self.abf_alt_mehrfach, 'l10n_at_abf_alt_mehrfach': self.abf_alt_mehrfach,
'l10n_at_abf_alt_nachweis': self.abf_alt_nachweis, 'l10n_at_abf_alt_nachweis': self.abf_alt_nachweis,
'l10n_at_abf_frei_basis_12': self.abf_frei_basis_12,
'l10n_at_abf_frei_dienstzeit_monate':
self.abf_frei_dienstzeit_monate,
'l10n_at_abf_frei_erhaltene_abfertigungen':
self.abf_frei_erhaltene_abfertigungen,
'l10n_at_abf_frei_gesetzlicher_anspruch':
self.abf_frei_gesetzlicher_anspruch,
'l10n_at_abf_frei_nachweis': self.abf_frei_nachweis,
}) })
eingaben = [ eingaben = [
('ATP_UEL_TAGE', self.uel_tage), ('ATP_UEL_TAGE', self.uel_tage),
('ATP_ABF_ALT', self.abf_alt_monatsentgelt ('ATP_ABF_ALT', self.abf_alt_monatsentgelt
* self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0), * self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0),
('ATP_ABF_FREI', self.abf_frei_betrag
if self.abf_frei_aktiv else 0.0),
] ]
slip.input_line_ids = [ slip.input_line_ids = [
(0, 0, { (0, 0, {
@@ -256,6 +364,8 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
('ATP_ABRECHNUNG_VORSCHUSS', self.vorschuss_saldo), ('ATP_ABRECHNUNG_VORSCHUSS', self.vorschuss_saldo),
('ATP_ABF_ALT', self.abf_alt_monatsentgelt ('ATP_ABF_ALT', self.abf_alt_monatsentgelt
* self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0), * self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0),
('ATP_ABF_FREI', self.abf_frei_betrag
if self.abf_frei_aktiv else 0.0),
] ]
def _abf_alt_validieren(self): def _abf_alt_validieren(self):
@@ -299,11 +409,30 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
'Entlassung. Bitte den Anspruch manuell bestätigen und ' 'Entlassung. Bitte den Anspruch manuell bestätigen und '
'begründen.')) 'begründen.'))
def _abf_frei_validieren(self):
self.ensure_one()
if not self.abf_frei_aktiv:
return
if not self.abf_frei_anspruch_bestaetigt:
raise UserError(_('Bitte Prüfung und Nachweise der freiwilligen '
'Abfertigung bestätigen.'))
if self.abf_frei_betrag <= 0:
raise UserError(_('Der Bruttobetrag der freiwilligen '
'Abfertigung muss größer als null sein.'))
if (self.abf_frei_basis_12 < 0
or self.abf_frei_erhaltene_abfertigungen < 0
or self.abf_frei_gesetzlicher_anspruch < 0
or self.abf_frei_dienstzeit_monate < 0):
raise UserError(_('12-Monats-Basis, erhaltene Abfertigungen, '
'gesetzlicher Anspruch und Dienstzeit dürfen '
'nicht negativ sein.'))
def action_payslip(self): def action_payslip(self):
"""Endabrechnungs-Lauf im Austrittsmonat anlegen und die """Endabrechnungs-Lauf im Austrittsmonat anlegen und die
gesammelten offenen Posten als Eingaben vorbefüllen.""" gesammelten offenen Posten als Eingaben vorbefüllen."""
self.ensure_one() self.ensure_one()
self._abf_alt_validieren() self._abf_alt_validieren()
self._abf_frei_validieren()
erster = self.austrittsdatum.replace(day=1) erster = self.austrittsdatum.replace(day=1)
letzter = (erster + timedelta(days=32)).replace(day=1) \ letzter = (erster + timedelta(days=32)).replace(day=1) \
- timedelta(days=1) - timedelta(days=1)
@@ -335,6 +464,18 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0, self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0,
'l10n_at_abf_alt_nachweis': 'l10n_at_abf_alt_nachweis':
self.abf_alt_nachweis if self.abf_alt_aktiv else False, self.abf_alt_nachweis if self.abf_alt_aktiv else False,
'l10n_at_abf_frei_basis_12':
self.abf_frei_basis_12 if self.abf_frei_aktiv else 0.0,
'l10n_at_abf_frei_dienstzeit_monate':
self.abf_frei_dienstzeit_monate if self.abf_frei_aktiv else 0,
'l10n_at_abf_frei_erhaltene_abfertigungen':
self.abf_frei_erhaltene_abfertigungen
if self.abf_frei_aktiv else 0.0,
'l10n_at_abf_frei_gesetzlicher_anspruch':
self.abf_frei_gesetzlicher_anspruch
if self.abf_frei_aktiv else 0.0,
'l10n_at_abf_frei_nachweis':
self.abf_frei_nachweis if self.abf_frei_aktiv else False,
}) })
typen = {code: self.env['hr.payslip.input.type'].search( typen = {code: self.env['hr.payslip.input.type'].search(
[('code', '=', code)], limit=1) [('code', '=', code)], limit=1)