mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 08:52:45 +00:00
[IMP] l10n_at_hr_payroll_private: GP7 mBGM/JASV + Versichertenmeldung + fallweise Anmeldung auf dem ELDA-Framework
mBGM/JASV (models/mbgm.py — DM-ORG 42.7.0 E.32, BEST MB, Version 02): Paket PS (BKNR/DGNA/BZRM/GSUM/ANZM) → mBGM G1 (REFW/VSNR/FANA/VONA/VSUM/VERG=1) → Tarifblock T1 (BSGR/VVON) → Verrechnungsbasis BS (VBTY/VBBT) → Verrechnungsposition V1 (VPTY/VPVZ/VPTA/RSVZ/RSUM). KEIN DN/DG-Zweig-Split: T01 = GESAMT-Prozentsatz des Tarifsystems je Beschäftigtengruppe (Parameter at_mbgm_tarif, normativ: laufend 39,05 % = KV 7,65 + PV 22,80 + AV 5,90 + UV 1,10 + WF 1,00 + IESG 0,10; SZ 37,55 % = DN KV 3,87 + PV 10,25 + AV 2,95 + DG 20,48). Abschläge als EIGENE Verrechnungspositionen je Basis: A03/A02/A01 = Minderung AV auf 0/1/2 % (§ 2a AMPFG, nur DN-AV, je Verrechnungsbasis AB UND SZ), A09 = UV-Entfall 60, A10 = AV+IE-Entfall Pensionsanspruch (Konkurrenz a4: sperrt Staffelabschlag), A15 = PV-Halbierung Bonusphase — konsistent mit der GP4-§-2a-DN-seitig-Konvention. BV (BMSVG): eigene Verrechnungsbasis BV (ungedeckelt, GP5) + Position V01 1,53 %. RSUM = Basis x Satz (mBGM-Tarifierung), Konsistenz-Check gegen GP4-Payslip-Beitragssummen ±0,10 € als Prüfwarnung. Storno: R1-Sätze mit REFU/VSUM wie Original. Fehlanzeige: Paket ohne mBGM-Sätze (ANZM 0). Geringfügige/fallweise ohne Parametrisierung: listenweiser Abbruch (keine Tarif-Erfindung; BSGR B044/B045/B010/B030 aus dem SV-Tarifsystem pflegen). Versichertenmeldung (models/versichertenmeldung.py — DM-ORG E.29, BEST VR, Version 03, Satzlänge 772): M3 vor Arbeitsantritt / M4 (7 Kalendertage ab PV-Ende) mit deklarativer Event-Erkennung über die Meldungs-Registry (l10n.at.payroll.vr.event) — idempotente Läufe, Dimona-auto-Idee ohne write-diff-Hacks. Felder: BKNR/VSNR/GEBD/FANA/VONA (Namenssplit am letzten Leerzeichen), ADAT/EBSV, BBER 01-04 aus l10n_at_personalgruppe/lehrling, GERF (Kataloggehalt vs. GFG), FRDV, BVAB/BVJN aus dem GP5-BMSVG-Flag, VWAZ (Stunden x100, neu ab 01.01.2026), AGRD aus dem hr.departure.reason-Mapping (Standard 00). Fallweise Anmeldung (BEST MA, SART 32, Version 07, Länge 450): MVP je Anmeldetag am Vertragsbeginn; tagesgenaue Einsatztage via Work-Entry-Auswertung = dokumentierter Rest. NSchAB-Branche als Company-Flag (l10n_at_nschab): B001-T01 + 3,80 % (Schlechtwetterentschädigung, ÖGK-Tarifsystem Normalbetriebe mit Schlechtwetterentschädigung). Tests: test_meldewesen_private (2 — mBGM-Satzhierarchie mit A03 je Basis aus November-Fixture + GSUM-Konsistenz; VR M3/M4 mit Registry-Idempotenz und AGRD). Rechenfälle docs/rechenfaelle/meldewesen.md. 19.0.7.0.0.
This commit is contained in:
@@ -6,4 +6,6 @@ from . import bv_kasse
|
||||
from . import abfertigung_private
|
||||
from . import bv_report
|
||||
from . import account_chart_private
|
||||
from . import account_posting_private
|
||||
from . import account_posting_private
|
||||
from . import mbgm
|
||||
from . import versichertenmeldung
|
||||
@@ -0,0 +1,506 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""Monatliche Beitragsgrundlagenmeldung mBGM/JASV (GP7/AP5) — DM-ORG
|
||||
42.7.0, Kapitel E.32 (BEST ``MB``, mBGM-Version 02; PDF-verifizierte
|
||||
Extraktion 2026-09-12).
|
||||
|
||||
Hierarchie: Paket (SART ``PS`` Selbstabrechner, Länge 305) → mBGM
|
||||
(SART ``G1``, 326) → Tarifblock (SART ``T1``, 42) → Verrechnungsbasis
|
||||
(``BS``, 33) → Verrechnungsposition (``V1``, 42).
|
||||
|
||||
Fachliche Grundlagen:
|
||||
- **KEIN DN/DG- oder Zweig-Split**: die Standardposition ``T01``
|
||||
trägt den GESAMT-Prozentsatz des Tarifsystems (Parameter
|
||||
``at_mbgm_tarif`` — BSGR-spezifisch; Werte aus dem ÖGK-
|
||||
Beitragstarifsystem, `.additional_docs/oegk/`, jeweils
|
||||
parametrisiert zu pflegen). Fehlende BSGR-Sätze brechen den Lauf
|
||||
listenweise ab (keine Erfindung von Tarifen).
|
||||
- **Abschläge als eigene Verrechnungspositionen** (D.60, verifiziert):
|
||||
A01/A02/A03 = Minderung AV auf 2/1/0 % (§ 2a AMPFG, DN-seitig —
|
||||
Abschlag = AB × (2,95 − Zielsatz) %), A09 = UV-Entfall ab 60
|
||||
(1,10 %), A10 = AV+IE-Entfall Pensionsanspruch (6,00 %), A15 =
|
||||
PV-Halbierung Bonusphase (11,40 %). A02/A10 schließen einander aus
|
||||
(Konkurrenz a4: Entfall vor Minderung — wie der GP4-Kern rechnet).
|
||||
- **BV (BMSVG)**: eigene Verrechnungsbasis ``BV`` (ungedeckelt, GP5)
|
||||
+ Position ``V01`` (1,53 %); SZ-Basis ``SZ`` (§-54-gedeckelt) +
|
||||
Position ``T02`` mit dem SZ-Gesamtsatz.
|
||||
- **RSUM aus Basis × Satz** (mBGM-eigene Tarifierung); Konsistenz
|
||||
gegen die GP4-Payslip-Summen als Prüfwarnung (Toleranz 0,10 €).
|
||||
- **Fehlanzeige**: Paket ohne mBGM-Sätze (ANZM 0) für Beitragsmonate
|
||||
ohne meldbare Daten (GP5-Deferral; DM-ORG-Form ⚠ Parallellauf
|
||||
verifizieren). **Storno**: R1-Sätze mit REFU + identischem BZRM
|
||||
(VSUM = gestornierte VSUM) für den Nachtragslauf; Nachreichung als
|
||||
neue Deklaration (GP4-Deferral Aufrollung).
|
||||
- Fallweise Beschäftigte (G3/G4 mit tagesgenauen Tarifblöcken) und
|
||||
Geringfügige ohne Tarifparameter: dokumentierter Rest — der Lauf
|
||||
bricht listenweise ab, bis `at_mbgm_tarif` gepflegt ist.
|
||||
|
||||
Dateibasiert (Prämisse P7): Generierung + Prüfprotokoll; Übertragung
|
||||
beim Mandanten. Fristen: Selbstabrechner 15., Vorschreibebetriebe 7.
|
||||
des Folgemonats (lb-mel-08) — Fristenkalender GP8."""
|
||||
from datetime import date, timedelta
|
||||
import base64
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.addons.l10n_at_hr_payroll.models.elda import (
|
||||
L10N_AT_ELDA_IDTEIL,
|
||||
L10nAtEldaBestand,
|
||||
L10nAtEldaDatei,
|
||||
L10nAtEldaSatz,
|
||||
l10n_at_euro_cent,
|
||||
)
|
||||
|
||||
from odoo.addons.l10n_at_hr_payroll.models.lohnzettel_l16 import \
|
||||
l10n_at_namen
|
||||
|
||||
# Paket (E.32, Selbstabrechner PS) — nach IDTEIL, Länge 305.
|
||||
MBGM_PAKET = [
|
||||
(2, 'REFP', 21, 40, 'a'),
|
||||
(3, 'BKNR', 61, 10, 'a'),
|
||||
(4, 'DGNA', 71, 70, 'a'),
|
||||
(5, 'MPKE', 141, 30, 'a'),
|
||||
(6, 'JAGB', 171, 1, 'a'),
|
||||
(7, 'DTEL', 172, 50, 'a'),
|
||||
(8, 'MAIL', 222, 60, 'a'),
|
||||
(9, 'BZRM', 282, 6, 'n'), # MMJJJJ
|
||||
(10, 'GSVZ', 288, 1, 'a'),
|
||||
(11, 'GSUM', 289, 11, 'n'), # Σ VSUM (Cent)
|
||||
(12, 'ANZM', 300, 6, 'n'), # Anzahl mBGM (G/R-Sätze)
|
||||
]
|
||||
|
||||
# mBGM-Satz (E.32, G1 Selbstabrechner) — nach IDTEIL, Länge 326.
|
||||
MBGM_SATZ = [
|
||||
(2, 'REFW', 21, 40, 'a'),
|
||||
(3, 'REFU', 61, 40, 'a'),
|
||||
(4, 'REFV', 101, 40, 'a'),
|
||||
(5, 'VSNR', 141, 10, 'n'),
|
||||
(6, 'FANA', 151, 70, 'a'),
|
||||
(7, 'VONA', 221, 70, 'a'),
|
||||
(8, 'VSUM', 291, 11, 'n'), # Σ RSUM (Cent)
|
||||
(9, 'VERG', 302, 1, 'a'), # 1 = SV+BV mit Versicherungszeit
|
||||
(10, 'INF1', 303, 12, 'a'),
|
||||
(11, 'INF2', 315, 12, 'a'),
|
||||
]
|
||||
|
||||
# Tarifblock (E.32, T1) — nach IDTEIL, Länge 42.
|
||||
MBGM_TARIFBLOCK = [
|
||||
(2, 'BSGR', 21, 4, 'a'), # Beschäftigtengruppe (SV-Tarifsystem)
|
||||
(3, 'ERGB', 25, 15, 'a'), # 5 × 3-stellige Ergänzungen
|
||||
(4, 'VVON', 40, 2, 'n'), # Beginn der Verrechnung (Tag)
|
||||
(5, 'KEUE', 42, 1, 'a'), # enthält KE/UE
|
||||
]
|
||||
|
||||
# Verrechnungsbasis (E.32, BS) — nach IDTEIL, Länge 33.
|
||||
MBGM_BASIS = [
|
||||
(2, 'VBTY', 21, 2, 'a'), # AB/SZ/BV/UU …
|
||||
(3, 'VBBT', 23, 11, 'n'), # Cent
|
||||
]
|
||||
|
||||
# Verrechnungsposition (E.32, V1) — nach IDTEIL, Länge 42.
|
||||
MBGM_POSITION = [
|
||||
(2, 'VPTY', 21, 3, 'a'), # T01/T02/V01/A01…A15/Z01 …
|
||||
(3, 'VPVZ', 24, 1, 'a'), # Vorzeichen Prozentsatz
|
||||
(4, 'VPTA', 25, 6, 'n'), # Prozentsatz ×1000 (3 Nachkommastellen)
|
||||
(5, 'RSVZ', 31, 1, 'a'), # Vorzeichen RSUM
|
||||
(6, 'RSUM', 32, 11, 'n'), # Cent
|
||||
]
|
||||
|
||||
|
||||
class L10nAtPayrollMbgm(models.Model):
|
||||
_name = 'l10n.at.payroll.mbgm'
|
||||
_description = 'Monatliche Beitragsgrundlagenmeldung (mBGM/JASV)'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'beitragsmonat desc, id desc'
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company', required=True,
|
||||
default=lambda self: self.env.company)
|
||||
beitragsmonat = fields.Date(
|
||||
required=True, string='Beitragsmonat',
|
||||
help='Erster Tag des Beitragsmonats (MMJJJJ).')
|
||||
art = fields.Selection(
|
||||
[('MB', 'mBGM (regulär)'),
|
||||
('ST', 'Storno (R-Sätze, iVm REFU)'),
|
||||
('FZ', 'Fehlanzeige (Paket ohne mBGM-Sätze)')],
|
||||
required=True, default='MB', tracking=True)
|
||||
storno_id = fields.Many2one(
|
||||
'l10n.at.payroll.mbgm', string='Stornierte Deklaration',
|
||||
help='Art ST: Referenz auf die ursprüngliche Deklaration; '
|
||||
'REFU der R-Sätze = REFW der stornierten mBGM je AN.')
|
||||
line_ids = fields.One2many(
|
||||
'l10n.at.payroll.mbgm.line', 'mbgm_id', string='mBGM-Sätze')
|
||||
datei = fields.Binary(attachment=True, 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 = [(
|
||||
'mbgm_company_monat_art_uniq',
|
||||
'unique(company_id, beitragsmonat, art)',
|
||||
'Für diesen Beitragsmonat existiert bereits eine mBGM-'
|
||||
'Deklaration dieser Art.')]
|
||||
|
||||
def _l10n_at_slips(self):
|
||||
self.ensure_one()
|
||||
erster = self.beitragsmonat
|
||||
letzter = (date(erster.year, erster.month, 28)
|
||||
+ timedelta(days=4)).replace(day=1) \
|
||||
- timedelta(days=1)
|
||||
return self.env['hr.payslip'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('date_from', '>=', erster),
|
||||
('date_from', '<=', letzter),
|
||||
('state', 'in', ('validated', 'paid')),
|
||||
])
|
||||
|
||||
def _l10n_at_slip(self, employee):
|
||||
"""Der (eindeutige) Lauf des AN im Beitragsmonat."""
|
||||
self.ensure_one()
|
||||
erster = self.beitragsmonat
|
||||
letzter = (date(erster.year, erster.month, 28)
|
||||
+ timedelta(days=4)).replace(day=1) \
|
||||
- timedelta(days=1)
|
||||
return self.env['hr.payslip'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('employee_id', '=', employee.id),
|
||||
('date_from', '>=', erster),
|
||||
('date_from', '<=', letzter),
|
||||
('state', 'in', ('validated', 'paid'))], limit=1)
|
||||
|
||||
def _l10n_at_bsgr(self, version, slip):
|
||||
"""Beschäftigtengruppe (SV-Tarifsystem): B001 Arbeiter, B002
|
||||
Angestellte; Lehrlinge B044/B045 und geringfügig/fallweise
|
||||
(B010/B030) ohne Default-Parameter → Lauf bricht ab, bis
|
||||
`at_mbgm_tarif` gepflegt ist (Tarifsystem-Werte, keine
|
||||
Erfindung)."""
|
||||
if version.l10n_at_lehrling:
|
||||
return 'B044' if getattr(
|
||||
version, 'l10n_at_personalgruppe', '') == 'arbeiter' \
|
||||
else 'B045'
|
||||
if slip.l10n_at_geringfuegig or version.l10n_at_fallweise:
|
||||
return 'B030'
|
||||
return 'B001' if getattr(
|
||||
version, 'l10n_at_personalgruppe', '') == 'arbeiter' \
|
||||
else 'B002'
|
||||
|
||||
def _l10n_at_tarif(self, bsgr):
|
||||
self.ensure_one()
|
||||
tarif = self.env['hr.rule.parameter']._get_parameter_from_code(
|
||||
'at_mbgm_tarif', self.beitragsmonat,
|
||||
raise_if_not_found=False) or {}
|
||||
return tarif.get(bsgr)
|
||||
|
||||
def _l10n_at_positionen(self, slip, tarif):
|
||||
"""Verrechnungspositionen je Tarifblock: T01 (Gesamtsatz,
|
||||
NSchAB-Zweig via Company-Flag) + Abschläge aus den GP4-
|
||||
Stichtagsflags; T02 auf SZ; V01 auf BV-Basis. Rückgabe:
|
||||
[(vpty, vpta, rsum_cent, basis_typ), …] — RSUM aus Basis ×
|
||||
Satz (mBGM-Tarifierung); Abschläge negativ (RSVZ '-')."""
|
||||
version = slip.version_id
|
||||
ab = sum(basis for _m, basis
|
||||
in slip._l10n_at_sv_basis_laufen_monate())
|
||||
nschab = 3.80 if (self.company_id.l10n_at_nschab
|
||||
and not version.l10n_at_lehrling
|
||||
and getattr(version, 'l10n_at_personalgruppe',
|
||||
'') == 'arbeiter') else 0.0
|
||||
satz_laufen = float(tarif['laufend']) + nschab
|
||||
positionen = [('T01', satz_laufen, ab * satz_laufen / 100.0,
|
||||
'AB')]
|
||||
# §-2a-AMPFG: Minderung nur des DN-AV (2,95 %), als eigene
|
||||
# Abschlagsposition auf JEDER betroffenen Verrechnungsbasis
|
||||
# (AB und SZ — der Abschlag senkt den Gesamtsatz je Basis;
|
||||
# DN-seitig, GP4-Konvention). Codes: 0 % → A03, 1 % → A02,
|
||||
# 2 % → A01 (D.60, verifiziert).
|
||||
minderung = 0.0
|
||||
monat = self.beitragsmonat
|
||||
if not (version.l10n_at_alv_entfall_ab
|
||||
and monat >= version.l10n_at_alv_entfall_ab):
|
||||
staffel = self.env['hr.rule.parameter'] \
|
||||
._get_parameter_from_code(
|
||||
'at_sv_av_staffel_ampfg', monat,
|
||||
raise_if_not_found=False) or {}
|
||||
for _von, bis, dn_satz in staffel.get('tiers', []):
|
||||
if ab and ab <= float(bis):
|
||||
minderung = 2.95 - float(dn_satz)
|
||||
break
|
||||
if minderung > 0:
|
||||
code = ('A03' if minderung >= 2.95
|
||||
else 'A02' if abs(minderung - 1.95) < 0.01
|
||||
else 'A01')
|
||||
positionen.append(
|
||||
(code, 0.0, -ab * minderung / 100.0, 'AB'))
|
||||
else:
|
||||
# A10 = Entfall AV+IE (DN-AV + DG-AV + DG-IESG).
|
||||
positionen.append(
|
||||
('A10', 0.0, -ab * 6.00 / 100.0, 'AB'))
|
||||
if version.l10n_at_uv_entfall_ab \
|
||||
and monat >= version.l10n_at_uv_entfall_ab:
|
||||
positionen.append(('A09', 0.0, -ab * 1.10 / 100.0, 'AB'))
|
||||
if version.l10n_at_pv_bonusphase_ab \
|
||||
and monat >= version.l10n_at_pv_bonusphase_ab:
|
||||
positionen.append(('A15', 0.0, -ab * 11.40 / 100.0, 'AB'))
|
||||
sz_basis = slip._l10n_at_svdn_basis_sz()
|
||||
if sz_basis > 0:
|
||||
satz_sz = float(tarif['sz'])
|
||||
positionen.append(
|
||||
('T02', satz_sz, sz_basis * satz_sz / 100.0, 'SZ'))
|
||||
if minderung > 0:
|
||||
code = ('A03' if minderung >= 2.95
|
||||
else 'A02' if abs(minderung - 1.95) < 0.01
|
||||
else 'A01')
|
||||
positionen.append(
|
||||
(code, 0.0, -sz_basis * minderung / 100.0, 'SZ'))
|
||||
bv_basis = (slip._at_bv_basis_laufen()
|
||||
+ slip._at_bv_basis_sonst())
|
||||
if bv_basis > 0:
|
||||
positionen.append(('V01', 1.53, bv_basis * 0.0153, 'BV'))
|
||||
return positionen
|
||||
|
||||
def action_erzeugen(self):
|
||||
for deklaration in self:
|
||||
fehler = []
|
||||
warnungen = []
|
||||
company = deklaration.company_id
|
||||
bknr = company.l10n_at_beitragskontonummer
|
||||
if not bknr:
|
||||
fehler.append('Beitragskontonummer (BKNR) fehlt.')
|
||||
vstr = (bknr or '')[:2]
|
||||
deklaration.line_ids.unlink()
|
||||
datensaetze = []
|
||||
summe_gesamt = 0
|
||||
anzahl = 0
|
||||
if deklaration.art != 'FZ':
|
||||
slips = deklaration._l10n_at_slips()
|
||||
nach_an = {}
|
||||
for slip in slips:
|
||||
nach_an.setdefault(slip.employee_id, []).append(slip)
|
||||
for employee, employee_slips in nach_an.items():
|
||||
version = employee.version_id
|
||||
if not (version and version.ssnid):
|
||||
fehler.append(
|
||||
'Mitarbeiter %s ohne Versicherungsnummer.'
|
||||
% employee.name)
|
||||
continue
|
||||
if len(employee_slips) > 1:
|
||||
fehler.append(
|
||||
'Mitarbeiter %s mit mehreren Läufen im '
|
||||
'Beitragsmonat — mBGM je Lauf ist ein '
|
||||
'dokumentierter Rest.' % employee.name)
|
||||
continue
|
||||
slip = employee_slips[0]
|
||||
if deklaration.art == 'ST':
|
||||
# Storno: R1-Satz je stornierter mBGM mit
|
||||
# REFU = REFW des Originals, VSUM wie Original
|
||||
# (E.32.2.8 — ohne T-/BS-/V-Sätze).
|
||||
original = deklaration.storno_id.line_ids \
|
||||
.filtered(lambda l: l.employee_id == employee)
|
||||
if not original:
|
||||
fehler.append(
|
||||
'Storno %s: keine stornierte mBGM '
|
||||
'am Mitarbeiter gefunden.' % employee.name)
|
||||
continue
|
||||
refw = 'MBGM-ST-%d-%s-%d' % (
|
||||
company.id,
|
||||
deklaration.beitragsmonat.strftime('%Y%m'),
|
||||
employee.id)
|
||||
storno_satz = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + MBGM_SATZ, 326,
|
||||
bezeichnung='Storno %s' % employee.name)
|
||||
storno_satz.setze('SART', 'R1')
|
||||
storno_satz.setze('REFW', refw)
|
||||
storno_satz.setze('REFU', original.refw)
|
||||
storno_satz.setze('VSNR', version.ssnid)
|
||||
storno_satz.setze('VSUM', abs(original.vsum),
|
||||
cent=True)
|
||||
datensaetze.append(storno_satz)
|
||||
deklaration.env['l10n.at.payroll.mbgm.line']\
|
||||
.create({
|
||||
'mbgm_id': deklaration.id,
|
||||
'employee_id': employee.id,
|
||||
'versicherungsnummer': version.ssnid,
|
||||
'bsgr': original.bsgr,
|
||||
'refw': refw,
|
||||
'vsum': original.vsum,
|
||||
})
|
||||
summe_gesamt += original.vsum
|
||||
anzahl += 1
|
||||
continue
|
||||
bsgr = deklaration._l10n_at_bsgr(version, slip)
|
||||
tarif = deklaration._l10n_at_tarif(bsgr)
|
||||
if not tarif:
|
||||
fehler.append(
|
||||
'Mitarbeiter %s: kein Tarifsatz für BSGR '
|
||||
'%s parametrisiert (at_mbgm_tarif, Werte '
|
||||
'aus dem SV-Tarifsystem pflegen).'
|
||||
% (employee.name, bsgr))
|
||||
continue
|
||||
if slip.l10n_at_geringfuegig \
|
||||
or version.l10n_at_fallweise:
|
||||
fehler.append(
|
||||
'Mitarbeiter %s: geringfügige/fallweise '
|
||||
'Beschäftigung in der mBGM ist ein '
|
||||
'dokumentierter Rest (BSGR %s, Tarifsystem '
|
||||
'verifizieren).' % (employee.name, bsgr))
|
||||
continue
|
||||
positionen = deklaration._l10n_at_positionen(
|
||||
slip, tarif)
|
||||
vsum = sum(rsum for _c, _s, rsum, _b in positionen)
|
||||
# Konsistenz gegen die GP4-Payslip-Summen.
|
||||
ist = (slip._l10n_at_svdn_gesamt()
|
||||
+ slip._l10n_at_svdg_gesamt()
|
||||
+ slip._l10n_at_svdn_sz()
|
||||
+ sum(
|
||||
slip._l10n_at_svdg_sz(k)
|
||||
for k in ('kv', 'pv', 'av', 'uv', 'iesg'))
|
||||
+ slip._at_bv_beitrag())
|
||||
if abs(vsum - ist) > 0.10:
|
||||
warnungen.append(
|
||||
'Mitarbeiter %s: mBGM-VSUM %.2f weicht um '
|
||||
'%.2f von den GP4-Beitragssummen ab '
|
||||
'(Tarif-/Rundungsdifferenz prüfen).'
|
||||
% (employee.name, vsum, vsum - ist))
|
||||
deklaration.env['l10n.at.payroll.mbgm.line'].create({
|
||||
'mbgm_id': deklaration.id,
|
||||
'employee_id': employee.id,
|
||||
'versicherungsnummer': version.ssnid,
|
||||
'bsgr': bsgr,
|
||||
'refw': 'MBGM-%d-%s-%d' % (
|
||||
company.id,
|
||||
deklaration.beitragsmonat.strftime('%Y%m'),
|
||||
employee.id),
|
||||
'vsum': vsum,
|
||||
})
|
||||
datensaetze.extend(
|
||||
deklaration._l10n_at_mbgm_datensaetze(
|
||||
employee, version, bsgr, positionen, vsum))
|
||||
summe_gesamt += vsum
|
||||
anzahl += 1
|
||||
paket = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + MBGM_PAKET, 326,
|
||||
bezeichnung='mBGM-Paket')
|
||||
paket.setze('SART', 'PS')
|
||||
paket.setze('VSTR', vstr)
|
||||
paket.setze('REFP', 'MBGM-%d-%s' % (
|
||||
company.id,
|
||||
deklaration.beitragsmonat.strftime('%Y%m')))
|
||||
paket.setze('BKNR', bknr)
|
||||
paket.setze('DGNA', company.name[:70])
|
||||
paket.setze('JAGB', 'N')
|
||||
paket.setze('BZRM', deklaration.beitragsmonat.strftime(
|
||||
'%m%Y'))
|
||||
paket.setze('GSVZ', '+' if summe_gesamt >= 0 else '-')
|
||||
paket.setze('GSUM', abs(summe_gesamt), cent=True)
|
||||
paket.setze('ANZM', anzahl)
|
||||
datensaetze.insert(0, paket)
|
||||
if fehler:
|
||||
deklaration.protokoll = 'FEHLER:\n%s\nWARNUNGEN:\n%s' % (
|
||||
'\n'.join(fehler), '\n'.join(warnungen) or '—')
|
||||
raise UserError('mBGM-Lauf mit Fehlern abgebrochen:\n'
|
||||
+ '\n'.join(fehler))
|
||||
bestand = L10nAtEldaBestand(
|
||||
'MB', vstr, '02',
|
||||
hersteller={'name': 'odoo-at-payroll', 'kfz': 'A',
|
||||
'plz': company.partner_id.zip,
|
||||
'ort': company.partner_id.city,
|
||||
'strasse': company.partner_id.street},
|
||||
datensaetze=datensaetze)
|
||||
datei = L10nAtEldaDatei([bestand])
|
||||
deklaration.write({
|
||||
'datei': base64.b64encode(datei.render()),
|
||||
'datei_name': 'mbgm_%s.txt' % (
|
||||
deklaration.beitragsmonat.strftime('%Y%m'),),
|
||||
'protokoll': 'WARNUNGEN:\n%s' % (
|
||||
'\n'.join(warnungen) or '—'),
|
||||
'state': 'done',
|
||||
})
|
||||
return True
|
||||
|
||||
def _l10n_at_mbgm_datensaetze(self, employee, version, bsgr,
|
||||
positionen, vsum):
|
||||
"""G1 + Tarifblock + je Basis (AB/SZ/BV) die Positionen als
|
||||
eigene Sätze (BS/V folgen hierarchisch dem Tarifblock)."""
|
||||
self.ensure_one()
|
||||
slip = self._l10n_at_slip(employee)
|
||||
mbgm_satz = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + MBGM_SATZ, 326,
|
||||
bezeichnung='mBGM %s' % employee.name)
|
||||
mbgm_satz.setze('SART', 'G1')
|
||||
mbgm_satz.setze('REFW', 'MBGM-%d-%s-%d' % (
|
||||
self.company_id.id,
|
||||
self.beitragsmonat.strftime('%Y%m'), employee.id))
|
||||
mbgm_satz.setze('VSNR', version.ssnid)
|
||||
familienname, vorname = l10n_at_namen(employee)
|
||||
mbgm_satz.setze('FANA', familienname[:70])
|
||||
mbgm_satz.setze('VONA', (vorname or '')[:70])
|
||||
mbgm_satz.setze('VSUM', abs(vsum), cent=True)
|
||||
mbgm_satz.setze('VERG', '1')
|
||||
tarifblock = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + MBGM_TARIFBLOCK, 326,
|
||||
bezeichnung='Tarifblock %s' % employee.name)
|
||||
tarifblock.setze('SART', 'T1')
|
||||
tarifblock.setze('BSGR', bsgr)
|
||||
tarifblock.setze('VVON', '1')
|
||||
saetze = [mbgm_satz, tarifblock]
|
||||
for code, satz_prozent, rsum, basis_typ in positionen:
|
||||
basis = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + MBGM_BASIS, 326,
|
||||
bezeichnung='Basis %s %s' % (basis_typ,
|
||||
employee.name))
|
||||
basis.setze('SART', 'BS')
|
||||
basis.setze('VBTY', basis_typ)
|
||||
basis_wert = abs(rsum) * 100.0 / satz_prozent \
|
||||
if satz_prozent else 0.0
|
||||
if basis_typ == 'AB':
|
||||
basis_wert = sum(
|
||||
b for _m, b in
|
||||
slip._l10n_at_sv_basis_laufen_monate())
|
||||
elif basis_typ == 'SZ':
|
||||
basis_wert = slip._l10n_at_svdn_basis_sz()
|
||||
else:
|
||||
basis_wert = slip._at_bv_basis_laufen() \
|
||||
+ slip._at_bv_basis_sonst()
|
||||
basis.setze('VBBT', basis_wert, cent=True)
|
||||
position = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + MBGM_POSITION, 326,
|
||||
bezeichnung='Position %s %s' % (code, employee.name))
|
||||
position.setze('SART', 'V1')
|
||||
position.setze('VPTY', code)
|
||||
position.setze('VPTA', satz_prozent, skala=3)
|
||||
position.setze('RSVZ', '-' if rsum < 0 else '+')
|
||||
position.setze('RSUM', abs(rsum), cent=True)
|
||||
saetze.extend([basis, position])
|
||||
return saetze
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
l10n_at_nschab = fields.Boolean(
|
||||
string='AT Schlechtwetterentschädigungsbeitrag-Branche',
|
||||
help='Betrieb mit Schlechtwetterentschädigung (ÖGK-'
|
||||
'Tarifsystem „Normalbetriebe mit '
|
||||
'Schlechtwetterentschädigung“): Arbeiter (BSGR B001) '
|
||||
'erhalten im mBGM-T01 den um NSchAB 3,80 % erhöhten '
|
||||
'Gesamtsatz.')
|
||||
|
||||
|
||||
class L10nAtPayrollMbgmLine(models.Model):
|
||||
_name = 'l10n.at.payroll.mbgm.line'
|
||||
_description = 'mBGM — Satzdaten je AN'
|
||||
_order = 'employee_name'
|
||||
mbgm_id = fields.Many2one(
|
||||
'l10n.at.payroll.mbgm', required=True, ondelete='cascade',
|
||||
index=True)
|
||||
employee_id = fields.Many2one('hr.employee', required=True)
|
||||
employee_name = fields.Char(related='employee_id.name', store=True,
|
||||
readonly=True)
|
||||
versicherungsnummer = fields.Char()
|
||||
bsgr = fields.Char(string='Beschäftigtengruppe')
|
||||
refw = fields.Char(string='Referenzwert (REFW)')
|
||||
vsum = fields.Monetary(
|
||||
string='VSUM', currency_field='currency_id')
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='mbgm_id.company_id.currency_id')
|
||||
@@ -0,0 +1,406 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""Versichertenmeldung reduziert (GP7/AP5) — DM-ORG 42.7.0 Kapitel
|
||||
E.29 (BEST ``VR``, Version 03, Satzlänge 772; PDF-verifizierte
|
||||
Feldtabelle 2026-09-12): Anmeldung M3 (vor Arbeitsantritt), Abmeldung
|
||||
M4 (7 Kalendertage ab PV-Ende), Änderungsmeldung M6 nur für
|
||||
Ausnahmefälle (Inhaltsänderungen laufen über die mBGM; lb-mel-08).
|
||||
Fallweise Anmeldung (BEST ``MA``, SART 32/33, Länge 450): je
|
||||
Beschäftigungstag vor Arbeitsantritt (§ 33 Abs 1a Z 1 ASVG).
|
||||
|
||||
Event-Modell deklarativ (Dimona-auto-Muster, ohne write-diff):
|
||||
offene Anmeldungen = älteste Version eines Beschäftigungsspannen-
|
||||
Beginns ohne M3-Eintrag in der Meldungs-Registry; offene Abmeldungen
|
||||
= beendete Versionen ohne M4-Eintrag. Die Registry
|
||||
(``l10n.at.payroll.vr.event``) protokolliert gesendete Meldungen
|
||||
(REFW/ADAT), damit erneute Läufe idempotent bleiben.
|
||||
|
||||
Felder: BBER 01 Arbeiter/02 Angestellte(r)/03–04 Lehrling (D.39),
|
||||
GERF geringfügig (J/N), FRDV freies Dienstverhältnis, ADAT An-/
|
||||
Abmeldedatum, EBSV arbeitsrechtliches Ende vs ADAT (UEL/KE-Verlänger
|
||||
ung der PV sind nicht modelliert → ADAT = contract_date_end),
|
||||
AGRD eSV-Abmeldegrund (Mapping am hr.departure.reason, Standard 00),
|
||||
BVAB/BVJN aus dem GP5-BMSVG-Flag, VWAZ vereinbarte Wochenarbeitszeit
|
||||
(Stunden ×100, neu für Meldedaten ab 01.01.2026).
|
||||
|
||||
Dateibasiert (Prämisse P7); Bestätigungen/Übertragung beim Mandanten."""
|
||||
from datetime import date
|
||||
import base64
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.addons.l10n_at_hr_payroll.models.elda import (
|
||||
L10N_AT_ELDA_IDTEIL,
|
||||
L10nAtEldaBestand,
|
||||
L10nAtEldaDatei,
|
||||
L10nAtEldaSatz,
|
||||
)
|
||||
|
||||
from odoo.addons.l10n_at_hr_payroll.models.lohnzettel_l16 import \
|
||||
l10n_at_namen
|
||||
|
||||
# Versichertenmeldung (E.29, Version 03) — nach IDTEIL, Länge 772.
|
||||
VR_SATZ = [
|
||||
(2, 'REFW', 21, 40, 'a'),
|
||||
(3, 'REFU', 61, 40, 'a'),
|
||||
(4, 'BKNR', 101, 10, 'a'),
|
||||
(5, 'DGNA', 111, 70, 'a'),
|
||||
(6, 'DTEL', 181, 50, 'a'),
|
||||
(7, 'MAIL', 231, 60, 'a'),
|
||||
(8, 'INF1', 291, 12, 'a'),
|
||||
(9, 'INF2', 303, 12, 'a'),
|
||||
(10, 'VSNR', 315, 10, 'n'),
|
||||
(11, 'GEBD', 325, 8, 'n'), # TTMMJJJJ
|
||||
(12, 'REFV', 333, 40, 'a'),
|
||||
(13, 'FANA', 373, 70, 'a'),
|
||||
(14, 'VONA', 443, 70, 'a'),
|
||||
(15, 'ADAT', 513, 8, 'n'), # TTMMJJJJ
|
||||
(16, 'BDAT', 521, 8, 'n'),
|
||||
(17, 'RDAT', 529, 8, 'n'),
|
||||
(18, 'BBER', 537, 2, 'a'), # Beschäftigungsbereich (D.39)
|
||||
(19, 'GERF', 539, 1, 'a'), # geringfügig J/N
|
||||
(20, 'FRDV', 540, 1, 'a'), # freier Dienstvertrag J/N
|
||||
(21, 'EBSV', 541, 8, 'n'), # Ende des Beschäftigungsverhältnisses
|
||||
(22, 'AGRD', 549, 2, 'a'), # Abmeldegrund (D.22)
|
||||
(23, 'SAGR', 551, 20, 'a'),
|
||||
(24, 'KEAB', 571, 8, 'n'),
|
||||
(25, 'KEBI', 579, 8, 'n'),
|
||||
(26, 'UEAB', 587, 8, 'n'),
|
||||
(27, 'UEBI', 595, 8, 'n'),
|
||||
(28, 'BVAB', 603, 8, 'n'), # BV-Beitrag ab
|
||||
(29, 'BVEN', 611, 8, 'n'),
|
||||
(30, 'BVJN', 619, 1, 'a'), # Betriebliche Vorsorge J/N
|
||||
(31, 'UMDA', 620, 8, 'n'),
|
||||
(32, 'RUMD', 628, 8, 'n'),
|
||||
(33, 'SOUM', 636, 1, 'a'),
|
||||
(34, 'ZTUM', 637, 2, 'a'),
|
||||
(35, 'ZKUM', 639, 10, 'a'),
|
||||
(36, 'RWUM', 649, 40, 'a'),
|
||||
(37, 'RUUM', 689, 40, 'a'),
|
||||
(38, 'BKUM', 729, 40, 'a'),
|
||||
(39, 'VWAZ', 769, 4, 'n'), # Stunden ×100
|
||||
]
|
||||
|
||||
# Fallweise Anmeldung (E.18, Version 07) — nach IDTEIL, Länge 450.
|
||||
FALLWEISE_SATZ = [
|
||||
(2, 'BKNR', 21, 10, 'a'),
|
||||
(3, 'DGNA', 31, 70, 'a'),
|
||||
(4, 'VSNR', 101, 10, 'n'),
|
||||
(5, 'GEBD', 111, 8, 'n'),
|
||||
(6, 'FANA', 119, 70, 'a'),
|
||||
(7, 'VONA', 189, 70, 'a'),
|
||||
(8, 'GESL', 259, 1, 'n'), # 1 männlich/2 weiblich …
|
||||
(9, 'ADAT', 260, 8, 'n'), # Tag der fallweisen Beschäftigung
|
||||
(10, 'BKFZ', 268, 3, 'a'),
|
||||
(11, 'BPLZ', 271, 9, 'a'),
|
||||
(12, 'BORT', 280, 40, 'a'),
|
||||
(14, 'WOBD', 321, 12, 'a'),
|
||||
(15, 'ZOBD', 333, 15, 'a'),
|
||||
(17, 'REFN', 410, 30, 'a'),
|
||||
(18, 'VTAZ', 440, 4, 'n'), # Stunden ×100
|
||||
]
|
||||
|
||||
|
||||
class L10nAtPayrollVrEvent(models.Model):
|
||||
"""Meldungs-Registry: je gesendeter Meldung ein Eintrag (Art,
|
||||
ADAT, REFW) — Grundlage für die deklarative Event-Erkennung der
|
||||
offenen An-/Abmeldungen."""
|
||||
_name = 'l10n.at.payroll.vr.event'
|
||||
_description = 'Versichertenmeldung — gesendete Meldungen'
|
||||
_order = 'adatum, id'
|
||||
|
||||
company_id = fields.Many2one('res.company', required=True,
|
||||
default=lambda self: self.env.company)
|
||||
employee_id = fields.Many2one('hr.employee', required=True,
|
||||
index=True, ondelete='cascade')
|
||||
version_id = fields.Many2one('hr.version', required=True,
|
||||
ondelete='cascade')
|
||||
art = fields.Selection(
|
||||
[('M3', 'Anmeldung'), ('M4', 'Abmeldung'), ('M6', 'Änderung'),
|
||||
('MA', 'Fallweise Anmeldung')],
|
||||
required=True)
|
||||
adatum = fields.Date(required=True)
|
||||
refw = fields.Char(required=True)
|
||||
|
||||
|
||||
class L10nAtPayrollVr(models.Model):
|
||||
_name = 'l10n.at.payroll.vr'
|
||||
_description = 'Versichertenmeldung (An-/Abmeldung) — ELDA-Lauf'
|
||||
_inherit = ['mail.thread']
|
||||
_order = 'id desc'
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company', required=True,
|
||||
default=lambda self: self.env.company)
|
||||
stichtag = fields.Date(
|
||||
default=fields.Date.today,
|
||||
help='Anmeldungen bis zu diesem Tag (M3 ist eigentlich VOR '
|
||||
'Arbeitsantritt zu übermitteln — der Lauf meldet '
|
||||
'beginnende Beschäftigungsverhältnisse).')
|
||||
nur_fallweise = fields.Boolean(
|
||||
string='Nur fallweise Anmeldungen (BEST MA)',
|
||||
help='Erzeugt ausschließlich Anmeldungen fallweise '
|
||||
'Beschäftigter (je Beschäftigungstag, SART 32).')
|
||||
datei = fields.Binary(attachment=True, 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)
|
||||
event_ids = fields.Many2many(
|
||||
'l10n.at.payroll.vr.event', string='Gesendete Meldungen',
|
||||
copy=False)
|
||||
|
||||
def _l10n_at_offene_anmeldungen(self):
|
||||
"""Älteste aktive Version je AN ohne M3-Eintrag: Beginn eines
|
||||
Beschäftigungsverhältnisses (früheste contract_date_start)."""
|
||||
self.ensure_one()
|
||||
offene = []
|
||||
versionen = self.env['hr.version'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('contract_date_start', '!=', False),
|
||||
('active', '=', True),
|
||||
]).with_company(self.company_id)
|
||||
nach_an = {}
|
||||
for version in versionen:
|
||||
nach_an.setdefault(version.employee_id, version)
|
||||
if version.contract_date_start \
|
||||
< nach_an[version.employee_id].contract_date_start:
|
||||
nach_an[version.employee_id] = version
|
||||
registry = self.env['l10n.at.payroll.vr.event'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('art', 'in', ('M3', 'MA')),
|
||||
])
|
||||
gesehen = {(e.employee_id.id, e.adatum) for e in registry}
|
||||
for employee, version in nach_an.items():
|
||||
if (employee.id, version.contract_date_start) in gesehen:
|
||||
continue
|
||||
if version.contract_date_start > self.stichtag:
|
||||
continue
|
||||
offene.append(version)
|
||||
return sorted(offene, key=lambda v: v.contract_date_start)
|
||||
|
||||
def _l10n_at_offene_abmeldungen(self):
|
||||
self.ensure_one()
|
||||
abmeldungen = []
|
||||
versionen = self.env['hr.version'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('contract_date_end', '!=', False),
|
||||
]).with_company(self.company_id)
|
||||
registry = self.env['l10n.at.payroll.vr.event'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('art', '=', 'M4'),
|
||||
])
|
||||
gesehen = {(e.employee_id.id, e.adatum) for e in registry}
|
||||
for version in versionen:
|
||||
if (version.employee_id.id, version.contract_date_end) \
|
||||
in gesehen:
|
||||
continue
|
||||
abmeldungen.append(version)
|
||||
return sorted(abmeldungen, key=lambda v: v.contract_date_end)
|
||||
|
||||
def _l10n_at_bber(self, version):
|
||||
if version.l10n_at_lehrling:
|
||||
return '03' if getattr(
|
||||
version, 'l10n_at_personalgruppe', '') == 'arbeiter' \
|
||||
else '04'
|
||||
return '01' if getattr(
|
||||
version, 'l10n_at_personalgruppe', '') == 'arbeiter' \
|
||||
else '02'
|
||||
|
||||
def _l10n_at_geringfuegig(self, version):
|
||||
"""Vorausschauende Geringfügigkeits-Einschätzung zur Anmeldung:
|
||||
Kataloggehalt/Überzahlung gegen die GFG (§ 5 Abs 2 ASVG); die
|
||||
endgültige Bewertung läuft monatlich über den GP4-Kern."""
|
||||
gfg = float(self.env['hr.rule.parameter']
|
||||
._get_parameter_from_code(
|
||||
'at_sv_gfg_monatlich', version.contract_date_start,
|
||||
raise_if_not_found=False) or 0.0)
|
||||
gehalt = version.wage \
|
||||
or (version.kv_gruppe_id._get_gehalt(
|
||||
version.erfahrungsstufe, version.contract_date_start)
|
||||
if getattr(version, 'kv_gruppe_id', False) else 0.0)
|
||||
ueberzahlung = getattr(version, 'ueberzahlung', 0.0) or 0.0
|
||||
if ueberzahlung:
|
||||
gehalt = ueberzahlung
|
||||
return bool(gfg and gehalt and gehalt <= gfg)
|
||||
|
||||
def action_erzeugen(self):
|
||||
for lauf in self:
|
||||
fehler = []
|
||||
company = lauf.company_id
|
||||
bknr = company.l10n_at_beitragskontonummer
|
||||
if not bknr:
|
||||
raise UserError('Beitragskontonummer (BKNR) fehlt.')
|
||||
datensaetze = []
|
||||
ereignisse = []
|
||||
vstr = bknr[:2]
|
||||
if lauf.nur_fallweise:
|
||||
versionen = self.env['hr.version'].search([
|
||||
('company_id', '=', company.id),
|
||||
('l10n_at_fallweise', '=', True),
|
||||
('active', '=', True),
|
||||
])
|
||||
registry = self.env['l10n.at.payroll.vr.event'].search([
|
||||
('company_id', '=', company.id),
|
||||
('art', '=', 'MA'),
|
||||
])
|
||||
gesehen = {(e.employee_id.id, e.adatum)
|
||||
for e in registry}
|
||||
for version in versionen:
|
||||
if not version.ssnid:
|
||||
fehler.append('%s ohne Versicherungsnummer.'
|
||||
% version.employee_id.name)
|
||||
continue
|
||||
# MVP: je AN ein Anmeldetag (der Vertragsbeginn);
|
||||
# tagesgenaue Einsatztage = documented Rest
|
||||
# (Work-Entry-Auswertung).
|
||||
adat = version.contract_date_start
|
||||
if not adat or (version.employee_id.id, adat) \
|
||||
in gesehen:
|
||||
continue
|
||||
satz = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + FALLWEISE_SATZ, 450,
|
||||
bezeichnung='Fallweise %s'
|
||||
% version.employee_id.name)
|
||||
satz.setze('SART', '32')
|
||||
satz.setze('VSTR', vstr)
|
||||
satz.setze('BKNR', bknr)
|
||||
satz.setze('DGNA', company.name[:70])
|
||||
satz.setze('VSNR', version.ssnid)
|
||||
satz.setze_datum('GEBD', version.employee_id.birthday)
|
||||
satz.setze('FANA', (version.private_last_name
|
||||
or '')[:70])
|
||||
satz.setze('VONA', (version.private_first_name
|
||||
or '')[:70])
|
||||
satz.setze('GESL', '2'
|
||||
if version.sex == 'female' else '1')
|
||||
satz.setze_datum('ADAT', adat)
|
||||
kalender = version.resource_calendar_id
|
||||
if kalender:
|
||||
satz.setze('VTAZ', kalender.hours_per_day,
|
||||
skala=2)
|
||||
refn = 'MA-%d-%s-%d' % (
|
||||
company.id, adat.strftime('%Y%m%d'),
|
||||
version.employee_id.id)
|
||||
satz.setze('REFN', refn)
|
||||
datensaetze.append(satz)
|
||||
ereignisse.append((version, 'MA', adat, refn))
|
||||
else:
|
||||
for version in lauf._l10n_at_offene_anmeldungen():
|
||||
if not version.ssnid:
|
||||
fehler.append(
|
||||
'Anmeldung %s ohne Versicherungsnummer.'
|
||||
% version.employee_id.name)
|
||||
continue
|
||||
satz = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + VR_SATZ, 772,
|
||||
bezeichnung='M3 %s' % version.employee_id.name)
|
||||
refw = 'M3-%d-%s-%d' % (
|
||||
company.id,
|
||||
version.contract_date_start.strftime('%Y%m%d'),
|
||||
version.employee_id.id)
|
||||
satz.setze('SART', 'M3')
|
||||
satz.setze('VSTR', vstr)
|
||||
satz.setze('REFW', refw)
|
||||
satz.setze('BKNR', bknr)
|
||||
satz.setze('DGNA', company.name[:70])
|
||||
satz.setze('VSNR', version.ssnid)
|
||||
satz.setze_datum(
|
||||
'GEBD', version.employee_id.birthday)
|
||||
familienname, vorname = l10n_at_namen(
|
||||
version.employee_id)
|
||||
satz.setze('FANA', familienname[:70])
|
||||
satz.setze('VONA', (vorname or '')[:70])
|
||||
satz.setze_datum('ADAT', version.contract_date_start)
|
||||
satz.setze('BBER', lauf._l10n_at_bber(version))
|
||||
satz.setze('GERF', 'J' if lauf
|
||||
._l10n_at_geringfuegig(version) else 'N')
|
||||
satz.setze('FRDV', 'N')
|
||||
if getattr(version, 'l10n_at_abfertigung_neu',
|
||||
False):
|
||||
satz.setze('BVAB',
|
||||
version.contract_date_start
|
||||
.strftime('%d%m%Y'))
|
||||
satz.setze('BVJN', 'J')
|
||||
kalender = version.resource_calendar_id
|
||||
if kalender:
|
||||
satz.setze('VWAZ', kalender.hours_per_week,
|
||||
skala=2)
|
||||
datensaetze.append(satz)
|
||||
ereignisse.append((version, 'M3',
|
||||
version.contract_date_start,
|
||||
refw))
|
||||
for version in lauf._l10n_at_offene_abmeldungen():
|
||||
if not version.ssnid:
|
||||
fehler.append(
|
||||
'Abmeldung %s ohne Versicherungsnummer.'
|
||||
% version.employee_id.name)
|
||||
continue
|
||||
satz = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + VR_SATZ, 772,
|
||||
bezeichnung='M4 %s' % version.employee_id.name)
|
||||
refw = 'M4-%d-%s-%d' % (
|
||||
company.id,
|
||||
version.contract_date_end.strftime('%Y%m%d'),
|
||||
version.employee_id.id)
|
||||
agrd = getattr(
|
||||
version.departure_reason_id, 'l10n_at_agrd',
|
||||
False) or '00'
|
||||
satz.setze('SART', 'M4')
|
||||
satz.setze('VSTR', vstr)
|
||||
satz.setze('REFW', refw)
|
||||
satz.setze('BKNR', bknr)
|
||||
satz.setze('DGNA', company.name[:70])
|
||||
satz.setze('VSNR', version.ssnid)
|
||||
satz.setze_datum(
|
||||
'GEBD', version.employee_id.birthday)
|
||||
familienname, vorname = l10n_at_namen(
|
||||
version.employee_id)
|
||||
satz.setze('FANA', familienname[:70])
|
||||
satz.setze('VONA', (vorname or '')[:70])
|
||||
satz.setze_datum('ADAT', version.contract_date_end)
|
||||
satz.setze_datum('EBSV', version.contract_date_end)
|
||||
satz.setze('AGRD', agrd)
|
||||
datensaetze.append(satz)
|
||||
ereignisse.append((version, 'M4',
|
||||
version.contract_date_end,
|
||||
refw))
|
||||
if fehler:
|
||||
lauf.protokoll = 'FEHLER:\n' + '\n'.join(fehler)
|
||||
raise UserError('Versichertenmeldung mit Fehlern '
|
||||
'abgebrochen:\n' + '\n'.join(fehler))
|
||||
if not datensaetze:
|
||||
raise UserError('Keine offenen Meldungen zum '
|
||||
'Stichtag %s.' % lauf.stichtag)
|
||||
bestand = L10nAtEldaBestand(
|
||||
'MA' if lauf.nur_fallweise else 'VR', vstr, '07'
|
||||
if lauf.nur_fallweise else '03',
|
||||
hersteller={'name': 'odoo-at-payroll', 'kfz': 'A',
|
||||
'plz': company.partner_id.zip,
|
||||
'ort': company.partner_id.city,
|
||||
'strasse': company.partner_id.street},
|
||||
datensaetze=datensaetze)
|
||||
datei = L10nAtEldaDatei([bestand])
|
||||
ereignis_werte = []
|
||||
for version, art, adatum, refw in ereignisse:
|
||||
ereignis_werte.append({
|
||||
'company_id': company.id,
|
||||
'employee_id': version.employee_id.id,
|
||||
'version_id': version.id,
|
||||
'art': art,
|
||||
'adatum': adatum,
|
||||
'refw': refw,
|
||||
})
|
||||
ereignisse = self.env['l10n.at.payroll.vr.event'].create(
|
||||
ereignis_werte) if ereignis_werte else \
|
||||
self.env['l10n.at.payroll.vr.event']
|
||||
lauf.write({
|
||||
'datei': base64.b64encode(datei.render()),
|
||||
'datei_name': 'versichertenmeldung_%s.txt' % (
|
||||
fields.Date.today().strftime('%Y%m%d'),),
|
||||
'event_ids': [(6, 0, ereignisse.ids)],
|
||||
'state': 'done',
|
||||
'protokoll': 'Meldungen: %d' % len(datensaetze),
|
||||
})
|
||||
return True
|
||||
Reference in New Issue
Block a user