Files
odoo-at-payroll/addons/l10n_at_hr_payroll_private/wizard/endabrechnung.py
T
fegger 472b19a4b2 [IMP] l10n_at_hr_payroll_private: add settlement payments under § 67 Abs 8 lit a (AP15-C)
Complete the termination sequence with the settlement-sum path: comparison sums from court or out-of-court settlements, judgment/decision arrears and dismissal-objection payments are split between the statutory-severance and voluntary-severance rules and the settlement remainder. The remainder (VERGLEICH, bemessung neutral, sv_art laufend) reuses the lit-a/b fifth rule: after deducting the § 62 employee-rate contributions, one fifth stays tax-free capped at one fifth of nine times the monthly HBG (12 474 EUR 2026), and the remaining four fifths run with the payment-month tariff without touching the annual sixth. A new rule flag l10n_at_vergleich_bv marks the part paid for periods with a BV-Kasse entitlement: up to the parameterised cap at_lst_67_abs8_vergleich_bv (7 500 EUR 2026; § 67 Abs 8 is § 33a/§ 124b indexed) it is taxed at the flat 6 % rate with the contributions allocated pro rata (LStR Rz 1102b), while the excess falls back into the fifth rule. Both settlement rules stay payroll-levy liable (§ 41 Abs 4 lit b FLAG exempts only § 67 Abs 3 and 6) and SV-liable in the payment month, with per-Beitragsmonat roll-forward and insurance-period extension remaining documented manual options (lb-end-22, E-MVB 011-02-00-001). Identifiable severance components keep running through the existing statutory and voluntary severance machinery; a settlement expressly agreed as statutory severance is taxed in full under § 67 Abs 3 (VwGH 2013/13/0001 = ARD 6490/20/2016), and pension buy-outs (lit e) and social-plan payments (lit f) are out of scope. The wizard section requires confirmation of the settlement's itemisation and contested-claim status plus the BV entitlement, enforces the BV cap with the excess captured as remainder, and previews the free fifth, tariff basis and 6 % tax; the settlement memo lands on the payslip for the separate Lohnzettel workflow (LStR Rz 911a). The account matrix posts both settlement rules to the provisional 6200/6000 (workers 6000) and the new tax rule to 3540. Verified against the original statute text in .ris/ (EStG § 67 Abs 8 lit a) and the knowledge-base sources lb-end-21/22; KZ positioning of the lit-a tariff parts and their § 291d pot allocation remain documented verification points against LStR Rz 1087 ff.

Version 19.0.15.0.0 (private)
2026-09-15 12:38:19 +02:00

