# 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