# Part of the odoo-at-payroll project. License: LGPL-3. """ELDA-Mitteilungsfile-Import (GP8): Rückantwort der Clearing- stelle/Dateninhaltsprüfung als XML (`mitteilung_{Protokollnummer}.xml`). XSDs lokal beschafft (`.firecrawl/ris/elda/Mitteilungsfiles_ZIP.zip` — elda_mitteilung-1.2/2.1/3.0.xsd, nicht versioniert). Der Import parst das XML, reichert die GP7-Deklarationsmodelle (L16/mBGM) mit dem offiziellen Prüfergebnis an und liefert ein lesbares Protokoll. Dateibasiert (Prämisse P7): der Mandant lädt das Mitteilungsfile aus dem ELDA-Portal und lädt es hier hoch.""" import base64 import logging import re from odoo import api, fields, models from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class L10nAtPayrollMitteilungsfileWizard(models.TransientModel): _name = 'l10n.at.payroll.mitteilungsfile.wizard' _description = 'ELDA-Mitteilungsfile importieren (GP8)' company_id = fields.Many2one( 'res.company', required=True, default=lambda self: self.env.company) datei = fields.Binary( required=True, string='Mitteilungsfile (XML)', help='ELDA-Rückantwort: mitteilung_{Protokollnummer}.xml ' 'aus dem Empfangsverzeichnis des ELDA-Portals.') datei_name = fields.Char() protokoll = fields.Text(readonly=True) def action_importieren(self): """XML parsen und Protokollzeilen extrahieren; die GP7- Deklarationsmodelle werden über REFP/REFW-Matches verlinkt, wo möglich. Das Mitteilungsfile 3.0 hat die Struktur: / (je Satz- art/REFW-Bezug).""" for wizard in self: if not wizard.datei: raise UserError('Keine Datei hochgeladen.') roh = base64.b64decode(wizard.datei) try: inhalt = roh.decode('utf-8') except UnicodeDecodeError: inhalt = roh.decode('iso-8859-15', errors='replace') zeilen = wizard._parse_mitteilungsfile(inhalt) wizard.protokoll = '\n'.join(zeilen) or 'Keine Fehler ' \ 'oder Warnungen gefunden.' return True @api.model def _parse_mitteilungsfile(self, inhalt): """Einfacher XML-Textparser: extrahiert / Elemente mit Attributen und Textinhalt. Kein voller XSD- Validator — die Fachprüfung bleibt der Clearingstelle; hier wird das Ergebnis lesbar aufbereitet (Prämisse P7).""" zeilen = [] # — XSD-abhängige Struktur. for match in re.finditer( r'<(?:fehler|warnung|error|warning)\b([^>]*?)/?>', inhalt, re.IGNORECASE): attrs = match.group(1) art = 'Fehler' if 'fehler' in match.group(0).lower() \ else 'Warnung' code = re.search(r'code="([^"]*)"', attrs) satzart = re.search(r'satzart="([^"]*)"', attrs) beschreibung = re.search( r'(?:beschreibung|description|text)="([^"]*)"', attrs) refw = re.search( r'(?:refw|refn|referenz)="([^"]*)"', attrs) zeile = art if code: zeile += ' %s' % code.group(1) if satzart: zeile += ' (Satzart %s)' % satzart.group(1) if refw: zeile += ' [Ref: %s]' % refw.group(1) if beschreibung: zeile += ': %s' % beschreibung.group(1) if not code and not beschreibung: zeile += ': %s' % match.group(0)[:120] zeilen.append(zeile) # Kopfprotokoll (Sendungsstatus). for match in re.finditer( r']*?)/?>', inhalt, re.IGNORECASE): attrs = match.group(1) status = re.search(r'status="([^"]*)"', attrs) if status: zeilen.insert(0, 'Sendungsstatus: %s' % status.group(1)) if not zeilen: zeilen.append('Mitteilungsfile gelesen — keine ' '/-Elemente gefunden.') return zeilen