606 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Part of the odoo-at-payroll project. License: LGPL-3.
"""AP14-A: Endabrechnungs-Wizard — sammelt die offenen Posten der
Beendigungs-Abrechnung (Urlaubsersatzleistung, Abgangsentschädigung,
Zeitguthaben/-schulden, offene Vorschüsse) und legt den
Endabrechnungs-Lauf mit vorbefüllten Eingaben an. Reiner
Sammel-/Anlageprozess; die BV-Anwartschaft läuft über den eigenen
BV-Wizard (GP5), die Pfändung über die AP11-Maschine.
AP15-C: Vergleichssumme (§ 67 Abs 8 lit a) — Rest (Fünftelregelung)
und BV-Anwartschafts-Teil (bis 7.500 € mit 6 %)."""
from datetime import timedelta
from dateutil.relativedelta import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class L10nAtEndabrechnungWizard(models.TransientModel):
_name = 'l10n.at.payroll.endabrechnung.wizard'
_description = 'Endabrechnung (Beendigung des Dienstverhältnisses)'
employee_id = fields.Many2one(
'hr.employee', string='Dienstnehmer:in', required=True)
austrittsdatum = fields.Date(
string='Austritt (Ende des Arbeitsverhältnisses)', required=True)
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 '
'Anspruch des laufenden Urlaubsjahres abzüglich verbrauchter '
'Tage, ohne Rundung (§ 10 UrlG, lb-url-16). Für '
'unberechtigten vorzeitigen Austritt sind die Tage der 5. '
'und 6. Urlaubswoche des laufenden Jahres herauszurechnen.')
abgang_betrag = fields.Monetary(
string='Abgangsentschädigung (Brutto, § 67 Abs 8 lit b EStG)',
currency_field='currency_id',
help='Entschädigung für den Verzicht auf die Arbeitsleistung '
'während der (fiktiven) Kündigungsfrist — LSt: Fünftelregelung; '
'SV: Umlegung auf den Kündigungsfrist-Zeitraum (§ 11 Abs 2 '
'ASVG); voll lohnnebenkostenpflichtig.')
abgang_ffrist_tage = fields.Float(
string='Kündigungsfrist (Kalendertage)',
help='Kalendertage der (fiktiven) Kündigungsfrist, auf die die '
'pauschalierte Abgangsentschädigung sv-rechtlich umzulegen '
'ist (§ 11 Abs 2 ASVG).')
zeit_guthaben_stunden = fields.Float(
string='Zeitguthaben an Normalarbeitszeit (Stunden)',
help='Offenes Guthaben an Normalarbeitszeit aus durchrechenbarem '
'Arbeitszeitmodell — Abgeltung mit 50 % Zuschlag (§ 19e '
'Abs 2 AZG); bei unberechtigtem vorzeitigem Austritt kein '
'Zuschlag.')
zeit_schulden_stunden = fields.Float(
string='Zeitschulden (Stunden)',
help='Offene Zeitschulden (negative Zeitguthaben), abgerechnet '
'mit der aktuellen Normalstunde (lb-end-08).')
vorschuss_saldo = fields.Monetary(
string='Offener Vorschuss-/Darlehenssaldo',
currency_field='currency_id',
help='Bei Beendigung zur Gänze fälliger, noch offener '
'Vorschusssaldo (lb-end-06) — Rückverrechnung vom Netto '
'nach SV und Lohnsteuer, ohne Abgabenberührung '
'(§ 293 Abs 3 EO).')
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.')
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.')
vergleich_aktiv = fields.Boolean(
string='Vergleich (§ 67 Abs 8 lit a)',
help='Auf gerichtlichen oder außergerichtlichen Vergleichen '
'beruhende Vergleichssummen sowie Bereinigungen/Nachzahlungen '
'aus Urteilen/Bescheiden und Zahlungen bei '
'Kündigungsanfechtung (lb-end-21). Erkennbare '
'Abfertigungsanteile werden getrennt über die Abschnitte '
'Abfertigung Alt (§ 67 Abs 3) bzw. freiwillige Abfertigung '
'(§ 67 Abs 6) erfasst; Pensionsabfindungen (lit e) und '
'Sozialplan-Zahlungen (lit f) gehören nicht hierher.')
vergleich_rest_betrag = fields.Monetary(
string='Vergleichssumme — Rest (Fünftelregelung)',
currency_field='currency_id',
help='Nach Abzug der §-62-Beiträge ist ein Fünftel steuerfrei '
'(Deckel 1/5 × 9 × monatliche HBG), 4/5 laufen tarifisch im '
'Zuflussmonat — ohne Jahressechstel-Effekt (§ 67 Abs 8 lit '
'a iVm Abs 10).')
vergleich_bv_betrag = fields.Monetary(
string='Vergleichssumme — BV-Anwartschafts-Teil',
currency_field='currency_id',
help='Anteil für Zeiträume mit BV-Kassen-Anwartschaft: bis zum '
'Deckel (2026: 7 500 €) mit 6 %, der übersteigende Teil '
'fällt in die Fünftelregelung zurück. Der Alt-/Neu-Übertritt '
'steuert die Anwendung (lb-end-21: eingefrorene Zeiträume '
'kein 6 %-Teil).')
vergleich_bv_anwartschaft_bestaetigt = fields.Boolean(
string='BV-Anwartschaft für die betroffenen Zeiträume',
help='Voraussetzung des 6-%-Teils: die Vergleichssumme wird für '
'Zeiträume ausbezahlt, für die eine Anwartschaft gegenüber '
'einer BV-Kasse besteht (§ 67 Abs 8 lit a letzter Satz).')
vergleich_aufteilung_bestaetigt = fields.Boolean(
string='Aufgliederung und Strittigkeit bestätigt',
help='Bestätigung der Aufgliederung in der Vergleichsvereinbarung '
'(Praxistipp lb-end-21): Widmungen sind abgabenrechtlich nur '
'anerkannt, wenn die Ansprüche strittig waren (lb-end-22, '
'VwGH); beim Pauschalvergleich ist der Verhältnisfaktor '
'Forderungsbetrag : Vergleichssumme zu dokumentieren '
'(E-MVB 011-02-00-001).')
vergleich_nachweis = fields.Text(
string='Nachweis/Begründung',
help='Vergleichstext, strittige Ansprüche, erkennbare '
'Abfertigungsanteile, BV-Zeiträume und allfälliger '
'Verhältnisfaktor dokumentieren.')
currency_id = fields.Many2one(
'res.currency', default=lambda self: self.env.company.currency_id)
uel_lfd_vorschau = fields.Monetary(
string='UEL laufender Anteil (Vorschau)',
currency_field='currency_id', compute='_compute_vorschau')
uel_sz_vorschau = fields.Monetary(
string='UEL Sonderzahlungsanteil (Vorschau)',
currency_field='currency_id', compute='_compute_vorschau')
sv_tage_vorschau = fields.Integer(
string='SV-Verlängerungstage (§ 11 Abs 2)',
compute='_compute_vorschau')
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')
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')
vergleich_fuenftel_frei_vorschau = fields.Monetary(
string='Steuerfreies Fünftel (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
vergleich_tarif_basis_vorschau = fields.Monetary(
string='Fünftel-Bezüge tarifischer Teil (Vorschau)',
currency_field='currency_id', compute='_compute_vorschau')
vergleich_bv_lst_vorschau = fields.Monetary(
string='LSt 6 % BV-Teil (Vorschau)', currency_field='currency_id',
compute='_compute_vorschau')
@api.onchange('employee_id')
def _onchange_employee(self):
for wizard in self:
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()
wizard._at_abf_frei_vorschlag_setzen()
@api.onchange('austrittsdatum', 'abf_alt_anspruchsart',
'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):
for wizard in self:
wizard._at_abf_alt_vorschlag_setzen()
wizard._at_abf_frei_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
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',
'abf_alt_aktiv', 'abf_alt_monatsentgelt',
'abf_alt_laufender_arbeitslohn', 'abf_alt_monatslohnsteuer',
'abf_alt_mehrfach',
'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',
'vergleich_aktiv', 'vergleich_rest_betrag',
'vergleich_bv_betrag')
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
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
wizard.vergleich_fuenftel_frei_vorschau = 0.0
wizard.vergleich_tarif_basis_vorschau = 0.0
wizard.vergleich_bv_lst_vorschau = 0.0
if not wizard.employee_id or not wizard.austrittsdatum:
continue
slip = wizard._vorschau_payslip()
wizard.uel_lfd_vorschau = slip._at_uel_lfd()
wizard.uel_sz_vorschau = slip._at_uel_sz()
wizard.sv_tage_vorschau = slip._at_end_sv_tage(
wizard.uel_tage)
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
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
if wizard.vergleich_aktiv:
# Der tarifische Fünftel-Teil läuft über die laufende
# Bemessung (LST_LFD); die Vorschau zeigt die Pfade
# getrennt (Fünftel-Pool inkl. ABGANG, falls erfasst).
wizard.vergleich_fuenftel_frei_vorschau = \
slip._at_fuenftel_frei_betr()
wizard.vergleich_tarif_basis_vorschau = \
slip._at_fuenftel_tarif_basis()
wizard.vergleich_bv_lst_vorschau = \
slip._at_vergleich_bv_lst_6()
def _vorschau_payslip(self):
"""Flüchtiger Payslip-Kontext für die Vorschau-Berechnung (kein
Speichern): trägt Austrittsmonat, Struktur und die Eingaben."""
self.ensure_one()
erster = self.austrittsdatum.replace(day=1)
letzter = (erster + timedelta(days=32)).replace(day=1) \
- timedelta(days=1)
struct = self.employee_id.version_id.structure_id
if not struct:
struct = self.env.ref(
'l10n_at_hr_payroll_private.structure_at_privat')
slip = self.env['hr.payslip'].new({
'employee_id': self.employee_id.id,
'struct_id': struct.id,
'date_from': erster,
'date_to': letzter,
'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,
'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,
'l10n_at_vergleich_nachweis': self.vergleich_nachweis,
})
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),
('ATP_ABF_FREI', self.abf_frei_betrag
if self.abf_frei_aktiv else 0.0),
('ATP_VERGLEICH', self.vergleich_rest_betrag
if self.vergleich_aktiv else 0.0),
('ATP_VERGLEICH_BV', self.vergleich_bv_betrag
if self.vergleich_aktiv else 0.0),
]
slip.input_line_ids = [
(0, 0, {
'input_type_id': self.env['hr.payslip.input.type']
.search([('code', '=', code)], limit=1).id,
'amount': betrag,
})
for code, betrag in eingaben if betrag
]
return slip
def _eingaben(self):
self.ensure_one()
return [
('ATP_UEL_TAGE', self.uel_tage),
('ATP_ABGANG', self.abgang_betrag),
('ATP_ABGANG_FFRIST_TAGE', self.abgang_ffrist_tage),
('ATP_ZEIT_GUTHABEN', self.zeit_guthaben_stunden),
('ATP_ZEIT_SCHULDEN', self.zeit_schulden_stunden),
('ATP_ABRECHNUNG_VORSCHUSS', self.vorschuss_saldo),
('ATP_ABF_ALT', self.abf_alt_monatsentgelt
* 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),
('ATP_VERGLEICH', self.vergleich_rest_betrag
if self.vergleich_aktiv else 0.0),
('ATP_VERGLEICH_BV', self.vergleich_bv_betrag
if self.vergleich_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 _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 _vergleich_validieren(self):
self.ensure_one()
if not self.vergleich_aktiv:
return
if not self.vergleich_aufteilung_bestaetigt:
raise UserError(_('Bitte die Aufgliederung der Vergleichs-'
'summe und die Strittigkeit der Ansprüche '
'bestätigen (lb-end-21/22).'))
if self.vergleich_rest_betrag < 0 or self.vergleich_bv_betrag < 0:
raise UserError(_('Vergleichsbeträge dürfen nicht negativ '
'sein.'))
if not self.vergleich_rest_betrag and not self.vergleich_bv_betrag:
raise UserError(_('Bitte den Rest der Vergleichssumme oder '
'den BV-Anwartschafts-Teil erfassen; '
'erkennbare Abfertigungsanteile gehören in '
'die Abfertigungs-Abschnitte.'))
slip = self._vorschau_payslip()
deckel = float(slip._l10n_at_param('at_lst_67_abs8_vergleich_bv'))
if self.vergleich_bv_betrag > deckel:
raise UserError(_(
'Der BV-Anwartschafts-Teil ist mit %.2f € gedeckelt '
'(§ 67 Abs 8 lit a letzter Satz); den übersteigenden '
'Betrag als Rest (Fünftelregelung) erfassen.') % deckel)
if self.vergleich_bv_betrag > 0 \
and not self.vergleich_bv_anwartschaft_bestaetigt:
raise UserError(_('Der 6-%-Teil setzt eine bestätigte '
'BV-Anwartschaft für die betroffenen '
'Zeiträume voraus (§ 67 Abs 8 lit a letzter '
'Satz).'))
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()
self._abf_frei_validieren()
self._vergleich_validieren()
erster = self.austrittsdatum.replace(day=1)
letzter = (erster + timedelta(days=32)).replace(day=1) \
- timedelta(days=1)
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,
'employee_id': self.employee_id.id,
'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,
'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,
'l10n_at_vergleich_nachweis':
self.vergleich_nachweis if self.vergleich_aktiv else False,
})
typen = {code: self.env['hr.payslip.input.type'].search(
[('code', '=', code)], limit=1)
for code, _betrag in self._eingaben()}
self.env['hr.payslip.input'].create([{
'payslip_id': slip.id,
'input_type_id': typen[code].id,
'amount': betrag,
} for code, betrag in self._eingaben() if betrag])
slip.compute_sheet()
return {
'type': 'ir.actions.act_window',
'name': _('Endabrechnung'),
'res_model': 'hr.payslip',
'view_mode': 'form',
'res_id': slip.id,
}