[IMP] l10n_at_hr_payroll_private: add statutory old-system severance (AP15-A)

Pay the pre-2003 AngG severance from the end-of-employment run as its own neutral benefit line: free of SV (§ 49 Abs 3 Z 7 ASVG) and payroll levies, outside the annual sixth, and collected into the § 291d EO termination pot net of its own wage tax. Taxation follows the binding better-of rule of § 67 Abs 3 EStG: multiple-of-method (tariff wage tax on the running monthly wage × the statutory multiple of the payout) versus the flat 6 % rate, applied mandatorily in favour of the employee. The tariff wage tax is captured as a separately confirmed value instead of deriving it from gross pay through the § 62 engine. The wizard enforces § 23a Abs 3 AngG (parental exit needs five uninterrupted years), blocks BMSVG-new contracts except frozen claims and validates AGRD 02/06 against the claim type. The LNK basis subtracts § 67 Abs 3/6 rules only when they actually run in the tax base, the L16 generator reports LST_ABF in key 260 outside 210/220, and result_rules access in the garnishment paths avoids DefaultDictPayroll materialisation. Verified against the original statute texts in .ris/ (AngG §§ 23/23a, EStG § 67 Abs 3).

Version 19.0.13.0.0 (private), 19.0.9.0.0 (core)
This commit is contained in:
2026-09-15 11:01:55 +02:00
parent ee94f4664f
commit 9add58fe83
21 changed files with 753 additions and 41 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
{
'name': 'Österreich - Personalverrechnung Kern (AT Payroll Core)',
'version': '19.0.8.0.0',
'version': '19.0.9.0.0',
'author': 'Florian Egger',
'website': 'https://gem360.at',
'category': 'Human Resources/Payroll',
@@ -400,6 +400,7 @@ class HrPayslip(models.Model):
# ------------------------------------------------------------------
# § 66 Monatslohnsteuer — kumulativ mit Rollung
# ------------------------------------------------------------------
def _l10n_at_lst_laufend(self, result_rules=None):
"""LSt auf laufende Bezüge (§ 66): kumulative Hochrechnung
(360-Tage-Jahr) minus bereits einbehaltene LSt — Vormonats-
@@ -390,7 +390,7 @@ class L10nAtPayrollL16Line(models.Model):
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
KZ 220; 230 = laufende SV-DN + AK + WF; VIEB = 230 + 225 +
226; 260/BIEL = LST_LFD + LST_SZ."""
226; 260/BIEL = LST_LFD + LST_SZ + LST_ABF."""
self.ensure_one()
salden = dict.fromkeys(
('kz210', 'kz215', 'kz220', 'kz225', 'kz226', 'kz230',
@@ -411,7 +411,7 @@ class L10nAtPayrollL16Line(models.Model):
elif line.code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV',
'SVDN_AK', 'SVDN_WF'):
salden['kz230'] += line.total
elif line.code in ('LST_LFD', 'LST_SZ'):
elif line.code in ('LST_LFD', 'LST_SZ', 'LST_ABF'):
salden['lohnsteuer'] += line.total
freibe = slip._l10n_at_freibetraege_68()
salden['kz215'] += freibe['szfn'] + freibe['ue_std']
@@ -5,6 +5,7 @@ Satz-Assertions, CRLF/ISO-8859-15-Encoding, SANR/SANZ-Kette,
KZ-245-Formel-Konsistenz aus GP3-Daten."""
import base64
import datetime
import types
from odoo.tests import TransactionCase, tagged
@@ -213,6 +214,37 @@ class L16GeneratorTest(TransactionCase):
self.assertEqual(schluss[0:2], '99')
self.assertEqual(schluss[20:26], '000004') # SANZ
def test_01b_abfertigung_lohnsteuer_salden(self):
"""Abs.-3-Bezug bleibt außerhalb KZ 210/220; LST_ABF zählt zu 260."""
deklaration = self.env['l10n.at.payroll.l16'].create({
'company_id': self.company.id,
'jahr': 2026,
'art': '01',
})
l16_line = self.env['l10n.at.payroll.l16.line'].create({
'l16_id': deklaration.id,
'employee_id': self.employee.id,
})
abs3_rule = types.SimpleNamespace(
l10n_at_bemessung='sonstig', l10n_at_s67_abs='abs3')
lst_rule = types.SimpleNamespace(
l10n_at_bemessung='neutral', l10n_at_s67_abs='keine')
slip = types.SimpleNamespace(
line_ids=[
types.SimpleNamespace(
salary_rule_id=abs3_rule, code='ABF_ALT', total=12000.0),
types.SimpleNamespace(
salary_rule_id=lst_rule, code='LST_ABF', total=720.0),
],
_l10n_at_freibetraege_68=lambda: {'szfn': 0.0, 'ue_std': 0.0},
)
salden = l16_line._kz_salden([slip])
self.assertEqual(salden['kz210'], 0.0)
self.assertEqual(salden['kz220'], 0.0)
self.assertEqual(salden['lohnsteuer'], 720.0)
def test_02_arbeitsstaette(self):
"""§ 34 Abs 6 ASVG: je AN ein Satz 45 (Länge 300) mit BKNR,
VSNR, STNR und GKZ aus dem Dienstort-Partner; Warnung, wenn
@@ -108,6 +108,7 @@ class LohnsteuerTest(TransactionCase):
slip._l10n_at_absetzbetraege(20000.0),
496.0 + 828.0 + 273.0, places=2)
# ------------------------------------------------------------------
# Lohnsteuertage (user decision: 30 je vollem Monat)
# ------------------------------------------------------------------
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
{
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
'version': '19.0.12.0.0',
'version': '19.0.13.0.0',
'author': 'Florian Egger',
'website': 'https://gem360.at',
'category': 'Human Resources/Payroll',
@@ -117,7 +117,6 @@ gem360-Brücke).
'data/salary_rules_endabrechnung_private.xml',
'data/salary_rules_sv_private.xml',
'data/salary_rules_bvg_private.xml',
'data/salary_rules_bvg_private.xml',
'data/hr_payroll_account_private.xml',
'data/rule_parameters_mbgm_private.xml',
'data/rule_parameters_wien_private.xml',
@@ -31,4 +31,8 @@
<field name="name">Aufrechnung offener Vorschuss (Netto)</field>
<field name="code">ATP_ABRECHNUNG_VORSCHUSS</field>
</record>
<record id="input_type_atp_abf_alt" model="hr.payslip.input.type">
<field name="name">Gesetzliche Abfertigung Alt (Brutto)</field>
<field name="code">ATP_ABF_ALT</field>
</record>
</odoo>
@@ -12,6 +12,6 @@
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>
<field name="parameter_value">{'divisor_werktage': 26.0, 'divisor_arbeitstage': 22.0, 'abfertigung_alt_staffel': [[36, 2.0], [60, 3.0], [120, 4.0], [180, 6.0], [240, 9.0], [300, 12.0]], 'abfertigung_steuersatz': 6.0}</field>
</record>
</odoo>
@@ -72,6 +72,25 @@ result = inputs['ATP_ABGANG'].amount
]]></field>
</record>
<!-- Gesetzliche Abfertigung Alt (§§ 23/23a AngG, § 67 Abs 3 EStG). -->
<record id="rule_abf_alt" model="hr.salary.rule">
<field name="name">Gesetzliche Abfertigung Alt (§ 67 Abs 3)</field>
<field name="code">ABF_ALT</field>
<field name="sequence" eval="30"/>
<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_ALT' in inputs and inputs['ATP_ABF_ALT'].amount</field>
<field name="l10n_at_bemessung">neutral</field>
<field name="l10n_at_sv_art">keine</field>
<field name="l10n_at_s67_abs">abs3</field>
<field name="l10n_at_pfandung_beendigung" eval="True"/>
<field name="amount_select">code</field>
<field name="amount_python_compute"><![CDATA[
result = inputs['ATP_ABF_ALT'].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>
@@ -104,4 +104,19 @@ result = payslip._l10n_at_lst_laufend(result_rules)
result = payslip._l10n_at_lst_sonstige(result_rules)
]]></field>
</record>
<record id="rule_lst_abf" model="hr.salary.rule">
<field name="name">Lohnsteuer Abfertigung Alt (§ 67 Abs 3)</field>
<field name="code">LST_ABF</field>
<field name="sequence" eval="72"/>
<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_alt_lohnsteuer(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_alt_lohnsteuer(result_rules)
]]></field>
</record>
</odoo>
@@ -56,4 +56,23 @@ result = (payslip._at_pfaendung_masse(result_rules, 'sz13')
+ payslip._at_pfaendung_masse(result_rules, 'sz14'))
]]></field>
</record>
<record id="rule_pfaendung_end" model="hr.salary.rule">
<field name="name">Lohnpfändung — Beendigungsansprüche (§ 291d EO)</field>
<field name="code">PFAENDUNG_END</field>
<field name="sequence" eval="77"/>
<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"><![CDATA[
result = bool(payslip.employee_id._at_pfaendungen_aktiv(payslip.date_to)) \
and payslip._at_pfaendung_masse(result_rules, 'beendigung') > 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_pfaendung_masse(result_rules, 'beendigung')
]]></field>
</record>
</odoo>
@@ -1,4 +1,4 @@
# Endabrechnungs-Zyklus — Rechenfälle AP14-A
# Endabrechnungs-Zyklus — Rechenfälle AP14-A / AP15-A
Stand: 2026-09. AP14-A bildet die Beendigungs-Abrechnung auf der
privaten Structure ab: Urlaubsersatzleistung (UEL), Abgangsentschädigung,
@@ -80,12 +80,54 @@ Netto; LSt-, SV- und LNK-Basen unverändert.
10 h Normalarbeitszeit-Guthaben × 1,5 + 5 h Zeitschulden × (1):
beides laufender Bezug, kein §-68-Freibetrag.
## Offene Punkte (dokumentiert, AP14-B/AP15)
## RF-ABF-1 — Abfertigung Alt, Vervielfachung günstiger
21 Dienstjahre ergeben nach § 23 Abs 1 AngG 9 Monatsentgelte. Bei
1.800 € laufendem Monatsarbeitslohn und 2.100 € arbeitsrechtlicher
Bemessungsgrundlage einschließlich 2/12 Sonderzahlungen:
- Bruttoabfertigung: 2.100 × 9 = **18.900 €**;
- steuerlicher Vervielfacher: 18.900 / 1.800 = **10,5** — nicht 9;
- auf den maßgeblichen laufenden Bezug entfallende Tarif-LSt laut
Ausgangsfall: **101,96 €**;
- Vervielfachungssteuer: 101,96 × 10,5 = **1.070,58 €**;
- Vergleich mit 18.900 × 6 % = **1.134 €**;
- zwingend wird der niedrigere Steuerbetrag angesetzt.
`ABF_ALT` ist SV-, BV-, DB-, DZ- und KommSt-frei, verbraucht kein
Jahressechstel und läuft als Beendigungsanspruch in den §-291d-EO-Topf.
## RF-ABF-2 — 6 % günstiger
Bei hohem laufendem Monatsarbeitslohn kann die vervielfachte Tarif-LSt
den Fixsatz überschreiten. Für 9.333,34 € Abfertigung wird dann zwingend
**560,00 €** Lohnsteuer (6 %) angesetzt.
## RF-ABF-3 — Staffel und Sonderfälle
- 3/5/10/15/20/25 Jahre → 2/3/4/6/9/12 Monatsentgelte;
- Elternaustritt nach § 23a Abs 3/4: erst nach mindestens fünf Jahren,
dann Hälfte, maximal 3 Monatsentgelte;
- Tod nach § 23 Abs 6: Hälfte des normalen Anspruchs;
- normale Arbeitnehmerkündigung, unberechtigter Austritt und
verschuldete Entlassung begründen grundsätzlich keinen Anspruch;
- Abfertigung-Neu-Verträge sind gesperrt, außer ein eingefrorener
Altanspruch wird ausdrücklich nachgewiesen.
Der Wizard schlägt Dienstzeit, Faktor und eine Basis aus laufendem Entgelt
plus aliquoten Sonderzahlungen vor. Die auf den maßgeblichen laufenden
Monatsarbeitslohn entfallende Tarif-LSt wird getrennt bestätigt, weil sie
von der §-62-Bemessung und den persönlichen Absetzbeträgen abhängt und nicht
mit dem Bruttolohn gleichgesetzt werden darf. Wegen regelmäßiger Zulagen,
Durchschnittsbezügen, Lehrzeiten, Teilübertritten und §-23a-Sonderfällen
müssen Anspruch, Basis und Nachweis vor Anlage des Laufs bestätigt werden.
Die geprüften Angaben werden am Payslip gespeichert.
## Offene Punkte (AP15-B/AP15-C)
- **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).
Abfindung (§ 67 Abs 6 6 %-Bänder)** → AP15-B/C mit rollierender
12-Monats-Basis, Dienstzeit-Nachweisen und Alt-/Neu-Aufteilung.
- Prämien-Beendigung (§ 16 AngG-Aliquotierung, Rollung) — PRAM/SZ-Regeln
vorhanden; Feinschliff AP14-B.
- Meldewesen-Schnittstellen: „Ende des Entgeltanspruches" in mBGM/
@@ -68,6 +68,11 @@ L10N_AT_BUCHUNGSMATRIX = {
'rule_svdn_sz': {'credit': ('3600',)},
'rule_lst_lfd': {'credit': ('3540',)},
'rule_lst_sz': {'credit': ('3540',)},
'rule_lst_abf': {'credit': ('3540',)},
# Gesetzliche Abfertigung Alt: bis zur Referenzbuchhaltungs-
# Fixierung allgemeiner Gehalts-/Lohnaufwand, company-dependent
# Regelkonto bleibt übersteuerbar.
'rule_abf_alt': {'debit': ('6200',), 'debit_arbeiter': ('6000',)},
# Nettoauszahlung (reconcilable, s. o.)
'rule_netto': {'credit': ('3850',)},
# DG-Beiträge (650655 SV Arbeiter · 656659 SV Angestellte)
@@ -56,6 +56,34 @@ class HrSalaryRule(models.Model):
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
l10n_at_abf_alt_anspruchsart = fields.Selection(
[('normal', 'Normalanspruch (§ 23 AngG)'),
('eltern', 'Elternaustritt (§ 23a Abs 3/4 AngG)'),
('tod', 'Tod (§ 23 Abs 6 AngG)'),
('pension', 'Pension/Invalidität (§ 23a Abs 1 AngG)'),
('eingefroren', 'Eingefrorener Altanspruch (§ 47 BMSVG)'),
('manuell', 'Sonstiger nachgewiesener Anspruch')],
string='AT Abfertigung Alt — Anspruchsart', copy=False)
l10n_at_abf_alt_dienstzeit_monate = fields.Integer(
string='AT Abfertigung Alt — Dienstzeitmonate', copy=False)
l10n_at_abf_alt_monatsentgelt = fields.Float(
string='AT Abfertigung Alt — Monatsentgelt', copy=False,
help='Bestätigte arbeitsrechtliche Bemessungsgrundlage inklusive '
'regelmäßiger Bezugsbestandteile und aliquoter Sonderzahlungen.')
l10n_at_abf_alt_laufender_arbeitslohn = fields.Float(
string='AT Abfertigung Alt — laufender Arbeitslohn', copy=False,
help='Laufender Monatsarbeitslohn für den steuerlichen '
'Vervielfacher nach § 67 Abs 3 EStG.')
l10n_at_abf_alt_monatslohnsteuer = fields.Float(
string='AT Abfertigung Alt — tarifmäßige Monatslohnsteuer',
copy=False,
help='Auf den maßgeblichen laufenden Monatsarbeitslohn entfallende '
'tarifmäßige Lohnsteuer nach den persönlichen Verhältnissen.')
l10n_at_abf_alt_mehrfach = fields.Float(
string='AT Abfertigung Alt — Anspruchsfaktor', copy=False)
l10n_at_abf_alt_nachweis = fields.Text(
string='AT Abfertigung Alt — Nachweis/Begründung', copy=False)
# ------------------------------------------------------------------
# SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2 ASVG)
# ------------------------------------------------------------------
@@ -135,6 +163,75 @@ class HrPayslip(models.Model):
lambda i: i.input_type_id.code == code)
return linie[:1].amount or 0.0
# ------------------------------------------------------------------
# Abfertigung Alt — §§ 23/23a AngG, § 67 Abs 3 EStG
# ------------------------------------------------------------------
def _at_abf_alt_staffel(self, dienstzeit_monate=None):
"""Arbeitsrechtlicher Grundfaktor nach § 23 Abs 1 AngG."""
self.ensure_one()
monate = dienstzeit_monate
if monate is None:
monate = self.l10n_at_abf_alt_dienstzeit_monate
staffel = self._l10n_at_param('at_endabrechnung').get(
'abfertigung_alt_staffel', [])
mehrfach = 0.0
for grenze, faktor in staffel:
if monate >= int(grenze):
mehrfach = float(faktor)
return mehrfach
def _at_abf_alt_mehrfach_vorschlag(self, dienstzeit_monate=None,
anspruchsart=None):
"""§-23-Faktor mit Halb-/Deckelregeln für Elternaustritt/Tod."""
self.ensure_one()
art = anspruchsart or self.l10n_at_abf_alt_anspruchsart
monate = dienstzeit_monate
if monate is None:
monate = self.l10n_at_abf_alt_dienstzeit_monate
mehrfach = self._at_abf_alt_staffel(monate)
if art == 'eltern':
if monate < 60:
return 0.0
return min(mehrfach / 2.0, 3.0) if mehrfach else 0.0
if art == 'tod':
return mehrfach / 2.0
return mehrfach
def _at_abf_alt_betrag(self, result_rules=None):
"""Gesetzliche Alt-Abfertigung als eigene Bezugszeile."""
self.ensure_one()
return self._at_input_amount('ATP_ABF_ALT')
def _at_abf_alt_steuer_6(self, result_rules=None):
self.ensure_one()
satz = float(self._l10n_at_param(
'at_endabrechnung')['abfertigung_steuersatz'])
return max(self._at_abf_alt_betrag(result_rules), 0.0) * satz / 100.0
def _at_abf_alt_steuer_vervielfachung(self, result_rules=None):
"""§ 67 Abs 3: Monats-Tarif-LSt × steuerlicher Vervielfacher.
Der steuerliche Vervielfacher ist Abfertigungsbetrag dividiert
durch den laufenden Arbeitslohn. Er kann vom arbeitsrechtlichen
Staffel-Faktor abweichen, weil die Abfertigungsbasis etwa aliquote
Sonderzahlungen enthält (lb-end-01, RF 21 Dienstjahre: 10,5 statt 9).
"""
self.ensure_one()
betrag = self._at_abf_alt_betrag(result_rules)
laufend = self.l10n_at_abf_alt_laufender_arbeitslohn
if betrag <= 0 or laufend <= 0:
return self._at_abf_alt_steuer_6(result_rules)
vervielfacher = betrag / laufend
return self.l10n_at_abf_alt_monatslohnsteuer * vervielfacher
def _at_abf_alt_lohnsteuer(self, result_rules=None):
"""Zwingend günstigere Steuer aus Vervielfachung und 6 %."""
self.ensure_one()
if self._at_abf_alt_betrag(result_rules) <= 0:
return 0.0
return min(self._at_abf_alt_steuer_vervielfachung(result_rules),
self._at_abf_alt_steuer_6(result_rules))
# ------------------------------------------------------------------
# Urlaubsersatzleistung — § 67 Abs 8 lit d, lb-url-15/16
# ------------------------------------------------------------------
@@ -238,7 +335,8 @@ class HrPayslip(models.Model):
+ 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 (rule.l10n_at_bemessung in ('laufend', 'sonstig')
and 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:
@@ -351,7 +351,8 @@ class HrPayslip(models.Model):
+ 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 (rule.l10n_at_bemessung in ('laufend', 'sonstig')
and 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:
@@ -322,7 +322,7 @@ class HrPayslip(models.Model):
# ------------------------------------------------------------------
def _at_pfa_rule_amount(self, result_rules, code):
if result_rules is not None:
werte = result_rules.get(code)
werte = result_rules[code] if code in result_rules else None
# result_rules enthält ungerundete Rule-Summen; die
# Bezugsart ist der gerundete Zeilenbetrag (Cents) —
# mit diesem wird auch die Pfändungsberechnungsgrundlage
@@ -378,14 +378,16 @@ class HrPayslip(models.Model):
if not rule.l10n_at_pfandung_beendigung:
continue
if result_rules is not None:
werte = result_rules.get(rule.code)
werte = result_rules[rule.code] \
if rule.code in result_rules else None
if werte:
total += float(werte['total'])
else:
linie = self.line_ids.filtered(
lambda l, c=rule.code: l.code == c)
total += sum(linie.mapped('total'))
return round(max(0.0, total), 2)
steuer = self._at_pfa_rule_amount(result_rules, 'LST_ABF')
return round(max(0.0, total - steuer), 2)
raise UserError('Unbekannter Pfändungstopf: %s' % topf)
# ------------------------------------------------------------------
@@ -420,6 +422,7 @@ class HrPayslip(models.Model):
total = self._at_pfaendung_masse(result_rules, 'laufend')
total += self._at_pfaendung_masse(result_rules, 'sz13')
total += self._at_pfaendung_masse(result_rules, 'sz14')
total += self._at_pfaendung_masse(result_rules, 'beendigung')
return round(total, 2)
def _at_pfaendung_kostenersatz(self, result_rules):
@@ -5,6 +5,7 @@ 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.exceptions import UserError
from odoo.tests import TransactionCase, tagged
@@ -278,7 +279,8 @@ class EndabrechnungPrivateTest(TransactionCase):
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
'employee_id': emp.id,
'austrittsdatum': date(2026, 3, 15),
'beendigungsart': 'einvernehmlich',
'departure_reason_id': self.env.ref(
'l10n_at_hr_payroll.departure_reason_einvernehmlich').id,
'uel_tage': 11.0,
'abgang_betrag': 9000.0,
'abgang_ffrist_tage': 42.0,
@@ -295,4 +297,185 @@ class EndabrechnungPrivateTest(TransactionCase):
self.assertAlmostEqual(
self._total(slip, 'URL_ERSATZ_LFD'), 1320.0, places=2)
self.assertAlmostEqual(self._total(slip, 'ABGANG'), 9000.0,
places=2)
places=2)
# ------------------------------------------------------------------
# AP15-A: gesetzliche Abfertigung Alt (§§ 23/23a, § 67 Abs 3)
# ------------------------------------------------------------------
def test_07_abfertigung_alt_staffel_und_sonderfaelle(self):
slip = self._slip(self._employee())
for monate, erwartet in (
(35, 0.0), (36, 2.0), (60, 3.0), (120, 4.0),
(180, 6.0), (240, 9.0), (300, 12.0)):
self.assertEqual(slip._at_abf_alt_staffel(monate), erwartet)
self.assertEqual(
slip._at_abf_alt_mehrfach_vorschlag(59, 'eltern'), 0.0)
self.assertEqual(
slip._at_abf_alt_mehrfach_vorschlag(60, 'eltern'), 1.5)
self.assertEqual(
slip._at_abf_alt_mehrfach_vorschlag(180, 'eltern'), 3.0)
self.assertEqual(
slip._at_abf_alt_mehrfach_vorschlag(240, 'tod'), 4.5)
def test_08_abfertigung_alt_vervielfachung_guenstiger(self):
"""21 Jahre: 9 Monatsentgelte inklusive 2/12 SZ ergeben den
steuerlichen Vervielfacher 10,5; der Tarif ist günstiger als 6 %."""
emp_mit = self._employee(ueberzahlung=1800.0)
emp_ohne = self._employee(ueberzahlung=1800.0)
slip_mit = self._slip(emp_mit)
slip_ohne = self._slip(emp_ohne)
slip_mit.write({
'l10n_at_abf_alt_anspruchsart': 'normal',
'l10n_at_abf_alt_dienstzeit_monate': 252,
'l10n_at_abf_alt_monatsentgelt': 2100.0,
'l10n_at_abf_alt_laufender_arbeitslohn': 1800.0,
'l10n_at_abf_alt_monatslohnsteuer': 101.96,
'l10n_at_abf_alt_mehrfach': 9.0,
'l10n_at_abf_alt_nachweis': 'Testnachweis',
})
self._input(slip_mit, 'ATP_ABF_ALT', 18900.0)
erwartet = 101.96 * 10.5
self.assertAlmostEqual(self._total(slip_mit, 'ABF_ALT'), 18900.0,
places=2)
self.assertAlmostEqual(
slip_mit._at_abf_alt_steuer_vervielfachung(), erwartet, places=2)
self.assertLess(erwartet, 18900.0 * 0.06)
self.assertAlmostEqual(
self._total(slip_mit, 'LST_ABF'),
self.env.company.currency_id.round(erwartet), 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._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
0.0, places=2)
self.assertAlmostEqual(
slip_mit._at_pfaendung_basis(None, 'beendigung'),
18900.0 - self._total(slip_mit, 'LST_ABF'), places=2)
def test_09_abfertigung_alt_sechs_prozent_guenstiger(self):
emp = self._employee(ueberzahlung=4000.0)
slip = self._slip(emp)
slip.write({
'l10n_at_abf_alt_laufender_arbeitslohn': 4000.0,
'l10n_at_abf_alt_monatslohnsteuer': 300.0,
'l10n_at_abf_alt_mehrfach': 2.0,
})
self._input(slip, 'ATP_ABF_ALT', 9333.34)
self.assertGreater(
slip._at_abf_alt_steuer_vervielfachung(), 9333.34 * 0.06)
self.assertAlmostEqual(
self._total(slip, 'LST_ABF'), 9333.34 * 0.06, places=2)
def test_10_abfertigung_alt_wizard(self):
emp = self._employee(ueberzahlung=3000.0)
version = emp.version_id
version.write({
'structure_type_id': self.env.ref(
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
'l10n_at_abfertigung_neu': False,
})
reason = self.env.ref(
'l10n_at_hr_payroll.departure_reason_einvernehmlich')
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
'employee_id': emp.id,
'austrittsdatum': date(2026, 3, 15),
'departure_reason_id': reason.id,
'abf_alt_aktiv': True,
'abf_alt_anspruch_bestaetigt': True,
'abf_alt_anspruchsart': 'normal',
'abf_alt_dienstzeit_monate': 74,
'abf_alt_monatsentgelt': 3500.0,
'abf_alt_laufender_arbeitslohn': 3000.0,
'abf_alt_monatslohnsteuer': 200.0,
'abf_alt_mehrfach': 3.0,
'abf_alt_nachweis': 'Dienstzeiten und Altstatus geprüft',
})
ergebnis = wizard.action_payslip()
slip = self.env['hr.payslip'].browse(ergebnis['res_id'])
self.assertAlmostEqual(self._total(slip, 'ABF_ALT'), 10500.0,
places=2)
self.assertGreater(self._total(slip, 'LST_ABF'), 0.0)
self.assertEqual(slip.l10n_at_abf_alt_dienstzeit_monate, 74)
self.assertEqual(slip.l10n_at_abf_alt_nachweis,
'Dienstzeiten und Altstatus geprüft')
self.assertEqual(version.departure_reason_id, reason)
def test_11_abfertigung_alt_neu_sperre(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': True,
})
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_alt_aktiv': True,
'abf_alt_anspruch_bestaetigt': True,
'abf_alt_anspruchsart': 'normal',
'abf_alt_dienstzeit_monate': 74,
'abf_alt_monatsentgelt': 3000.0,
'abf_alt_laufender_arbeitslohn': 2640.0,
'abf_alt_monatslohnsteuer': 150.0,
'abf_alt_mehrfach': 3.0,
})
with self.assertRaises(UserError):
wizard.action_payslip()
def test_12_abfertigung_alt_arbeitnehmerkuendigung_ohne_sonderfall(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('hr.departure_resigned').id,
'abf_alt_aktiv': True,
'abf_alt_anspruch_bestaetigt': True,
'abf_alt_anspruchsart': 'normal',
'abf_alt_dienstzeit_monate': 74,
'abf_alt_monatsentgelt': 3000.0,
'abf_alt_laufender_arbeitslohn': 2640.0,
'abf_alt_monatslohnsteuer': 150.0,
'abf_alt_mehrfach': 3.0,
})
with self.assertRaises(UserError):
wizard.action_payslip()
def test_13_abfertigung_alt_elternaustritt_unter_fuenf_jahren(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('hr.departure_resigned').id,
'abf_alt_aktiv': True,
'abf_alt_anspruch_bestaetigt': True,
'abf_alt_anspruchsart': 'eltern',
'abf_alt_dienstzeit_monate': 59,
'abf_alt_monatsentgelt': 3000.0,
'abf_alt_laufender_arbeitslohn': 2640.0,
'abf_alt_monatslohnsteuer': 150.0,
'abf_alt_mehrfach': 1.0,
})
with self.assertRaises(UserError):
wizard.action_payslip()
@@ -185,6 +185,30 @@ class PfaendungPrivateTest(TransactionCase):
self.assertAlmostEqual(self._total(slip, 'PFAENDUNG'),
pfaendbar, places=2)
def test_09b_pfaendung_regel_beendigung(self):
"""AP15-A: Netto-Abfertigung läuft über den eigenen §-291d-Topf."""
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_alt_laufender_arbeitslohn': 3000.0,
'l10n_at_abf_alt_monatslohnsteuer': 200.0,
'l10n_at_abf_alt_mehrfach': 3.0,
})
input_type = self.env.ref(
'l10n_at_hr_payroll_private.input_type_atp_abf_alt')
self.env['hr.payslip.input'].create({
'payslip_id': slip.id,
'input_type_id': input_type.id,
'amount': 10500.0,
})
slip.compute_sheet()
masse = slip._at_pfaendung_masse(None, 'beendigung')
self.assertGreater(masse, 0.0)
self.assertAlmostEqual(
self._total(slip, 'PFAENDUNG_END'), masse, places=2)
def test_10_rang(self):
"""Zwei Pfändungen → Rang 1 (früheres Einlangen) und Rang 2."""
employee = self._employee(datetime.date(2026, 1, 1))
@@ -12,7 +12,7 @@
<group string="Beendigung">
<field name="employee_id"/>
<field name="austrittsdatum"/>
<field name="beendigungsart"/>
<field name="departure_reason_id" options="{'no_create': True}"/>
</group>
<group string="Offene Posten">
<field name="uel_tage"/>
@@ -24,6 +24,26 @@
<field name="vorschuss_saldo"/>
</group>
</group>
<group string="Abfertigung Alt (§§ 23/23a AngG)">
<field name="abf_alt_aktiv"/>
<group invisible="not abf_alt_aktiv">
<field name="abf_alt_anspruchsart"/>
<field name="abf_alt_dienstzeit_monate"/>
<field name="abf_alt_monatsentgelt"/>
<field name="abf_alt_laufender_arbeitslohn"/>
<field name="abf_alt_monatslohnsteuer"/>
<field name="abf_alt_mehrfach"/>
<field name="abf_alt_nachweis" placeholder="Dienstzeitnachweis, Alt-/Neu-Status und Anspruchsbegründung"/>
<field name="abf_alt_anspruch_bestaetigt"/>
</group>
<group invisible="not abf_alt_aktiv">
<field name="abf_alt_betrag_vorschau"/>
<field name="abf_alt_steuer_vervielfachung_vorschau"/>
<field name="abf_alt_steuer_6_vorschau"/>
<field name="abf_alt_lohnsteuer_vorschau"/>
<field name="abf_alt_netto_vorschau"/>
</group>
</group>
<group string="Vorschau (§ 10 UrlG, § 11 Abs 2 ASVG)">
<group>
<field name="uel_lfd_vorschau"/>
@@ -44,6 +64,32 @@
</field>
</record>
<record id="hr_payslip_view_form_abf_alt" model="ir.ui.view">
<field name="name">hr.payslip.form.abfertigung.alt</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="Abfertigung Alt" name="l10n_at_abfertigung_alt"
invisible="not l10n_at_abf_alt_mehrfach">
<group>
<group>
<field name="l10n_at_abf_alt_anspruchsart" readonly="state != 'draft'"/>
<field name="l10n_at_abf_alt_dienstzeit_monate" readonly="state != 'draft'"/>
<field name="l10n_at_abf_alt_monatsentgelt" readonly="state != 'draft'"/>
</group>
<group>
<field name="l10n_at_abf_alt_laufender_arbeitslohn" readonly="state != 'draft'"/>
<field name="l10n_at_abf_alt_monatslohnsteuer" readonly="state != 'draft'"/>
<field name="l10n_at_abf_alt_mehrfach" readonly="state != 'draft'"/>
<field name="l10n_at_abf_alt_nachweis" readonly="state != 'draft'"/>
</group>
</group>
</page>
</xpath>
</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>
@@ -7,7 +7,10 @@ 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 dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class L10nAtEndabrechnungWizard(models.TransientModel):
@@ -18,20 +21,11 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
'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)
departure_reason_id = fields.Many2one(
'hr.departure.reason', string='Abgangsgrund', required=True,
domain="['|', ('country_id', '=', False), ('country_id.code', '=', 'AT')]",
help='Odoo-Abgangsgrund des Beschäftigtendatensatzes; die '
'Abfertigungsberechtigung wird zusätzlich ausdrücklich bestätigt.')
uel_tage = fields.Float(
string='Offene Urlaubstage (Arbeitstage)',
help='Resturlaub früherer Urlaubsjahre zur Gänze plus aliquoter '
@@ -68,6 +62,45 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
'Vorschusssaldo (lb-end-06) — Rückverrechnung vom Netto '
'nach SV und Lohnsteuer, ohne Abgabenberührung '
'(§ 293 Abs 3 EO).')
abf_alt_aktiv = fields.Boolean(string='Gesetzliche Abfertigung Alt')
abf_alt_anspruch_bestaetigt = fields.Boolean(
string='Anspruch und Nachweise geprüft',
help='Bestätigung, dass Alt-Anwartschaft, Beendigungsgrund, '
'Dienstzeiten und Bemessungsgrundlage geprüft wurden.')
abf_alt_anspruchsart = fields.Selection(
[('normal', 'Normalanspruch (§ 23 AngG)'),
('eltern', 'Elternaustritt (§ 23a Abs 3/4 AngG)'),
('tod', 'Tod (§ 23 Abs 6 AngG)'),
('pension', 'Pension/Invalidität (§ 23a Abs 1 AngG)'),
('eingefroren', 'Eingefrorener Altanspruch (§ 47 BMSVG)'),
('manuell', 'Sonstiger nachgewiesener Anspruch')],
string='Anspruchsart', default='normal')
abf_alt_dienstzeit_monate = fields.Integer(
string='Nachgewiesene Abfertigungsdienstzeit (Monate)')
abf_alt_monatsentgelt = fields.Monetary(
string='Maßgebliches Monatsentgelt', currency_field='currency_id',
help='Bestätigte arbeitsrechtliche Bemessungsgrundlage: letzter '
'Monat einschließlich regelmäßiger Bezugsteile, Sachbezüge '
'und aliquoter Sonderzahlungen.')
abf_alt_laufender_arbeitslohn = fields.Monetary(
string='Laufender Monatsarbeitslohn (§ 67 Abs 3)',
currency_field='currency_id',
help='Laufender Arbeitslohn für den steuerlichen Vervielfacher; '
'aliquote Sonderzahlungen gehören hier nicht dazu.')
abf_alt_monatslohnsteuer = fields.Monetary(
string='Tarif-LSt des laufenden Monats', currency_field='currency_id',
help='Die auf den maßgeblichen laufenden Monatsarbeitslohn '
'entfallende tarifmäßige Lohnsteuer nach den persönlichen '
'Verhältnissen. Bei steuerfreiem Monatslohn ausdrücklich 0 '
'bestätigen.')
abf_alt_mehrfach = fields.Float(
string='Bestätigter Anspruchsfaktor',
help='§-23-Staffel, gegebenenfalls halbiert/gedeckelt oder als '
'eingefrorener beziehungsweise nachgewiesener Faktor.')
abf_alt_nachweis = fields.Text(
string='Nachweis/Begründung',
help='Quelle der Dienstzeiten, Anspruchsprüfung, Alt-/Neu-Status '
'und allfällige manuelle Abweichung dokumentieren.')
currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id)
@@ -80,21 +113,76 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
sv_tage_vorschau = fields.Integer(
string='SV-Verlängerungstage (§ 11 Abs 2)',
compute='_compute_vorschau')
abf_alt_betrag_vorschau = fields.Monetary(
string='Abfertigung Alt brutto', currency_field='currency_id',
compute='_compute_vorschau')
abf_alt_steuer_vervielfachung_vorschau = fields.Monetary(
string='LSt Vervielfachungsmethode', currency_field='currency_id',
compute='_compute_vorschau')
abf_alt_steuer_6_vorschau = fields.Monetary(
string='LSt 6 %', currency_field='currency_id',
compute='_compute_vorschau')
abf_alt_lohnsteuer_vorschau = fields.Monetary(
string='Angewendete günstigere LSt', currency_field='currency_id',
compute='_compute_vorschau')
abf_alt_netto_vorschau = fields.Monetary(
string='Abfertigung Alt netto', currency_field='currency_id',
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())
if not wizard.employee_id:
continue
version = wizard.employee_id.version_id
wizard.austrittsdatum = (
version.contract_date_end or fields.Date.today())
wizard.departure_reason_id = version.departure_reason_id
wizard._at_abf_alt_vorschlag_setzen()
@api.depends('employee_id', 'austrittsdatum', 'uel_tage')
@api.onchange('austrittsdatum', 'abf_alt_anspruchsart',
'abf_alt_dienstzeit_monate')
def _onchange_abf_alt(self):
for wizard in self:
wizard._at_abf_alt_vorschlag_setzen()
def _at_abf_alt_vorschlag_setzen(self):
self.ensure_one()
if not self.employee_id or not self.austrittsdatum:
return
version = self.employee_id.version_id
start = version.contract_date_start
if start and not self.abf_alt_dienstzeit_monate:
dauer = relativedelta(
self.austrittsdatum + timedelta(days=1), start)
self.abf_alt_dienstzeit_monate = dauer.years * 12 + dauer.months
slip = self._vorschau_payslip()
if not self.abf_alt_laufender_arbeitslohn:
self.abf_alt_laufender_arbeitslohn = slip._at_kv_gehalt_basis()
if not self.abf_alt_monatsentgelt:
self.abf_alt_monatsentgelt = (
self.abf_alt_laufender_arbeitslohn
+ 2.0 * slip._at_sz_basis(self.austrittsdatum) / 12.0)
vorgeschlagen = slip._at_abf_alt_mehrfach_vorschlag(
self.abf_alt_dienstzeit_monate, self.abf_alt_anspruchsart)
if vorgeschlagen or not self.abf_alt_mehrfach:
self.abf_alt_mehrfach = vorgeschlagen
@api.depends('employee_id', 'austrittsdatum', 'uel_tage',
'abf_alt_aktiv', 'abf_alt_monatsentgelt',
'abf_alt_laufender_arbeitslohn', 'abf_alt_monatslohnsteuer',
'abf_alt_mehrfach',
'abf_alt_dienstzeit_monate', 'abf_alt_anspruchsart')
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
wizard.abf_alt_betrag_vorschau = 0.0
wizard.abf_alt_steuer_vervielfachung_vorschau = 0.0
wizard.abf_alt_steuer_6_vorschau = 0.0
wizard.abf_alt_lohnsteuer_vorschau = 0.0
wizard.abf_alt_netto_vorschau = 0.0
if not wizard.employee_id or not wizard.austrittsdatum:
continue
slip = wizard._vorschau_payslip()
@@ -102,6 +190,18 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
wizard.uel_sz_vorschau = slip._at_uel_sz()
wizard.sv_tage_vorschau = slip._at_end_sv_tage(
wizard.uel_tage)
if wizard.abf_alt_aktiv:
betrag = slip._at_abf_alt_betrag()
steuer_vervielfachung = \
slip._at_abf_alt_steuer_vervielfachung()
steuer_6 = slip._at_abf_alt_steuer_6()
steuer = min(steuer_vervielfachung, steuer_6)
wizard.abf_alt_betrag_vorschau = betrag
wizard.abf_alt_steuer_vervielfachung_vorschau = \
steuer_vervielfachung
wizard.abf_alt_steuer_6_vorschau = steuer_6
wizard.abf_alt_lohnsteuer_vorschau = steuer
wizard.abf_alt_netto_vorschau = betrag - steuer
def _vorschau_payslip(self):
"""Flüchtiger Payslip-Kontext für die Vorschau-Berechnung (kein
@@ -119,8 +219,22 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
'struct_id': struct.id,
'date_from': erster,
'date_to': letzter,
'l10n_at_abf_alt_anspruchsart': self.abf_alt_anspruchsart,
'l10n_at_abf_alt_dienstzeit_monate':
self.abf_alt_dienstzeit_monate,
'l10n_at_abf_alt_monatsentgelt': self.abf_alt_monatsentgelt,
'l10n_at_abf_alt_laufender_arbeitslohn':
self.abf_alt_laufender_arbeitslohn,
'l10n_at_abf_alt_monatslohnsteuer':
self.abf_alt_monatslohnsteuer,
'l10n_at_abf_alt_mehrfach': self.abf_alt_mehrfach,
'l10n_at_abf_alt_nachweis': self.abf_alt_nachweis,
})
eingaben = [('ATP_UEL_TAGE', self.uel_tage)]
eingaben = [
('ATP_UEL_TAGE', self.uel_tage),
('ATP_ABF_ALT', self.abf_alt_monatsentgelt
* self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0),
]
slip.input_line_ids = [
(0, 0, {
'input_type_id': self.env['hr.payslip.input.type']
@@ -140,16 +254,65 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
('ATP_ZEIT_GUTHABEN', self.zeit_guthaben_stunden),
('ATP_ZEIT_SCHULDEN', self.zeit_schulden_stunden),
('ATP_ABRECHNUNG_VORSCHUSS', self.vorschuss_saldo),
('ATP_ABF_ALT', self.abf_alt_monatsentgelt
* self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0),
]
def _abf_alt_validieren(self):
self.ensure_one()
if not self.abf_alt_aktiv:
return
if not self.abf_alt_anspruch_bestaetigt:
raise UserError(_('Bitte Anspruch und Nachweise der '
'Abfertigung Alt bestätigen.'))
if self.employee_id.version_id.l10n_at_abfertigung_neu \
and self.abf_alt_anspruchsart != 'eingefroren':
raise UserError(_(
'Das Dienstverhältnis ist als Abfertigung Neu markiert. '
'Eine Alt-Abfertigung ist nur als nachgewiesener '
'eingefrorener Anspruch zulässig.'))
if self.abf_alt_anspruchsart == 'eltern' \
and self.abf_alt_dienstzeit_monate < 60:
raise UserError(_(
'Ein Elternaustritt nach § 23a Abs. 3/4 AngG setzt ein '
'ununterbrochenes Dienstverhältnis von fünf Jahren voraus.'))
if (self.abf_alt_dienstzeit_monate <= 0
or self.abf_alt_monatsentgelt <= 0
or self.abf_alt_laufender_arbeitslohn <= 0
or self.abf_alt_monatslohnsteuer < 0
or self.abf_alt_mehrfach <= 0):
raise UserError(_('Dienstzeit, Monatsentgelt, laufender '
'Arbeitslohn und Anspruchsfaktor müssen '
'größer als null sein; die Tarif-LSt darf '
'nicht negativ sein.'))
agrd = self.departure_reason_id.l10n_at_agrd
if agrd == '02' and self.abf_alt_anspruchsart not in (
'eltern', 'pension', 'manuell'):
raise UserError(_(
'Eine gewöhnliche Arbeitnehmerkündigung begründet keinen '
'Anspruch nach § 23 AngG. Bitte einen §-23a-Sonderfall '
'wählen oder den Anspruch manuell nachweisen.'))
if agrd == '06' and self.abf_alt_anspruchsart != 'manuell':
raise UserError(_(
'Der Abgangsgrund „Fristlose Entlassung“ unterscheidet '
'nicht zwischen verschuldeter und unverschuldeter '
'Entlassung. Bitte den Anspruch manuell bestätigen und '
'begründen.'))
def action_payslip(self):
"""Endabrechnungs-Lauf im Austrittsmonat anlegen und die
gesammelten offenen Posten als Eingaben vorbefüllen."""
self.ensure_one()
self._abf_alt_validieren()
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 \
version = self.employee_id.version_id
version.write({
'contract_date_end': self.austrittsdatum,
'departure_reason_id': self.departure_reason_id.id,
})
struct = version.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,
@@ -157,6 +320,21 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
'struct_id': struct.id,
'date_from': erster,
'date_to': letzter,
'l10n_at_abf_alt_anspruchsart': self.abf_alt_anspruchsart
if self.abf_alt_aktiv else False,
'l10n_at_abf_alt_dienstzeit_monate':
self.abf_alt_dienstzeit_monate if self.abf_alt_aktiv else 0,
'l10n_at_abf_alt_monatsentgelt':
self.abf_alt_monatsentgelt if self.abf_alt_aktiv else 0.0,
'l10n_at_abf_alt_laufender_arbeitslohn':
self.abf_alt_laufender_arbeitslohn
if self.abf_alt_aktiv else 0.0,
'l10n_at_abf_alt_monatslohnsteuer':
self.abf_alt_monatslohnsteuer if self.abf_alt_aktiv else 0.0,
'l10n_at_abf_alt_mehrfach':
self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0,
'l10n_at_abf_alt_nachweis':
self.abf_alt_nachweis if self.abf_alt_aktiv else False,
})
typen = {code: self.env['hr.payslip.input.type'].search(
[('code', '=', code)], limit=1)