# 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}