9ff977e0d4
Model commuter declarations as effective-dated L 34 records so monthly changes, L 34/Pendlerrechner evidence and historical payslip reproduction stay auditable. PP enters as an aliquoted § 66 freibetrag of the running tax base while PE joins the annual absetzbetraege, matching the cumulative engine's rollup. Ticket handling follows § 26 Z 5: employer ticket costs reduce PP pro rata (lit i), Werkverkehr is data-driven via the residual route and salary conversion does not block PP/PE. Also register the AP13-A tests that were accidentally left out of the previous commit.
118 lines
5.7 KiB
Python
118 lines
5.7 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""AP13-B: L-34-backed commuter allowance and commuter tax credit."""
|
||
from odoo import api, fields, models, _
|
||
from odoo.exceptions import UserError
|
||
|
||
|
||
class L10nAtPayrollPendlerErklaerung(models.Model):
|
||
_name = 'l10n.at.payroll.pendler.erklaerung'
|
||
_description = 'AT Pendlererklärung (L 34 EDV)'
|
||
_order = 'date_start desc, id desc'
|
||
|
||
employee_id = fields.Many2one('hr.employee', required=True, index=True)
|
||
company_id = fields.Many2one(
|
||
related='employee_id.company_id', readonly=True, required=True)
|
||
date_start = fields.Date(string='Gültig ab', required=True, index=True)
|
||
date_end = fields.Date(string='Gültig bis')
|
||
strecke_km = fields.Integer(string='Einfache Wegstrecke (km)', required=True)
|
||
pauschale_art = fields.Selection(
|
||
[('klein', 'Kleines Pendlerpauschale'),
|
||
('gross', 'Großes Pendlerpauschale')], required=True)
|
||
fahrten_stufe = fields.Selection(
|
||
[('drittel', '4–7 Fahrten (1/3)'),
|
||
('zwei_drittel', '8–10 Fahrten (2/3)'),
|
||
('voll', 'Ab 11 Fahrten (voll)')], required=True, default='voll')
|
||
pendlerrechner_datum = fields.Date(string='Pendlerrechner-Abfragedatum', required=True)
|
||
l34_attachment_ids = fields.Many2many('ir.attachment', string='L 34 EDV / Pendlerrechner')
|
||
arbeitgeber_kfz = fields.Boolean(string='Arbeitgebereigenes Kfz für Pendelstrecke')
|
||
ticket_art = fields.Selection(
|
||
[('keines', 'Kein Werkverkehr/Öffi-Ticket'),
|
||
('werkverkehr', '§ 26 Z 5 lit a Werkverkehr'),
|
||
('oeffi', '§ 26 Z 5 lit b Öffi-Ticket')], default='keines', required=True)
|
||
ticket_kosten_monatlich = fields.Monetary(string='AG-Ticketkosten pro Monat', currency_field='currency_id')
|
||
ticket_bezugsumwandlung = fields.Boolean(string='Ticket durch Bezugsumwandlung')
|
||
currency_id = fields.Many2one(related='company_id.currency_id', readonly=True)
|
||
note = fields.Text(string='Bemerkung')
|
||
|
||
@api.constrains('date_start', 'date_end', 'strecke_km', 'ticket_art', 'ticket_kosten_monatlich', 'ticket_bezugsumwandlung')
|
||
def _check_values(self):
|
||
for rec in self:
|
||
if rec.date_end and rec.date_end < rec.date_start:
|
||
raise UserError(_('The commuter declaration end date cannot precede its start date.'))
|
||
if rec.strecke_km < 0:
|
||
raise UserError(_('The commuter distance cannot be negative.'))
|
||
if rec.ticket_kosten_monatlich and rec.ticket_art == 'keines':
|
||
raise UserError(_('Ticket costs require a ticket classification.'))
|
||
if rec.ticket_bezugsumwandlung and rec.ticket_art == 'keines':
|
||
raise UserError(_('Salary conversion requires a ticket classification.'))
|
||
|
||
def _l10n_at_pendler_werte(self, payslip):
|
||
"""(PP-LZP-Freibetrag, PE-Jahreswert mit Stufenfaktor).
|
||
|
||
PP ist ein §-66-Abs-3-aliquotierter Freibetrag: Jahreswert ×
|
||
Fahrten-Stufe / 360 × Lohnsteuertage des LZP (lb-pen-09); die
|
||
kumulative Engine annualisiert ihn über 360/tage.
|
||
|
||
Der Pendlereuro ist ein Absetzbetrag auf der Steuer (lb-pen-04)
|
||
und muss daher — wie der Verkehrsabsetzbetrag — auf der
|
||
annualisierten Basis als Jahreswert einfließen: km × €/J ×
|
||
Fahrten-Stufe.
|
||
|
||
§ 26 Z 5 lit b: die AG-Ticketkosten mindern das PP anteilig
|
||
(§ 16 Abs 1 Z 6 lit i); § 26 Z 5 lit a (Werkverkehr) ist
|
||
datengetrieben über die L-34-Reststrecke abgebildet. Eine
|
||
Bezugsumwandlung sperrt PP/PE nicht (Ticket = steuerpflichtiger
|
||
laufender Bezug, der DN trägt die Kosten). PE ist mit dem
|
||
PP-Anspruch Fixkopplung (lb-pen-04): ohne PP-Anspruch kein PE.
|
||
"""
|
||
self.ensure_one()
|
||
if self.arbeitgeber_kfz:
|
||
return 0.0, 0.0
|
||
params = payslip._l10n_at_param('at_pendler')
|
||
km = self.strecke_km
|
||
grenzen = params['gross' if self.pauschale_art == 'gross' else 'klein']
|
||
annual = 0.0
|
||
for minimum, value in grenzen:
|
||
if km >= minimum:
|
||
annual = float(value)
|
||
factor = {'drittel': 1.0 / 3.0, 'zwei_drittel': 2.0 / 3.0,
|
||
'voll': 1.0}[self.fahrten_stufe]
|
||
pp = annual / 12.0 * factor \
|
||
* payslip._l10n_at_lohnsteuertage() / 30.0
|
||
if self.ticket_art == 'oeffi':
|
||
pp = max(pp - self.ticket_kosten_monatlich, 0.0)
|
||
pe = km * float(params['pendlereuro_km_jahr']) * factor
|
||
if pp <= 0.0:
|
||
pe = 0.0
|
||
return pp, pe
|
||
|
||
|
||
class HrPayslip(models.Model):
|
||
_inherit = 'hr.payslip'
|
||
|
||
def _l10n_at_pendler_erklaerung(self):
|
||
self.ensure_one()
|
||
return self.env['l10n.at.payroll.pendler.erklaerung'].search([
|
||
('employee_id', '=', self.employee_id.id),
|
||
('date_start', '<=', self.date_to),
|
||
'|', ('date_end', '=', False), ('date_end', '>=', self.date_from),
|
||
], order='date_start desc, id desc', limit=1)
|
||
|
||
def _l10n_at_pendler_werte(self):
|
||
self.ensure_one()
|
||
erklaerung = self._l10n_at_pendler_erklaerung()
|
||
return erklaerung._l10n_at_pendler_werte(self) if erklaerung else (0.0, 0.0)
|
||
|
||
def _l10n_at_bemessung_laufen(self, result_rules=None):
|
||
basis = super()._l10n_at_bemessung_laufen(result_rules)
|
||
pp, _pe = self._l10n_at_pendler_werte()
|
||
return basis - pp
|
||
|
||
def _l10n_at_absetzbetraege(self, jbg):
|
||
betrag = super()._l10n_at_absetzbetraege(jbg)
|
||
_pp, pe = self._l10n_at_pendler_werte()
|
||
# Pendlereuro = direkter LSt-Absetzbetrag (§ 33 Abs 5 Z 4):
|
||
# Jahreswert mit Fahrten-Stufe; der Jahresverbrauch läuft über
|
||
# die kumulative Engine (wie der Verkehrsabsetzbetrag).
|
||
return betrag + pe
|