[IMP] l10n_at_hr_payroll_private: GP8 Vertragsstichtage-UI, All-in-Deckungsrechnung, DAG-Ausweis, Frist-mBGM-Verlinkung

Vertragsstichtage-UI (views/vertragsfelder_views.xml): GP4-Felder (ALV-Entfall, UV-Entfall, PV-Bonusphase, LNK-frei) + Status (Lehrling, fallweise, Familienabsetzbetrag, ueberw. Nachtarbeit) als gruppierte Payroll-Seite am Mitarbeiterformular (Muster: KV-Einstufung + BV-Gruppe).

All-in-Deckungsrechnung (report/gp8_reports.xml — QWeb auf hr.payslip): GP2-_at_allin_deckung als Ausweis (Pauschale vs. Bezuege, Rest = Über-/Unterdeckung; § 2g AVRAG). DAG-Ausweis (QWeb auf hr.payslip.run): § 1 DAG je Monat — Lohnsumme der Geringfuegigen, Schwelle (1,5 × GFG), Abgabe 19,4 % (GP4 _l10n_at_dag).

Frist-mBGM-Verlinkung (models/fristen_private.py): ref_mbgm_id-Feld am Fristenkalender (Kernmodell kennt mBGM nicht) + action_zum_objekt-Super-Chain; _l10n_at_verlinke_deklarationen setzt Sprungziele fuer offene L16- und mBGM-Fristen.

