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).
464 lines
21 KiB
Python
464 lines
21 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""Lohnzettel Finanz — Lohnnachweis/Lohnbescheinigung L17 (AP17) —
|
||
DM-ORG 42.7.0, Kapitel E.24 (BEST ``LF``, VSTR ``94``, Satzart ``A1``,
|
||
Länge 1100, Version 28 — zwingender Einsatz ab 01.03.2026, fachliche
|
||
Gültigkeit für Zeiträume ab 01.01.2025; PDF-verifizierte ODL-Extraktion
|
||
2026-09-16 aus `.firecrawl/ris/elda/DM-ORG_*v42.7.0*.pdf`).
|
||
|
||
Der A1-Satz ist je **Lohnzahlungszeitraum** je Dienstnehmer aufzubauen
|
||
(D.32: BELZ/ENLZ/JALZ; REFN je AG+Jahr+Dienstverhältnis eindeutig und
|
||
auch bei Zeiträumsänderung unverändert). Kennzahlen-Semantik laut
|
||
Prüfkatalog L17 (Stand 12/2025, lokal `.firecrawl/ris/elda/`):
|
||
**KZ 350 = Bruttobezüge GESAMT**; die Teil-KZs 351 (Zuwendungen neben
|
||
Arbeitslohn), 352 (Abfertigungen/Abfindungen), 354 (normale ÜStd-
|
||
Zuschläge), 356/738 (Pensionsabfindungen), UEB, 394, KUR, ALT, SOP,
|
||
AUS, ZAKV, MAGB sind **Teilmengen** von 350 (Prüfkatalog-Formel:
|
||
Feld 28 < (Feld 30 + 32 + 34 + 36 + 38 + 50 + 72 + 74 + 76 + 80 + 82
|
||
+ 95 + 100) = Fehler). KZ 738/739 sind nur für Jahre bis 2017 zulässig
|
||
(Pensionsabfindung Pensionskasse).
|
||
|
||
KZ-Mapping aus den validierten Läufen (GP3-Kern-Flags, Muster
|
||
`lohnzettel_l16.py`):
|
||
- 350: laufende Bezüge + alle sonstigen §-67-Bezüge (gesamtes §-25-Brutto
|
||
des LZP inkl. 351/352/354)
|
||
- 351: sonstige Bezüge ohne Abs-3/6-Markierung (Sechstel-Route —
|
||
Sonderzahlungen und Zuwendungen)
|
||
- 352: sonstige Bezüge mit ``l10n_at_s67_abs`` = abs3/abs6
|
||
(Abfertigung Alt / freiwillige Abfertigung / Abfindungen)
|
||
- 354: § 68-freie Überstundenzuschläge (``_l10n_at_freibetraege_68``)
|
||
- 357: einbehaltene SV-Beiträge des laufenden Arbeitslohns
|
||
(SVDN_KV/PV/AV/AK/WF)
|
||
- 347: einbehaltene SV-Beiträge für Zuwendungen (SVDN_SZ)
|
||
- nicht automatisiert (dokumentierter Rest, Grundstellung 0 + Protokoll-
|
||
Hinweis): 356/738 Pensionsabfindungen, 736/737/739 SV-Zerlegung auf
|
||
Abfindungs-KZs, BUEB, 358, BSPE, BARB, WERK, UEBMO, ANZAT, TSTAAT,
|
||
TEPR/MAPR/ZAKV/ANKV/MAGB, KOUN, 394, KUR, ALT, SOP, AUS.
|
||
|
||
Formale Prüfungen nach Prüfkatalog L17: KZ-350-Formel, 357 ≤ 40 % × 350,
|
||
347 ≤ 25 % × 351, 354 ≤ 2.400 € (+1 %) je LZP aliquotiert, HOPA ≤ 3 € ×
|
||
HOTA und ≤ 300 € je LZP, DIEN ≤ 99, WERK/UEBMO ≤ 12, ANZAT ≤ 366,
|
||
HOTA ≤ LZP-Tage."""
|
||
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
|
||
|
||
# ------------------------------------------------------------------
|
||
# Mitteilungssatz A1 (E.24; Länge 1100) — Datenteil
|
||
# ------------------------------------------------------------------
|
||
L17_SATZ = [
|
||
(2, 'FSART', 21, 1, 'a'), # A
|
||
(3, 'CLADR', 22, 6, 'a'),
|
||
(4, 'CLDVR', 28, 7, 'a'),
|
||
(6, 'STNRL', 35, 9, 'n'), # Steuernummer Lohnverrechnungsstelle
|
||
(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+DV eindeutig
|
||
(12, 'RE12', 96, 2, 'a'),
|
||
(13, 'FEHL', 98, 20, 'a'),
|
||
(14, 'STAT', 118, 1, 'a'),
|
||
(15, 'FIND', 119, 1, 'a'), # 'J' = vom Hersteller für richtig
|
||
(16, 'BELZ', 120, 4, 'n'), # Beginn LZP TTMM
|
||
(17, 'ENLZ', 124, 4, 'n'), # Ende LZP TTMM
|
||
(18, 'JALZ', 128, 4, 'n'), # Jahr LZP
|
||
(19, 'PENS', 132, 1, 'a'), # J/N
|
||
(20, 'AVSN', 133, 10, 'n'), # LLLPTTMMJJ
|
||
(21, 'ANAM', 143, 37, 'a'),
|
||
(22, 'ATIT', 180, 20, 'a'),
|
||
(23, 'AADR', 200, 37, 'a'),
|
||
(24, 'ALKZ', 237, 3, 'a'),
|
||
(25, 'APLZ', 240, 10, 'a'),
|
||
(26, 'AORT', 250, 30, 'a'),
|
||
(27, 'V350', 280, 1, 'a'),
|
||
(28, 'B350', 281, 10, 'n'), # Bruttobezüge gesamt (KZ 350)
|
||
(29, 'V351', 291, 1, 'a'),
|
||
(30, 'B351', 292, 10, 'n'), # Zuwendungen neben Arbeitslohn
|
||
(31, 'V352', 302, 1, 'a'),
|
||
(32, 'B352', 303, 10, 'n'), # Abfertigungen und Abfindungen
|
||
(33, 'V354', 313, 1, 'a'),
|
||
(34, 'B354', 314, 10, 'n'), # (normale) ÜStd-Zuschläge
|
||
(35, 'V356', 324, 1, 'a'),
|
||
(36, 'B356', 325, 10, 'n'), # Pensionsabfindung Arbeitgeber
|
||
(37, 'V738', 335, 1, 'a'),
|
||
(38, 'B738', 336, 10, 'n'), # Pensionsabfindung Pensionskasse
|
||
(39, 'V357', 346, 1, 'a'),
|
||
(40, 'B357', 347, 10, 'n'), # SV-Beiträge laufender Arbeitslohn
|
||
(41, 'V347', 357, 1, 'a'),
|
||
(42, 'B347', 358, 10, 'n'), # SV-Beiträge für Zuwendungen
|
||
(43, 'V736', 368, 1, 'a'),
|
||
(44, 'B736', 369, 10, 'n'), # SV-Beiträge für KZ 352
|
||
(45, 'V737', 379, 1, 'a'),
|
||
(46, 'B737', 380, 10, 'n'), # SV-Beiträge für KZ 356
|
||
(47, 'V739', 390, 1, 'a'),
|
||
(48, 'B739', 391, 10, 'n'), # SV-Beiträge für KZ 738
|
||
(49, 'VUEB', 401, 1, 'a'),
|
||
(50, 'BUEB', 402, 10, 'n'), # übrige Bezüge außerhalb Österreichs
|
||
(51, 'RE51', 412, 1, 'a'),
|
||
(52, 'RE52', 413, 10, 'a'),
|
||
(53, 'RE53', 423, 1, 'a'),
|
||
(54, 'RE54', 424, 10, 'a'),
|
||
(55, 'V358', 434, 1, 'a'),
|
||
(56, 'B358', 435, 10, 'n'), # anrechenbare ausländische Steuer
|
||
(57, 'VSPE', 445, 1, 'a'),
|
||
(58, 'BSPE', 446, 10, 'n'), # Spesensätze außerhalb KZ 350
|
||
(59, 'VARB', 456, 1, 'a'),
|
||
(60, 'BARB', 457, 10, 'n'), # AG-Beiträge an Pensionskassen
|
||
(61, 'DIEN', 467, 2, 'n'), # Dienstzeit (Jahre) bei KZ 352
|
||
(62, 'STDT', 469, 8, 'n'), # Sterbedatum JJJJMMTT
|
||
(63, 'OELD', 477, 20, 'a'), # Ordnungsbegriff ELDA
|
||
(64, 'KORR', 497, 1, 'a'), # 'K'/'S'
|
||
(65, 'INTE', 498, 11, 'a'),
|
||
(66, 'WOBD', 509, 12, 'a'),
|
||
(67, 'ZOBD', 521, 15, 'a'),
|
||
(68, 'WAHR', 536, 3, 'a'), # Währung
|
||
(69, 'MUEB', 539, 2, 'n'), # Monate überwiegende Nachtarbeit
|
||
(70, 'V394', 541, 1, 'a'),
|
||
(71, 'B394', 542, 10, 'n'), # Sonn-/Feiertags-/Nachtzuschläge
|
||
(72, 'VKUR', 552, 1, 'a'),
|
||
(73, 'BKUR', 553, 10, 'n'), # Kurzarbeit
|
||
(74, 'VALT', 563, 1, 'a'),
|
||
(75, 'BALT', 564, 10, 'n'), # Altersteilzeit
|
||
(76, 'RE76', 574, 1, 'a'),
|
||
(77, 'RE77', 575, 10, 'n'),
|
||
(78, 'VSOP', 585, 1, 'a'),
|
||
(79, 'BSOP', 586, 10, 'n'), # Sozialplanzahlungen
|
||
(80, 'VAUS', 596, 1, 'a'),
|
||
(81, 'BAUS', 597, 10, 'n'), # begünstigte Auslandstätigkeit
|
||
(82, 'WERK', 607, 2, 'n'), # Werkverkehr (Monate)
|
||
(83, 'UEBMO', 609, 2, 'n'), # Überlassung Kfz (Monate)
|
||
(84, 'ANZAT', 611, 3, 'n'), # Arbeitstage begünstigte Auslandstätigk.
|
||
(85, 'HOTA', 614, 3, 'n'), # Telearbeitstage (Homeoffice)
|
||
(86, 'VHOPA', 617, 1, 'a'),
|
||
(87, 'HOPA', 618, 10, 'n'), # Homeoffice-Pauschale (§ 26 Z 9 lit. a)
|
||
(88, 'VKOUN', 628, 1, 'a'),
|
||
(89, 'KOUN', 629, 10, 'n'), # Kostenübernahme § 26 Z 5 lit. b
|
||
(90, 'TSTAAT', 639, 3, 'a'), # Tätigkeitsstaat
|
||
(91, 'VTEPR', 642, 1, 'a'),
|
||
(92, 'TEPR', 643, 10, 'n'), # Teuerungsprämie (§ 124b Z 408)
|
||
(93, 'VMAPR', 653, 1, 'a'),
|
||
(94, 'MAPR', 654, 10, 'n'), # Mitarbeiterprämie (§ 124b Z 478)
|
||
(95, 'VZAKV', 664, 1, 'a'),
|
||
(96, 'ZAKV', 665, 10, 'n'), # AG-Zuschuss freiw. KV
|
||
(97, 'ANKV', 675, 1, 'a'), # (freiw.) gesetzlich krankenversichert
|
||
(98, 'VMAGB', 676, 1, 'a'),
|
||
(99, 'MAGB', 677, 10, 'n'), # Mitarbeitergewinnbeteiligung
|
||
(100, 'RE100', 687, 314, 'a'),
|
||
(101, 'REFN2', 1001, 30, 'a'), # Softwarehersteller-REFN
|
||
(102, 'RE102', 1031, 70, 'a'),
|
||
]
|
||
|
||
|
||
class L10nAtPayrollL17Line(models.Model):
|
||
_name = 'l10n.at.payroll.l17.line'
|
||
_description = 'L17 — Lohnnachweis je Dienstnehmer und LZP'
|
||
_order = 'employee_name, date_from'
|
||
|
||
l17_id = fields.Many2one(
|
||
'l10n.at.payroll.l17', required=True, ondelete='cascade',
|
||
index=True)
|
||
employee_id = fields.Many2one('hr.employee', required=True)
|
||
employee_name = fields.Char(related='employee_id.name',
|
||
readonly=True, store=True)
|
||
date_from = fields.Date(string='LZP von', required=True)
|
||
date_to = fields.Date(string='LZP bis', required=True)
|
||
versicherungsnummer = fields.Char()
|
||
kz350 = fields.Monetary(currency_field='currency_id')
|
||
kz351 = fields.Monetary(currency_field='currency_id')
|
||
kz352 = fields.Monetary(currency_field='currency_id')
|
||
kz354 = fields.Monetary(currency_field='currency_id')
|
||
kz357 = fields.Monetary(currency_field='currency_id')
|
||
kz347 = fields.Monetary(currency_field='currency_id')
|
||
hota = fields.Integer(string='Homeoffice-Tage')
|
||
hopa = fields.Monetary(currency_field='currency_id')
|
||
mueb = fields.Integer(string='Monate überwiegende Nachtarbeit')
|
||
refn = fields.Char()
|
||
currency_id = fields.Many2one(
|
||
'res.currency', related='l17_id.company_id.currency_id')
|
||
|
||
def _l10n_at_pruefungen(self, fehler, warnungen):
|
||
"""Formale Prüfungen nach Prüfkatalog L17 (Stand 12/2025)."""
|
||
self.ensure_one()
|
||
lzp_tage = max(1, (self.date_to - self.date_from).days + 1)
|
||
teilsumme = self.kz351 + self.kz352 + self.kz354
|
||
if self.kz350 + 0.02 < teilsumme:
|
||
fehler.append(
|
||
'%s (%s): KZ 350 < 351+352+354 (Prüfkatalog-Formel).'
|
||
% (self.employee_name, self.date_from))
|
||
if self.kz357 > self.kz350 * 0.4 + 0.02:
|
||
fehler.append(
|
||
'%s (%s): KZ 357 > 40 %% von KZ 350 (Prüfkatalog).'
|
||
% (self.employee_name, self.date_from))
|
||
if self.kz351 and self.kz347 > self.kz351 * 0.25 + 0.02:
|
||
fehler.append(
|
||
'%s (%s): KZ 347 > 25 %% von KZ 351 (Prüfkatalog).'
|
||
% (self.employee_name, self.date_from))
|
||
if self.kz354 > 2400.0 * lzp_tage / 365.0 * 1.01 + 0.02:
|
||
warnungen.append(
|
||
'%s (%s): KZ 354 übersteigt den aliquotierten '
|
||
'Prüfkatalog-Cap 2.400 € je Jahr (+1 %% Toleranz).'
|
||
% (self.employee_name, self.date_from))
|
||
if self.hota > lzp_tage:
|
||
fehler.append(
|
||
'%s (%s): HOTA > LZP-Tage.' % (
|
||
self.employee_name, self.date_from))
|
||
if self.hopa > self.hota * 3.0 + 0.02:
|
||
fehler.append(
|
||
'%s (%s): HOPA > 3 € × HOTA.' % (
|
||
self.employee_name, self.date_from))
|
||
if self.hopa > 300.0 * lzp_tage / 365.0 * 1.01 + 0.02:
|
||
warnungen.append(
|
||
'%s (%s): HOPA übersteigt den aliquotierten '
|
||
'Prüfkatalog-Cap 300 € je Jahr.' % (
|
||
self.employee_name, self.date_from))
|
||
|
||
|
||
class L10nAtPayrollL17(models.Model):
|
||
_name = 'l10n.at.payroll.l17'
|
||
_description = 'L17 — Lohnnachweis/Lohnbescheinigung (Finanz)'
|
||
_inherit = ['mail.thread']
|
||
_order = 'jahr desc, id desc'
|
||
|
||
company_id = fields.Many2one(
|
||
'res.company', required=True,
|
||
default=lambda self: self.env.company)
|
||
jahr = fields.Integer(required=True, string='Jahr')
|
||
line_ids = fields.One2many(
|
||
'l10n.at.payroll.l17.line', 'l17_id', string='A1-Sätze')
|
||
datei = fields.Binary(attachment=True, string='L17-Datei', copy=False)
|
||
datei_name = fields.Char(copy=False)
|
||
protokoll = fields.Text(
|
||
string='Prüfprotokoll',
|
||
help='Formale Prüfung nach Prüfkatalog L17 (Stand 12/2025) und '
|
||
'dokumentierte Reste der KZ-Belegung.')
|
||
state = fields.Selection(
|
||
[('draft', 'Entwurf'), ('done', 'Erzeugt')], default='draft',
|
||
required=True, tracking=True)
|
||
|
||
_sql_constraints = [(
|
||
'l17_company_jahr_uniq', 'unique(company_id, jahr)',
|
||
'Für dieses Jahr existiert bereits eine L17-Deklaration.')]
|
||
|
||
def _l10n_at_slips(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')),
|
||
], order='date_from, id')
|
||
|
||
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}
|
||
|
||
@api.model
|
||
def _vorzeichen(self, betrag):
|
||
if not betrag:
|
||
return None
|
||
return '+' if betrag > 0 else '-'
|
||
|
||
def _l10n_at_kz_salden(self, slip):
|
||
"""(salden, hota) je Lohnzahlungszeitraum aus dem Lauf —
|
||
Kennzahlen-Mapping siehe Modul-Doku (GP3-Flags, Muster
|
||
`_kz_salden` der L16-Zeile)."""
|
||
self.ensure_one()
|
||
salden = dict.fromkeys(
|
||
('kz350', 'kz351', 'kz352', 'kz354', 'kz357', 'kz347',
|
||
'hopa'), 0.0)
|
||
hota = 0
|
||
for line in slip.line_ids:
|
||
rule = line.salary_rule_id
|
||
if not line.total:
|
||
continue
|
||
if rule.l10n_at_bemessung == 'laufend':
|
||
salden['kz350'] += line.total
|
||
if line.code == 'SB_HOMEOFFICE':
|
||
salden['hopa'] += line.total
|
||
elif rule.l10n_at_bemessung == 'sonstig':
|
||
salden['kz350'] += line.total
|
||
if rule.l10n_at_s67_abs in ('abs3', 'abs6'):
|
||
salden['kz352'] += line.total
|
||
else:
|
||
salden['kz351'] += line.total
|
||
elif line.code == 'SVDN_SZ':
|
||
salden['kz347'] += line.total
|
||
elif line.code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV',
|
||
'SVDN_AK', 'SVDN_WF'):
|
||
salden['kz357'] += line.total
|
||
freibe = slip._l10n_at_freibetraege_68()
|
||
salden['kz354'] = freibe['ue_std']
|
||
for eingabe in slip.input_line_ids:
|
||
if eingabe.input_type_id.code == 'ATP_SB_HOMEOFFICE':
|
||
hota += int(eingabe.quantity or 0)
|
||
return salden, hota
|
||
|
||
def _l10n_at_a1_satz(self, line, stnr, heute, jetzt):
|
||
self.ensure_one()
|
||
employee = line.employee_id
|
||
version = employee.version_id
|
||
satz = L10nAtEldaSatz(
|
||
L10N_AT_ELDA_IDTEIL + L17_SATZ, 1100,
|
||
bezeichnung='A1 %s %s' % (employee.name, line.date_from))
|
||
satz.setze('SART', 'A1')
|
||
satz.setze('VSTR', '94')
|
||
satz.setze('FSART', 'A')
|
||
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('BELZ', line.date_from.strftime('%d%m'))
|
||
satz.setze('ENLZ', line.date_to.strftime('%d%m'))
|
||
satz.setze('JALZ', self.jahr)
|
||
satz.setze('PENS', 'N')
|
||
satz.setze('AVSN', line.versicherungsnummer)
|
||
satz.setze('ANAM', (employee.name or '')[:37])
|
||
satz.setze('AADR', (version.private_street or '')[:37]
|
||
if version else '')
|
||
satz.setze('ALKZ', (version.private_country_id.code
|
||
if version and version.private_country_id
|
||
else 'AUT'))
|
||
satz.setze('APLZ', version.private_zip if version else '')
|
||
satz.setze('AORT', (version.private_city or '')[:30]
|
||
if version else '')
|
||
for betrag, vfeld, bfeld, negativ in (
|
||
(line.kz350, 'V350', 'B350', False),
|
||
(line.kz351, 'V351', 'B351', False),
|
||
(line.kz352, 'V352', 'B352', True),
|
||
(line.kz354, 'V354', 'B354', True),
|
||
(line.kz357, 'V357', 'B357', False),
|
||
(line.kz347, 'V347', 'B347', True)):
|
||
if betrag < 0 and not negativ:
|
||
raise UserError(
|
||
'%s (%s): negativer Betrag in %s — das Vorzeichenfeld '
|
||
'zulässigerweise "-" nur nach DM-ORG-Feldbeschreibung.'
|
||
% (employee.name, line.date_from, vfeld))
|
||
satz.setze(vfeld, self._vorzeichen(betrag))
|
||
satz.setze(bfeld, abs(betrag) if betrag else 0, cent=True)
|
||
if line.mueb:
|
||
satz.setze('MUEB', line.mueb)
|
||
if line.hota:
|
||
satz.setze('HOTA', line.hota)
|
||
if line.hopa:
|
||
satz.setze('VHOPA', self._vorzeichen(line.hopa))
|
||
satz.setze('HOPA', abs(line.hopa), cent=True)
|
||
satz.setze('WOBD', 'EMP-%d' % employee.id)
|
||
satz.setze('REFN2', line.refn)
|
||
return satz
|
||
|
||
def action_erzeugen(self):
|
||
"""A1 je (Dienstnehmer, Lohnzahlungszeitraum) aus validierten
|
||
Läufen; formale Prüfungen nach Prüfkatalog L17; Fehler brechen
|
||
ab (UserError, DmfA-Muster), Warnungen ins Protokoll."""
|
||
for deklaration in self:
|
||
fehler = []
|
||
warnungen = []
|
||
company = deklaration.company_id
|
||
if not company.l10n_at_lohnsteuernummer:
|
||
fehler.append(
|
||
'Lohnsteuernummer der Company fehlt '
|
||
'(l10n_at_lohnsteuernummer).')
|
||
deklaration.line_ids.unlink()
|
||
for slip in deklaration._l10n_at_slips():
|
||
version = slip.version_id or slip.employee_id.version_id
|
||
if not (version and version.ssnid):
|
||
fehler.append(
|
||
'Mitarbeiter %s ohne Versicherungsnummer '
|
||
'(hr.version.ssnid).' % slip.employee_id.name)
|
||
continue
|
||
if not slip.employee_id.birthday:
|
||
fehler.append(
|
||
'Mitarbeiter %s ohne Geburtsdatum.'
|
||
% slip.employee_id.name)
|
||
continue
|
||
salden, hota = deklaration._l10n_at_kz_salden(slip)
|
||
refn = 'L17-%d-%d-%d-%s' % (
|
||
company.id, deklaration.jahr, slip.employee_id.id,
|
||
slip.date_from.strftime('%Y%m%d'))
|
||
line = deklaration.env['l10n.at.payroll.l17.line'].create({
|
||
'l17_id': deklaration.id,
|
||
'employee_id': slip.employee_id.id,
|
||
'date_from': slip.date_from,
|
||
'date_to': slip.date_to,
|
||
'versicherungsnummer': version.ssnid,
|
||
'kz350': salden['kz350'],
|
||
'kz351': salden['kz351'],
|
||
'kz352': salden['kz352'],
|
||
'kz354': salden['kz354'],
|
||
'kz357': salden['kz357'],
|
||
'kz347': salden['kz347'],
|
||
'hota': hota,
|
||
'hopa': salden['hopa'],
|
||
'mueb': 0,
|
||
'refn': refn,
|
||
})
|
||
line._l10n_at_pruefungen(fehler, warnungen)
|
||
if deklaration.line_ids:
|
||
for line in deklaration.line_ids:
|
||
if line.kz352:
|
||
warnungen.append(
|
||
'%s: KZ 352 belegt — die SV-Zerlegung auf '
|
||
'KZ 736 bleibt dokumentierter Rest (v1 = 0); '
|
||
'Prüfkatalog-Grenze 40 %% × KZ 352 prüfen.'
|
||
% line.employee_name)
|
||
if fehler:
|
||
deklaration.protokoll = 'FEHLER:\n%s\nWARNUNGEN:\n%s' % (
|
||
'\n'.join(fehler), '\n'.join(warnungen) or '—')
|
||
raise UserError('L17-Lauf mit Fehlern abgebrochen:\n'
|
||
+ '\n'.join(fehler))
|
||
if deklaration.line_ids:
|
||
heute = date.today()
|
||
jetzt = datetime.now()
|
||
partner = company.partner_id
|
||
stnr = company.l10n_at_lohnsteuernummer
|
||
informationssatz = L10nAtEldaSatz(
|
||
L10N_AT_ELDA_IDTEIL + L16_INFORMATIONSSATZ, 1100,
|
||
bezeichnung='L17-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(deklaration.line_ids))
|
||
informationssatz.setze('ANZA', len(deklaration.line_ids))
|
||
datensaetze = [informationssatz] + [
|
||
deklaration._l10n_at_a1_satz(line, stnr, heute, jetzt)
|
||
for line in deklaration.line_ids]
|
||
bestand = L10nAtEldaBestand(
|
||
'LF', '94', '28', hersteller=deklaration
|
||
._l10n_at_hersteller(), datensaetze=datensaetze)
|
||
datei = L10nAtEldaDatei([bestand])
|
||
deklaration.write({
|
||
'datei': base64.b64encode(datei.render()),
|
||
'datei_name': 'l17_%d_%s.txt' % (
|
||
deklaration.jahr, stnr),
|
||
})
|
||
deklaration.protokoll = 'WARNUNGEN:\n%s' % (
|
||
'\n'.join(warnungen) or '—')
|
||
deklaration.state = 'done'
|
||
return True |