# 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. Quelle ist der TASY-Katalog (AP0, Variante b): Beschäftigtengruppe + Ergänzungen (ERGB am Vertrag) + Dienstgebergruppe (Company) → Tarifgruppe → Tarifset; fehlt der Katalog, greift der Hand-Parameter ``at_mbgm_tarif`` als Fallback (Werte aus dem ÖGK-Handbuch, `.additional_docs/oegk/`). Fehlende Sätze brechen den Lauf listenweise ab (keine Erfindung von Tarifen). - **BSGR** (DM-ORG D.48/TASY): B001 Arbeiter, B002 Angestellte, B045 Arbeiterlehrlinge, B044 Angestelltenlehrlinge, B010 gering- fällige Arbeiter, B030 geringfügige Angestellte — TASY-Bezeichner verifiziert (B044 = „Ang. Lg.“, B045 = „Arb. Lg.“). - **ERGB** (D.49): Ergänzungen am Vertrag (z. B. E01 Nachtschwer- arbeit, E02 Schlechtwetterentschädigung) werden im Tarifblock belegt und steuern die Tarifgruppen-Auflösung im Katalog; das Company-Flag ``l10n_at_nschab`` bleibt Fallback ohne Katalog. - **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, TASY-Bezeichner): B001 Arbeiter, B002 Angestellte; Lehrlinge B045 (Arbeiterlehrlinge) / B044 (Angestelltenlehrlinge); geringfügig/fallweise B010 (Arbeiter) / B030 (Angestellte). Ohne Default-Parameter → Lauf bricht ab, bis der Katalog importiert oder ``at_mbgm_tarif`` gepflegt ist (Tarifsystem- Werte, keine Erfindung).""" personalgruppe = getattr( version, 'l10n_at_personalgruppe', '') if version.l10n_at_lehrling: return 'B045' if personalgruppe == 'arbeiter' else 'B044' if slip.l10n_at_geringfuegig or version.l10n_at_fallweise: return 'B010' if personalgruppe == 'arbeiter' else 'B030' return 'B001' if personalgruppe == 'arbeiter' else 'B002' def _l10n_at_tarif(self, bsgr, version=None): """T01/T02-Gesamtsätze je BSGR — Katalog vor Hand-Parameter. 1. TASY-Katalog: Tarifgruppe aus BSGR + ERGB am Vertrag + Dienstgebergruppe der Company am Stichtag; Sätze = Summe der Tarifset-Komponenten je Verrechnungsbasis (AB/SZ). 2. Fallback: ``at_mbgm_tarif``-Parameter (BSGR-Schlüssel). Rückgabe ``{'laufend', 'sz', 'quelle'}`` oder None → Lauf bricht listenweise ab. """ self.ensure_one() ergaenzungen = version.l10n_at_ergaenzung_ids.mapped('code') \ if version is not None else [] dgg = self.company_id.l10n_at_dienstgebergruppe or 'NBALG' tarifgruppe = self.env[ 'l10n.at.payroll.tasy.tarifgruppe']._l10n_at_find( bsgr, ergaenzungen, dgg, self.beitragsmonat) if tarifgruppe: ab = tarifgruppe._l10n_at_saetze('AB', self.beitragsmonat) sz = tarifgruppe._l10n_at_saetze('SZ', self.beitragsmonat) if ab is None or sz is None: return None return {'laufend': ab['gesamt'], 'sz': sz['gesamt'], 'quelle': 'katalog'} tarif = self.env['hr.rule.parameter']._get_parameter_from_code( 'at_mbgm_tarif', self.beitragsmonat, raise_if_not_found=False) or {} eintrag = tarif.get(bsgr) if eintrag: return {'laufend': eintrag['laufend'], 'sz': eintrag['sz'], 'quelle': 'parameter'} return None 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 über das Company-Flag nur im Parameter-Fallback — # im Katalog-Pfad steckt der NB-Zweig über die ERGB-E01- # Tarifgruppe (Dienstgebergruppe NBNSA) bereits im T01. nschab = 3.80 if (self.company_id.l10n_at_nschab and tarif.get('quelle') != 'katalog' 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, version) if not tarif: fehler.append( 'Mitarbeiter %s: kein Tarifsatz für BSGR ' '%s (weder TASY-Katalog noch Parameter ' 'at_mbgm_tarif am Beitragsmonat).' % (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) # ERGB (DM-ORG D.49): bis zu 5 Ergänzungen à 3 Stellen — # Grundstellung blank, wenn keine gepflegt (Katalog- # Normalfall ohne Ergänzung). ergaenzungen = version.l10n_at_ergaenzung_ids.mapped('code')[:5] if ergaenzungen: tarifblock.setze('ERGB', ''.join(ergaenzungen)) 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 Nachtschwerarbeitsbeitrags-Betrieb (mBGM-Fallback)', help='Betrieb mit Nachtschwerarbeit (ÖGK-Tarifsystem ' '„Normalbetriebe mit Nachtschwerarbeit“, Dienstgebergruppe ' 'NBNSA): ohne TASY-Katalog erhalten Arbeiter (BSGR B001) ' 'im mBGM-T01 den um NSchAB 3,80 % erhöhten Gesamtsatz. ' 'Mit Katalog stattdessen die Ergänzung E01 am Vertrag ' 'setzen — der NB-Zweig ist dann Teil der E01-Tarifgruppe.') l10n_at_dienstgebergruppe = fields.Char( string='AT Dienstgebergruppe (SV-Tarifsystem)', default='NBALG', help='Code der Dienstgebergruppe des TASY-Tarifsystems, z. B. ' 'NBALG „Normalbetriebe — Allgemeiner Teil“, NBNSA ' '(Nachtschwerarbeit), NBBAU (Schlechtwetter), HAUSH ' '(Haushalte), DGONO (ohne Niederlassung). Bestimmt mit ' 'BSGR + Ergänzungen die Tarifgruppe der mBGM-Tarifierung ' '(AP0). Vollständige Liste: Tarifsystem-Katalog-Menü ' 'nach dem TASY-Import.') class HrVersion(models.Model): _inherit = 'hr.version' l10n_at_ergaenzung_ids = fields.Many2many( 'l10n.at.payroll.tasy.ergaenzung', 'l10n_at_hr_payroll_version_ergaenzung_rel', 'version_id', 'ergaenzung_id', string='AT Ergänzungen (ERGB, SV-Tarifsystem)', groups="hr_payroll.group_hr_payroll_user", help='Ergänzungen zur Beschäftigtengruppe (DM-ORG D.49) für die ' 'mBGM-Tarifierung — gemeinsam mit BSGR und Dienstgebergruppe ' 'bilden sie die Tarifgruppe. Z. B. E01 Nachtschwerarbeit, ' 'E02 Schlechtwetterentschädigung. Katalog nach dem TASY-' 'Import; ohne Eintrag gilt der Normalfall ohne Ergänzung.') 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')