# Part of the odoo-at-payroll project. License: LGPL-3. """Finanz-Mitteilungen § 109a/§ 109b EStG (AP17) — DM-ORG 42.7.0, Kapitel E.16 (Satzart ``W1``, Code „W“, Länge 1100, Version 28) und E.26 (Satzart ``B1``, Code „B“, Länge 1100, Version 28); PDF-verifizierte ODL-Extraktion 2026-09-16. Beide Mitteilungssätze laufen im BEST ``LF`` / VSTR ``94`` mit dem Informationssatz I1 (ARTD „LZ“, D.4). Rechtsanker (RIS-verifiziert 2026-09-16, `.additional_docs/ris_normtexte` -Regime: NormDokument.wxe-Downloads): - **§ 109a EStG** (NOR40263382, Fassung 20.07.2024): Mitteilungen über Leistungen von (Sub-)Unternehmern/PVG — Name/Anschrift/SVN bzw. StNr, Art der Leistung (D.34: 8 Tätigkeitsgruppen), Kalenderjahr, Entgelt + Umsatzsteuer. Entgelt = ohne USt einschließlich Kostenersätze und Sachbezüge; bei freien Dienstnehmern inkl. DN-SV-Anteil und BMV-Kassen-Zahlungen (B270/B271). - **§ 109b EStG** (NOR40263383, Fassung 20.07.2024): Zahlungen INS AUSLAND für Leistungen § 22 (AL31), Vermittlung (AL32), kaufm./techn. Beratung im Inland (AL33). Angaben je Leistungserbringer (incl. Länderkennung(en) der Zielländer, UID) sowie je Personengemeinschaft/ Körperschaft die im Inland maßgeblich auftretende natürliche Person (bis 3 NP-Blöcke). Mitteilung unterbleibt, wenn a) die Kalenderjahres- Zahlungen desselben Leistungserbringers 100.000 € nicht übersteigen, b) ein Steuerabzug § 99 zu erfolgen hat, c) die ausländische KSt um nicht mehr als 10 %-Punkte unter der österreichischen KSt liegt (Abs 4; a) automatisiert, b)/c) als Fallauswahl). - **Fristen** (Fristenkalender): § 109a — Ende Februar (Fristart `paragraph_109a`); § 109b Abs 6 — elektronisch bis Ende Februar des Folgejahres (amtlicher Vordruck bis Ende Jänner). Schwellen als `hr.rule.parameter` `at_m109_schwellen` (versioniert): m109a_leistung 450 €, m109a_kumulativ 900 € (je Leistungserbringer, Kalenderjahr — Fristenkalender/§-109a-VO), m109b_jahr 100.000 €. Storno: KORR „S“ (W1/B1) — E.16.1/E.26.1 zulassen beide Varianten (Beträge auf 0 oder KORR „S“).""" from datetime import date, datetime import base64 from odoo import api, fields, models from odoo.exceptions import UserError from .elda import ( L10N_AT_ELDA_IDTEIL, L10nAtEldaBestand, L10nAtEldaDatei, L10nAtEldaSatz, ) from .lohnzettel_l16 import L16_INFORMATIONSSATZ # ------------------------------------------------------------------ # W1 — Mitteilungen gemäß § 109a EStG (E.16; Länge 1100) — Datenteil # ------------------------------------------------------------------ MITTEILUNG_W1_SATZ = [ (2, 'FSART', 21, 1, 'a'), # W (3, 'CLADR', 22, 6, 'a'), (4, 'CLDVR', 28, 7, 'a'), (6, 'STNRL', 35, 9, 'n'), (7, 'DVRNL', 44, 7, 'n'), (8, 'ARTU', 51, 1, 'a'), (9, 'DTUE', 52, 8, 'n'), # JJJJMMTT, wie I1 (10, 'ZTUE', 60, 6, 'n'), (11, 'REFN', 66, 30, 'a'), # je AG+Jahr+Auftragsverhältnis eindeutig (12, 'INTE', 96, 2, 'a'), (13, 'FEHL', 98, 20, 'a'), (14, 'STAT', 118, 1, 'a'), (15, 'FIND', 119, 1, 'a'), (16, 'JAHR', 120, 4, 'n'), (17, 'AVSN', 124, 10, 'n'), # Auftragnehmer VN LLLPTTMMJJ (19, 'ASTN', 134, 9, 'n'), # Auftragnehmer Steuernummer (20, 'ANAM', 143, 37, 'a'), (21, 'ATIT', 180, 20, 'a'), (22, 'AADR', 200, 37, 'a'), (23, 'ALKZ', 237, 3, 'a'), (24, 'APLZ', 240, 10, 'a'), (25, 'AORT', 250, 30, 'a'), (26, 'ARTE', 280, 2, 'n'), # Art der erbrachten Leistung (D.34) (27, 'V341', 282, 1, 'a'), (28, 'B341', 283, 10, 'n'), # Entgelt (KZ 341) (29, 'V344', 293, 1, 'a'), (30, 'B344', 294, 10, 'n'), # Umsatzsteuer (KZ 344) (31, 'FANRA', 304, 2, 'n'), # Finanzamtsnummer Auftraggeber (32, 'STNRA', 306, 7, 'n'), # Steuernummer Auftraggeber (33, 'DVRNA', 313, 7, 'n'), # DVR-Nummer Auftraggeber (34, 'TELN', 320, 20, 'a'), (35, 'FAXN', 340, 20, 'a'), (36, 'KORR', 360, 1, 'a'), # K/S (37, 'WOBD', 361, 12, 'a'), (38, 'ZOBD', 373, 15, 'a'), (39, 'V270', 388, 1, 'a'), (40, 'B270', 389, 10, 'n'), # DN-Anteil SV (KZ 270) (41, 'V271', 399, 1, 'a'), (42, 'B271', 400, 10, 'n'), # eingezahlter BMV-Kasse-Beitrag (43, 'RE43', 410, 591, 'a'), (44, 'REFN2', 1001, 30, 'a'), (45, 'RE45', 1031, 70, 'a'), ] # ------------------------------------------------------------------ # B1 — Mitteilungen gemäß § 109b EStG (E.26; Länge 1100) — Datenteil # ------------------------------------------------------------------ MITTEILUNG_B1_SATZ = [ (2, 'FSART', 21, 1, 'a'), # B (3, 'CLADR', 22, 6, 'a'), (4, 'CLDVR', 28, 7, 'a'), (6, 'STNRL', 35, 9, 'n'), (7, 'DVRNL', 44, 7, 'n'), (8, 'ARTU', 51, 1, 'a'), (9, 'DTUE', 52, 8, 'n'), # JJJJMMTT, wie I1 (10, 'ZTUE', 60, 6, 'n'), (11, 'REFN', 66, 30, 'a'), # je AG+Jahr+Auftragsverhältnis eindeutig (12, 'INTE', 96, 2, 'a'), (13, 'FEHL', 98, 20, 'a'), (14, 'STAT', 118, 1, 'a'), (15, 'FIND', 119, 1, 'a'), (16, 'JAHR', 120, 4, 'n'), (17, 'LVSN', 124, 10, 'n'), # Leistungserbringer VN LLLPTTMMJJ (19, 'LSTN', 134, 9, 'n'), # Leistungserbringer Steuernummer (20, 'LNAM', 143, 37, 'a'), (21, 'RE21', 180, 20, 'a'), (22, 'LADR', 200, 37, 'a'), (23, 'LLKZ', 237, 3, 'a'), (24, 'LPLZ', 240, 10, 'a'), (25, 'LORT', 250, 30, 'a'), (26, 'RE26', 280, 2, 'a'), (27, 'V499', 282, 1, 'a'), (28, 'B499', 283, 10, 'n'), # Zahlungen inkl. USt (KZ 499) (29, 'V344', 293, 1, 'a'), (30, 'B344', 294, 10, 'n'), # darin enthaltene USt (KZ 344) (32, 'STNRA', 304, 9, 'n'), # Steuernummer Auftraggeber (33, 'RE33', 313, 7, 'a'), (34, 'TELN', 320, 20, 'a'), (35, 'FAXN', 340, 20, 'a'), (36, 'KORR', 360, 1, 'a'), # K/S (37, 'AL31', 361, 1, 'a'), # § 109b Abs 2 Z 1 (§-22-Tätigkeiten) (38, 'AL32', 362, 1, 'a'), # Z 2 Vermittlungsleistungen (39, 'AL33', 363, 1, 'a'), # Z 3 kaufm./techn. Beratung (40, 'LKZ1', 364, 3, 'a'), (41, 'LKZ2', 367, 3, 'a'), (42, 'LKZ3', 370, 3, 'a'), (43, 'LKZ4', 373, 3, 'a'), (44, 'LKZ5', 376, 3, 'a'), (45, 'LKZ6', 379, 3, 'a'), (46, 'LKZ7', 382, 3, 'a'), (47, 'UIDL', 385, 15, 'a'), # UID Leistungserbringer (48, 'UIDA', 400, 15, 'a'), # UID Auftraggeber (49, 'VSNRA', 415, 10, 'n'), # VN Auftraggeber (50, 'NPVS1', 425, 10, 'n'), # NP-Block 1 (maßgebliche Person) (51, 'NPFA1', 435, 2, 'n'), (52, 'NPST1', 437, 7, 'n'), (53, 'NPNA1', 444, 30, 'a'), (54, 'NPAD1', 474, 30, 'a'), (55, 'NPLK1', 504, 3, 'a'), (56, 'NPPL1', 507, 10, 'a'), (57, 'NPOR1', 517, 30, 'a'), (58, 'NPUI1', 547, 15, 'a'), (59, 'NPVS2', 562, 10, 'n'), (60, 'NPFA2', 572, 2, 'n'), (61, 'NPST2', 574, 7, 'n'), (62, 'NPNA2', 581, 30, 'a'), (63, 'NPAD2', 611, 30, 'a'), (64, 'NPLK2', 641, 3, 'a'), (65, 'NPPL2', 644, 10, 'a'), (66, 'NPOR2', 654, 30, 'a'), (67, 'NPUI2', 684, 15, 'a'), (68, 'NPVS3', 699, 10, 'n'), (69, 'NPFA3', 709, 2, 'n'), (70, 'NPST3', 711, 7, 'n'), (71, 'NPNA3', 718, 30, 'a'), (72, 'NPAD3', 748, 30, 'a'), (73, 'NPLK3', 778, 3, 'a'), (74, 'NPPL3', 781, 10, 'a'), (75, 'NPOR3', 791, 30, 'a'), (76, 'NPUI3', 821, 15, 'a'), (77, 'WOBD', 836, 12, 'a'), (78, 'ZOBD', 848, 15, 'a'), (79, 'RE79', 863, 123, 'a'), (80, 'STAT', 986, 5, 'a'), (81, 'NMEL', 991, 7, 'n'), (82, 'RE82', 998, 3, 'a'), (83, 'REFN2', 1001, 30, 'a'), (84, 'RE84', 1031, 70, 'a'), ] # W1/B1 gemeinsame Vorzeichen-/Betragsfelder je Zeile. _BETRAEGE_W1 = (('entgelt', 'V341', 'B341', True), ('umsatzsteuer', 'V344', 'B344', True), ('dn_sv_anteil', 'V270', 'B270', True), ('bmv_beitrag', 'V271', 'B271', True)) _BETRAEGE_B1 = (('entgelt', 'V499', 'B499', True), ('umsatzsteuer', 'V344', 'B344', True)) class L10nAtPayrollMitteilung109Np(models.Model): _name = 'l10n.at.payroll.mitteilung109.np' _description = '§ 109b — maßgeblich auftretende natürliche Person' _order = 'sequence' sequence = fields.Integer(default=10) line_id = fields.Many2one( 'l10n.at.payroll.mitteilung109.line', required=True, ondelete='cascade', index=True) versicherungsnummer = fields.Char(string='VN (LLLPTTMMJJ)') finanzamtsnummer = fields.Char(size=2) steuernummer = fields.Char(size=7) name = fields.Char(string='Name (Familienname Vorname)') adresse = fields.Char() landeskennung = fields.Char(size=3) plz = fields.Char(size=10) ort = fields.Char() uid = fields.Char(string='UID-Nummer') class L10nAtPayrollMitteilung109Line(models.Model): _name = 'l10n.at.payroll.mitteilung109.line' _description = 'Finanz-Mitteilung je Auftraggeber/Auftragnehmer' _order = 'name_partner' deklaration_id = fields.Many2one( 'l10n.at.payroll.mitteilung109', required=True, ondelete='cascade', index=True) name_partner = fields.Char( string='Auftragnehmer/Leistungserbringer', required=True) versicherungsnummer = fields.Char( string='VN (LLLPTTMMJJ)', help='D.6-Format; statt VN ist das Geburtsdatenformat ohne ' 'VN-Prüfziffer zulässig (Storno-Regel der Kapitel E.16/E.26).') steuernummer = fields.Char(string='Steuernummer (7, Prüfziffer an 7)') uid = fields.Char(string='UID-Nummer') name = fields.Char(string='Familienname und Vorname') adresse = fields.Char() landeskennung = fields.Char(size=3, default='AUT') plz = fields.Char(size=10) ort = fields.Char(size=30) entgelt = fields.Monetary( currency_field='currency_id', help='W1: Entgelt ohne USt inkl. Kostenersätze und Sachbezüge ' '(bei freien Dienstnehmern inkl. DN-SV-Anteil und BMV-Kasse). ' 'B1: Zahlungen einschließlich Umsatzsteuer (KZ 499).') umsatzsteuer = fields.Monetary( currency_field='currency_id', help='W1: in Rechnung gestellte USt (KZ 344); B1: darin ' 'enthaltene USt (KZ 344).') dn_sv_anteil = fields.Monetary( currency_field='currency_id', string='DN-Anteil SV (KZ 270, W1)') bmv_beitrag = fields.Monetary( currency_field='currency_id', string='BMV-Kasse-Beitrag (KZ 271, W1)') art_leistung = fields.Selection( [(code, label) for code, label in ( ('01', 'Aufsichtsrat/Verwaltungsrat'), ('02', 'Bausparkassen-/Versicherungsvertreter'), ('03', 'Stiftungsvorstand'), ('04', 'Vortragende/Lehrende/Unterrichtende'), ('05', 'Kolporteure/Zeitungszusteller'), ('06', 'Privatgeschäftsvermittler'), ('07', 'Funktionäre öff.-rechtl. Körperschaften'), ('08', 'sonstige Leistungen (freier DV, § 4 Abs 4 ASVG)'))], string='Art der erbrachten Leistung (D.34)') al31 = fields.Boolean( string='§ 109b Abs 2 Z 1 (§ 22-Tätigkeit im Inland)') al32 = fields.Boolean(string='§ 109b Abs 2 Z 2 (Vermittlung)') al33 = fields.Boolean(string='§ 109b Abs 2 Z 3 (Beratung im Inland)') ziellaender = fields.Char( string='Länderkennungen der Zielländer', help='Bis zu 7 internationale Länderkennungen à 3 Stellen, ' 'komma-separiert (z. B. DE,CH).') ausnahme_grund = fields.Selection( [('keine', 'Mitteilungspflicht'), ('steuerabzug', 'Steuerabzug gem. § 99 EStG (Abs 4)'), ('kst_gleich', 'Ausländische KSt nicht > 10 %-Punkte unter ' 'österreichischer KSt (Abs 4)')], string='B1-Ausnahme (§ 109b Abs 4)', default='keine', help='Die 100.000-€-Schwelle prüft der Lauf automatisch; ' 'Steuerabzug und KSt-Vergleich sind Sachentscheidungen ' 'als Auswahlfelder.') refn = fields.Char() gemeldet = fields.Boolean( string='Meldepflichtig (Schwellenprüfung)', default=True, readonly=True, help='Wird vom Lauf gesetzt: Zeilen unter den Schwellen bzw. mit ' 'B1-Ausnahme entfallen aus der Datei.') korr = fields.Selection( [('keine', 'Neue Mitteilung'), ('K', 'Berichtigung'), ('S', 'Storno')], default='keine', string='Korrekturindikation') np_ids = fields.One2many( 'l10n.at.payroll.mitteilung109.np', 'line_id', string='Maßgebliche natürliche Personen (max. 3)') currency_id = fields.Many2one( 'res.currency', related='deklaration_id.company_id.currency_id') class L10nAtPayrollMitteilung109(models.Model): _name = 'l10n.at.payroll.mitteilung109' _description = 'Finanz-Mitteilungen § 109a/§ 109b EStG' _inherit = ['mail.thread'] _order = 'jahr desc, art, id desc' company_id = fields.Many2one( 'res.company', required=True, default=lambda self: self.env.company) jahr = fields.Integer(required=True, string='Kalenderjahr') art = fields.Selection( [('w1', '§ 109a — Werkverträge (W1)'), ('b1', '§ 109b — Auslandszahlungen (B1)')], required=True, string='Mitteilungsart') line_ids = fields.One2many( 'l10n.at.payroll.mitteilung109.line', 'deklaration_id', string='Mitteilungssätze') datei = fields.Binary(attachment=True, string='LF-Datei', copy=False) datei_name = fields.Char(copy=False) protokoll = fields.Text(string='Prüfprotokoll') state = fields.Selection( [('draft', 'Entwurf'), ('done', 'Erzeugt')], default='draft', required=True, tracking=True) _sql_constraints = [( 'm109_company_jahr_art_uniq', 'unique(company_id, jahr, art)', 'Für dieses Jahr existiert bereits eine Deklaration dieser Art.')] def _l10n_at_schwellen(self): self.ensure_one() return self.env['hr.rule.parameter']._get_parameter_from_code( 'at_m109_schwellen', date(self.jahr, 1, 1), raise_if_not_found=False) or {} def _l10n_at_hersteller(self): partner = self.company_id.partner_id return {'name': 'odoo-at-payroll', 'kfz': 'A', 'plz': partner.zip, 'ort': partner.city, 'strasse': partner.street} def _l10n_at_satz(self, line, stnr, heute, jetzt): """W1/B1-Mitteilungssatz je Zeile; IDTEIL (SART W1/B1, VSTR 94).""" self.ensure_one() if self.art == 'w1': struktur, sart, fsart = MITTEILUNG_W1_SATZ, 'W1', 'W' else: struktur, sart, fsart = MITTEILUNG_B1_SATZ, 'B1', 'B' satz = L10nAtEldaSatz( L10N_AT_ELDA_IDTEIL + struktur, 1100, bezeichnung='%s %s' % (sart, line.name_partner)) satz.setze('SART', sart) satz.setze('VSTR', '94') satz.setze('FSART', fsart) satz.setze('STNRL', stnr) satz.setze_datum('DTUE', heute, 'JJJJMMTT') satz.setze_zeit('ZTUE', jetzt) satz.setze('REFN', line.refn) satz.setze('FIND', 'J') satz.setze('JAHR', self.jahr) if self.art == 'w1': satz.setze('AVSN', line.versicherungsnummer) satz.setze('ASTN', line.steuernummer) satz.setze('ANAM', (line.name or '')[:37]) satz.setze('AADR', (line.adresse or '')[:37]) satz.setze('ALKZ', (line.landeskennung or 'AUT')[:3]) satz.setze('APLZ', (line.plz or '')[:10]) satz.setze('AORT', (line.ort or '')[:30]) satz.setze('ARTE', line.art_leistung) for feld, vfeld, bfeld, _neg in _BETRAEGE_W1: betrag = getattr(line, feld) satz.setze(vfeld, self._vorzeichen(betrag)) satz.setze(bfeld, abs(betrag) if betrag else 0, cent=True) satz.setze('FANRA', (stnr or '')[:2]) satz.setze('STNRA', (stnr or '')[2:9]) satz.setze('WOBD', 'LE-%d' % line.id) else: satz.setze('LVSN', line.versicherungsnummer) satz.setze('LSTN', line.steuernummer) satz.setze('LNAM', (line.name or '')[:37]) satz.setze('LADR', (line.adresse or '')[:37]) satz.setze('LLKZ', (line.landeskennung or 'AUT')[:3]) satz.setze('LPLZ', (line.plz or '')[:10]) satz.setze('LORT', (line.ort or '')[:30]) for feld, vfeld, bfeld, _neg in _BETRAEGE_W1: if feld == 'umsatzsteuer': satz.setze(vfeld, self._vorzeichen(line.umsatzsteuer)) satz.setze(bfeld, abs(line.umsatzsteuer) if line.umsatzsteuer else 0, cent=True) elif feld == 'entgelt': satz.setze('V499', self._vorzeichen(line.entgelt)) satz.setze('B499', abs(line.entgelt) if line.entgelt else 0, cent=True) satz.setze('AL31', 'J' if line.al31 else 'N') satz.setze('AL32', 'J' if line.al32 else 'N') satz.setze('AL33', 'J' if line.al33 else 'N') laender = [k.strip().upper() for k in ( line.ziellaender or '').split(',') if k.strip()][:7] if laender: for idx, laender_code in enumerate(laender, start=1): satz.setze('LKZ%d' % idx, laender_code) satz.setze('UIDL', (line.uid or '')[:15]) satz.setze('UIDA', (self.company_id.vat or '')[:15]) satz.setze('STNRA', stnr) satz.setze('WOBD', 'LE-%d' % line.id) for nummer, np_zeile in enumerate( line.np_ids.sorted('sequence')[:3], start=1): satz.setze('NPVS%d' % nummer, np_zeile.versicherungsnummer) satz.setze('NPFA%d' % nummer, np_zeile.finanzamtsnummer) satz.setze('NPST%d' % nummer, np_zeile.steuernummer) satz.setze('NPNA%d' % nummer, (np_zeile.name or '')[:30]) satz.setze('NPAD%d' % nummer, (np_zeile.adresse or '')[:30]) satz.setze('NPLK%d' % nummer, (np_zeile.landeskennung or '')[:3]) satz.setze('NPPL%d' % nummer, (np_zeile.plz or '')[:10]) satz.setze('NPOR%d' % nummer, (np_zeile.ort or '')[:30]) satz.setze('NPUI%d' % nummer, (np_zeile.uid or '')[:15]) satz.setze('REFN2', line.refn) return satz @api.model def _vorzeichen(self, betrag): if not betrag: return None return '+' if betrag > 0 else '-' @api.model def _vorzeichen(self, betrag): if not betrag: return None return '+' if betrag > 0 else '-' def action_erzeugen(self): """W1/B1-Mitteilungssätze je Zeile bauen; Schwellen/Ausnahmen prüfen (§ 109a-VO 450/900 € je Leistungserbringer; § 109b Abs 4 100.000 € je Leistungserbringer und Kalenderjahr); unter- schwellige Zeilen bleiben mit ``gemeldet = False`` (Protokoll- Hinweis) und entfallen aus der Datei. LF-Bestand: I1 (ARTD 'LZ') + die meldepflichtigen Sätze.""" for deklaration in self: fehler = [] warnungen = [] company = deklaration.company_id stnr = company.l10n_at_lohnsteuernummer if not stnr: fehler.append('Lohnsteuernummer der Company fehlt.') if deklaration.art == 'w1' and len(stnr or '') < 9: fehler.append( 'FANRA/STNRA erfordern eine 9-stellige ' 'Lohnsteuernummer (2 FA + 7 StNr).') schwellen = deklaration._l10n_at_schwellen() if not schwellen: fehler.append( 'Parameter at_m109_schwellen fehlt (Werte nur aus ' 'Quelle — RIS/VO-Verifikation vor Belegung).') if fehler: deklaration.protokoll = 'FEHLER:\n%s' % ( '\n'.join(fehler)) raise UserError('Mitteilungs-Lauf mit Fehlern abgebrochen:\n' + '\n'.join(fehler)) # Schwellenprüfung je Leistungserbringer (Kalenderjahr). if deklaration.art == 'w1': je_leistung = float(schwellen.get('m109a_leistung', 0.0)) kumulativ_cap = float( schwellen.get('m109a_kumulativ', 0.0)) else: je_leistung = None kumulativ_cap = float( schwellen.get('m109b_jahr', 0.0)) nach_le = {} for line in deklaration.line_ids: schluessel = (line.name_partner, line.steuernummer or '', line.versicherungsnummer or '') nach_le.setdefault(schluessel, []).append(line) for schluessel, zeilen in nach_le.items(): summe = sum(z.entgelt for z in zeilen) for line in zeilen: gemeldet = True if deklaration.art == 'w1': unter_schwellen = ( (line.entgelt or 0) <= je_leistung and summe <= kumulativ_cap) if unter_schwellen: gemeldet = False warnungen.append( '%s: Entgelt %.2f unter den §-109a-' 'Schwellen (je Leistung ≤ %.2f, kumulativ ' 'je LE ≤ %.2f) — keine Meldung.' % ( line.name_partner, line.entgelt or 0, je_leistung, kumulativ_cap)) else: if line.ausnahme_grund != 'keine': gemeldet = False warnungen.append( '%s: Ausnahme nach § 109b Abs 4 ' '(%s) — keine Mitteilung.' % ( line.name_partner, line.ausnahme_grund)) elif summe <= kumulativ_cap: gemeldet = False warnungen.append( '%s: Kalenderjahres-Zahlungen %.2f ' 'übersteigen 100.000 € nicht ' '(§ 109b Abs 4 lit a) — keine ' 'Mitteilung.' % ( line.name_partner, summe)) elif not (line.al31 or line.al32 or line.al33): fehler.append( '%s: B1 ohne Art der Leistung (AL31/32/33).' % line.name_partner) elif not (line.ziellaender or '').strip(): fehler.append( '%s: B1 ohne Zielländer-Länderkennung.' % line.name_partner) line.gemeldet = gemeldet if deklaration.art == 'b1': for line in deklaration.line_ids: np_anzahl = len(line.np_ids) ist_koerperschaft = bool( line.steuernummer and not line.versicherungsnummer) if ist_koerperschaft and not np_anzahl: warnungen.append( '%s: Personenvereinigung/Körperschaft als ' 'Leistungserbringer ohne maßgeblich ' 'auftretende natürliche Person ' '(§ 109b Abs 3 Z 2) prüfen.' % line.name_partner) if np_anzahl > 3: fehler.append( '%s: mehr als 3 maßgebliche natürliche ' 'Personen (DM-ORG: 3 Blöcke).' % ( line.name_partner)) if fehler: deklaration.protokoll = 'FEHLER:\n%s\nWARNUNGEN:\n%s' % ( '\n'.join(fehler), '\n'.join(warnungen) or '—') raise UserError('Mitteilungs-Lauf mit Fehlern ' 'abgebrochen:\n' + '\n'.join(fehler)) gemeldete = deklaration.line_ids.filtered('gemeldet') if gemeldete: heute = date.today() jetzt = datetime.now() partner = company.partner_id informationssatz = L10nAtEldaSatz( L10N_AT_ELDA_IDTEIL + L16_INFORMATIONSSATZ, 1100, bezeichnung='Mitteilungs-Informationssatz') informationssatz.setze('SART', 'I1') informationssatz.setze('VSTR', '94') informationssatz.setze('FSART', 'I') informationssatz.setze('CLEAR', 'RADAUS') informationssatz.setze('STNRL', stnr) informationssatz.setze('STNRA', stnr) informationssatz.setze('ARTD', 'LZ') informationssatz.setze_datum('DTUE', heute, 'JJJJMMTT') informationssatz.setze_zeit('ZTUE', jetzt) informationssatz.setze('STVE', '03') informationssatz.setze('ANAM', (partner.name or '')[:37]) informationssatz.setze('AADR', (partner.street or '')[:37]) informationssatz.setze('ALKZ', 'AUT') informationssatz.setze('APLZ', partner.zip) informationssatz.setze('AORT', (partner.city or '')[:30]) informationssatz.setze('JAHR', deklaration.jahr) informationssatz.setze('GESA', len(gemeldete)) informationssatz.setze('ANZA', len(gemeldete)) datensaetze = [informationssatz] + [ deklaration._l10n_at_satz(line, stnr, heute, jetzt) for line in gemeldete] bestand = L10nAtEldaBestand( 'LF', '94', '28', hersteller=deklaration ._l10n_at_hersteller(), datensaetze=datensaetze) datei = L10nAtEldaDatei([bestand]) deklaration.write({ 'datei': base64.b64encode(datei.render()), 'datei_name': 'mitteilung109_%s_%d_%s.txt' % ( deklaration.art, deklaration.jahr, stnr), }) deklaration.protokoll = 'WARNUNGEN:\n%s' % ( '\n'.join(warnungen) or '—') deklaration.state = 'done' return True