mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
[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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user