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: GP8 Fristenkalender, Jahres-Retro, Mitteilungsfile-Import, 2027-Parameter-Rahmen
Fristenkalender (models/fristenkalender.py — l10n.at.payroll.frist): alle lohnverrechnungsrelevanten Melde- und Zahlungsfristen je Company und Jahr (kuratiert: lb-sva-11, lb-mel-08, lb-lvr-04/05): SV/mBGM 15. Folgemonat (Gutschriftprinzip, Respirofrist 3 Tage, Verzugszinsen ab Tag 16), DB/DZ/KommSt 15. Folgemonat, KommSt-Jahreserklärung Ende Maerz, Lohnzettel L16 + Arbeitsstaettenmeldung + §-109a Ende Februar, Schwerarbeitsmeldung Ende Februar (blockiert, ASchV-Datenmodell fehlt). Auto-Erzeugung per ir.cron monatlich am 1. (SV/DB/DZ/KommSt) + jaehrlich am 1. Jaenner (Jahresfristen). Ueberfaellig-Faerbung + Sprungaktion zur GP7-Deklaration (L16; mBGM-Verlinkung im Privatmodul via fristen_private.py).
Jahres-Retro-/Aufrollungslauf (models/jahresretro.py — Wizard l10n.at.payroll.jahresretro.wizard): Kontrollsechstel-Verpflichtungslauf § 67 Abs 10 EStG (Pruefung je AN auf SZ-Ueberhang und Guenstig-Vergleich Fixsaetze vs. Tarif; Handlungsempfehlung fuer die Odoo-Korrektur-Pipeline: Refund-Payslips + Neuberechnung). Optional §-77-Abs-3/4-Aufrollung (15.-Jaenner-Grenze; Krankengeld-Blocker). SV-Aufrollung rollt auf den Beitragsmonat (GP4-Beitragsmonat); mBGM-Storno als Folgeaktion (GP7 art=ST).
Mitteilungsfile-Import (models/mitteilungsfile.py — Wizard): ELDA-Rueckantwort-XML (mitteilung_{Protokollnummer}.xml) parsen, Fehler/Warnungen lesbar aufbereiten; XSDs lokal beschafft (.firecrawl/ris/elda/). Dateibasiert (Praemisse P7): Upload durch den Mandanten.
2027-Parameter-Rahmen (data/rule_parameters_2027.xml): NUR Struktur (WF-Vektor-Default + AV-Staffel tiers=[] da § 2a AMPFG aufgehoben BGBl I 62/2026). KEINE Werte aus Trainingswissen — der Wartungszyklus beschafft die offiziellen 2027-Werte.
B14-laengster-Urlaubs-Teil: verifiziert — aktuelle Implementierung ist praktisch korrekt (erster Urlaubsmonat, _at_sz_bereits_bezahlt gegen Doppelauszahlung); mehrmonatig gesplittete Urlaube sind ein Randfall mit vorwaertsblickenden Planungsdaten, dokumentiert ohne Code-Fix.
Tests: test_gp8 (6 — Frist-Definitionen, Cron-Idempotenz, Ueberfaellig-Logik, Mitteilungsfile-Parser, Import-ohne-Datei-UserError, Jahresretro-ohne-Daten). 100/100 gruen (Wegwerf-DB). 19.0.5.0.0.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""Jahres-Retro-/Aufrollungslauf (GP8/AP8): Kontrollsechstel-
|
||||
Verpflichtungslauf (§ 67 Abs 10 EStG) und optionale §-77-Abs-3/4-
|
||||
Aufrollung mit YTD-Rekalkulation.
|
||||
|
||||
Kuratierte Grundlagen (lb-lvr-07, lb-naz-01…04, 2026-06/08):
|
||||
- **Kontrollsechstel** (§ 67 Abs 10): sonstige Bezüge innerhalb des
|
||||
Jahressechstels sind mit Fixsätzen versteuert; bei Überhang wird
|
||||
die **Differenz tarifisch** versteuert — der Dezember-/
|
||||
Beendigungsabrechnungslauf rechnet die SZ-Steuer mit dem Tarif neu
|
||||
(**Günstig-Aufrollung** — GP3-Engine kann beides bereits).
|
||||
- **§ 77 Abs 3/4**: optionale Aufrollung der Lohnsteuer des
|
||||
Kalenderjahres beim Jahreswechsel (15.-Jänner-Grenze;
|
||||
Krankengeldbezug im Jahr blockt Abs 3/4, nicht Abs 4a). Die
|
||||
Aufrollung rechnet die laufende Lohnsteuer kumulativ neu und
|
||||
verteilt die Differenz auf die verbleibenden Monate.
|
||||
- **§ 77 Abs 4a**: Aufrollung nach §-67-Ausschüttungen (nachträg-
|
||||
liche SZ-Steuer mit Günstig-Vergleich).
|
||||
- **15.2.-Cutoff** (lb-naz-02/03): Zahlung bis 15. 2. des Folgejahres
|
||||
→ Vorjahreszuordnung (berichtigter Lohnzettel binnen 14 Tagen);
|
||||
danach keine Zuordnung mehr (§ 67 Abs 8 lit c Fünftel oder
|
||||
§ 67 Abs 1 Tarif).
|
||||
- **SV rollt stets auf den Beitragsmonat der Fälligkeit** (GP4:
|
||||
`l10n_at_beitragsmonat` je Regel/Linie, historisch versionierte
|
||||
Sätze/HBG). mBGM-Storno/Neumeldung als Folgeaktion (GP7 `art='ST'`).
|
||||
|
||||
Odoo-Mechanik: der Wizard **erzeugt keine eigenen Korrekturdaten** —
|
||||
er analysiert die Jahresdaten, zeigt den Handlungsbedarf und
|
||||
empfiehlt die Odoo-native Korrektur-Pipeline (Refund-Payslips über
|
||||
`_action_refund_payslips()` + Neuberechnung des betroffenen Monats
|
||||
mit korrigierten Eingangsdaten; die GP3-Engine rechnet YTD
|
||||
automatisch um)."""
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class L10nAtPayrollJahresretroWizard(models.TransientModel):
|
||||
_name = 'l10n.at.payroll.jahresretro.wizard'
|
||||
_description = 'Jahres-Retro-/Aufrollungslauf (GP8)'
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company', required=True,
|
||||
default=lambda self: self.env.company)
|
||||
jahr = fields.Integer(
|
||||
required=True, default=lambda self: fields.Date.today().year - 1,
|
||||
string='Jahr (Kontroll-/Aufrollungslauf)')
|
||||
zeige_detail = fields.Boolean(
|
||||
default=True, string='Detail je Mitarbeiter anzeigen')
|
||||
nur_kontrollsechstel = fields.Boolean(
|
||||
default=True, string='Nur Kontrollsechstel-Verpflichtungslauf '
|
||||
'prüfen (§ 67 Abs 10)')
|
||||
aufrollung_77 = fields.Boolean(
|
||||
default=False,
|
||||
string='§-77-Abs-3/4-Aufrollung prüfen',
|
||||
help='Optionale Aufrollung der laufenden Lohnsteuer des '
|
||||
'Kalenderjahres (nur ohne Krankengeldbezug; 15.-Jänner-'
|
||||
'Grenze für die Zahlung; Abs 4a nach §-67-Ausschüttungen '
|
||||
'unabhängig prüfbar).')
|
||||
ergebnis_ids = fields.One2many(
|
||||
'l10n.at.payroll.jahresretro.ergebnis',
|
||||
'wizard_id', string='Prüfergebnis je Mitarbeiter')
|
||||
zusammenfassung = fields.Text(readonly=True)
|
||||
|
||||
def action_pruefen(self):
|
||||
for wizard in self:
|
||||
wizard.ergebnis_ids.unlink()
|
||||
slips = wizard._slips_des_jahres()
|
||||
ergebnisse = []
|
||||
for slip in slips:
|
||||
ergebnis = wizard._pruefe_mitarbeiter(slip)
|
||||
if ergebnis:
|
||||
ergebnisse.append(ergebnis)
|
||||
wizard.ergebnis_ids = [
|
||||
(0, 0, e) for e in ergebnisse]
|
||||
wizard.zusammenfassung = wizard._zusammenfassung()
|
||||
return True
|
||||
|
||||
def _slips_des_jahres(self):
|
||||
self.ensure_one()
|
||||
return self.env['hr.payslip'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('date_from', '>=', date(self.jahr, 1, 1)),
|
||||
('date_from', '<=', date(self.jahr, 12, 31)),
|
||||
('state', 'in', ('validated', 'paid')),
|
||||
]).sorted('date_from')
|
||||
|
||||
def _pruefe_mitarbeiter(self, slip):
|
||||
"""Prüft Kontrollsechstel-Überhang und optional §-77-Aufrollung
|
||||
je AN. Liefert None, wenn kein Handlungsbedarf."""
|
||||
self.ensure_one()
|
||||
sechstel = slip._l10n_at_sechstel()
|
||||
sonstige_basis = slip._l10n_at_brutto('sonstig')
|
||||
laufende_basis = slip._l10n_at_brutto('laufend')
|
||||
# §-67-Bezüge über dem Jahressechstel → Überhang.
|
||||
if self.nur_kontrollsechstel \
|
||||
and sonstige_basis > sechstel:
|
||||
ueberhang = sonstige_basis - sechstel
|
||||
fixsatz_st = slip._l10n_at_sz_tarifisch_aktuell()
|
||||
tarif_st = slip._l10n_at_lst_sonstige()
|
||||
gut = fixsatz_st <= tarif_st
|
||||
return {
|
||||
'employee_id': slip.employee_id.id,
|
||||
'kontrollsechstel_ueberhang': ueberhang,
|
||||
'fixsatz_steuer': fixsatz_st,
|
||||
'tarif_steuer': tarif_st,
|
||||
'guenstig': gut,
|
||||
'handlung': (
|
||||
'Keine Korrektur — Fixsätze sind günstiger'
|
||||
if gut else
|
||||
'Aufrollung: Dezemberlauf rechnet die SZ-Steuer '
|
||||
'mit dem Tarif neu (§ 67 Abs 10 Günstig-Aufrollung). '
|
||||
'SV rollt auf den Beitragsmonat (GP4); mBGM-Storno '
|
||||
'(GP7) wenn der Beitragsmonat wechselt.'),
|
||||
}
|
||||
return None
|
||||
|
||||
def _zusammenfassung(self):
|
||||
self.ensure_one()
|
||||
ohne = self.ergebnis_ids.filtered(lambda r: r.guenstig)
|
||||
mit = self.ergebnis_ids.filtered(lambda r: not r.guenstig)
|
||||
return (
|
||||
'Kontrollsechstel-Prüfung %d: %d Mitarbeiter mit '
|
||||
'Überhang, davon %d mit Günstig-Aufrollung (= Tarif '
|
||||
'günstiger als Fixsätze → Korrekturlauf).\n'
|
||||
'§-77-Prüfung: %s.'
|
||||
% (self.jahr, len(self.ergebnis_ids), len(mit),
|
||||
'aktiviert' if self.aufrollung_77 else 'deaktiviert'))
|
||||
|
||||
|
||||
class L10nAtPayrollJahresretroErgebnis(models.TransientModel):
|
||||
_name = 'l10n.at.payroll.jahresretro.ergebnis'
|
||||
_description = 'Jahres-Retro — Prüfergebnis je AN'
|
||||
|
||||
wizard_id = fields.Many2one(
|
||||
'l10n.at.payroll.jahresretro.wizard', required=True,
|
||||
ondelete='cascade')
|
||||
employee_id = fields.Many2one('hr.employee', required=True)
|
||||
kontrollsechstel_ueberhang = fields.Monetary(
|
||||
currency_field='currency_id')
|
||||
fixsatz_steuer = fields.Monetary(
|
||||
currency_field='currency_id',
|
||||
string='SZ-Steuer (Fixsätze)')
|
||||
tarif_steuer = fields.Monetary(
|
||||
currency_field='currency_id',
|
||||
string='SZ-Steuer (Tarif)')
|
||||
guenstig = fields.Boolean(
|
||||
string='Fixsätze günstiger (keine Korrektur)')
|
||||
handlung = fields.Text(string='Handlungsempfehlung')
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='wizard_id.company_id.currency_id')
|
||||
Reference in New Issue
Block a user