[IMP] l10n_at_hr_payroll: TASY-Tarifsystem-Katalog, AGRD-Auswahl, DG-Kosten-/Lohnkonten-Reports, Wr.-DAG-Grundlagen (AP0-AP3)
AP0 Variante b — TASY-Tarifsystem-Katalog: - Neue Katalogmodelle (BSGR/ERGB/Dienstgebergruppen/Tarifgruppen/ Tarifsets/Tarife/Zuordnungen/Zu-Abschläge inkl. Steuerung), models/tasy_katalog.py — Auflösung Tarifgruppe (BSGR+ERGB+DGG) -> Sätze je Verrechnungsbasis - Import-Wizard l10n.at.payroll.tasy.katalog.wizard (alle Träger, sektionsscharf, idempotent), Views/Menüs, Security - Realexport-Test gegen TASY-LSWH-Export (B001/B002 = 39,05/37,55 %) AP1 — eSV-Abmeldegründe (DM-ORG D.22): - l10n_at_agrd Char -> vollständige Selection (33 Codes), Seed der Standard-Abgangsursachen (noupdate, hr-Defaults vorverbunden), Form-/Listen-Erbe AP2a-c — Auswertungen: - Monatsjournal: eigener Dienstgeberanteile-Block (ATP_DG vorher still ausgefiltert); Fix: models/monatsreports.py war nie in models/__init__.py importiert (GP7-Reports waren toter Code) - Neuer Report Dienstgeber-Gesamtkosten (Brutto + LNK aufgeschlüsselt/ kumuliert je AN + Laufsummen, Spalten FLAF/KommSt/DZ/BV/Wr. DAG) - Jahreslohnkonto je DN + Betriebsjahreslohnkonto (Jahresbericht- Wizard, Monatsmatrix je Lohnart), report/jahresreports.xml AP3 — Wr. Dienstgeberabgabe (RIS LrW GesNr 20000133 verifiziert): - Company-Flag l10n_at_wien_dag (Kern), Fristenarten wien_dag/ wien_dag_jahres (cron-geflaggt, 15./31. 3.), Company-View-Feld, Wr.-DAG-Spalte im DG-Kosten-Report
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Personalverrechnung Kern (AT Payroll Core)',
|
||||
'version': '19.0.5.1.0',
|
||||
'version': '19.0.6.0.0',
|
||||
'author': 'ixsol',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -74,11 +74,14 @@ Kein VRV-Bezug: die Gemeinde-Buchhaltung (Anlage 3b, Ansatz/MVAG) läuft
|
||||
'data/rule_parameters_sv_2026.xml',
|
||||
'data/rule_parameters_2027.xml',
|
||||
'data/tasy_mapping.xml',
|
||||
'data/hr_departure_reason_seed.xml',
|
||||
'views/tasy_views.xml',
|
||||
'views/tasy_katalog_views.xml',
|
||||
'views/company_views.xml',
|
||||
'views/meldewesen_views.xml',
|
||||
'views/fristen_views.xml',
|
||||
'report/belege_reports.xml',
|
||||
'report/jahresreports.xml',
|
||||
],
|
||||
'countries': ['at'],
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
<!-- ============================================================ -->
|
||||
<!-- AP1: eSV-Abmeldegründe (AGRD, DM-ORG D.22) an den -->
|
||||
<!-- Abgangsursachen. -->
|
||||
<!-- -->
|
||||
<!-- Die Odoo-Standardursachen (hr.departure_fired/resigned/ -->
|
||||
<!-- retired) werden vorverbunden statt dupliziert; die übrigen -->
|
||||
<!-- AT-typischen Ursachen kommen hinzu. noupdate: mandantenseitig -->
|
||||
<!-- geänderte oder ergänzte Ursachen bleiben unberührt. -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<record id="hr.departure_fired" model="hr.departure.reason">
|
||||
<field name="l10n_at_agrd">01</field>
|
||||
</record>
|
||||
<record id="hr.departure_resigned" model="hr.departure.reason">
|
||||
<field name="l10n_at_agrd">02</field>
|
||||
</record>
|
||||
<record id="hr.departure_retired" model="hr.departure.reason">
|
||||
<field name="l10n_at_agrd">16</field>
|
||||
</record>
|
||||
|
||||
<record id="departure_reason_einvernehmlich"
|
||||
model="hr.departure.reason">
|
||||
<field name="name">Einvernehmliche Lösung</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">40</field>
|
||||
<field name="l10n_at_agrd">03</field>
|
||||
</record>
|
||||
<record id="departure_reason_zeitablauf" model="hr.departure.reason">
|
||||
<field name="name">Zeitablauf</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">41</field>
|
||||
<field name="l10n_at_agrd">04</field>
|
||||
</record>
|
||||
<record id="departure_reason_berechtigter_austritt"
|
||||
model="hr.departure.reason">
|
||||
<field name="name">Berechtigter vorzeitiger Austritt</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">42</field>
|
||||
<field name="l10n_at_agrd">05</field>
|
||||
</record>
|
||||
<record id="departure_reason_fristlose" model="hr.departure.reason">
|
||||
<field name="name">Fristlose Entlassung</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">43</field>
|
||||
<field name="l10n_at_agrd">06</field>
|
||||
</record>
|
||||
<record id="departure_reason_karenz" model="hr.departure.reason">
|
||||
<field name="name">Karenz nach MSchG / VKG</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">44</field>
|
||||
<field name="l10n_at_agrd">07</field>
|
||||
</record>
|
||||
<record id="departure_reason_tod" model="hr.departure.reason">
|
||||
<field name="name">Tod des/der Dienstnehmers:in</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">45</field>
|
||||
<field name="l10n_at_agrd">13</field>
|
||||
</record>
|
||||
<record id="departure_reason_sonstiges_ende"
|
||||
model="hr.departure.reason">
|
||||
<field name="name">Sonstiger Grund mit Ende des Dienstverhältnisses</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="sequence">46</field>
|
||||
<field name="l10n_at_agrd">00</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,5 +1,6 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
from . import tasy
|
||||
from . import tasy_katalog
|
||||
from . import lohnsteuer
|
||||
from . import sozialversicherung
|
||||
from . import elda
|
||||
@@ -9,3 +10,4 @@ from . import lohnzettel_l16
|
||||
from . import fristenkalender
|
||||
from . import jahresretro
|
||||
from . import mitteilungsfile
|
||||
from . import monatsreports
|
||||
@@ -105,6 +105,32 @@ FRISTART_DEFINITIONEN = {
|
||||
'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.',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +214,10 @@ class L10nAtPayrollFrist(models.Model):
|
||||
deadline_monat = date(
|
||||
folgemonat.year, folgemonat.month,
|
||||
FRISTART_DEFINITIONEN['sv_mbgm']['tag'])
|
||||
for art in ('sv_mbgm', 'db_dz_kommst'):
|
||||
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)
|
||||
@@ -209,6 +238,11 @@ class L10nAtPayrollFrist(models.Model):
|
||||
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):
|
||||
|
||||
@@ -15,13 +15,56 @@ Dienstgeberin und der Arbeitsstätten.
|
||||
Prüfzifferalgorithmus der SVS ist nicht beschafft → nur Format/
|
||||
Plausibilität, Rest dokumentiert).
|
||||
- ``hr.departure.reason``: Mapping auf den eSV-Abmeldegrund (AGRD,
|
||||
DM-ORG D.22; Standard ``00`` sonstiger Grund mit Ende des
|
||||
Beschäftigungsverhältnisses)."""
|
||||
DM-ORG D.22 — vollständige Code-Liste als Selection, Standard
|
||||
``00`` sonstiger Grund mit Ende des Beschäftigungsverhältnisses;
|
||||
die üblichen Abgangsursachen sind im Seed vorverbunden,
|
||||
noupdate — mandantenseitig ergänzte Ursachen bleiben unberührt)."""
|
||||
from odoo import api, fields, models
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
# eSV-Abmeldegründe, DM-ORG 42.7.0 Kapitel D.22 (AGRD – Abmeldegrund,
|
||||
# Code) — verifiziert gegen .firecrawl/ris/elda/DM-ORG_v42.7.0.txt
|
||||
# (42. Ergänzung 07/2026). Codes 26/28 existieren nicht (Lücken im
|
||||
# Katalog), Code 00 = sonstiger Grund mit Ende des DV.
|
||||
L10N_AT_AGRD = [
|
||||
('01', '01 — Kündigung durch den Dienstgeber'),
|
||||
('02', '02 — Kündigung durch den Dienstnehmer'),
|
||||
('03', '03 — Einvernehmliche Lösung'),
|
||||
('04', '04 — Zeitablauf'),
|
||||
('05', '05 — Berechtigter vorzeitiger Austritt'),
|
||||
('06', '06 — Fristlose Entlassung'),
|
||||
('07', '07 — Karenz nach MSchG / VKG'),
|
||||
('08', '08 — Präsenzdienstleistung im Bundesheer'),
|
||||
('09', '09 — Zivildienst'),
|
||||
('10', '10 — Pragmatisierung'),
|
||||
('11', '11 — Länger als 1 Monat währender unbezahlter Urlaub'),
|
||||
('12', '12 — Ummeldung'),
|
||||
('13', '13 — Tod des Dienstnehmers'),
|
||||
('14', '14 — Änderung der SV-Pflicht'),
|
||||
('15', '15 — Truppenübung'),
|
||||
('16', '16 — Pensionierung'),
|
||||
('17', '17 — Ende freier Dienstvertrag (§ 4 Abs 4 ASVG)'),
|
||||
('18', '18 — Enthebung von der Gerichtspraxis'),
|
||||
('19', '19 — Unterbrechung der Gerichtspraxis'),
|
||||
('20', '20 — Entlassung aus der Bundesbetreuung'),
|
||||
('21', '21 — Kündigung durch DN während Elternteilzeit (MSchG/VKG)'),
|
||||
('22', '22 — Unberechtigter vorzeitiger Austritt'),
|
||||
('23', '23 — Bildungskarenz gemäß § 11 AVRAG'),
|
||||
('24', '24 — Vorzeitiger Austritt gem. § 25 IO durch Dienstnehmer'),
|
||||
('25', '25 — Kündigung gem. § 25 IO durch Insolvenzverwalter:in'),
|
||||
('27', '27 — Kündigung gem. § 25 IO durch Dienstgeber'),
|
||||
('29', '29 — SV-Ende – Beschäftigung aufrecht'),
|
||||
('30', '30 — Lösung in der Probezeit durch Dienstgeber'),
|
||||
('31', '31 — Pflegekarenz (interner SV-Abmeldegrund)'),
|
||||
('32', '32 — Bildungskarenz gemäß § 12 AVRAG'),
|
||||
('33', '33 — Familienhospizkarenz (interner SV-Abmeldegrund)'),
|
||||
('34', '34 — Lösung in der Probezeit durch Dienstnehmer'),
|
||||
('00', '00 — Sonstiger Grund mit Ende des DV'),
|
||||
]
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
@@ -36,6 +79,16 @@ class ResCompany(models.Model):
|
||||
help='9-stellig: 2 Stellen Finanzamtsnummer + 7 Stellen\n'
|
||||
'Steuernummer (Lohnzettel Finanz I1 STNRA/STNRL;\n'
|
||||
'Arbeitsstättenmeldung STNR).')
|
||||
l10n_at_wien_dag = fields.Boolean(
|
||||
string='AT Beschäftigungsort Wien (Wr. DAG)',
|
||||
help='Wiener Dienstgeberabgabe („U-Bahn-Steuer“, Wr. DAG,\n'
|
||||
'RIS LrW GesNr 20000133): 2 € je Dienstnehmer:in und\n'
|
||||
'angefangene Woche je Bestehen eines Dienstverhältnisses\n'
|
||||
'mit Beschäftigungsort Wien (§§ 1, 2, 5); Befreiungen § 3\n'
|
||||
'(z. B. Überschreiten des 55. Lebensjahres, Lehrlinge,\n'
|
||||
'≤ 10 Wochenstunden, Mutterschaftsverbote/Karenz,\n'
|
||||
'Präsenzdienst, Gebietskörperschaften); Selbstberechnung\n'
|
||||
'15. des Folgemonats, Jahreserklärung bis 31. 3. (§ 6).')
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
@@ -89,8 +142,10 @@ class HrVersion(models.Model):
|
||||
class HrDepartureReason(models.Model):
|
||||
_inherit = 'hr.departure.reason'
|
||||
|
||||
l10n_at_agrd = fields.Char(
|
||||
l10n_at_agrd = fields.Selection(
|
||||
L10N_AT_AGRD,
|
||||
string='AT eSV-Abmeldegrund (AGRD)',
|
||||
help='2-stelliger Abmeldegrund-Code der Versichertenmeldung '
|
||||
'(DM-ORG D.22); Standard 00 „Sonstiger Grund mit Ende des '
|
||||
'Beschäftigungsverhältnisses“.')
|
||||
'Beschäftigungsverhältnisses“. Die Abmeldung M4 liest das '
|
||||
'Mapping dieser Abgangsursache.')
|
||||
@@ -1,16 +1,29 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""Monatsdienstliste und Monatsjournal (GP7/AP6): QWeb-Datenlieferanten
|
||||
auf dem Lohnlauf (hr.payslip.run) — voraberechnete Aggregation über
|
||||
`hr.payslip.line` (read_group-Muster des Contribution Register), damit
|
||||
die Templates reine Darstellung bleiben.
|
||||
"""Monatsreports und Jahresberichte der AT-Lohnverrechnung.
|
||||
|
||||
- **Monatsdienstliste**: je AN eine Zeile mit den mBGM-Kontrollgrößen
|
||||
(Versicherungstage/Beitragsgrundlagen je Beitragsmonat, DN- und
|
||||
DG-Beiträge, DG-Anteile) — Abstimmung Abrechnung ↔ SV-Meldung.
|
||||
- **Monatsjournal**: je Lohnart die Summe über den Lauf (Brutto,
|
||||
Abzüge, DG-Anteile, Netto) mit Kontrollsummen gegen die
|
||||
GP6-Buchung."""
|
||||
from odoo import models
|
||||
- **Monatsdienstliste** (GP7/AP6): je AN die mBGM-Kontrollgrößen —
|
||||
Abstimmung Abrechnung ↔ SV-Meldung.
|
||||
- **Monatsjournal** (GP7/AP6, AP2a erweitert): je Lohnart die Summe
|
||||
über den Lauf, gegliedert nach Bezügen/Abzügen (Bemessung ≠
|
||||
neutral) und — seit AP2a — **eigenem Block Dienstgeberanteile**
|
||||
(Kategorie ``ATP_DG``: SV-DG je Zweig, FLAF-DB, KommSt, DZ, BV —
|
||||
zuvor wurden die neutralen DG-Regeln still ausgefiltert) mit
|
||||
Kontrollsummen gegen die GP6-Buchung.
|
||||
- **Dienstgeber-Gesamtkosten** (AP2a): je AN Bruttolohn und die
|
||||
Lohnnebenkosten aufgeschlüsselt (SV-DG laufend/SZ, FLAF-DB,
|
||||
KommSt, DZ, BV, weitere DG-Positionen) und kumuliert
|
||||
(Σ Lohnnebenkosten, Gesamtkosten = Brutto + LNK) mit Laufsummen.
|
||||
- **Jahreslohnkonto je Dienstnehmer:in** (AP2b) und
|
||||
**Betriebsjahreslohnkonto** (AP2c): Monatsmatrix je Lohnart aus
|
||||
``hr.payslip.line`` — Datenbereitstellung über den Wizard
|
||||
``l10n.at.payroll.jahresbericht.wizard``.
|
||||
|
||||
Die Templates bleiben reine Darstellung (QWeb); alle Zahlen werden
|
||||
hier vorberechnet (read_group-Muster des Contribution Register).
|
||||
"""
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class L10nAtPayrollMonatsReports(models.AbstractModel):
|
||||
@@ -61,24 +74,32 @@ class L10nAtPayrollMonatsReports(models.AbstractModel):
|
||||
|
||||
class L10nAtPayrollMonatsjournal(models.AbstractModel):
|
||||
_name = 'report.l10n_at_hr_payroll.report_monatsjournal'
|
||||
_description = 'Monatsjournal (GP7/AP6)'
|
||||
_description = 'Monatsjournal (GP7/AP6, AP2a)'
|
||||
|
||||
def _get_report_values(self, docids, data=None):
|
||||
runs = self.env['hr.payslip.run'].browse(docids)
|
||||
zeilen = []
|
||||
for run in runs:
|
||||
gruppen = {}
|
||||
dg_gruppen = {}
|
||||
for slip in run.slip_ids.filtered(
|
||||
lambda p: p.state not in ('draft', 'cancel')):
|
||||
for line in slip.line_ids:
|
||||
regel = line.salary_rule_id
|
||||
if not line.total or regel.l10n_at_bemessung \
|
||||
== 'neutral':
|
||||
if not line.total:
|
||||
continue
|
||||
kategorie = regel.category_id.code or ''
|
||||
schluessel = (regel.code, regel.name,
|
||||
regel.l10n_at_bemessung)
|
||||
eintrag = gruppen.setdefault(schluessel, 0.0)
|
||||
gruppen[schluessel] = eintrag + line.total
|
||||
if kategorie == 'ATP_DG':
|
||||
# AP2a: Dienstgeberanteile eigener Block —
|
||||
# die neutralen DG-Regeln (SVDG_*, FLAFDB,
|
||||
# KOMMST, DZ, BVG) nicht mehr ausfiltern.
|
||||
dg_gruppen[schluessel] = \
|
||||
dg_gruppen.get(schluessel, 0.0) + line.total
|
||||
elif regel.l10n_at_bemessung != 'neutral':
|
||||
gruppen[schluessel] = \
|
||||
gruppen.get(schluessel, 0.0) + line.total
|
||||
zeilen.append({
|
||||
'run': run,
|
||||
'arten': [
|
||||
@@ -86,5 +107,182 @@ class L10nAtPayrollMonatsjournal(models.AbstractModel):
|
||||
'summe': summe}
|
||||
for (code, name, bemessung), summe
|
||||
in sorted(gruppen.items())],
|
||||
'dg_arten': [
|
||||
{'code': code, 'name': name, 'bemessung': bemessung,
|
||||
'summe': summe}
|
||||
for (code, name, bemessung), summe
|
||||
in sorted(dg_gruppen.items())],
|
||||
'summe_arten': sum(gruppen.values()),
|
||||
'summe_dg': sum(dg_gruppen.values()),
|
||||
})
|
||||
return {'docs': runs, 'journal': zeilen}
|
||||
|
||||
|
||||
class L10nAtPayrollDgKosten(models.AbstractModel):
|
||||
_name = 'report.l10n_at_hr_payroll.report_dg_kosten'
|
||||
_description = 'Dienstgeber-Gesamtkosten (AP2a)'
|
||||
|
||||
# Festspalten des Reports; weitere DG-Positionen (z. B. §-53a-UV
|
||||
# der Geringfügigen) laufen unter 'weitere'.
|
||||
_SPALTEN = ('FLAFDB', 'KOMMST', 'DZ', 'BVG', 'WIEN_DAG')
|
||||
|
||||
def _get_report_values(self, docids, data=None):
|
||||
runs = self.env['hr.payslip.run'].browse(docids)
|
||||
zeilen = []
|
||||
summen = {
|
||||
'brutto': 0.0, 'svdg': 0.0, 'dg_summe': 0.0,
|
||||
'gesamtkosten': 0.0,
|
||||
'spalten': {code: 0.0 for code in self._SPALTEN},
|
||||
'weitere': 0.0,
|
||||
}
|
||||
for run in runs:
|
||||
for slip in run.slip_ids.filtered(
|
||||
lambda p: p.state not in ('draft', 'cancel')):
|
||||
brutto = sum(
|
||||
slip.line_ids.filtered(
|
||||
lambda l: l.total
|
||||
and l.salary_rule_id.category_id.code
|
||||
== 'ATP_BRUTTO'
|
||||
and l.salary_rule_id.l10n_at_bemessung != 'neutral'
|
||||
).mapped('total'))
|
||||
dg_komponenten = {}
|
||||
for line in slip.line_ids:
|
||||
regel = line.salary_rule_id
|
||||
if not line.total \
|
||||
or regel.category_id.code != 'ATP_DG':
|
||||
continue
|
||||
dg_komponenten[regel.code] = \
|
||||
dg_komponenten.get(regel.code, 0.0) + line.total
|
||||
svdg = (slip._l10n_at_svdg_gesamt()
|
||||
+ sum(slip._l10n_at_svdg_sz(k) for k in
|
||||
('kv', 'pv', 'av', 'uv', 'iesg')))
|
||||
spalten = {code: dg_komponenten.get(code, 0.0)
|
||||
for code in self._SPALTEN}
|
||||
dg_summe = sum(dg_komponenten.values())
|
||||
weitere = dg_summe - svdg - sum(spalten.values())
|
||||
zeilen.append({
|
||||
'slip': slip,
|
||||
'brutto': brutto,
|
||||
'svdg': svdg,
|
||||
'spalten': spalten,
|
||||
'weitere': max(weitere, 0.0),
|
||||
'dg_summe': dg_summe,
|
||||
'gesamtkosten': brutto + dg_summe,
|
||||
})
|
||||
summen['brutto'] += brutto
|
||||
summen['svdg'] += svdg
|
||||
summen['dg_summe'] += dg_summe
|
||||
summen['gesamtkosten'] += brutto + dg_summe
|
||||
summen['weitere'] += max(weitere, 0.0)
|
||||
for code in self._SPALTEN:
|
||||
summen['spalten'][code] += spalten[code]
|
||||
return {'docs': runs, 'zeilen': zeilen, 'summen': summen}
|
||||
|
||||
|
||||
class L10nAtPayrollJahresberichtWizard(models.TransientModel):
|
||||
"""Druck-Wrapper für Jahreslohnkonto (je DN) und
|
||||
Betriebsjahreslohnkonto (Summen je Lohnart) — AP2b/AP2c."""
|
||||
_name = 'l10n.at.payroll.jahresbericht.wizard'
|
||||
_description = 'Jahresberichte: Lohnkonten drucken'
|
||||
|
||||
jahr = fields.Integer(
|
||||
required=True,
|
||||
default=lambda self: fields.Date.context_today(self).year)
|
||||
art = fields.Selection(
|
||||
[('dn', 'Jahreslohnkonto je Dienstnehmer:in'),
|
||||
('betrieb', 'Betriebsjahreslohnkonto (Summen je Lohnart)')],
|
||||
required=True, default='dn')
|
||||
company_id = fields.Many2one(
|
||||
'res.company', required=True,
|
||||
default=lambda self: self.env.company)
|
||||
employee_ids = fields.Many2many(
|
||||
'hr.employee', string='Dienstnehmer:innen',
|
||||
help='Leer = alle Dienstnehmer:innen des Unternehmens.')
|
||||
|
||||
def action_drucken(self):
|
||||
self.ensure_one()
|
||||
if self.art == 'dn':
|
||||
employees = self.employee_ids or self.env[
|
||||
'hr.employee'].search(
|
||||
[('company_id', '=', self.company_id.id)])
|
||||
report = self.env.ref(
|
||||
'l10n_at_hr_payroll.action_report_jahreslohnkonto')
|
||||
return report.report_action(
|
||||
employees, data={'jahr': self.jahr})
|
||||
report = self.env.ref(
|
||||
'l10n_at_hr_payroll.action_report_betriebslohnkonto')
|
||||
return report.report_action(
|
||||
self.company_id, data={'jahr': self.jahr})
|
||||
|
||||
@api.model
|
||||
def _l10n_at_lohnkonto_zeilen(self, jahr, company_id,
|
||||
employee_id=None):
|
||||
"""Aggregierte Lohnartzeilen des Jahres: [(code, name,
|
||||
bemessung, kategorie, [Jan..Dez], total), …] — sortiert nach
|
||||
Kategorie und Code."""
|
||||
domain = [
|
||||
('company_id', '=', company_id),
|
||||
('date_from', '>=', date(jahr, 1, 1)),
|
||||
('date_from', '<=', date(jahr, 12, 31)),
|
||||
('slip_id.state', 'not in', ('draft', 'cancel')),
|
||||
]
|
||||
if employee_id:
|
||||
domain.append(('employee_id', '=', employee_id))
|
||||
zeilen = {}
|
||||
for line in self.env['hr.payslip.line'].search(domain):
|
||||
regel = line.salary_rule_id
|
||||
schluessel = (regel.code, regel.name,
|
||||
regel.l10n_at_bemessung,
|
||||
regel.category_id.code or '')
|
||||
eintrag = zeilen.setdefault(
|
||||
schluessel, {'monate': [0.0] * 12, 'total': 0.0})
|
||||
eintrag['monate'][line.date_from.month - 1] += line.total
|
||||
eintrag['total'] += line.total
|
||||
result = []
|
||||
for (code, name, bemessung, kategorie), eintrag in sorted(
|
||||
zeilen.items()):
|
||||
result.append({
|
||||
'code': code, 'name': name, 'bemessung': bemessung,
|
||||
'kategorie': kategorie,
|
||||
'monate': eintrag['monate'], 'total': eintrag['total'],
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
class L10nAtPayrollJahreslohnkonto(models.AbstractModel):
|
||||
_name = 'report.l10n_at_hr_payroll.report_jahreslohnkonto'
|
||||
_description = 'Jahreslohnkonto je Dienstnehmer:in (AP2b)'
|
||||
|
||||
def _get_report_values(self, docids, data=None):
|
||||
jahr = (data or {}).get('jahr') \
|
||||
or fields.Date.context_today(self).year
|
||||
employees = self.env['hr.employee'].browse(docids)
|
||||
berichte = []
|
||||
for employee in employees:
|
||||
berichte.append({
|
||||
'employee': employee,
|
||||
'zeilen': self.env[
|
||||
'l10n.at.payroll.jahresbericht.wizard'
|
||||
]._l10n_at_lohnkonto_zeilen(
|
||||
jahr, employee.company_id.id, employee.id),
|
||||
})
|
||||
return {'docs': employees, 'jahr': jahr, 'berichte': berichte}
|
||||
|
||||
|
||||
class L10nAtPayrollBetriebslohnkonto(models.AbstractModel):
|
||||
_name = 'report.l10n_at_hr_payroll.report_betriebslohnkonto'
|
||||
_description = 'Betriebsjahreslohnkonto (AP2c)'
|
||||
|
||||
def _get_report_values(self, docids, data=None):
|
||||
jahr = (data or {}).get('jahr') \
|
||||
or fields.Date.context_today(self).year
|
||||
companies = self.env['res.company'].browse(docids)
|
||||
berichte = []
|
||||
for company in companies:
|
||||
berichte.append({
|
||||
'company': company,
|
||||
'zeilen': self.env[
|
||||
'l10n.at.payroll.jahresbericht.wizard'
|
||||
]._l10n_at_lohnkonto_zeilen(jahr, company.id),
|
||||
})
|
||||
return {'docs': companies, 'jahr': jahr, 'berichte': berichte}
|
||||
@@ -1,4 +1,17 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""TASY-LSWH-Import (SV-Tarifsystem, sozialversicherung.at/tarifsystem).
|
||||
|
||||
Zwei Stufen:
|
||||
|
||||
1. ``l10n.at.payroll.tasy.import.wizard`` — SV-Werte als
|
||||
hr.rule.parameter (Mapping-gesteuert, Vorschau, historische
|
||||
Werte bleiben erhalten; GP4).
|
||||
2. ``l10n.at.payroll.tasy.katalog.wizard`` — Vollständiger
|
||||
Tarifsystem-Katalog (AP0, Variante b): BSGR/ERGB/Dienstgeber-
|
||||
gruppen/Tarifgruppen/Tarifsets/Tarife/Zu-/Abschläge, alle
|
||||
Träger, versioniert über von/bis. Die mBGM-Tarifierung liest
|
||||
daraus; der Hand-Parameter ``at_mbgm_tarif`` bleibt Fallback.
|
||||
"""
|
||||
from lxml import etree
|
||||
|
||||
import base64
|
||||
@@ -7,6 +20,19 @@ from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
def _l10n_at_tasy_root(file_field):
|
||||
"""TASY-XML robust laden: rohe XML-Bytes ODER base64."""
|
||||
parser = etree.XMLParser(resolve_entities=False, no_network=True)
|
||||
data = file_field or b''
|
||||
if isinstance(data, str):
|
||||
data = data.encode()
|
||||
# Binary kann XML-Bytes ODER base64 (str/Bytes) liefern:
|
||||
# dekodieren, wenn kein '<' am Anfang steht.
|
||||
if not data.lstrip().startswith(b'<'):
|
||||
data = base64.b64decode(data)
|
||||
return etree.fromstring(data, parser)
|
||||
|
||||
|
||||
class L10nAtPayrollTasyMapping(models.Model):
|
||||
"""Mapping TASY-Code/-Zweig -> SV-Regelparameter (hr.rule.parameter).
|
||||
|
||||
@@ -102,15 +128,7 @@ class L10nAtPayrollTasyImportWizard(models.TransientModel):
|
||||
|
||||
def _tasy_root(self):
|
||||
self.ensure_one()
|
||||
parser = etree.XMLParser(resolve_entities=False, no_network=True)
|
||||
data = self.file or b''
|
||||
if isinstance(data, str):
|
||||
data = data.encode()
|
||||
# Binary kann XML-Bytes ODER base64 (str/Bytes) liefern:
|
||||
# dekodieren, wenn kein '<' am Anfang steht.
|
||||
if not data.lstrip().startswith(b'<'):
|
||||
data = base64.b64decode(data)
|
||||
return etree.fromstring(data, parser)
|
||||
return _l10n_at_tasy_root(self.file)
|
||||
|
||||
@staticmethod
|
||||
def _tasy_text(node, tag):
|
||||
@@ -300,3 +318,391 @@ class L10nAtPayrollTasyImportWizard(models.TransientModel):
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class L10nAtPayrollTasyKatalogWizard(models.TransientModel):
|
||||
"""Lädt den vollständigen Tarifsystem-Katalog aus dem
|
||||
TASY-LSWH-Export (AP0, Variante b) — alle Trägergruppen,
|
||||
versioniert über von/bis-Datum. Idempotent: wiederholter Import
|
||||
derselben Datei aktualisiert statt zu duplizieren; historische
|
||||
Gültigkeitsfenster bleiben erhalten (Jahreswechsel = neuer
|
||||
Import, keine Löschung).
|
||||
|
||||
Die Sätze selbst werden NICHT hieraus in hr.rule.parameter
|
||||
geschrieben — die mBGM-Tarifierung liest den Katalog direkt
|
||||
(models/tasy_katalog.py); nur die Zweig-Split-Werte für den
|
||||
GP4-Kern laufen über den Wert-Import-Wizard.
|
||||
"""
|
||||
_name = 'l10n.at.payroll.tasy.katalog.wizard'
|
||||
_description = 'SV-Tarifsystem-Katalog importieren (TASY)'
|
||||
|
||||
file = fields.Binary(required=True, string='TASY-Export (XML)')
|
||||
filename = fields.Char()
|
||||
stichtag = fields.Date(
|
||||
readonly=True,
|
||||
help="Gültigkeitstichtag des Exports (nur Dokumentation — "
|
||||
"die Katalogzeilen tragen eigene von/bis-Fenster).")
|
||||
protocol = fields.Text(readonly=True)
|
||||
|
||||
@staticmethod
|
||||
def _tasy_text(node, tag):
|
||||
child = node.find(tag)
|
||||
return (child.text or '').strip() if child is not None else ''
|
||||
|
||||
@staticmethod
|
||||
def _tasy_texts(node, tag):
|
||||
"""Alle Kindelemente dieses Tags — TASY nutzt wiederholte
|
||||
Referenz-Elemente (z. B. <tarife>xmlId</tarife>)."""
|
||||
return [child.text.strip() for child in node.findall(tag)
|
||||
if child.text and child.text.strip()]
|
||||
|
||||
@staticmethod
|
||||
def _tasy_bool(text):
|
||||
return (text or '').strip().lower() in ('true', '1', 'j', 'ja',
|
||||
'yes', 'y')
|
||||
|
||||
def _tasy_root(self):
|
||||
self.ensure_one()
|
||||
return _l10n_at_tasy_root(self.file)
|
||||
|
||||
@staticmethod
|
||||
def _section(root, tag):
|
||||
"""Sektionsscharfe Iteration — root.iter() erfasste auch die
|
||||
Referenz-Elemente (z. B. <tarifgruppe>xmlId</tarifgruppe> in
|
||||
Zuordnungszeilen); die Sektionen enthalten nur echte Zeilen."""
|
||||
section = root.find(tag)
|
||||
return list(section) if section is not None else []
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Upsert-Helfer
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _upsert_code_katalog(self, model, nodes, protocol, label,
|
||||
extra_vals=None):
|
||||
"""Katalogzeilen mit (Trägergruppe, Code, von) als Schlüssel."""
|
||||
created = updated = 0
|
||||
for node in nodes:
|
||||
code = self._tasy_text(node, 'code')
|
||||
von = self._tasy_text(node, 'vonDatum')
|
||||
if not code or not von:
|
||||
continue
|
||||
vals = {
|
||||
'code': code,
|
||||
'bezeichnung': self._tasy_text(node, 'bezeichnung'),
|
||||
'kurzbezeichnung': self._tasy_text(
|
||||
node, 'kurzbezeichnung'),
|
||||
'beschreibung': self._tasy_text(node, 'beschreibung'),
|
||||
'traegergruppe': self._tasy_text(node, 'traegergruppe'),
|
||||
'von_datum': fields.Date.to_date(von),
|
||||
'bis_datum': fields.Date.to_date(
|
||||
self._tasy_text(node, 'bisDatum') or '2999-12-31'),
|
||||
'tasy_xml_id': node.get('xmlId'),
|
||||
}
|
||||
vals.update(extra_vals(node) if extra_vals else {})
|
||||
rec = self.env[model].search([
|
||||
('traegergruppe', '=', vals['traegergruppe']),
|
||||
('code', '=', code),
|
||||
('von_datum', '=', vals['von_datum'])], limit=1)
|
||||
if rec:
|
||||
rec.write(vals)
|
||||
updated += 1
|
||||
else:
|
||||
self.env[model].create(vals)
|
||||
created += 1
|
||||
protocol.append('%s: %d neu, %d aktualisiert'
|
||||
% (label, created, updated))
|
||||
|
||||
def _upsert_tarif(self, nodes, protocol):
|
||||
"""Tarife ohne Code — Identität ist der Inhalt (Zweig, Sätze,
|
||||
Gültigkeit); xmlId zuerst, dann Inhaltsvergleich."""
|
||||
created = updated = 0
|
||||
for node in nodes:
|
||||
zweig = self._tasy_text(node, 'tarifZweig')
|
||||
von = self._tasy_text(node, 'vonDatum')
|
||||
if not zweig or not von:
|
||||
continue
|
||||
vals = {
|
||||
'zweig': zweig,
|
||||
'dn': float(self._tasy_text(
|
||||
node, 'dienstnehmerAnteil') or 0.0),
|
||||
'dg': float(self._tasy_text(
|
||||
node, 'dienstgeberAnteil') or 0.0),
|
||||
'traegergruppe': self._tasy_text(node, 'traegergruppe'),
|
||||
'von_datum': fields.Date.to_date(von),
|
||||
'bis_datum': fields.Date.to_date(
|
||||
self._tasy_text(node, 'bisDatum') or '2999-12-31'),
|
||||
'tasy_xml_id': node.get('xmlId'),
|
||||
}
|
||||
rec = self.env['l10n.at.payroll.tasy.tarif'].search([
|
||||
('zweig', '=', zweig),
|
||||
('traegergruppe', '=', vals['traegergruppe']),
|
||||
('von_datum', '=', vals['von_datum'])]).filtered(
|
||||
lambda t, v=vals: abs(t.dn - v['dn']) < 1e-9
|
||||
and abs(t.dg - v['dg']) < 1e-9)[:1]
|
||||
if rec:
|
||||
rec.write(vals)
|
||||
updated += 1
|
||||
else:
|
||||
self.env['l10n.at.payroll.tasy.tarif'].create(vals)
|
||||
created += 1
|
||||
protocol.append('Tarife: %d neu, %d aktualisiert'
|
||||
% (created, updated))
|
||||
|
||||
def _upsert_tarifset(self, nodes, tarif_map, protocol):
|
||||
"""Tarifsets — Schlüssel (Träger, Kurzbezeichnung, von);
|
||||
Tarif-Verknüpfungen werden je Import neu gesetzt."""
|
||||
created = updated = 0
|
||||
for node in nodes:
|
||||
von = self._tasy_text(node, 'vonDatum')
|
||||
kurz = self._tasy_text(node, 'kurzbezeichnung')
|
||||
if not von:
|
||||
continue
|
||||
vals = {
|
||||
'bezeichnung': self._tasy_text(node, 'bezeichnung'),
|
||||
'kurzbezeichnung': kurz,
|
||||
'traegergruppe': self._tasy_text(node, 'traegergruppe'),
|
||||
'von_datum': fields.Date.to_date(von),
|
||||
'bis_datum': fields.Date.to_date(
|
||||
self._tasy_text(node, 'bisDatum') or '2999-12-31'),
|
||||
'tasy_xml_id': node.get('xmlId'),
|
||||
}
|
||||
rec = self.env['l10n.at.payroll.tasy.tarifset'].search([
|
||||
('traegergruppe', '=', vals['traegergruppe']),
|
||||
('kurzbezeichnung', '=', kurz),
|
||||
('von_datum', '=', vals['von_datum'])], limit=1)
|
||||
tarife = [tarif_map[ref] for ref in self._tasy_texts(
|
||||
node, 'tarife') if ref in tarif_map]
|
||||
vals['tarif_ids'] = [(6, 0, [t.id for t in tarife])]
|
||||
if rec:
|
||||
rec.write(vals)
|
||||
updated += 1
|
||||
else:
|
||||
rec = self.env['l10n.at.payroll.tasy.tarifset'].create(
|
||||
vals)
|
||||
created += 1
|
||||
protocol.append('Tarifsets: %d neu, %d aktualisiert'
|
||||
% (created, updated))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Import
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def action_import(self):
|
||||
self.ensure_one()
|
||||
root = self._tasy_root()
|
||||
stichtag = (root.findtext('gueltigkeitstichtag') or '').strip()
|
||||
if not stichtag:
|
||||
raise UserError(_('Ungültiger TASY-Export: '
|
||||
'Gültigkeitstichtag fehlt.'))
|
||||
self.stichtag = fields.Date.to_date(stichtag)
|
||||
protocol = ['Stichtag des Exports: %s' % stichtag]
|
||||
|
||||
# 1) Code-Kataloge: BSGR, Ergänzungen, Dienstgebergruppen,
|
||||
# Zu-/Abschläge (inkl. Steuerung).
|
||||
self._upsert_code_katalog(
|
||||
'l10n.at.payroll.tasy.bsgr',
|
||||
self._section(root, 'beschaeftigtengruppen'), protocol,
|
||||
'BSGR')
|
||||
self._upsert_code_katalog(
|
||||
'l10n.at.payroll.tasy.ergaenzung',
|
||||
self._section(root, 'ergaenzungen'), protocol,
|
||||
'Ergänzungen')
|
||||
self._upsert_code_katalog(
|
||||
'l10n.at.payroll.tasy.dienstgebergruppe',
|
||||
self._section(root, 'dienstgebergruppen'), protocol,
|
||||
'Dienstgebergruppen')
|
||||
|
||||
bsgr_map = {b.tasy_xml_id: b for b in self.env[
|
||||
'l10n.at.payroll.tasy.bsgr'].search([])}
|
||||
erg_map = {e.tasy_xml_id: e for e in self.env[
|
||||
'l10n.at.payroll.tasy.ergaenzung'].search([])}
|
||||
dgg_map = {d.tasy_xml_id: d for d in self.env[
|
||||
'l10n.at.payroll.tasy.dienstgebergruppe'].search([])}
|
||||
|
||||
zuabschlag_map = {}
|
||||
zuabschlag_nodes = self._section(root, 'zuAbschlaege')
|
||||
self._upsert_code_katalog(
|
||||
'l10n.at.payroll.tasy.zuabschlag',
|
||||
zuabschlag_nodes, protocol, 'Zu-/Abschläge',
|
||||
extra_vals=lambda n: {
|
||||
'zuabschlagkz': self._tasy_text(n, 'zuabschlagkz'),
|
||||
'versicherungsrelevant': self._tasy_bool(
|
||||
self._tasy_text(n, 'versicherungsRelevant')),
|
||||
})
|
||||
for za in self.env['l10n.at.payroll.tasy.zuabschlag'].search([]):
|
||||
zuabschlag_map[za.tasy_xml_id] = za
|
||||
steuerung_created = 0
|
||||
steuerung_obj = self.env[
|
||||
'l10n.at.payroll.tasy.zuabschlag.steuerung']
|
||||
for node in self._section(root, 'zuAbschlaegeSteuerung'):
|
||||
zuabschlag = zuabschlag_map.get(
|
||||
self._tasy_text(node, 'zuAbschlag'))
|
||||
von = self._tasy_text(node, 'vonDatum')
|
||||
if not zuabschlag or not von:
|
||||
continue
|
||||
vals = {
|
||||
'zuabschlag_id': zuabschlag.id,
|
||||
'altersgrenze_min_jahr': float(
|
||||
self._tasy_text(node, 'altersgrenzeMinJahr') or 0.0),
|
||||
'altersgrenze_soll_jahr': float(
|
||||
self._tasy_text(node, 'altersgrenzeSollJahr') or 0.0),
|
||||
'geboren_bis': fields.Date.to_date(
|
||||
self._tasy_text(node, 'geborenBis'))
|
||||
if self._tasy_text(node, 'geborenBis') else False,
|
||||
'geschlecht': self._tasy_text(node, 'geschlecht'),
|
||||
'traegergruppe': self._tasy_text(node, 'traegergruppe'),
|
||||
'von_datum': fields.Date.to_date(von),
|
||||
'bis_datum': fields.Date.to_date(
|
||||
self._tasy_text(node, 'bisDatum') or '2999-12-31'),
|
||||
'tasy_xml_id': node.get('xmlId'),
|
||||
}
|
||||
if not steuerung_obj.search([
|
||||
('tasy_xml_id', '=', vals['tasy_xml_id'])], limit=1):
|
||||
steuerung_obj.create(vals)
|
||||
steuerung_created += 1
|
||||
protocol.append('Zu-/Abschlag-Steuerungen: %d neu'
|
||||
% steuerung_created)
|
||||
|
||||
# 2) Tarife, dann Tarifgruppen (m2m auf die Kataloge),
|
||||
# dann Tarifsets (m2m auf die Tarife).
|
||||
self._upsert_tarif(self._section(root, 'tarife'), protocol)
|
||||
tarif_map = {t.tasy_xml_id: t for t in self.env[
|
||||
'l10n.at.payroll.tasy.tarif'].search([])}
|
||||
|
||||
tarifgruppe_obj = self.env['l10n.at.payroll.tasy.tarifgruppe']
|
||||
created = updated = 0
|
||||
for node in self._section(root, 'tarifgruppen'):
|
||||
code = self._tasy_text(node, 'code')
|
||||
von = self._tasy_text(node, 'vonDatum')
|
||||
if not code or not von:
|
||||
continue
|
||||
vals = {
|
||||
'code': code,
|
||||
'bezeichnung': self._tasy_text(node, 'bezeichnung'),
|
||||
'traegergruppe': self._tasy_text(node, 'traegergruppe'),
|
||||
'von_datum': fields.Date.to_date(von),
|
||||
'bis_datum': fields.Date.to_date(
|
||||
self._tasy_text(node, 'bisDatum') or '2999-12-31'),
|
||||
'tasy_xml_id': node.get('xmlId'),
|
||||
'fallweise_zulaessig': self._tasy_bool(
|
||||
self._tasy_text(node,
|
||||
'fallweiseBeschaeftigungZulaessig')),
|
||||
'bv_zulaessig': self._tasy_bool(
|
||||
self._tasy_text(node,
|
||||
'betrieblicheVorsorgeZulaessig')),
|
||||
}
|
||||
rec = tarifgruppe_obj.search([
|
||||
('traegergruppe', '=', vals['traegergruppe']),
|
||||
('code', '=', code),
|
||||
('von_datum', '=', vals['von_datum'])], limit=1)
|
||||
if not rec:
|
||||
rec = tarifgruppe_obj.create(vals)
|
||||
created += 1
|
||||
else:
|
||||
rec.write(vals)
|
||||
updated += 1
|
||||
rec.write({
|
||||
'bsgr_ids': [(6, 0, [
|
||||
bsgr_map[r].id for r in self._tasy_texts(
|
||||
node, 'beschaeftigtengruppen')
|
||||
if r in bsgr_map])],
|
||||
'ergaenzung_ids': [(6, 0, [
|
||||
erg_map[r].id for r in self._tasy_texts(
|
||||
node, 'ergaenzungen') if r in erg_map])],
|
||||
'dienstgebergruppe_ids': [(6, 0, [
|
||||
dgg_map[r].id for r in self._tasy_texts(
|
||||
node, 'dienstgeberGruppen')
|
||||
if r in dgg_map])],
|
||||
})
|
||||
protocol.append('Tarifgruppen: %d neu, %d aktualisiert'
|
||||
% (created, updated))
|
||||
|
||||
self._upsert_tarifset(self._section(root, 'tarifsets'),
|
||||
tarif_map, protocol)
|
||||
tarifset_map = {s.tasy_xml_id: s for s in self.env[
|
||||
'l10n.at.payroll.tasy.tarifset'].search([])}
|
||||
tarifgruppe_map = {t.tasy_xml_id: t for t in tarifgruppe_obj
|
||||
.search([])}
|
||||
|
||||
# 3) Verrechnungsbasis-/Positionstypen + Kombinationen
|
||||
# auflösen, dann tarifgruppeSetZuord importieren.
|
||||
basis_typ = {b.get('xmlId'): self._tasy_text(b, 'code')
|
||||
for b in self._section(root, 'verrechBasisTypen')}
|
||||
pos_typ = {p.get('xmlId'): self._tasy_text(p, 'code')
|
||||
for p in self._section(root, 'verrechPosTypen')}
|
||||
kombinationen = {}
|
||||
for k in self._section(root, 'verrechBasisPositionKombinationen'):
|
||||
kombinationen[k.get('xmlId')] = (
|
||||
basis_typ.get(self._tasy_text(k, 'verrechnungBasisTyp'),
|
||||
''),
|
||||
pos_typ.get(self._tasy_text(k, 'verrechnungPosTyp'), ''),
|
||||
)
|
||||
|
||||
zuord_obj = self.env['l10n.at.payroll.tasy.tarifset.zuord']
|
||||
created = updated = 0
|
||||
for node in self._section(root, 'tarifgruppeSetZuordnungen'):
|
||||
tarifgruppe = tarifgruppe_map.get(
|
||||
self._tasy_text(node, 'tarifgruppe'))
|
||||
tarifset = tarifset_map.get(self._tasy_text(node, 'tarifset'))
|
||||
von = self._tasy_text(node, 'vonDatum')
|
||||
if not tarifgruppe or not tarifset or not von:
|
||||
continue
|
||||
basis, pos = kombinationen.get(
|
||||
self._tasy_text(node, 'verrechBasPosKombination'),
|
||||
('', ''))
|
||||
vals = {
|
||||
'tarifgruppe_id': tarifgruppe.id,
|
||||
'tarifset_id': tarifset.id,
|
||||
'basis_typ': basis,
|
||||
'pos_typ': pos,
|
||||
'traegergruppe': self._tasy_text(node, 'traegergruppe'),
|
||||
'von_datum': fields.Date.to_date(von),
|
||||
'bis_datum': fields.Date.to_date(
|
||||
self._tasy_text(node, 'bisDatum') or '2999-12-31'),
|
||||
'tasy_xml_id': node.get('xmlId'),
|
||||
}
|
||||
rec = zuord_obj.search([
|
||||
('tarifgruppe_id', '=', tarifgruppe.id),
|
||||
('tarifset_id', '=', tarifset.id),
|
||||
('basis_typ', '=', basis),
|
||||
('pos_typ', '=', pos),
|
||||
('von_datum', '=', vals['von_datum'])], limit=1)
|
||||
if rec:
|
||||
rec.write(vals)
|
||||
updated += 1
|
||||
else:
|
||||
zuord_obj.create(vals)
|
||||
created += 1
|
||||
protocol.append('Tarifgruppe-Tarifset-Zuordnungen: '
|
||||
'%d neu, %d aktualisiert' % (created, updated))
|
||||
|
||||
# 4) Zu-/Abschlag-Zuordnungen (m2m auf Tarifsets/Tarifgruppen).
|
||||
verknuepft = 0
|
||||
for node in self._section(root, 'zuAbschlagTarifsetZuordnungen'):
|
||||
zuabschlag = zuabschlag_map.get(
|
||||
self._tasy_text(node, 'zuAbschlag'))
|
||||
if not zuabschlag:
|
||||
continue
|
||||
tarifset = tarifset_map.get(
|
||||
self._tasy_text(node, 'tarifsetByTarifsetId'))
|
||||
tarifgruppen = [
|
||||
tarifgruppe_map[r] for r in self._tasy_texts(
|
||||
node, 'tarifgruppe') if r in tarifgruppe_map]
|
||||
if tarifset:
|
||||
zuabschlag.tarifset_ids = [(4, tarifset.id)]
|
||||
for tarifgruppe in tarifgruppen:
|
||||
zuabschlag.tarifgruppe_ids = [(4, tarifgruppe.id)]
|
||||
verknuepft += 1
|
||||
protocol.append('Zu-/Abschlag-Zuordnungen: %d verknüpft'
|
||||
% verknuepft)
|
||||
|
||||
self.protocol = '\n'.join(protocol)
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'success',
|
||||
'message': _('Tarifsystem-Katalog importiert.'),
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""SV-Tarifsystem-Katalog (AP0, Variante b): Beschäftigtengruppen
|
||||
(BSGR), Ergänzungen (ERGB), Dienstgebergruppen, Tarifgruppen,
|
||||
Tarifsets/Tarife und Zu-/Abschläge — geladen aus dem TASY-LSWH-Export
|
||||
(sozialversicherung.at/tarifsystem; DM-ORG D.48/D.49: Tarifgruppe =
|
||||
BSGR + ERGB).
|
||||
|
||||
Der Katalog wird zur Laufzeit über den Import-Wizard
|
||||
(``l10n.at.payroll.tasy.katalog.wizard``, models/tasy.py) gepflegt —
|
||||
alle Trägergruppen, versioniert über von/bis-Datum; kein XML-Seed
|
||||
(keine erfundenen Werte). Die mBGM-Tarifierung des Privatmoduls liest
|
||||
die Sätze von hier (Katalog vor Hand-Parameter ``at_mbgm_tarif``).
|
||||
"""
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class L10nAtPayrollTasyBsgr(models.Model):
|
||||
_name = 'l10n.at.payroll.tasy.bsgr'
|
||||
_description = 'SV-Tarifsystem: Beschäftigtengruppe (BSGR)'
|
||||
_order = 'code, von_datum'
|
||||
|
||||
code = fields.Char(required=True, index=True)
|
||||
bezeichnung = fields.Char()
|
||||
kurzbezeichnung = fields.Char()
|
||||
beschreibung = fields.Char()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
|
||||
_unique_bsgr = models.Constraint(
|
||||
'unique (traegergruppe, code, von_datum)',
|
||||
"Je Trägergruppe, Code und Gültig-ab gibt es nur eine "
|
||||
"Beschäftigtengruppe.")
|
||||
|
||||
|
||||
class L10nAtPayrollTasyErgaenzung(models.Model):
|
||||
_name = 'l10n.at.payroll.tasy.ergaenzung'
|
||||
_description = 'SV-Tarifsystem: Ergänzung zur BSGR (ERGB)'
|
||||
_order = 'code, von_datum'
|
||||
|
||||
code = fields.Char(required=True, index=True)
|
||||
bezeichnung = fields.Char()
|
||||
kurzbezeichnung = fields.Char()
|
||||
beschreibung = fields.Char()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
|
||||
_unique_ergaenzung = models.Constraint(
|
||||
'unique (traegergruppe, code, von_datum)',
|
||||
"Je Trägergruppe, Code und Gültig-ab gibt es nur eine "
|
||||
"Ergänzung.")
|
||||
|
||||
|
||||
class L10nAtPayrollTasyDienstgebergruppe(models.Model):
|
||||
_name = 'l10n.at.payroll.tasy.dienstgebergruppe'
|
||||
_description = 'SV-Tarifsystem: Dienstgebergruppe'
|
||||
_order = 'code, von_datum'
|
||||
|
||||
code = fields.Char(required=True, index=True)
|
||||
bezeichnung = fields.Char()
|
||||
kurzbezeichnung = fields.Char()
|
||||
beschreibung = fields.Char()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
|
||||
_unique_dgg = models.Constraint(
|
||||
'unique (traegergruppe, code, von_datum)',
|
||||
"Je Trägergruppe, Code und Gültig-ab gibt es nur eine "
|
||||
"Dienstgebergruppe.")
|
||||
|
||||
|
||||
class L10nAtPayrollTasyTarif(models.Model):
|
||||
"""Einzelner Tarifsatz (Zweig × DN/DG-Anteil × Gültigkeit).
|
||||
|
||||
Kein natürlicher Code im Export — Identität ist der Inhalt
|
||||
(Zweig, Sätze, Gültigkeit); der Katalog-Import vereinheitlicht
|
||||
über diese Inhaltsmerkmale statt über die wechselnden xmlIds.
|
||||
"""
|
||||
_name = 'l10n.at.payroll.tasy.tarif'
|
||||
_description = 'SV-Tarifsystem: Tarif (Zweig-Satz)'
|
||||
_order = 'zweig, von_datum'
|
||||
|
||||
zweig = fields.Char(required=True, index=True)
|
||||
dn = fields.Float(string='DN-Anteil (%)', digits=(16, 3))
|
||||
dg = fields.Float(string='DG-Anteil (%)', digits=(16, 3))
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
tarifset_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.tarifset',
|
||||
'l10n_at_payroll_tasy_tarif_tarifset_rel',
|
||||
'tarif_id', 'tarifset_id', string='Tarifsets')
|
||||
|
||||
|
||||
class L10nAtPayrollTasyTarifset(models.Model):
|
||||
_name = 'l10n.at.payroll.tasy.tarifset'
|
||||
_description = 'SV-Tarifsystem: Tarifset'
|
||||
_order = 'kurzbezeichnung, von_datum'
|
||||
|
||||
bezeichnung = fields.Char()
|
||||
kurzbezeichnung = fields.Char()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
tarif_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.tarif',
|
||||
'l10n_at_payroll_tasy_tarif_tarifset_rel',
|
||||
'tarifset_id', 'tarif_id', string='Tarife')
|
||||
|
||||
_unique_tarifset = models.Constraint(
|
||||
'unique (traegergruppe, kurzbezeichnung, von_datum)',
|
||||
"Je Trägergruppe, Kurzbezeichnung und Gültig-ab gibt es nur "
|
||||
"ein Tarifset.")
|
||||
|
||||
|
||||
class L10nAtPayrollTasyTarifgruppe(models.Model):
|
||||
"""Tarifgruppe = BSGR (+ Ergänzungen) × Dienstgebergruppen.
|
||||
|
||||
Auflösung: ``_l10n_at_find`` (BSGR + ERGB-Menge + DGG am Stichtag)
|
||||
→ ``_l10n_at_saetze`` (Sätze je Verrechnungsbasis aus dem
|
||||
zugeordneten Tarifset).
|
||||
"""
|
||||
_name = 'l10n.at.payroll.tasy.tarifgruppe'
|
||||
_description = 'SV-Tarifsystem: Tarifgruppe'
|
||||
_order = 'code, von_datum'
|
||||
|
||||
code = fields.Char(required=True, index=True)
|
||||
bezeichnung = fields.Char()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
bsgr_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.bsgr',
|
||||
'l10n_at_payroll_tasy_tarifgruppe_bsgr_rel',
|
||||
'tarifgruppe_id', 'bsgr_id', string='Beschäftigtengruppen')
|
||||
ergaenzung_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.ergaenzung',
|
||||
'l10n_at_payroll_tasy_tarifgruppe_ergaenzung_rel',
|
||||
'tarifgruppe_id', 'ergaenzung_id', string='Ergänzungen (ERGB)')
|
||||
dienstgebergruppe_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.dienstgebergruppe',
|
||||
'l10n_at_payroll_tasy_tarifgruppe_dgg_rel',
|
||||
'tarifgruppe_id', 'dienstgebergruppe_id',
|
||||
string='Dienstgebergruppen')
|
||||
fallweise_zulaessig = fields.Boolean(string='Fallweise zulässig')
|
||||
bv_zulaessig = fields.Boolean(
|
||||
string='Betriebliche Vorsorge zulässig')
|
||||
zuord_ids = fields.One2many(
|
||||
'l10n.at.payroll.tasy.tarifset.zuord', 'tarifgruppe_id',
|
||||
string='Tarifset-Zuordnungen')
|
||||
|
||||
_unique_tarifgruppe = models.Constraint(
|
||||
'unique (traegergruppe, code, von_datum)',
|
||||
"Je Trägergruppe, Code und Gültig-ab gibt es nur eine "
|
||||
"Tarifgruppe.")
|
||||
|
||||
@api.model
|
||||
def _l10n_at_find(self, bsgr_code, ergaenzung_codes, dgg_code,
|
||||
stichtag):
|
||||
"""Tarifgruppe zu BSGR + ERGB-Menge + DGG am Stichtag.
|
||||
|
||||
Die ERGB-Menge muss exakt passen (leere Menge = Normalfall
|
||||
ohne Ergänzung) — DM-ORG D.48/D.49. Liefert leere Recordset,
|
||||
wenn keine Zeile existiert (Aufrufer entscheidet über
|
||||
Fallback/Abbruch).
|
||||
"""
|
||||
ergaenzung_codes = set(ergaenzung_codes or ())
|
||||
kandidaten = self.search([
|
||||
('bsgr_ids.code', '=', bsgr_code),
|
||||
('von_datum', '<=', stichtag),
|
||||
('bis_datum', '>=', stichtag),
|
||||
])
|
||||
if dgg_code:
|
||||
kandidaten = kandidaten.filtered(
|
||||
lambda t: dgg_code
|
||||
in t.dienstgebergruppe_ids.mapped('code'))
|
||||
for tarifgruppe in kandidaten:
|
||||
if {e.code for e in tarifgruppe.ergaenzung_ids} \
|
||||
== ergaenzung_codes:
|
||||
return tarifgruppe
|
||||
return self.env['l10n.at.payroll.tasy.tarifgruppe']
|
||||
|
||||
def _l10n_at_saetze(self, basis_typ, stichtag):
|
||||
"""Sätze je Verrechnungsbasis (AB/SZ/BV/UU …) am Stichtag.
|
||||
|
||||
Rückgabe ``None`` ohne gültige Tarifset-Zuordnung, sonst
|
||||
``{'dn': x, 'dg': y, 'gesamt': x + y, 'komponenten': tarif}``.
|
||||
Je Zweig zählt der späteste gültige Tarif (Schutz gegen
|
||||
duplette Import-Zeilen).
|
||||
"""
|
||||
self.ensure_one()
|
||||
zuord = self.zuord_ids.filtered(
|
||||
lambda z: z.basis_typ == basis_typ
|
||||
and z.von_datum <= stichtag <= z.bis_datum)
|
||||
if not zuord:
|
||||
return None
|
||||
zuord = zuord.sorted(key=lambda z: z.von_datum)[-1]
|
||||
tarife = zuord.tarifset_id.tarif_ids.filtered(
|
||||
lambda t: t.von_datum <= stichtag <= t.bis_datum)
|
||||
komponenten = []
|
||||
gesehene_zweige = set()
|
||||
for tarif in tarife.sorted(key=lambda t: t.von_datum,
|
||||
reverse=True):
|
||||
if tarif.zweig in gesehene_zweige:
|
||||
continue
|
||||
gesehene_zweige.add(tarif.zweig)
|
||||
komponenten.append(tarif)
|
||||
dn = sum(t.dn for t in komponenten)
|
||||
dg = sum(t.dg for t in komponenten)
|
||||
return {'dn': dn, 'dg': dg, 'gesamt': dn + dg,
|
||||
'komponenten': komponenten}
|
||||
|
||||
|
||||
class L10nAtPayrollTasyTarifsetZuord(models.Model):
|
||||
"""tarifgruppeSetZuord: Tarifgruppe × Tarifset × Basis/Position."""
|
||||
_name = 'l10n.at.payroll.tasy.tarifset.zuord'
|
||||
_description = 'SV-Tarifsystem: Tarifgruppe-Tarifset-Zuordnung'
|
||||
_order = 'tarifgruppe_id, basis_typ, von_datum'
|
||||
|
||||
tarifgruppe_id = fields.Many2one(
|
||||
'l10n.at.payroll.tasy.tarifgruppe', required=True, index=True,
|
||||
ondelete='cascade')
|
||||
tarifset_id = fields.Many2one(
|
||||
'l10n.at.payroll.tasy.tarifset', required=True, index=True,
|
||||
ondelete='cascade')
|
||||
basis_typ = fields.Char(string='Verrechnungsbasis', index=True)
|
||||
pos_typ = fields.Char(string='Verrechnungsposition', index=True)
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
|
||||
_unique_zuord = models.Constraint(
|
||||
'unique (tarifgruppe_id, tarifset_id, basis_typ, pos_typ, '
|
||||
'von_datum)',
|
||||
"Diese Tarifgruppe-Tarifset-Zuordnung existiert bereits.")
|
||||
|
||||
|
||||
class L10nAtPayrollTasyZuabschlag(models.Model):
|
||||
_name = 'l10n.at.payroll.tasy.zuabschlag'
|
||||
_description = 'SV-Tarifsystem: Zu-/Abschlag'
|
||||
_order = 'code, von_datum'
|
||||
|
||||
code = fields.Char(required=True, index=True)
|
||||
bezeichnung = fields.Char()
|
||||
kurzbezeichnung = fields.Char()
|
||||
beschreibung = fields.Char()
|
||||
zuabschlagkz = fields.Char(string='Zu-/Abschlag-Kennzeichen')
|
||||
versicherungsrelevant = fields.Boolean()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
steuerung_ids = fields.One2many(
|
||||
'l10n.at.payroll.tasy.zuabschlag.steuerung', 'zuabschlag_id',
|
||||
string='Steuerung (Alters-/Einkommensgrenzen)')
|
||||
tarifset_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.tarifset',
|
||||
'l10n_at_payroll_tasy_zuabschlag_tarifset_rel',
|
||||
'zuabschlag_id', 'tarifset_id', string='Tarifsets')
|
||||
tarifgruppe_ids = fields.Many2many(
|
||||
'l10n.at.payroll.tasy.tarifgruppe',
|
||||
'l10n_at_payroll_tasy_zuabschlag_tarifgruppe_rel',
|
||||
'zuabschlag_id', 'tarifgruppe_id', string='Tarifgruppen')
|
||||
|
||||
_unique_zuabschlag = models.Constraint(
|
||||
'unique (traegergruppe, code, von_datum)',
|
||||
"Je Trägergruppe, Code und Gültig-ab gibt es nur einen "
|
||||
"Zu-/Abschlag.")
|
||||
|
||||
|
||||
class L10nAtPayrollTasyZuabschlagSteuerung(models.Model):
|
||||
"""Steuerung eines Zu-/Abschlags (Altersgrenzen, Geschlecht,
|
||||
Geburtsjahrgang) — informativ; die mBGM-Abschlagslogik rechnet
|
||||
aus den GP4-Stichtagsflags, nicht aus dieser Tabelle."""
|
||||
_name = 'l10n.at.payroll.tasy.zuabschlag.steuerung'
|
||||
_description = 'SV-Tarifsystem: Zu-/Abschlag-Steuerung'
|
||||
_order = 'zuabschlag_id, von_datum'
|
||||
|
||||
zuabschlag_id = fields.Many2one(
|
||||
'l10n.at.payroll.tasy.zuabschlag', required=True, index=True,
|
||||
ondelete='cascade')
|
||||
altersgrenze_min_jahr = fields.Float(
|
||||
string='Altersgrenze min. (Jahre)', digits=(16, 1))
|
||||
altersgrenze_soll_jahr = fields.Float(
|
||||
string='Altersgrenze soll (Jahre)', digits=(16, 1))
|
||||
geboren_bis = fields.Date(string='Geboren bis')
|
||||
geschlecht = fields.Char()
|
||||
traegergruppe = fields.Char(index=True)
|
||||
von_datum = fields.Date(string='Gültig ab', required=True)
|
||||
bis_datum = fields.Date(string='Gültig bis', required=True)
|
||||
tasy_xml_id = fields.Char(index=True)
|
||||
@@ -38,6 +38,17 @@
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_dg_kosten" model="ir.actions.report">
|
||||
<field name="name">Dienstgeber-Gesamtkosten</field>
|
||||
<field name="model">hr.payslip.run</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">l10n_at_hr_payroll.report_dg_kosten</field>
|
||||
<field name="report_file">l10n_at_hr_payroll.report_dg_kosten</field>
|
||||
<field name="paperformat_id" ref="hr_payroll.paperformat_euro_light"/>
|
||||
<field name="binding_model_id" ref="hr_payroll.model_hr_payslip_run"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<!-- Lohnzettel AT: AT-Layout — Lohnarten nach Kategorie, DN-Abzüge,
|
||||
DG-Anteile (SV-DG, FLAF, KommSt, DZ, BV), Netto. -->
|
||||
<template id="report_lohnzettel_at">
|
||||
@@ -170,13 +181,15 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Monatsjournal: je Lohnart die Summe über den Lauf -->
|
||||
<!-- Monatsjournal: je Lohnart die Summe über den Lauf — Bezüge/ -->
|
||||
<!-- Abzüge und (seit AP2a) eigener Block Dienstgeberanteile. -->
|
||||
<template id="report_monatsjournal">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="journal" t-as="j">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<h2>Monatsjournal — <t t-out="j['run'].name"/></h2>
|
||||
<h4>Bezüge & Abzüge</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -193,6 +206,92 @@
|
||||
<td><t t-out="art['bemessung']"/></td>
|
||||
<td class="text-end"><span t-out="art['summe']" t-options="{"widget": "monetary", "display_currency": j['run'].company_id.currency_id}"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><strong>Summe Bezüge & Abzüge</strong></td>
|
||||
<td class="text-end"><strong><span t-out="j['summe_arten']" t-options="{"widget": "monetary", "display_currency": j['run'].company_id.currency_id}"/></strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Dienstgeberanteile (Lohnnebenkosten)</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Position</th>
|
||||
<th>Bemessung</th>
|
||||
<th class="text-end">Summe</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="j['dg_arten']" t-as="art">
|
||||
<td><t t-out="art['code']"/></td>
|
||||
<td><t t-out="art['name']"/></td>
|
||||
<td><t t-out="art['bemessung']"/></td>
|
||||
<td class="text-end"><span t-out="art['summe']" t-options="{"widget": "monetary", "display_currency": j['run'].company_id.currency_id}"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><strong>Summe Dienstgeberanteile</strong></td>
|
||||
<td class="text-end"><strong><span t-out="j['summe_dg']" t-options="{"widget": "monetary", "display_currency": j['run'].company_id.currency_id}"/></strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Dienstgeber-Gesamtkosten: Brutto + Lohnnebenkosten je AN, -->
|
||||
<!-- aufgeschlüsselt und kumuliert (AP2a). -->
|
||||
<template id="report_dg_kosten">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="run">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<h2>Dienstgeber-Gesamtkosten — <t t-out="run.name"/></h2>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mitarbeiter:in</th>
|
||||
<th class="text-end">Bruttolohn</th>
|
||||
<th class="text-end">SV-DG (laufend+SZ)</th>
|
||||
<th class="text-end">FLAF-DB</th>
|
||||
<th class="text-end">KommSt</th>
|
||||
<th class="text-end">DZ (WKO)</th>
|
||||
<th class="text-end">BV</th>
|
||||
<th class="text-end">Wr. DAG</th>
|
||||
<th class="text-end">Weitere DG</th>
|
||||
<th class="text-end">Σ Lohnnebenkosten</th>
|
||||
<th class="text-end">Gesamtkosten</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="zeilen" t-as="z">
|
||||
<td><t t-out="z['slip'].employee_id.name"/></td>
|
||||
<td class="text-end"><span t-out="z['brutto']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['svdg']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['spalten']['FLAFDB']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['spalten']['KOMMST']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['spalten']['DZ']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['spalten']['BVG']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['spalten']['WIEN_DAG']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['weitere']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><span t-out="z['dg_summe']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></td>
|
||||
<td class="text-end"><strong><span t-out="z['gesamtkosten']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Summe Lauf</strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['brutto']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['svdg']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['spalten']['FLAFDB']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['spalten']['KOMMST']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['spalten']['DZ']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['spalten']['BVG']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['spalten']['WIEN_DAG']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['weitere']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['dg_summe']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
<td class="text-end"><strong><span t-out="summen['gesamtkosten']" t-options="{"widget": "monetary", "display_currency": run.company_id.currency_id}"/></strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- AP2b/AP2c: Jahreslohnkonto je Dienstnehmer:in und -->
|
||||
<!-- Betriebsjahreslohnkonto — Monatsmatrix je Lohnart; Druck -->
|
||||
<!-- über den Wizard l10n.at.payroll.jahresbericht.wizard. -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<record id="action_report_jahreslohnkonto" model="ir.actions.report">
|
||||
<field name="name">Jahreslohnkonto (Dienstnehmer:in)</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">l10n_at_hr_payroll.report_jahreslohnkonto</field>
|
||||
<field name="report_file">l10n_at_hr_payroll.report_jahreslohnkonto</field>
|
||||
<field name="paperformat_id" ref="hr_payroll.paperformat_euro_light"/>
|
||||
</record>
|
||||
|
||||
<record id="action_report_betriebslohnkonto" model="ir.actions.report">
|
||||
<field name="name">Betriebsjahreslohnkonto</field>
|
||||
<field name="model">res.company</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">l10n_at_hr_payroll.report_betriebslohnkonto</field>
|
||||
<field name="report_file">l10n_at_hr_payroll.report_betriebslohnkonto</field>
|
||||
<field name="paperformat_id" ref="hr_payroll.paperformat_euro_light"/>
|
||||
</record>
|
||||
|
||||
<template id="report_jahreslohnkonto">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="berichte" t-as="b">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<h2>Jahreslohnkonto <t t-out="jahr"/> — <t t-out="b['employee'].name"/></h2>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Lohnart</th>
|
||||
<th>Kategorie</th>
|
||||
<th class="text-end" t-foreach="range(1, 13)" t-as="monat">
|
||||
<t t-out="monat"/>
|
||||
</th>
|
||||
<th class="text-end">Jahr</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="b['zeilen']" t-as="zeile">
|
||||
<td><t t-out="zeile['code']"/></td>
|
||||
<td><t t-out="zeile['name']"/></td>
|
||||
<td><t t-out="zeile['kategorie']"/></td>
|
||||
<td class="text-end" t-foreach="zeile['monate']" t-as="wert">
|
||||
<span t-out="wert" t-options='{"widget": "monetary", "display_currency": b["employee"].company_id.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-end"><strong><span t-out="zeile['total']" t-options='{"widget": "monetary", "display_currency": b["employee"].company_id.currency_id}'/></strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="report_betriebslohnkonto">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="berichte" t-as="b">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page">
|
||||
<h2>Betriebsjahreslohnkonto <t t-out="jahr"/> — <t t-out="b['company'].name"/></h2>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Lohnart</th>
|
||||
<th>Kategorie</th>
|
||||
<th class="text-end" t-foreach="range(1, 13)" t-as="monat">
|
||||
<t t-out="monat"/>
|
||||
</th>
|
||||
<th class="text-end">Jahr</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="b['zeilen']" t-as="zeile">
|
||||
<td><t t-out="zeile['code']"/></td>
|
||||
<td><t t-out="zeile['name']"/></td>
|
||||
<td><t t-out="zeile['kategorie']"/></td>
|
||||
<td class="text-end" t-foreach="zeile['monate']" t-as="wert">
|
||||
<span t-out="wert" t-options='{"widget": "monetary", "display_currency": b["company"].currency_id}'/>
|
||||
</td>
|
||||
<td class="text-end"><strong><span t-out="zeile['total']" t-options='{"widget": "monetary", "display_currency": b["company"].currency_id}'/></strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<!-- Druck-Wizard -->
|
||||
<record id="jahresbericht_wizard_form_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.jahresbericht.wizard.form</field>
|
||||
<field name="model">l10n.at.payroll.jahresbericht.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Jahresberichte drucken">
|
||||
<group>
|
||||
<group>
|
||||
<field name="jahr"/>
|
||||
<field name="art"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"/>
|
||||
<field name="employee_ids" widget="many2many_tags"
|
||||
invisible="art != 'dn'"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Drucken" name="action_drucken"
|
||||
type="object" class="btn-primary"/>
|
||||
<button string="Abbrechen" class="btn-secondary"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_jahresbericht_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Jahresberichte (Lohnkonten)</field>
|
||||
<field name="res_model">l10n.at.payroll.jahresbericht.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_jahresberichte"
|
||||
name="Jahresberichte (Lohnkonten)"
|
||||
parent="menu_l10n_at_payroll_root"
|
||||
action="action_jahresbericht_wizard" sequence="80"/>
|
||||
</odoo>
|
||||
@@ -13,3 +13,23 @@ access_l10n_at_payroll_frist_manager,L10n AT Payroll Frist Manager,model_l10n_at
|
||||
access_l10n_at_payroll_jahresretro_wizard,L10n AT Payroll Jahresretro Wizard,model_l10n_at_payroll_jahresretro_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_l10n_at_payroll_jahresretro_ergebnis,L10n AT Payroll Jahresretro Ergebnis,model_l10n_at_payroll_jahresretro_ergebnis,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_l10n_at_payroll_mitteilungsfile_wizard,L10n AT Payroll Mitteilungsfile Wizard,model_l10n_at_payroll_mitteilungsfile_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_l10n_at_payroll_jahresbericht_wizard,L10n AT Payroll Jahresbericht Wizard,model_l10n_at_payroll_jahresbericht_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_katalog_wizard,L10n AT Payroll TASY-Katalog Wizard,model_l10n_at_payroll_tasy_katalog_wizard,hr_payroll.group_hr_payroll_user,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_bsgr_user,L10n AT Payroll TASY-BSGR User,model_l10n_at_payroll_tasy_bsgr,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_bsgr_manager,L10n AT Payroll TASY-BSGR Manager,model_l10n_at_payroll_tasy_bsgr,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_ergaenzung_user,L10n AT Payroll TASY-Ergänzung User,model_l10n_at_payroll_tasy_ergaenzung,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_ergaenzung_manager,L10n AT Payroll TASY-Ergänzung Manager,model_l10n_at_payroll_tasy_ergaenzung,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_dgg_user,L10n AT Payroll TASY-Dienstgebergruppe User,model_l10n_at_payroll_tasy_dienstgebergruppe,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_dgg_manager,L10n AT Payroll TASY-Dienstgebergruppe Manager,model_l10n_at_payroll_tasy_dienstgebergruppe,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_tarifgruppe_user,L10n AT Payroll TASY-Tarifgruppe User,model_l10n_at_payroll_tasy_tarifgruppe,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_tarifgruppe_manager,L10n AT Payroll TASY-Tarifgruppe Manager,model_l10n_at_payroll_tasy_tarifgruppe,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_tarifset_user,L10n AT Payroll TASY-Tarifset User,model_l10n_at_payroll_tasy_tarifset,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_tarifset_manager,L10n AT Payroll TASY-Tarifset Manager,model_l10n_at_payroll_tasy_tarifset,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_tarif_user,L10n AT Payroll TASY-Tarif User,model_l10n_at_payroll_tasy_tarif,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_tarif_manager,L10n AT Payroll TASY-Tarif Manager,model_l10n_at_payroll_tasy_tarif,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_zuord_user,L10n AT Payroll TASY-Zuordnung User,model_l10n_at_payroll_tasy_tarifset_zuord,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_zuord_manager,L10n AT Payroll TASY-Zuordnung Manager,model_l10n_at_payroll_tasy_tarifset_zuord,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_zuabschlag_user,L10n AT Payroll TASY-Zuabschlag User,model_l10n_at_payroll_tasy_zuabschlag,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_zuabschlag_manager,L10n AT Payroll TASY-Zuabschlag Manager,model_l10n_at_payroll_tasy_zuabschlag,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
access_l10n_at_payroll_tasy_zuabschlag_steuerung_user,L10n AT Payroll TASY-Zuabschlag-Steuerung User,model_l10n_at_payroll_tasy_zuabschlag_steuerung,hr_payroll.group_hr_payroll_user,1,0,0,0
|
||||
access_l10n_at_payroll_tasy_zuabschlag_steuerung_manager,L10n AT Payroll TASY-Zuabschlag-Steuerung Manager,model_l10n_at_payroll_tasy_zuabschlag_steuerung,hr_payroll.group_hr_payroll_manager,1,1,1,1
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
from . import test_tasy_import
|
||||
from . import test_tasy_katalog
|
||||
from . import test_lohnsteuer
|
||||
from . import test_sozialversicherung
|
||||
from . import test_elda_l16
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP0 (Variante b): TASY-Katalog-Import und Tarifgruppen-Auflösung.
|
||||
|
||||
Fixture-Slice mit den normativen Normalbetriebe-Werten 2026 (AB 39,05 %,
|
||||
SZ 37,55 % — verifiziert gegen den echten TASY-LSWH-Export 2026-07-01)
|
||||
plus E01-Ergänzung (NB-Zweig 3,80 % DG, Dienstgebergruppe NBNSA).
|
||||
"""
|
||||
import base64
|
||||
from datetime import date
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
TASY_KATALOG_FIXTURE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tarifssystemlsh>
|
||||
<gueltigkeitstichtag>2026-07-01</gueltigkeitstichtag>
|
||||
<beschaeftigtengruppen>
|
||||
<beschaeftigtengruppe xmlId="G001">
|
||||
<code>B001</code>
|
||||
<bezeichnung>Arbeiter</bezeichnung>
|
||||
<kurzbezeichnung>Arb.</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</beschaeftigtengruppe>
|
||||
<beschaeftigtengruppe xmlId="G002">
|
||||
<code>B002</code>
|
||||
<bezeichnung>Angestellte</bezeichnung>
|
||||
<kurzbezeichnung>Ang.</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</beschaeftigtengruppe>
|
||||
<beschaeftigtengruppe xmlId="G044">
|
||||
<code>B044</code>
|
||||
<bezeichnung>Angestelltenlehrlinge</bezeichnung>
|
||||
<kurzbezeichnung>Ang. Lg.</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</beschaeftigtengruppe>
|
||||
<beschaeftigtengruppe xmlId="G045">
|
||||
<code>B045</code>
|
||||
<bezeichnung>Arbeiterlehrlinge</bezeichnung>
|
||||
<kurzbezeichnung>Arb. Lg.</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</beschaeftigtengruppe>
|
||||
<beschaeftigtengruppe xmlId="G010">
|
||||
<code>B010</code>
|
||||
<bezeichnung>Geringfügig beschäftigte Arbeiter</bezeichnung>
|
||||
<kurzbezeichnung>Gfg. Arb.</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</beschaeftigtengruppe>
|
||||
</beschaeftigtengruppen>
|
||||
<ergaenzungen>
|
||||
<ergaenzung xmlId="E01X">
|
||||
<code>E01</code>
|
||||
<bezeichnung>Nachtschwerarbeitsbeitrag</bezeichnung>
|
||||
<kurzbezeichnung>NSchAB</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</ergaenzung>
|
||||
</ergaenzungen>
|
||||
<dienstgebergruppen>
|
||||
<dienstgebergruppe xmlId="NBAL">
|
||||
<code>NBALG</code>
|
||||
<bezeichnung>Normalbetriebe - Allgemeiner Teil</bezeichnung>
|
||||
<kurzbezeichnung>NBALG</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</dienstgebergruppe>
|
||||
<dienstgebergruppe xmlId="NBNS">
|
||||
<code>NBNSA</code>
|
||||
<bezeichnung>Normalbetriebe mit Nachtschwerarbeit</bezeichnung>
|
||||
<kurzbezeichnung>NBNSA</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</dienstgebergruppe>
|
||||
</dienstgebergruppen>
|
||||
<tarifgruppen>
|
||||
<tarifgruppe xmlId="T001">
|
||||
<beschaeftigtengruppen>G001</beschaeftigtengruppen>
|
||||
<dienstgeberGruppen>NBAL</dienstgeberGruppen>
|
||||
<code>B001</code>
|
||||
<bezeichnung>Arbeiter</bezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
<fallweiseBeschaeftigungZulaessig>true</fallweiseBeschaeftigungZulaessig>
|
||||
<betrieblicheVorsorgeZulaessig>true</betrieblicheVorsorgeZulaessig>
|
||||
</tarifgruppe>
|
||||
<tarifgruppe xmlId="T002">
|
||||
<beschaeftigtengruppen>G002</beschaeftigtengruppen>
|
||||
<dienstgeberGruppen>NBAL</dienstgeberGruppen>
|
||||
<code>B002</code>
|
||||
<bezeichnung>Angestellte</bezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
<fallweiseBeschaeftigungZulaessig>false</fallweiseBeschaeftigungZulaessig>
|
||||
<betrieblicheVorsorgeZulaessig>true</betrieblicheVorsorgeZulaessig>
|
||||
</tarifgruppe>
|
||||
<tarifgruppe xmlId="T002E01">
|
||||
<beschaeftigtengruppen>G002</beschaeftigtengruppen>
|
||||
<ergaenzungen>E01X</ergaenzungen>
|
||||
<dienstgeberGruppen>NBNS</dienstgeberGruppen>
|
||||
<code>B002E01</code>
|
||||
<bezeichnung>Angestellte/Nachtschwerarbeitsbeitrag</bezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
<betrieblicheVorsorgeZulaessig>true</betrieblicheVorsorgeZulaessig>
|
||||
</tarifgruppe>
|
||||
<tarifgruppe xmlId="T045">
|
||||
<beschaeftigtengruppen>G045</beschaeftigtengruppen>
|
||||
<dienstgeberGruppen>NBAL</dienstgeberGruppen>
|
||||
<code>B045</code>
|
||||
<bezeichnung>Arbeiterlehrlinge</bezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarifgruppe>
|
||||
</tarifgruppen>
|
||||
<tarife>
|
||||
<tarif xmlId="R001">
|
||||
<tarifZweig>KV</tarifZweig>
|
||||
<dienstnehmerAnteil>3.87</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>3.78</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R002">
|
||||
<tarifZweig>PV</tarifZweig>
|
||||
<dienstnehmerAnteil>10.25</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>12.55</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R003">
|
||||
<tarifZweig>AV</tarifZweig>
|
||||
<dienstnehmerAnteil>2.95</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>2.95</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R004">
|
||||
<tarifZweig>UV</tarifZweig>
|
||||
<dienstnehmerAnteil>0</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>1.1</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R005">
|
||||
<tarifZweig>WF</tarifZweig>
|
||||
<dienstnehmerAnteil>0.5</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>0.5</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R006">
|
||||
<tarifZweig>AK</tarifZweig>
|
||||
<dienstnehmerAnteil>0.5</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>0</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R007">
|
||||
<tarifZweig>IE</tarifZweig>
|
||||
<dienstnehmerAnteil>0</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>0.1</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
<tarif xmlId="R008">
|
||||
<tarifZweig>NB</tarifZweig>
|
||||
<dienstnehmerAnteil>0</dienstnehmerAnteil>
|
||||
<dienstgeberAnteil>3.8</dienstgeberAnteil>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2020-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</tarif>
|
||||
</tarife>
|
||||
<tarifsets>
|
||||
<tarifset xmlId="S001">
|
||||
<bezeichnung>40/AK/AV/IE/KV/PV/UV/WF</bezeichnung>
|
||||
<kurzbezeichnung>40/AK/AV/IE/KV/PV/UV/WF</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
<tarife>R001</tarife>
|
||||
<tarife>R002</tarife>
|
||||
<tarife>R003</tarife>
|
||||
<tarife>R004</tarife>
|
||||
<tarife>R005</tarife>
|
||||
<tarife>R006</tarife>
|
||||
<tarife>R007</tarife>
|
||||
</tarifset>
|
||||
<tarifset xmlId="S002">
|
||||
<bezeichnung>47/AV/IE/KV/PV/UV</bezeichnung>
|
||||
<kurzbezeichnung>47/AV/IE/KV/PV/UV</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
<tarife>R001</tarife>
|
||||
<tarife>R002</tarife>
|
||||
<tarife>R003</tarife>
|
||||
<tarife>R004</tarife>
|
||||
<tarife>R007</tarife>
|
||||
</tarifset>
|
||||
<tarifset xmlId="S003">
|
||||
<bezeichnung>40/AK/AV/IE/KV/PV/UV/WF/NB</bezeichnung>
|
||||
<kurzbezeichnung>40/AK/AV/IE/KV/PV/UV/WF/NB</kurzbezeichnung>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
<tarife>R001</tarife>
|
||||
<tarife>R002</tarife>
|
||||
<tarife>R003</tarife>
|
||||
<tarife>R004</tarife>
|
||||
<tarife>R005</tarife>
|
||||
<tarife>R006</tarife>
|
||||
<tarife>R007</tarife>
|
||||
<tarife>R008</tarife>
|
||||
</tarifset>
|
||||
</tarifsets>
|
||||
<verrechBasisTypen>
|
||||
<verrechBasisTyp xmlId="VBTAB"><code>AB</code><bezeichnung>Arbeitsbezug</bezeichnung><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></verrechBasisTyp>
|
||||
<verrechBasisTyp xmlId="VBTSZ"><code>SZ</code><bezeichnung>Sonderzahlung</bezeichnung><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></verrechBasisTyp>
|
||||
</verrechBasisTypen>
|
||||
<verrechPosTypen>
|
||||
<verrechPosTyp xmlId="VPT01"><code>T01</code><bezeichnung>Gesamtsatz laufend</bezeichnung><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></verrechPosTyp>
|
||||
<verrechPosTyp xmlId="VPT02"><code>T02</code><bezeichnung>Gesamtsatz Sonderzahlung</bezeichnung><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></verrechPosTyp>
|
||||
</verrechPosTypen>
|
||||
<verrechBasisPositionKombinationen>
|
||||
<verrechBasisPositionKombination xmlId="K01"><verrechnungBasisTyp>VBTAB</verrechnungBasisTyp><verrechnungPosTyp>VPT01</verrechnungPosTyp><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></verrechBasisPositionKombination>
|
||||
<verrechBasisPositionKombination xmlId="K02"><verrechnungBasisTyp>VBTSZ</verrechnungBasisTyp><verrechnungPosTyp>VPT02</verrechnungPosTyp><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></verrechBasisPositionKombination>
|
||||
</verrechBasisPositionKombinationen>
|
||||
<tarifgruppeSetZuordnungen>
|
||||
<tarifgruppeSetZuord xmlId="Z001"><tarifgruppe>T001</tarifgruppe><tarifset>S001</tarifset><verrechBasPosKombination>K01</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z002"><tarifgruppe>T001</tarifgruppe><tarifset>S002</tarifset><verrechBasPosKombination>K02</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z003"><tarifgruppe>T002</tarifgruppe><tarifset>S001</tarifset><verrechBasPosKombination>K01</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z004"><tarifgruppe>T002</tarifgruppe><tarifset>S002</tarifset><verrechBasPosKombination>K02</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z005"><tarifgruppe>T002E01</tarifgruppe><tarifset>S003</tarifset><verrechBasPosKombination>K01</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z006"><tarifgruppe>T002E01</tarifgruppe><tarifset>S002</tarifset><verrechBasPosKombination>K02</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z007"><tarifgruppe>T045</tarifgruppe><tarifset>S001</tarifset><verrechBasPosKombination>K01</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
<tarifgruppeSetZuord xmlId="Z008"><tarifgruppe>T045</tarifgruppe><tarifset>S002</tarifset><verrechBasPosKombination>K02</verrechBasPosKombination><traegergruppe>OEGK</traegergruppe><vonDatum>2019-01-01</vonDatum><bisDatum>2999-12-31</bisDatum></tarifgruppeSetZuord>
|
||||
</tarifgruppeSetZuordnungen>
|
||||
<zuAbschlaege>
|
||||
<zuAbschlag xmlId="A09X">
|
||||
<code>A09</code>
|
||||
<bezeichnung>Entfall UV 60. Lj</bezeichnung>
|
||||
<kurzbezeichnung>UV 60</kurzbezeichnung>
|
||||
<zuabschlagkz>A</zuabschlagkz>
|
||||
<versicherungsRelevant>false</versicherungsRelevant>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</zuAbschlag>
|
||||
</zuAbschlaege>
|
||||
<zuAbschlaegeSteuerung>
|
||||
<zuAbschlagSteuerung xmlId="A09S1">
|
||||
<zuAbschlag>A09X</zuAbschlag>
|
||||
<altersgrenzeMinJahr>60</altersgrenzeMinJahr>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</zuAbschlagSteuerung>
|
||||
</zuAbschlaegeSteuerung>
|
||||
<zuAbschlagTarifsetZuordnungen>
|
||||
<zuAbschlagTarifsetZuord xmlId="AZ01">
|
||||
<zuAbschlag>A09X</zuAbschlag>
|
||||
<tarifsetByTarifsetId>S001</tarifsetByTarifsetId>
|
||||
<tarifgruppe>T001</tarifgruppe>
|
||||
<tarifgruppe>T002</tarifgruppe>
|
||||
<traegergruppe>OEGK</traegergruppe>
|
||||
<vonDatum>2019-01-01</vonDatum>
|
||||
<bisDatum>2999-12-31</bisDatum>
|
||||
</zuAbschlagTarifsetZuord>
|
||||
</zuAbschlagTarifsetZuordnungen>
|
||||
</tarifssystemlsh>
|
||||
"""
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TasyKatalogTest(TransactionCase):
|
||||
"""Katalog-Import (Fixture + Realexport) und Tarifgruppen-Auflösung."""
|
||||
|
||||
def _import(self, raw=None):
|
||||
import os
|
||||
if raw is None:
|
||||
path = os.path.join(os.path.dirname(__file__), *[
|
||||
'..', '..', '..', 'personalverrechnung', 'quellen',
|
||||
'TASY-LSWH-Export-20260324.xml'])
|
||||
if not os.path.exists(path):
|
||||
self.skipTest('TASY-Quelldatei nicht im Workingtree')
|
||||
with open(path, 'rb') as fh:
|
||||
raw = fh.read()
|
||||
wizard = self.env['l10n.at.payroll.tasy.katalog.wizard'].create({
|
||||
'file': base64.b64encode(raw).decode(),
|
||||
'filename': 'fixture.xml',
|
||||
})
|
||||
wizard.action_import()
|
||||
return wizard
|
||||
|
||||
def test_01_import_fixture_und_aufloesung(self):
|
||||
"""Import erzeugt den Katalog; B001/B002 lösen zu den
|
||||
normativen Normalbetriebe-Sätzen auf (AB 39,05, SZ 37,55)."""
|
||||
self._import(TASY_KATALOG_FIXTURE.encode())
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.bsgr'].search_count([]), 5)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.ergaenzung'].search_count([]),
|
||||
1)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.dienstgebergruppe']
|
||||
.search_count([]), 2)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.tarifgruppe'].search_count([]),
|
||||
4)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.tarifset'].search_count([]), 3)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.tarifset.zuord']
|
||||
.search_count([]), 8)
|
||||
stichtag = date(2026, 7, 1)
|
||||
for code in ('B001', 'B002'):
|
||||
tarifgruppe = self.env[
|
||||
'l10n.at.payroll.tasy.tarifgruppe']._l10n_at_find(
|
||||
code, [], 'NBALG', stichtag)
|
||||
self.assertTrue(tarifgruppe, code)
|
||||
ab = tarifgruppe._l10n_at_saetze('AB', stichtag)
|
||||
sz = tarifgruppe._l10n_at_saetze('SZ', stichtag)
|
||||
self.assertAlmostEqual(ab['gesamt'], 39.05, places=2)
|
||||
self.assertAlmostEqual(ab['dn'], 18.07, places=2)
|
||||
self.assertAlmostEqual(ab['dg'], 20.98, places=2)
|
||||
self.assertAlmostEqual(sz['gesamt'], 37.55, places=2)
|
||||
|
||||
def test_02_import_idempotent(self):
|
||||
"""Erneuter Import derselben Datei dupliziert nichts."""
|
||||
self._import(TASY_KATALOG_FIXTURE.encode())
|
||||
self._import(TASY_KATALOG_FIXTURE.encode())
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.tarifgruppe'].search_count([]),
|
||||
4)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.tarif'].search_count([]), 8)
|
||||
self.assertEqual(
|
||||
self.env['l10n.at.payroll.tasy.tarifset.zuord']
|
||||
.search_count([]), 8)
|
||||
|
||||
def test_03_ergaenzung_e01(self):
|
||||
"""B002 + E01 + NBNSA → T01 inkl. NB-Zweig 3,80 % DG
|
||||
(42,85 %); ohne Ergänzung unter NBNSA keine Tarifgruppe."""
|
||||
self._import(TASY_KATALOG_FIXTURE.encode())
|
||||
stichtag = date(2026, 7, 1)
|
||||
find = self.env['l10n.at.payroll.tasy.tarifgruppe']._l10n_at_find
|
||||
# B002 + E01 unter NBALG existiert nicht (E01 gehört zu NBNSA)
|
||||
self.assertFalse(find('B002', ['E01'], 'NBALG', stichtag))
|
||||
tarifgruppe = find('B002', ['E01'], 'NBNSA', stichtag)
|
||||
self.assertTrue(tarifgruppe)
|
||||
ab = tarifgruppe._l10n_at_saetze('AB', stichtag)
|
||||
self.assertAlmostEqual(ab['gesamt'], 42.85, places=2)
|
||||
zweige = {t.zweig for t in ab['komponenten']}
|
||||
self.assertIn('NB', zweige)
|
||||
# SZ ohne NB (Sonderzahlungen tragen keinen NSchAB)
|
||||
sz = tarifgruppe._l10n_at_saetze('SZ', stichtag)
|
||||
self.assertAlmostEqual(sz['gesamt'], 37.55, places=2)
|
||||
|
||||
def test_04_lehrlings_bsg(self):
|
||||
"""B045 (Arbeiterlehrlinge) ist auflösbar — die
|
||||
Lehrlings-BSGR-Korrektur beruht auf diesen Bezeichnern."""
|
||||
self._import(TASY_KATALOG_FIXTURE.encode())
|
||||
stichtag = date(2026, 7, 1)
|
||||
b044 = self.env['l10n.at.payroll.tasy.bsgr'].search([
|
||||
('code', '=', 'B044')])
|
||||
b045 = self.env['l10n.at.payroll.tasy.bsgr'].search([
|
||||
('code', '=', 'B045')])
|
||||
self.assertEqual(b044.bezeichnung, 'Angestelltenlehrlinge')
|
||||
self.assertEqual(b045.bezeichnung, 'Arbeiterlehrlinge')
|
||||
tarifgruppe = self.env[
|
||||
'l10n.at.payroll.tasy.tarifgruppe']._l10n_at_find(
|
||||
'B045', [], 'NBALG', stichtag)
|
||||
self.assertTrue(tarifgruppe)
|
||||
|
||||
def test_05_real_export(self):
|
||||
"""Echter TASY-LSWH-Export: Katalog-Volumen und die
|
||||
normativen 2026er Sätze aus der Katalogauflösung."""
|
||||
self._import()
|
||||
stichtag = date(2026, 7, 1)
|
||||
find = self.env['l10n.at.payroll.tasy.tarifgruppe']._l10n_at_find
|
||||
for code in ('B001', 'B002'):
|
||||
tarifgruppe = find(code, [], 'NBALG', stichtag)
|
||||
self.assertTrue(tarifgruppe, code)
|
||||
ab = tarifgruppe._l10n_at_saetze('AB', stichtag)
|
||||
sz = tarifgruppe._l10n_at_saetze('SZ', stichtag)
|
||||
self.assertAlmostEqual(ab['gesamt'], 39.05, places=2)
|
||||
self.assertAlmostEqual(sz['gesamt'], 37.55, places=2)
|
||||
# Lehrlings-BSGR-Bezeichner (Korrektur-Grundlage)
|
||||
self.assertEqual(self.env['l10n.at.payroll.tasy.bsgr'].search([
|
||||
('code', '=', 'B044')]).bezeichnung, 'Angestelltenlehrlinge')
|
||||
self.assertEqual(self.env['l10n.at.payroll.tasy.bsgr'].search([
|
||||
('code', '=', 'B045')]).bezeichnung, 'Arbeiterlehrlinge')
|
||||
# Geringfügige je Personalgruppe
|
||||
self.assertEqual(self.env['l10n.at.payroll.tasy.bsgr'].search([
|
||||
('code', '=', 'B010')]).bezeichnung,
|
||||
'Geringfügig beschäftigte Arbeiter')
|
||||
# Ergänzungen sind im Katalog
|
||||
self.assertTrue(self.env['l10n.at.payroll.tasy.ergaenzung'].search([
|
||||
('code', '=', 'E01')]))
|
||||
@@ -28,6 +28,7 @@
|
||||
<field name="l10n_at_bundesland"/>
|
||||
<field name="l10n_at_wk_mitglied"/>
|
||||
<field name="l10n_at_wf_wien"/>
|
||||
<field name="l10n_at_wien_dag"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -124,4 +124,26 @@
|
||||
</notebook>
|
||||
</field>
|
||||
</record>
|
||||
<!-- AGRD-Mapping an der Abgangsursache (AP1) -->
|
||||
<record id="hr_departure_reason_view_form_agrd" model="ir.ui.view">
|
||||
<field name="name">hr.departure.reason.form.l10n_at_agrd</field>
|
||||
<field name="model">hr.departure.reason</field>
|
||||
<field name="inherit_id" ref="hr.hr_departure_reason_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="l10n_at_agrd"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_departure_reason_view_list_agrd" model="ir.ui.view">
|
||||
<field name="name">hr.departure.reason.list.l10n_at_agrd</field>
|
||||
<field name="model">hr.departure.reason</field>
|
||||
<field name="inherit_id" ref="hr.hr_departure_reason_view_list"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="l10n_at_agrd" optional="show"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- AP0 (Variante b): SV-Tarifsystem-Katalog — BSGR/ERGB/DGG/ -->
|
||||
<!-- Tarifgruppen/Tarifsets/Zu-/Abschläge aus dem TASY-Import. -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<record id="tasy_tarifgruppe_list_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.tasy.tarifgruppe.list</field>
|
||||
<field name="model">l10n.at.payroll.tasy.tarifgruppe</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Tarifgruppen">
|
||||
<field name="code"/>
|
||||
<field name="bezeichnung"/>
|
||||
<field name="bsgr_ids" widget="many2many_tags"/>
|
||||
<field name="ergaenzung_ids" widget="many2many_tags"/>
|
||||
<field name="dienstgebergruppe_ids" widget="many2many_tags"/>
|
||||
<field name="von_datum" optional="show"/>
|
||||
<field name="bis_datum" optional="show"/>
|
||||
<field name="fallweise_zulaessig" optional="hide"/>
|
||||
<field name="bv_zulaessig" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="tasy_tarifgruppe_form_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.tasy.tarifgruppe.form</field>
|
||||
<field name="model">l10n.at.payroll.tasy.tarifgruppe</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tarifgruppe">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code"/>
|
||||
<field name="bezeichnung"/>
|
||||
<field name="traegergruppe"/>
|
||||
<field name="von_datum"/>
|
||||
<field name="bis_datum"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="bsgr_ids" widget="many2many_tags"/>
|
||||
<field name="ergaenzung_ids"
|
||||
widget="many2many_tags"/>
|
||||
<field name="dienstgebergruppe_ids"
|
||||
widget="many2many_tags"/>
|
||||
<field name="fallweise_zulaessig"/>
|
||||
<field name="bv_zulaessig"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Tarifset-Zuordnungen"
|
||||
name="zuordnungen">
|
||||
<field name="zuord_ids" readonly="1">
|
||||
<list>
|
||||
<field name="basis_typ"/>
|
||||
<field name="pos_typ"/>
|
||||
<field name="tarifset_id"/>
|
||||
<field name="von_datum"/>
|
||||
<field name="bis_datum"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_tasy_tarifgruppe" model="ir.actions.act_window">
|
||||
<field name="name">Tarifgruppen (SV-Tarifsystem)</field>
|
||||
<field name="res_model">l10n.at.payroll.tasy.tarifgruppe</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<record id="tasy_bsgr_list_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.tasy.bsgr.list</field>
|
||||
<field name="model">l10n.at.payroll.tasy.bsgr</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Beschäftigtengruppen">
|
||||
<field name="code"/>
|
||||
<field name="kurzbezeichnung"/>
|
||||
<field name="bezeichnung"/>
|
||||
<field name="traegergruppe"/>
|
||||
<field name="von_datum" optional="show"/>
|
||||
<field name="bis_datum" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_tasy_bsgr" model="ir.actions.act_window">
|
||||
<field name="name">Beschäftigtengruppen (BSGR)</field>
|
||||
<field name="res_model">l10n.at.payroll.tasy.bsgr</field>
|
||||
<field name="view_mode">list</field>
|
||||
</record>
|
||||
|
||||
<record id="tasy_ergaenzung_list_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.tasy.ergaenzung.list</field>
|
||||
<field name="model">l10n.at.payroll.tasy.ergaenzung</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Ergänzungen">
|
||||
<field name="code"/>
|
||||
<field name="kurzbezeichnung"/>
|
||||
<field name="bezeichnung"/>
|
||||
<field name="traegergruppe"/>
|
||||
<field name="von_datum" optional="show"/>
|
||||
<field name="bis_datum" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_tasy_ergaenzung" model="ir.actions.act_window">
|
||||
<field name="name">Ergänzungen (ERGB)</field>
|
||||
<field name="res_model">l10n.at.payroll.tasy.ergaenzung</field>
|
||||
<field name="view_mode">list</field>
|
||||
</record>
|
||||
|
||||
<record id="tasy_zuabschlag_list_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.tasy.zuabschlag.list</field>
|
||||
<field name="model">l10n.at.payroll.tasy.zuabschlag</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Zu-/Abschläge">
|
||||
<field name="code"/>
|
||||
<field name="kurzbezeichnung"/>
|
||||
<field name="bezeichnung"/>
|
||||
<field name="zuabschlagkz" optional="show"/>
|
||||
<field name="versicherungsrelevant" optional="show"/>
|
||||
<field name="von_datum" optional="show"/>
|
||||
<field name="bis_datum" optional="hide"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_tasy_zuabschlag" model="ir.actions.act_window">
|
||||
<field name="name">Zu-/Abschläge</field>
|
||||
<field name="res_model">l10n.at.payroll.tasy.zuabschlag</field>
|
||||
<field name="view_mode">list</field>
|
||||
</record>
|
||||
|
||||
<!-- Katalog-Import-Wizard -->
|
||||
<record id="tasy_katalog_wizard_form_view" model="ir.ui.view">
|
||||
<field name="name">l10n.at.payroll.tasy.katalog.wizard.form</field>
|
||||
<field name="model">l10n.at.payroll.tasy.katalog.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="SV-Tarifsystem-Katalog importieren">
|
||||
<group>
|
||||
<field name="file" filename="filename"/>
|
||||
<field name="filename" invisible="1"/>
|
||||
<field name="stichtag" readonly="1"/>
|
||||
</group>
|
||||
<group string="Protokoll">
|
||||
<field name="protocol" readonly="1"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Katalog importieren"
|
||||
name="action_import" type="object"
|
||||
class="btn-primary"/>
|
||||
<button string="Abbrechen" class="btn-secondary"
|
||||
special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_tasy_katalog_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Tarifsystem-Katalog importieren (TASY)</field>
|
||||
<field name="res_model">l10n.at.payroll.tasy.katalog.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_tasy_tarifgruppen"
|
||||
name="Tarifsystem: Tarifgruppen"
|
||||
parent="menu_l10n_at_payroll_root"
|
||||
action="action_tasy_tarifgruppe" sequence="30"/>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_tasy_bsgr"
|
||||
name="Tarifsystem: Beschäftigtengruppen"
|
||||
parent="menu_l10n_at_payroll_root"
|
||||
action="action_tasy_bsgr" sequence="40"/>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_tasy_ergaenzung"
|
||||
name="Tarifsystem: Ergänzungen"
|
||||
parent="menu_l10n_at_payroll_root"
|
||||
action="action_tasy_ergaenzung" sequence="50"/>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_tasy_zuabschlaege"
|
||||
name="Tarifsystem: Zu-/Abschläge"
|
||||
parent="menu_l10n_at_payroll_root"
|
||||
action="action_tasy_zuabschlag" sequence="60"/>
|
||||
|
||||
<menuitem id="menu_l10n_at_payroll_tasy_katalog_import"
|
||||
name="Tarifsystem-Katalog importieren"
|
||||
parent="menu_l10n_at_payroll_root"
|
||||
action="action_tasy_katalog_wizard" sequence="70"/>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user