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).
295 lines
12 KiB
Python
295 lines
12 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""Fristenkalender (GP8/AP8): alle lohnverrechnungsrelevanten
|
||
Melde-/Zahlungsfristen je Company und Jahr.
|
||
|
||
Kuratierte Fristen (lb-sva-11, lb-mel-08, lb-lvr-04/05, Stand
|
||
2026-06…08):
|
||
- SV-Beiträge (mBGM): Einlangen **15. des Folgemonats** (Gutschrift-
|
||
prinzip; Respirofrist 3 Tage; Verzugszinsen ab dem 16. Tag);
|
||
Vorschreibebetriebe 7.
|
||
- DB (FLAF) / DZ / KommSt: je **15. des Folgemonats**.
|
||
- KommSt-Jahreserklärung: über FinanzOnline **bis Ende März** des
|
||
Folgejahres.
|
||
- Lohnzettel L16 (§ 84 EStG): **Ende Februar** des Folgejahres via
|
||
ELDA (Papierformular ohne technische Voraussetzungen: Ende Jänner);
|
||
berichtigter Lohnzettel nach steuerlich relevanter Ergänzung des
|
||
Lohnkontos **binnen 14 Tagen**.
|
||
- Arbeitsstättenmeldung § 34 Abs 6 ASVG: **Ende Februar** (via ELDA
|
||
an Statistik Austria).
|
||
- § 109a-EStG-Meldung: **Ende Februar** (ELDA); Papierformular E 109a
|
||
+ Kopie an den Entgeltempfänger Ende Jänner (nicht implementiert —
|
||
Honorar-Basis).
|
||
- Schwerarbeitsmeldung ASchV § 5: Jänner bis **Ende Februar** via
|
||
ELDA (nicht implementiert — blockiert, ASchV-Datenmodell fehlt).
|
||
|
||
Odoo-Integration: `mail.activity.mixin` für optionale Benachrichtigung
|
||
bei Überschreitung; native Fristart-Definition mit Sprungaktion zur
|
||
GP7-Deklaration (L16/mBGM erzeugen); Auto-Erzeugung per ir.cron
|
||
(monatlich am 1.: offene SV/DB/DZ/KommSt-Fristen des Vormonats;
|
||
jährlich am 1. Jänner: Jahresfristen des Vorjahres)."""
|
||
from datetime import date, timedelta
|
||
|
||
from odoo import api, fields, models
|
||
|
||
FRISTART_DEFINITIONEN = {
|
||
'sv_mbgm': {
|
||
'name': 'SV-Beiträge (mBGM) einlangen',
|
||
'monatlich': True,
|
||
'tag': 15,
|
||
'action': 'mbgm',
|
||
'hilfetext': '15. des Folgemonats (Gutschriftprinzip; '
|
||
'Vorschreibebetriebe: 7.). Verzugszinsen ab dem '
|
||
'16. Tag.',
|
||
},
|
||
'db_dz_kommst': {
|
||
'name': 'DB/DZ/KommSt einlangen',
|
||
'monatlich': True,
|
||
'tag': 15,
|
||
'action': None,
|
||
'hilfetext': 'FLAF-Dienstgeberbeitrag, Zuschlag zum '
|
||
'Dienstgeberbeitrag und Kommunalsteuer: je 15. '
|
||
'des Folgemonats.',
|
||
},
|
||
'kommst_jahres': {
|
||
'name': 'KommSt-Jahreserklärung (FinanzOnline)',
|
||
'monatlich': False,
|
||
'monat': 3,
|
||
'tag_im_monat': 31,
|
||
'action': None,
|
||
'hilfetext': 'Jahreserklärung der Kommunalsteuer über '
|
||
'FinanzOnline bis Ende März des Folgejahres '
|
||
'(§ 11 Abs 5 KommStG).',
|
||
},
|
||
'l16': {
|
||
'name': 'Lohnzettel L16 (§ 84 EStG)',
|
||
'monatlich': False,
|
||
'monat': 2,
|
||
'tag_im_monat': 28,
|
||
'action': 'l16',
|
||
'hilfetext': 'Jahres-Lohnzettel bis Ende Februar des '
|
||
'Folgejahres via ELDA; Papierformular ohne '
|
||
'technische Voraussetzungen: Ende Jänner. '
|
||
'Berichtigter Lohnzettel nach steuerlich '
|
||
'relevanter Ergänzung des Lohnkontos binnen '
|
||
'14 Tagen.',
|
||
},
|
||
'arbeitsstaette': {
|
||
'name': 'Arbeitsstättenmeldung (§ 34 Abs 6 ASVG)',
|
||
'monatlich': False,
|
||
'monat': 2,
|
||
'tag_im_monat': 28,
|
||
'action': 'l16',
|
||
'hilfetext': 'Adresse der Arbeitsstätte am 31. 12. bis '
|
||
'Ende Februar des Folgejahres via ELDA (VSTR '
|
||
'ST). Läuft im GP7-Jahreslauf mit der L16.',
|
||
},
|
||
'paragraph_109a': {
|
||
'name': '§ 109a-EStG-Meldung (Honorare)',
|
||
'monatlich': False,
|
||
'monat': 2,
|
||
'tag_im_monat': 28,
|
||
'action': None,
|
||
'hilfetext': 'Meldung der Honorare außerhalb von '
|
||
'Dienstverhältnissen bis Ende Februar via '
|
||
'ELDA (8 Tätigkeitsgruppen; kumulativ ≤ 900 € '
|
||
'und ≤ 450 € je Leistung → entfällt). '
|
||
'Fixsatz: l10n.at.payroll.mitteilung109 '
|
||
'(AP17).',
|
||
},
|
||
'mitteilung_109b': {
|
||
'name': '§ 109b-EStG-Meldung (Auslandszahlungen)',
|
||
'monatlich': False,
|
||
'monat': 2,
|
||
'tag_im_monat': 28,
|
||
'action': None,
|
||
'hilfetext': 'Mitteilung über Zahlungen ins Ausland '
|
||
'(§ 109b EStG: §-22-Tätigkeiten, Vermittlung, '
|
||
'Beratung im Inland) elektronisch bis Ende '
|
||
'Februar des Folgejahres; amtlicher Vordruck '
|
||
'bis Ende Jänner (§ 109b Abs 6, RIS '
|
||
'NOR40263383); entfällt unter der '
|
||
'100.000-€-Schwelle bzw. bei Steuerabzug '
|
||
'§ 99/KSt-Vergleich (Abs 4). Fixsatz: '
|
||
'l10n.at.payroll.mitteilung109 (AP17).',
|
||
},
|
||
'l17': {
|
||
'name': 'Lohnnachweis/Lohnbescheinigung L17 (A1)',
|
||
'monatlich': False,
|
||
'monat': 2,
|
||
'tag_im_monat': 28,
|
||
'action': 'l16',
|
||
'hilfetext': 'Lohnzettel Finanz je Lohnzahlungszeitraum '
|
||
'(DM-ORG E.24, Satzart A1, Version 28 — '
|
||
'zwingender Einsatz ab 01.03.2026, fachliche '
|
||
'Gültigkeit für Zeiträume ab 01.01.2025); '
|
||
'Übermittlung im Jahreslauf mit der L16 '
|
||
'(Ende Februar).',
|
||
},
|
||
'schwerarbeit': {
|
||
'name': 'Schwerarbeitsmeldung (ASchV § 5)',
|
||
'monatlich': False,
|
||
'monat': 2,
|
||
'tag_im_monat': 28,
|
||
'action': None,
|
||
'hilfetext': 'Jährlich rückwirkend Jänner bis Ende Februar '
|
||
'via ELDA an den KV-Träger; meldepflichtig '
|
||
'ab 40 M / 35 F (⚠ blockiert — ASchV-Daten-'
|
||
'modell fehlt im Modul).',
|
||
},
|
||
'wien_dag': {
|
||
'name': 'Wr. Dienstgeberabgabe entrichten (Stadtkasse)',
|
||
'monatlich': True,
|
||
'tag': 15,
|
||
'action': None,
|
||
'wien_dag': True,
|
||
'hilfetext': 'Wr. DAG („U-Bahn-Steuer“, GesNr 20000133): '
|
||
'Selbstberechnungsabgabe — die im Vormonat '
|
||
'entstandene Abgabenschuld bis zum 15. des '
|
||
'Folgemonats an die Stadtkasse (Magistrat) '
|
||
'entrichten (§ 6 Abs 1). Nur Firmen mit Flag '
|
||
'l10n_at_wien_dag (Beschäftigungsort Wien).',
|
||
},
|
||
'wien_dag_jahres': {
|
||
'name': 'Wr. DAG-Jahreserklärung (Magistrat)',
|
||
'monatlich': False,
|
||
'monat': 3,
|
||
'tag_im_monat': 31,
|
||
'action': None,
|
||
'wien_dag': True,
|
||
'hilfetext': 'Bis 31. März die im Vorjahr entstandene '
|
||
'Abgabenschuld beim Magistrat schriftlich '
|
||
'erklären; darin auch die befreiten '
|
||
'Dienstverhältnisse angeben (§ 6 Abs 2 Wr. '
|
||
'DAG). Nur Firmen mit Flag l10n_at_wien_dag.',
|
||
},
|
||
}
|
||
|
||
|
||
class L10nAtPayrollFrist(models.Model):
|
||
_name = 'l10n.at.payroll.frist'
|
||
_description = 'Lohnverrechnungs-Fristenkalender'
|
||
_inherit = ['mail.thread']
|
||
_order = 'deadline, id'
|
||
|
||
company_id = fields.Many2one(
|
||
'res.company', required=True,
|
||
default=lambda self: self.env.company, index=True)
|
||
fristart = fields.Selection(
|
||
[(k, v['name']) for k, v in FRISTART_DEFINITIONEN.items()],
|
||
required=True, index=True)
|
||
jahr = fields.Integer(
|
||
required=True, string='Bezugs-/Kalenderjahr',
|
||
help='Beitragsjahr (monatliche Fristen) bzw. gemeldetes '
|
||
'Jahr (Jahresfristen: L16/Arbeitsstätte/§-109a gelten '
|
||
'für das Vorjahr).')
|
||
monat = fields.Integer(
|
||
string='Beitragsmonat (monatliche Fristen)',
|
||
help='1–12; leer bei Jahresfristen.')
|
||
deadline = fields.Date(required=True, string='Frist')
|
||
state = fields.Selection(
|
||
[('offen', 'Offen'), ('erledigt', 'Erledigt'),
|
||
('ueberfaellig', 'Überfällig')],
|
||
compute='_compute_state', store=True, index=True)
|
||
erledigt_am = fields.Date(readonly=True)
|
||
hilfetext = fields.Text(readonly=True)
|
||
ref_l16_id = fields.Many2one(
|
||
'l10n.at.payroll.l16', string='L16-Deklaration',
|
||
help='Sprungziel für Fristen mit L16/Arbeitsstätten-Meldung.')
|
||
|
||
_sql_constraints = [(
|
||
'frist_company_art_jahr_monat_uniq',
|
||
'unique(company_id, fristart, jahr, monat)',
|
||
'Diese Frist existiert bereits für das Unternehmen.')]
|
||
|
||
@api.depends('deadline', 'erledigt_am')
|
||
def _compute_state(self):
|
||
heute = fields.Date.today()
|
||
for frist in self:
|
||
if frist.erledigt_am:
|
||
frist.state = 'erledigt'
|
||
elif frist.deadline and frist.deadline < heute:
|
||
frist.state = 'ueberfaellig'
|
||
else:
|
||
frist.state = 'offen'
|
||
|
||
def action_erledigt(self):
|
||
self.write({'erledigt_am': fields.Date.today()})
|
||
return True
|
||
|
||
def action_wieder_eroeffnen(self):
|
||
self.write({'erledigt_am': False})
|
||
return True
|
||
|
||
def action_zum_objekt(self):
|
||
"""Sprung zur GP7-Deklaration (L16 oder mBGM)."""
|
||
self.ensure_one()
|
||
if self.ref_l16_id:
|
||
return {
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'l10n.at.payroll.l16',
|
||
'res_id': self.ref_l16_id.id,
|
||
'view_mode': 'form',
|
||
}
|
||
return True
|
||
|
||
@api.model
|
||
def _cron_fristen_erzeugen(self):
|
||
"""Monatlich am 1.: offene Fristen des Vormonats (SV/DB/DZ/
|
||
KommSt) + jährlich am 1. Jänner: Jahresfristen des
|
||
Vorjahres. Idempotent (Unique-Constraint)."""
|
||
heute = fields.Date.today()
|
||
vormonat = (heute.replace(day=1) - timedelta(days=1))
|
||
for company in self.env['res.company'].search([]):
|
||
# Monatliche Fristen des Vormonats (15. des Folgemonats).
|
||
folgemonat = heute.replace(day=1)
|
||
deadline_monat = date(
|
||
folgemonat.year, folgemonat.month,
|
||
FRISTART_DEFINITIONEN['sv_mbgm']['tag'])
|
||
monatliche_arten = ['sv_mbgm', 'db_dz_kommst']
|
||
if company.l10n_at_wien_dag:
|
||
monatliche_arten.append('wien_dag')
|
||
for art in monatliche_arten:
|
||
self._sicherstellen(
|
||
company, art, vormonat.year, vormonat.month,
|
||
deadline_monat)
|
||
# Jahresfristen nur am 1. Jänner (für das Vorjahr).
|
||
if heute.month == 1:
|
||
for art in ('l16', 'arbeitsstaette', 'paragraph_109a',
|
||
'mitteilung_109b', 'l17', 'schwerarbeit'):
|
||
monat = FRISTART_DEFINITIONEN[art]['monat']
|
||
tag = FRISTART_DEFINITIONEN[art]['tag_im_monat']
|
||
jahr_deadline = heute.year
|
||
deadline_jahr = date(jahr_deadline, monat, tag)
|
||
if deadline_jahr.month == 2 and tag > 28:
|
||
deadline_jahr = date(jahr_deadline, monat, 28)
|
||
self._sicherstellen(
|
||
company, art, heute.year - 1, None,
|
||
deadline_jahr)
|
||
# KommSt-Jahreserklärung (Ende März).
|
||
self._sicherstellen(
|
||
company, 'kommst_jahres', heute.year - 1, None,
|
||
date(heute.year, 3, 31))
|
||
# Wr. DAG-Jahreserklärung (31. 3., § 6 Abs 2).
|
||
if company.l10n_at_wien_dag:
|
||
self._sicherstellen(
|
||
company, 'wien_dag_jahres', heute.year - 1,
|
||
None, date(heute.year, 3, 31))
|
||
|
||
@api.model
|
||
def _sicherstellen(self, company, art, jahr, monat, deadline):
|
||
vals = {
|
||
'company_id': company.id,
|
||
'fristart': art,
|
||
'jahr': jahr,
|
||
'monat': monat,
|
||
'deadline': deadline,
|
||
'hilfetext': FRISTART_DEFINITIONEN[art]['hilfetext'],
|
||
}
|
||
bestehend = self.search([
|
||
('company_id', '=', company.id),
|
||
('fristart', '=', art),
|
||
('jahr', '=', jahr),
|
||
('monat', '=', monat)])
|
||
if not bestehend:
|
||
self.create(vals)
|
||
return bestehend or self.env['l10n.at.payroll.frist'] |