mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 16:56:42 +00:00
ec33dfacf2
AP17 — Lohnnachweis L17, Finanz-Mitteilungen § 109a/§ 109b, GfB-Jahreszahlung:
- L17 (DM-ORG E.24, BEST LF, SART A1/1100, Version 28): je
Lohnzahlungszeitraum je DN aus den GP3-Kennzahlen-Flags; Prüfkatalog-
Semantik (KZ 350 = Brutto gesamt, 351/352/354 Teilbeträge) und formale
Prüfungen (350-Formel, 357/347-Caps, aliquotierte 354/HOPA-Caps);
ODL-PDF-Extraktion der Kapitel E.24/E.16/E.26 (txt feldversetzt)
- Finanz-Mitteilungen § 109a (W1, E.16) und § 109b (B1, E.26): Modelle
je Leistungserbringer/Auftragnehmer, Schwellen als hr.rule.parameter
(450/900 — § 109a-VO; 100.000 — § 109b Abs 4 lit a, RIS NOR40263383),
AL31/32/33, LKZ1-7, UID, bis 3 maßgebliche natürliche Personen,
KORR K/S; Unter-Schwellen-Zeilen entfallen mit Protokollhinweis
- mBGM: geringfügig Beschäftigte (BSGR B010/B030) — T01 § 53a-UV-
Pauschale + V01 BMSVG 1,53 %, kein T02 (GfB-SZ beitragsfrei,
§ 49 Abs 3); JAGB-Pakettrennung für die jährliche Abrechnung
(§ 58 Abs 8 ASVG iVm § 6 Abs 2a BMSVG, DM-ORG D.52) mit
2,5 vH-Zuschlag (Z04) in der GfB-Jahresabrechnung (Fälligkeit
15. 1. Folgejahr)
- Fristenkalender: L17- und § 109b-Fristarten (Ende Februar;
§ 109a/§ 109b EStG und § 58 Abs 8 ASVG RIS-verifiziert)
AP18 — Schwerarbeit/NSchG (Beschaffungsgate erledigt, RIS):
- NSchG (RIS 10008502, BGBl 1981/354) Art XI Abs 3 (2 vH Stammnorm,
3,80 % via Sistierung) und Abs 6 (Nachtschwerarbeitsmonat: 6-Tage-
Kriterium, Rückblick-Fenster 12/18, Randmonat); ASchV (20004642)
§ 1/§ 4/§ 5; DM-ORG E.22.2-Erstellvorschriften (Z 1 analog NSchG,
Z 2/4/5 ≥ 15 Tage, Z 3/GfB nicht melden)
- Kern: Schwerarbeitsmeldung (BEST SM, SART 65/66, Länge 800,
Tätigkeitsblöcke 26 × 10) mit Altersfilter- und Erstellvorschrift-
Prüfungen
- Privat: Nachtschwerarbeitsmonat-Zähler (WET ATP_NACHTSCHWER,
Art-XI-Abs-6-Fenster inkl. Randmonat-Proxy) als stored Flag am Lauf
AP19 — Status- und Verhinderungs-Sonderfälle:
- hr.version-Status (Organ ≤/≥ 25 %, lb-vst-03): Organ ≥ 25 % — kein
SV-Verhältnis (SVDN/SVDG 0 via SV-Satz-Override, mBGM-Ausschluss,
LSt läuft weiter); Ferialpraktikanten-Flag (dokumentativ — BMSVG § 6
nennt keinen Ausschluss, Entgelt-/SV-Logik bleibt normal)
- Präsenz-/Ausbildungs-/Zivildienst-Fiktion (§ 7 Abs 1/2 BMSVG):
fiktive BV-Bemessungsgrundlage 435,90 € (KBGG § 3 Abs 1 idF vor
BGBl I 53/2016, lb-vor-04) aliquotiert auf ATP_PRD_TAGE
- Dienstverhinderungs-Sonderfälle bleiben dokumentiert offen
(lb-dvh-01/04, KB-❓); Feiertags-EFZ ist durch das AP4-WET-Modell
abgedeckt
Tests: 21 neue (L17-Positionen/Prüfkatalog, W1/B1-Schwellen, GfB-mBGM/
JAGB/Jahresabrechnung, NSchM-Fenster/Randmonat, SM-Fixsatz, Organ-Sperre,
PRD-Fiktion); Suite 252/252 grün auf frischer -i-DB (85 Kern, 18 GemBG,
213 Privat, 10 Dokumente). Dokumentierte Reste: DZR-36-Fenster,
Randmonat-Proxy und UV-Pauschale-vs-mBGM im Parallellauf (GP8),
fallweise mBGM (G3/G4), 2027er-Schwellen (C7).
573 lines
26 KiB
Python
573 lines
26 KiB
Python
# 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 |