19.0.8.0.0. Tests: GP8-Kernsuite 6 (100/100 gesamt).
This commit is contained in:
2026-09-12 19:59:03 +02:00
parent c843701d1a
commit 57522b5be3
5 changed files with 234 additions and 2 deletions
@@ -1,7 +1,7 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
{
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
'version': '19.0.7.0.0',
'version': '19.0.8.0.0',
'author': 'ixsol / gem360',
'website': 'https://gem360.at',
'category': 'Human Resources/Payroll',
@@ -111,6 +111,8 @@ gem360-Brücke).
'views/kv_views.xml',
'views/bv_views.xml',
'views/meldewesen_views.xml',
'views/vertragsfelder_views.xml',
'report/gp8_reports.xml',
'report/bv_report.xml',
],
'countries': ['at'],
@@ -8,4 +8,5 @@ from . import bv_report
from . import account_chart_private
from . import account_posting_private
from . import mbgm
from . import versichertenmeldung
from . import versichertenmeldung
from . import fristen_private
@@ -0,0 +1,48 @@
# Part of the odoo-at-payroll project. License: LGPL-3.
"""GP8-Privat: Verlinkung des Fristenkalenders mit den
privatwirtschaftlichen GP7-Deklarationen (L16/mBGM-Sprungziele)."""
from odoo import fields, models
class L10nAtPayrollFrist(models.Model):
_inherit = 'l10n.at.payroll.frist'
ref_mbgm_id = fields.Many2one(
'l10n.at.payroll.mbgm', string='mBGM-Deklaration',
help='Sprungziel für SV-Fristen (Privatmodul — das '
'Kernmodell kennt mBGM nicht).')
def action_zum_objekt(self):
"""Sprung zur GP7-Deklaration (L16 oder mBGM)."""
self.ensure_one()
result = super().action_zum_objekt()
if result is not True:
return result
if self.ref_mbgm_id:
return {
'type': 'ir.actions.act_window',
'res_model': 'l10n.at.payroll.mbgm',
'res_id': self.ref_mbgm_id.id,
'view_mode': 'form',
}
return True
def _l10n_at_verlinke_deklarationen(self):
"""Sprungziele für offene L16- und mBGM-Fristen setzen: neueste
Deklaration des Bezugsjahres verlinken, wenn vorhanden."""
for frist in self:
if frist.fristart in ('l16', 'arbeitsstaette'):
l16 = self.env['l10n.at.payroll.l16'].search([
('company_id', '=', frist.company_id.id),
('jahr', '=', frist.jahr),
], order='id desc', limit=1)
frist.ref_l16_id = l16 or False
elif frist.fristart == 'sv_mbgm':
mbgm = self.env['l10n.at.payroll.mbgm'].search([
('company_id', '=', frist.company_id.id),
('beitragsmonat', '>=',
'%d-%02d-01' % (frist.jahr, frist.monat or 1)),
('beitragsmonat', '<=',
'%d-%02d-01' % (frist.jahr, frist.monat or 12)),
], order='id desc', limit=1)
frist.ref_mbgm_id = mbgm or False
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- GP8: All-in-Deckungsrechnung (QWeb auf hr.payslip) und -->
<!-- DAG-Ausweis (QWeb auf hr.payslip.run). -->
<!-- ============================================================ -->
<record id="action_report_allin_deckung" model="ir.actions.report">
<field name="name">All-in-Deckungsrechnung (§ 2g AVRAG)</field>
<field name="model">hr.payslip</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">l10n_at_hr_payroll_private.report_allin_deckung</field>
<field name="report_file">l10n_at_hr_payroll_private.report_allin_deckung</field>
<field name="paperformat_id" ref="hr_payroll.paperformat_euro_light"/>
<field name="binding_model_id" ref="hr_payroll.model_hr_payslip"/>
<field name="binding_type">report</field>
</record>
<record id="action_report_dag_ausweis" model="ir.actions.report">
<field name="name">DAG-Ausweis (§ 1 DAG)</field>
<field name="model">hr.payslip.run</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">l10n_at_hr_payroll_private.report_dag_ausweis</field>
<field name="report_file">l10n_at_hr_payroll_private.report_dag_ausweis</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>
<!-- All-in-Deckungsrechnung: GP2 `_at_allin_deckung()` als
Ausweis — wie viel der Pauschale von welchen Bezügen gedeckt
ist; Rest = Über-/Unterdeckung. -->
<template id="report_allin_deckung">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="slip">
<t t-call="web.external_layout">
<div class="page">
<h2>All-in-Deckungsrechnung</h2>
<table class="table table-sm">
<tr>
<td><strong>Mitarbeiter:in</strong></td>
<td><t t-out="slip.employee_id.name"/></td>
<td><strong>Periode</strong></td>
<td><t t-out="slip.date_from"/> <t t-out="slip.date_to"/></td>
</tr>
<tr>
<td><strong>All-in-Pauschale</strong></td>
<td>
<span t-out="slip.version_id.allin_pauchale"
t-options='{"widget": "monetary", "display_currency": slip.currency_id}'/>
</td>
<td><strong>Grundlohn (KV)</strong></td>
<td>
<span t-out="slip.version_id.allin_grundlohn"
t-options='{"widget": "monetary", "display_currency": slip.currency_id}'/>
</td>
</tr>
</table>
<h3>Bezüge vs. Pauschale</h3>
<table class="table table-sm">
<thead>
<tr>
<th>Bezug</th>
<th class="text-end">Betrag</th>
</tr>
</thead>
<tbody>
<tr t-foreach="slip.line_ids.filtered(lambda l: l.total and l.salary_rule_id.l10n_at_bemessung != 'neutral' and l.salary_rule_id.category_id.code == 'ATP_BRUTTO')" t-as="line">
<td><t t-out="line.name"/></td>
<td class="text-end">
<span t-out="line.total" t-options='{"widget": "monetary", "display_currency": slip.currency_id}'/>
</td>
</tr>
</tbody>
</table>
<t t-set="deckung" t-value="slip._at_allin_deckung()"/>
<t t-if="deckung and deckung.get('rest') is not None">
<p class="h4 text-end">
<strong>Rest:</strong>
<span t-out="deckung['rest']"
t-options='{"widget": "monetary", "display_currency": slip.currency_id}'/>
<t t-if="deckung['rest'] > 0">
<span class="text-warning">(Überdeckung — Pauschale übersteigt die Bezüge)</span>
</t>
<t t-if="deckung['rest'] &lt; 0">
<span class="text-danger">(Unterdeckung — Bezüge übersteigen die Pauschale)</span>
</t>
</p>
</t>
</div>
</t>
</t>
</t>
</template>
<!-- DAG-Ausweis: § 1 DAG je Monat — Lohnsumme der Geringfügigen,
Schwelle, Abgabe. -->
<template id="report_dag_ausweis">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="run">
<t t-call="web.external_layout">
<div class="page">
<h2>DAG-Ausweis (§ 1 DAG) — <t t-out="run.name"/></h2>
<t t-set="dag" t-value="run.company_id._l10n_at_dag(run.slip_ids[:1].date_from if run.slip_ids else None)"/>
<table class="table table-sm">
<thead>
<tr>
<th>Kennzahl</th>
<th class="text-end">Wert</th>
</tr>
</thead>
<tbody>
<tr>
<td>Anzahl geringfügig Beschäftigte</td>
<td class="text-end"><t t-out="dag and dag.get('anzahl', 0) or 0"/></td>
</tr>
<tr>
<td>Lohnsumme (ohne SZ)</td>
<td class="text-end">
<span t-out="dag and dag.get('lohnsumme', 0.0) or 0.0"
t-options='{"widget": "monetary", "display_currency": run.company_id.currency_id}'/>
</td>
</tr>
<tr>
<td>Schwelle (1,5 × GFG)</td>
<td class="text-end">
<span t-out="dag and dag.get('schwelle', 0.0) or 0.0"
t-options='{"widget": "monetary", "display_currency": run.company_id.currency_id}'/>
</td>
</tr>
<tr>
<td><strong>Dienstgeberabgabe (19,4 %)</strong></td>
<td class="text-end">
<strong><span t-out="dag and dag.get('abgabe', 0.0) or 0.0"
t-options='{"widget": "monetary", "display_currency": run.company_id.currency_id}'/></strong>
</td>
</tr>
</tbody>
</table>
<p class="small text-muted">
Voraussetzungen: mindestens 2 geringfügig Beschäftigte
und Lohnsumme über der Schwelle (1,5 × GFG).
</p>
</div>
</t>
</t>
</t>
</template>
</odoo>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- GP8: UI der GP4-Vertragsstichtage + GP5/GP6-Felder am -->
<!-- Mitarbeiterformular (Payroll-Gruppe, Muster KV-Einstufung). -->
<!-- ============================================================ -->
<record id="hr_employee_view_form_stichtage" model="ir.ui.view">
<field name="name">hr.employee.form.l10n_at_stichtage</field>
<field name="model">hr.employee</field>
<field name="inherit_id"
ref="l10n_at_hr_payroll_private.payroll_hr_employee_view_form_bv"/>
<field name="arch" type="xml">
<group name="bv_einstufung" position="after">
<group name="stichtage" string="SV-Stichtage &amp; Status (GP4)"
groups="hr_payroll.group_hr_payroll_user">
<group string="Satzausschaltungen">
<field name="l10n_at_alv_entfall_ab"/>
<field name="l10n_at_uv_entfall_ab"/>
<field name="l10n_at_pv_bonusphase_ab"/>
<field name="l10n_at_lnkp_frei_ab"/>
</group>
<group string="Status &amp; Abrechnung">
<field name="l10n_at_lehrling"/>
<field name="l10n_at_fallweise"/>
<field name="l10n_at_familienabsetzbetrag"/>
<field name="l10n_at_ueberw_nachtarbeit"/>
</group>
</group>
</group>
</field>
</record>
</odoo>