Files
odoo-at-payroll/addons/l10n_at_hr_payroll_private/wizard/endabrechnung.py
T
fegger ee94f4664f [IMP] l10n_at_hr_payroll_private: add end-of-employment payroll cycle (AP14-A)
Pay the untaken vacation, dismissal compensation, time balances and open advances from one exit run. The vacation replacement splits natively into a running and a special-payment component per § 67 Abs 8 lit d, only the running part extends the mandatory insurance under § 11 Abs 2 ASVG with sequential windows (dismissal compensation first) and daily-value ceilings. Dismissal compensation follows the one-fifth rule of § 67 Abs 8 lit b without raising the annual sixth, stays fully social-insurable by allocation over the notice period and fully subject to payroll taxes. Advances are offset against net pay only (§ 293 Abs 3 EO) and time credits carry the 50 % statutory surcharge without § 68 relief. Verified against the original statute texts in .ris/; § 67 Abs 6 lump-sum payments are deliberately deferred to AP15.
2026-09-14 16:08:44 +02:00

176 lines
8.0 KiB
Python

# 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."""
from datetime import timedelta
from odoo import _, api, fields, models
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)
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)
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).')
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')
@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())
@api.depends('employee_id', 'austrittsdatum', 'uel_tage')
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
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)
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,
})
eingaben = [('ATP_UEL_TAGE', self.uel_tage)]
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),
]
def action_payslip(self):
"""Endabrechnungs-Lauf im Austrittsmonat anlegen und die
gesammelten offenen Posten als Eingaben vorbefüllen."""
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 \
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,
})
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,
}