Files
odoo-at-payroll/addons/l10n_at_hr_payroll_private/wizard/endabrechnung.py
T
fegger 850e99df20 [IMP] l10n_at_hr_payroll_private: add premiums at termination under § 16 AngG (AP14-B)
Close the premium-at-termination gap of the end-of-employment cycle: an aliquote periodic premium (annual, sales, bonus, 15th payment) whose entitlement period outlasts the termination is covered by § 16 AngG — the pro-rata amount follows the ratio of the elapsed service to the entitlement period, is due for employees regardless of the termination ground (loss clauses void, 9 ObA 82/13v), while performance-linked premiums for workers may validly hinge on non-fault grounds and premiums tied to a specific service (e.g. Bilanzgeld) are not aliquote without the service (lb-end-18). Two routes: PRAM_END (bemessung sonstig) covers premiums paid with the end-of-employment run — a special payment inside the annual sixth at the flat 6 % rate with the 620 EUR free amount and 2 615 EUR free threshold checked by the core, SV as special-payment contributions (KV/PV/AV without AK/WF) against the monthly HBG and the annual SZ cap, fully payroll-levy liable and BV-contributory at the uncapped 1.53 % rate (§ 6 Abs 1 iVm Abs 5 BMSVG). PRAM_END_TARIF (bemessung laufend plus the core l10n_at_s67_abs6_ueberhang flag) covers premiums due at a pre-agreed maturity because their amount only becomes determinable after the entitlement period ends (e.g. balance-sheet premiums): taxed like running pay with the payment-month tariff without touching the annual sixth (§ 67 Abs 10, not a § 67 Abs 8 lit c case per 98/14/0009 = ARD 5483/14/2004) — the SV contribution month rolls back into the termination year via a new payslip field consumed by an extended branch of the existing SV-basis-by-Beitragsmonat override (historical caps and rates, E-MVB 044-01-00-006), while the LSt stays in the payment month. The wizard section previews the § 16 pro-rata amount from the full premium, entitlement period and elapsed service, keeps the payable amount confirmable (KB agreement), requires the service/claim-basis confirmation, enforces the contribution month for the deferred case, and lands the memo on the payslip for the separate Lohnzettel workflow (LStR Rz 911a); the existing manual PRAM rule is untouched. The account matrix posts both rules to 6230. Verified against the original statute text in .ris/ (AngG § 16, BMSVG § 6 Abs 5) and the knowledge-base source lb-end-18; the full SZ roll-forward (storno plus recomputation against the termination year's SZ annual cap) and the mBGM storno/re-transmission remain open in the GP7 payroll-reporting track, and the BV attribution of the deferred premium remains in the payment month as a documented approximation.

Version 19.0.16.0.0 (private)
2026-09-15 13:33:49 +02:00

742 lines
38 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.')
pram_aktiv = fields.Boolean(
string='Prämie bei Beendigung (§ 16 AngG)',
help='Aliquote periodische Prämie/Sonderzahlung (Jahres-, '
'Umsatzprämie, Bonifikation, 15. Bezug), deren Anspruchs-'
'zeitraum bei der Beendigung noch nicht abgelaufen ist '
'(lb-end-18).')
pram_zahlungsart = fields.Selection(
[('mit_endabrechnung',
'Mit der Endabrechnung ausgezahlt (sonstiger Bezug, '
'Jahressechstel)'),
('spaeter_faellig',
'Später fällig (§ 67 Abs 10 — Tarif im Zahlungsmonat, '
'ohne Sechstel; gesonderter Lohnzettel)')],
string='Zahlungs-/Fälligkeitsart', default='mit_endabrechnung',
help='Wird die Prämie erst zum im Vorhinein vereinbarten '
'Fälligkeitszeitpunkt fällig, weil ihre Höhe erst nach '
'Ablauf der Anspruchsperiode ermittelbar ist (z. B. '
'Bilanzprämie), liegt keine Nachzahlung vor und läuft die '
'Besteuerung über § 67 Abs 10 (lb-end-18).')
pram_anspruchszeitraum_monate = fields.Integer(
string='Anspruchszeitraum (Monate)', default=12,
help='Dienstperiode, für die die Prämie gewährt wird — z. B. '
'Kalenderjahr = 12 Monate (§ 16 AngG).')
pram_dienstzeit_monate = fields.Integer(
string='Zurückgelegte Dienstzeit im Anspruchszeitraum (Monate)',
help='Grundlage der §-16-Aliquot-Vorschau; der tatsächliche '
'Betrag folgt der KV-/Vertragsvereinbarung und bleibt '
'bestätigbar.')
pram_voller_betrag = fields.Monetary(
string='Voller Prämien-Betrag (vor Aliquotierung)',
currency_field='currency_id',
help='Betrag für die gesamte Anspruchsperiode — Basis der '
'§-16-Aliquot-Vorschau.')
pram_betrag = fields.Monetary(
string='Zu zahlender (aliquote) Brutto-Betrag',
currency_field='currency_id')
pram_beitragsmonat = fields.Date(
string='SV-Beitragsmonat (Rollung ins Austrittsjahr)',
help='Nur bei späterer Fälligkeit: Beitragsmonat, dem die '
'Prämie sv-rechtlich zuzuordnen ist — der Zeitraum, in dem '
'die Leistungen erbracht wurden (E-MVB 044-01-00-006). '
'Die mBGM-Storno-/Neuübermittlung erfolgt im GP7-Track.')
pram_leistung_bestaetigt = fields.Boolean(
string='Leistungs-/Anspruchsgrundlage bestätigt',
help='Bestätigung, dass die Prämie nicht an eine noch nicht '
'erbrachte spezifische Leistung gebunden ist (z. B. '
'Bilanzgeld — kein aliquoter Anspruch ohne Tätigkeit) und '
'allfällige leistungsabhängige Verlustklauseln (nur '
'Arbeiter, wirksam nur bei schuldhaftem Beendigungsgrund) '
'geprüft sind. Für Angestellte gebührt die aliquote Prämie '
'unabhängig von der Art der Beendigung (§ 16 AngG).')
pram_nachweis = fields.Text(
string='Nachweis/Begründung',
help='KV-/Vertragsvereinbarung, Anspruchszeitraum, '
'Aliquotierung, Fälligkeitszeitpunkt und '
'Leistungsverknüpfung 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')
pram_aliquot_vorschau = fields.Monetary(
string='§-16-Aliquote Prämie (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('pram_aktiv', 'pram_voller_betrag',
'pram_dienstzeit_monate', 'pram_anspruchszeitraum_monate')
def _onchange_pram(self):
for wizard in self:
if not wizard.pram_aktiv:
continue
aliquote = wizard._at_pram_aliquot()
if aliquote and not wizard.pram_betrag:
wizard.pram_betrag = aliquote
def _at_pram_aliquot(self):
"""§ 16 AngG (Originalwortlaut): Betrag, der dem Verhältnis
zwischen der Dienstperiode des Anspruchs und der zurückgelegten
Dienstzeit entspricht."""
self.ensure_one()
if (not self.pram_anspruchszeitraum_monate
or self.pram_anspruchszeitraum_monate < 0
or self.pram_dienstzeit_monate < 0
or not self.pram_voller_betrag):
return 0.0
return (self.pram_voller_betrag
* min(self.pram_dienstzeit_monate,
self.pram_anspruchszeitraum_monate)
/ self.pram_anspruchszeitraum_monate)
@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',
'pram_aktiv', 'pram_voller_betrag',
'pram_dienstzeit_monate',
'pram_anspruchszeitraum_monate')
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
wizard.pram_aliquot_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()
wizard.pram_aliquot_vorschau = wizard._at_pram_aliquot()
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,
'l10n_at_pram_nachweis': self.pram_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),
('ATP_PRAM_END', self.pram_betrag
if self.pram_aktiv
and self.pram_zahlungsart == 'mit_endabrechnung' else 0.0),
('ATP_PRAM_END_TARIF', self.pram_betrag
if self.pram_aktiv
and self.pram_zahlungsart == 'spaeter_faellig' 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),
('ATP_PRAM_END', self.pram_betrag
if self.pram_aktiv
and self.pram_zahlungsart == 'mit_endabrechnung' else 0.0),
('ATP_PRAM_END_TARIF', self.pram_betrag
if self.pram_aktiv
and self.pram_zahlungsart == 'spaeter_faellig' 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 _pram_validieren(self):
self.ensure_one()
if not self.pram_aktiv:
return
if not self.pram_leistung_bestaetigt:
raise UserError(_('Bitte die Leistungs-/Anspruchsgrundlage '
'der Prämie bestätigen (lb-end-18).'))
if self.pram_betrag <= 0:
raise UserError(_('Der zu zahlende Prämien-Betrag muss '
'größer als null sein.'))
if (self.pram_anspruchszeitraum_monate < 0
or self.pram_dienstzeit_monate < 0
or self.pram_voller_betrag < 0):
raise UserError(_('Anspruchszeitraum, Dienstzeit und '
'voller Betrag dürfen nicht negativ '
'sein.'))
if (self.pram_anspruchszeitraum_monate
and self.pram_dienstzeit_monate
> self.pram_anspruchszeitraum_monate):
raise UserError(_('Die zurückgelegte Dienstzeit kann den '
'Anspruchszeitraum nicht übersteigen.'))
if (self.pram_zahlungsart == 'spaeter_faellig'
and not self.pram_beitragsmonat):
raise UserError(_('Bei späterer Fälligkeit ist der SV-'
'Beitragsmonat der Rollung ins '
'Austrittsjahr erforderlich '
'(E-MVB 044-01-00-006).'))
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()
self._pram_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,
'l10n_at_pram_nachweis':
self.pram_nachweis if self.pram_aktiv else False,
'l10n_at_pram_beitragsmonat': self.pram_beitragsmonat
if (self.pram_aktiv
and self.pram_zahlungsart == 'spaeter_faellig') 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,
}