mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 16:56:42 +00:00
c843701d1a
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.
107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
|
"""GP8 Kern: Fristenkalender (Auto-Erzeugung, Frist-Typen, Überfällig-
|
|
Logik), Jahres-Retro (Kontrollsechstel-Prüfung), Mitteilungsfile-Parser."""
|
|
from datetime import date
|
|
|
|
from odoo.tests import TransactionCase, tagged
|
|
|
|
from odoo.addons.l10n_at_hr_payroll.models.fristenkalender import (
|
|
FRISTART_DEFINITIONEN,
|
|
)
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class FristenTest(TransactionCase):
|
|
|
|
def test_01_frist_definitionen(self):
|
|
"""Alle Fristarten sind definiert mit Name/Deadline-Logik."""
|
|
for art, definition in FRISTART_DEFINITIONEN.items():
|
|
self.assertIn('name', definition)
|
|
self.assertIn('hilfetext', definition)
|
|
self.assertTrue(
|
|
'monatlich' in definition or 'monat' in definition,
|
|
'Fristart %s ohne Fälligkeits-Logik' % art)
|
|
|
|
def test_02_cron_erzeugt_fristen(self):
|
|
"""Cron erzeugt monatliche SV/DB-Fristen idempotent + jährliche
|
|
Fristen am 1. Jänner."""
|
|
frist = self.env['l10n.at.payroll.frist']
|
|
company = self.env.company
|
|
# Manueller Cron-Lauf (heute ist ein Tag im Jahr).
|
|
frist._cron_fristen_erzeugen()
|
|
# Monatliche Fristen existieren für den Vormonat.
|
|
heute = date.today()
|
|
vormonat = (heute.replace(day=1) - __import__('datetime')
|
|
.timedelta(days=1))
|
|
monatliche = frist.search([
|
|
('company_id', '=', company.id),
|
|
('fristart', 'in', ('sv_mbgm', 'db_dz_kommst')),
|
|
('jahr', '=', vormonat.year),
|
|
('monat', '=', vormonat.month)])
|
|
self.assertEqual(len(monatliche), 2)
|
|
# Idempotenz: zweiter Lauf erzeugt keine Duplikate.
|
|
frist._cron_fristen_erzeugen()
|
|
monatliche2 = frist.search([
|
|
('company_id', '=', company.id),
|
|
('fristart', 'in', ('sv_mbgm', 'db_dz_kommst')),
|
|
('jahr', '=', vormonat.year),
|
|
('monat', '=', vormonat.month)])
|
|
self.assertEqual(len(monatliche2), 2)
|
|
|
|
def test_03_state_ueberfaellig(self):
|
|
"""Überfällige Fristen markieren sich nach Deadline-
|
|
Überschreitung; action_erledigt setzt erledigt."""
|
|
frist = self.env['l10n.at.payroll.frist'].create({
|
|
'company_id': self.env.company.id,
|
|
'fristart': 'l16',
|
|
'jahr': 2025,
|
|
'monat': 0,
|
|
'deadline': '2026-01-01',
|
|
})
|
|
self.assertEqual(frist.state, 'ueberfaellig')
|
|
frist.action_erledigt()
|
|
self.assertEqual(frist.state, 'erledigt')
|
|
self.assertTrue(frist.erledigt_am)
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class MitteilungsfileTest(TransactionCase):
|
|
|
|
def test_01_parse_mitteilungsfile(self):
|
|
"""XML-Parser extrahiert Fehler-/Warnung-Elemente."""
|
|
wizard = self.env['l10n.at.payroll.mitteilungsfile.wizard']
|
|
xml = """<?xml version="1.0"?>
|
|
<mitteilung>
|
|
<sendung status="ok"/>
|
|
<fehler code="F0100" satzart="L1"
|
|
beschreibung="STEUERNUMMER fehlt"/>
|
|
<warnung code="W0102" satzart="G1"
|
|
beschreibung="Beitragssatz-Differenz"/>
|
|
</mitteilung>"""
|
|
zeilen = wizard._parse_mitteilungsfile(xml)
|
|
self.assertIn('Sendungsstatus: ok', zeilen)
|
|
self.assertTrue(any('F0100' in z for z in zeilen))
|
|
self.assertTrue(any('W0102' in z for z in zeilen))
|
|
self.assertTrue(any('STEUERNUMMER' in z for z in zeilen))
|
|
|
|
def test_02_import_ohne_datei(self):
|
|
"""Import ohne Datei löst UserError."""
|
|
from odoo.exceptions import UserError
|
|
wizard = self.env['l10n.at.payroll.mitteilungsfile.wizard'] \
|
|
.create({'company_id': self.env.company.id})
|
|
with self.assertRaises(UserError):
|
|
wizard.action_importieren()
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class JahresretroTest(TransactionCase):
|
|
|
|
def test_01_wizard_ohne_daten(self):
|
|
"""Wizard ohne Läufe im Jahr: leeres Ergebnis, keine Fehler."""
|
|
wizard = self.env['l10n.at.payroll.jahresretro.wizard'].create({
|
|
'company_id': self.env.company.id,
|
|
'jahr': 1999, # weit in der Vergangenheit
|
|
})
|
|
wizard.action_pruefen()
|
|
self.assertFalse(wizard.ergebnis_ids)
|
|
self.assertIn('1999', wizard.zusammenfassung) |