mirror of
http://100.103.83.12:3003/fegger/odoo-at-payroll.git
synced 2026-09-17 16:56:42 +00:00
3c85cc6790
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
288 lines
12 KiB
Python
288 lines
12 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
|
"""Monatsreports und Jahresberichte der AT-Lohnverrechnung.
|
|
|
|
- **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):
|
|
_name = 'report.l10n_at_hr_payroll.report_monatsdienstliste'
|
|
_description = 'Monatsdienstliste (GP7/AP6)'
|
|
|
|
def _get_report_values(self, docids, data=None):
|
|
runs = self.env['hr.payslip.run'].browse(docids)
|
|
zeilen = []
|
|
for run in runs:
|
|
for slip in run.slip_ids.filtered(
|
|
lambda p: p.state not in ('draft', 'cancel')):
|
|
version = slip.version_id
|
|
basen = slip._l10n_at_sv_basis_laufen_monate()
|
|
sv_dn = sum(
|
|
slip._l10n_at_svdn(k) for k in
|
|
('kv', 'pv', 'av', 'ak', 'wf'))
|
|
sv_dg = slip._l10n_at_svdg_gesamt()
|
|
sz_dn = slip._l10n_at_svdn_sz()
|
|
sz_basis = slip._l10n_at_svdn_basis_sz()
|
|
zeilen.append({
|
|
'slip': slip,
|
|
'versicherungsnummer': version.ssnid if version else '',
|
|
'personalgruppe': getattr(
|
|
version, 'l10n_at_personalgruppe', '')
|
|
if version else '',
|
|
'geringfuegig': slip.l10n_at_geringfuegig,
|
|
'basis_laufen': sum(
|
|
b for _m, b in basen),
|
|
'sz_basis': sz_basis,
|
|
'sv_dn': sv_dn,
|
|
'sv_dg': sv_dg,
|
|
'sv_dn_sz': sz_dn,
|
|
'lohnsteuer': sum(
|
|
slip.line_ids.filtered(
|
|
lambda l: l.code in ('LST_LFD', 'LST_SZ')
|
|
).mapped('total')),
|
|
'netto': sum(
|
|
slip.line_ids.filtered(
|
|
lambda l: l.code == 'NET').mapped('total')),
|
|
'brutto': sum(
|
|
slip.line_ids.filtered(
|
|
lambda l: l.salary_rule_id.l10n_at_bemessung
|
|
!= 'neutral').mapped('total')),
|
|
})
|
|
return {'docs': runs, 'zeilen': zeilen}
|
|
|
|
|
|
class L10nAtPayrollMonatsjournal(models.AbstractModel):
|
|
_name = 'report.l10n_at_hr_payroll.report_monatsjournal'
|
|
_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:
|
|
continue
|
|
kategorie = regel.category_id.code or ''
|
|
schluessel = (regel.code, regel.name,
|
|
regel.l10n_at_bemessung)
|
|
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': [
|
|
{'code': code, 'name': name, 'bemessung': bemessung,
|
|
'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} |