[IMP] l10n_at_hr_payroll_private: add statutory old-system severance (AP15-A)
Pay the pre-2003 AngG severance from the end-of-employment run as its own neutral benefit line: free of SV (§ 49 Abs 3 Z 7 ASVG) and payroll levies, outside the annual sixth, and collected into the § 291d EO termination pot net of its own wage tax. Taxation follows the binding better-of rule of § 67 Abs 3 EStG: multiple-of-method (tariff wage tax on the running monthly wage × the statutory multiple of the payout) versus the flat 6 % rate, applied mandatorily in favour of the employee. The tariff wage tax is captured as a separately confirmed value instead of deriving it from gross pay through the § 62 engine. The wizard enforces § 23a Abs 3 AngG (parental exit needs five uninterrupted years), blocks BMSVG-new contracts except frozen claims and validates AGRD 02/06 against the claim type. The LNK basis subtracts § 67 Abs 3/6 rules only when they actually run in the tax base, the L16 generator reports LST_ABF in key 260 outside 210/220, and result_rules access in the garnishment paths avoids DefaultDictPayroll materialisation. Verified against the original statute texts in .ris/ (AngG §§ 23/23a, EStG § 67 Abs 3). Version 19.0.13.0.0 (private), 19.0.9.0.0 (core)
This commit is contained in:
+43
-1
@@ -2,8 +2,50 @@
|
||||
|
||||
## Current focus
|
||||
|
||||
**AP15-A gesetzliche Abfertigung Alt umgesetzt — Privat 141/141, Kern 54/54 Tests grün**
|
||||
(2026-09-15, uncommittet bis Nutzerfreigabe; Privat **19.0.13.0.0**, Kern **19.0.9.0.0**):
|
||||
|
||||
- **Recht am Originalwortlaut verifiziert (.ris/):** § 23a Abs 3 AngG
|
||||
verlangt für den Elternaustritt ein **ununterbrochen fünfjähriges**
|
||||
DV (Hälfte des §-23-Betrags, Deckel 3 Monatsentgelte) — Vorschlag
|
||||
bei < 60 Monaten korrigiert (0-Faktor + Wizard-UserError). § 67
|
||||
Abs 3 EStG (RIS-Wortlaut): Tarif-LSt **auf den laufenden
|
||||
Arbeitslohn** × Mehrfaches, zwingend günstiger als 6 %. Neu:
|
||||
getrenntes, bestätigtes Feld `abf_alt_monatslohnsteuer`/
|
||||
`l10n_at_abf_alt_monatslohnsteuer` (Tarif-LSt des laufenden
|
||||
Monats nach persönlichen Verhältnissen) — Bruttolohn wird NICHT
|
||||
über die §-62-Engine tarifiert. Rechenfall lb-end-01 exakt:
|
||||
101,96 × 10,5 = 1.070,58 (< 1.134 = 6 %).
|
||||
- **Privat:** `models/endabrechnung.py` ABF_ALT-Helfer (Staffel,
|
||||
Mehrfach-Vorschlag eltern/tod, 6 %, Vervielfachung, günstige LSt),
|
||||
Regel `ABF_ALT` (neutral/keine/abs3/pfandung_beendigung),
|
||||
`LST_ABF` seq 72, `PFAENDUNG_END` seq 77 (Beendigungstopf −
|
||||
LST_ABF, Gesamttopf + beendigung), LNK-Fix in beiden Kopien
|
||||
(`_at_lnk_basis` zieht Abs-3/6-Regeln nur mit bemessung
|
||||
laufend/sonstig ab), Wizard-Felder + Validierungen (BMSVG-Neu-
|
||||
Sperre außer eingefroren, AGRD 02/06-Regeln, Eltern < 5 J),
|
||||
Payslip-Tab, Konten `rule_abf_alt` 6200/6000 (vorläufig),
|
||||
`rule_lst_abf` → 3540. `.get()`-Zugriffe auf `result_rules` in
|
||||
`models/pfaendung.py` durch Membership-Prüfung ersetzt.
|
||||
- **Kern:** LST_ABF in KZ-260-Salden (Lohnsteuer), außerhalb
|
||||
KZ 210/220 (test_01b). Kern-Hilfer `_l10n_at_monatslohnsteuer`
|
||||
des Subagenten wieder ENTFERNT (ungenutzt, semantisch
|
||||
irreführend) samt test_02b.
|
||||
- **Tests:** endabrechnung 13 (inkl. Arbeitnehmerkündigung-
|
||||
UserError, Eltern < 5 J, Wizard-E2E, Pfändungsbasis), pfändung
|
||||
12 (test_09b Beendigungstopf), Privat-Suite 141/141 (`ap13a_test`),
|
||||
Kern-Suite 54/54 (auf `ap13a_test` mit Privatmodul — **Achtung:
|
||||
L16-Tests referenzieren `l10n_at_hr_payroll_private.`
|
||||
`structure_at_privat`, Kern-Rein-DB scheitert an setUp, ist
|
||||
Bestandsverhalten**). Python/XML/`git diff --check` grün.
|
||||
- **Offen:** AP15-B (freiwillige Abfertigung § 67 Abs 6, 6 %-Bänder
|
||||
+ rollierende 12-Monats-Basis), AP15-C (§ 67 Abs 8 lit a
|
||||
Vergleichssummen). Wizard-Transient NOT-NULL-Warnung für
|
||||
`departure_reason_id` ist kosmetisch (Alt-Zeilen). Nicht
|
||||
committen ohne Nutzeranforderung.
|
||||
|
||||
**AP14-A Endabrechnungs-Zyklus umgesetzt — 133 Tests grün**
|
||||
(2026-09-14, uncommittet bis Nutzerfreigabe):
|
||||
(2026-09-14, Commit **`ee94f46`** nach Nutzerfreigabe):
|
||||
|
||||
- **Rechtskorrektur gegen Originalwortlaut (.ris/, RIS-Gate erledigt):**
|
||||
§ 67 Abs 6 (freiwillige Abfertigung/Abfindung) = 6 %-Bänder
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Personalverrechnung Kern (AT Payroll Core)',
|
||||
'version': '19.0.8.0.0',
|
||||
'version': '19.0.9.0.0',
|
||||
'author': 'Florian Egger',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
|
||||
@@ -400,6 +400,7 @@ class HrPayslip(models.Model):
|
||||
# ------------------------------------------------------------------
|
||||
# § 66 Monatslohnsteuer — kumulativ mit Rollung
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _l10n_at_lst_laufend(self, result_rules=None):
|
||||
"""LSt auf laufende Bezüge (§ 66): kumulative Hochrechnung
|
||||
(360-Tage-Jahr) minus bereits einbehaltene LSt — Vormonats-
|
||||
|
||||
@@ -390,7 +390,7 @@ class L10nAtPayrollL16Line(models.Model):
|
||||
GP3-Kerns (Monats-Caps 400/600 und ÜStd 15/170 eingerechnet);
|
||||
220 = sonstige § 67 Abs 1/2/5 ohne Abs-3/6; 225 = SV-DN auf
|
||||
KZ 220; 230 = laufende SV-DN + AK + WF; VIEB = 230 + 225 +
|
||||
226; 260/BIEL = LST_LFD + LST_SZ."""
|
||||
226; 260/BIEL = LST_LFD + LST_SZ + LST_ABF."""
|
||||
self.ensure_one()
|
||||
salden = dict.fromkeys(
|
||||
('kz210', 'kz215', 'kz220', 'kz225', 'kz226', 'kz230',
|
||||
@@ -411,7 +411,7 @@ class L10nAtPayrollL16Line(models.Model):
|
||||
elif line.code in ('SVDN_KV', 'SVDN_PV', 'SVDN_AV',
|
||||
'SVDN_AK', 'SVDN_WF'):
|
||||
salden['kz230'] += line.total
|
||||
elif line.code in ('LST_LFD', 'LST_SZ'):
|
||||
elif line.code in ('LST_LFD', 'LST_SZ', 'LST_ABF'):
|
||||
salden['lohnsteuer'] += line.total
|
||||
freibe = slip._l10n_at_freibetraege_68()
|
||||
salden['kz215'] += freibe['szfn'] + freibe['ue_std']
|
||||
|
||||
@@ -5,6 +5,7 @@ Satz-Assertions, CRLF/ISO-8859-15-Encoding, SANR/SANZ-Kette,
|
||||
KZ-245-Formel-Konsistenz aus GP3-Daten."""
|
||||
import base64
|
||||
import datetime
|
||||
import types
|
||||
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
@@ -213,6 +214,37 @@ class L16GeneratorTest(TransactionCase):
|
||||
self.assertEqual(schluss[0:2], '99')
|
||||
self.assertEqual(schluss[20:26], '000004') # SANZ
|
||||
|
||||
def test_01b_abfertigung_lohnsteuer_salden(self):
|
||||
"""Abs.-3-Bezug bleibt außerhalb KZ 210/220; LST_ABF zählt zu 260."""
|
||||
deklaration = self.env['l10n.at.payroll.l16'].create({
|
||||
'company_id': self.company.id,
|
||||
'jahr': 2026,
|
||||
'art': '01',
|
||||
})
|
||||
l16_line = self.env['l10n.at.payroll.l16.line'].create({
|
||||
'l16_id': deklaration.id,
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
abs3_rule = types.SimpleNamespace(
|
||||
l10n_at_bemessung='sonstig', l10n_at_s67_abs='abs3')
|
||||
lst_rule = types.SimpleNamespace(
|
||||
l10n_at_bemessung='neutral', l10n_at_s67_abs='keine')
|
||||
slip = types.SimpleNamespace(
|
||||
line_ids=[
|
||||
types.SimpleNamespace(
|
||||
salary_rule_id=abs3_rule, code='ABF_ALT', total=12000.0),
|
||||
types.SimpleNamespace(
|
||||
salary_rule_id=lst_rule, code='LST_ABF', total=720.0),
|
||||
],
|
||||
_l10n_at_freibetraege_68=lambda: {'szfn': 0.0, 'ue_std': 0.0},
|
||||
)
|
||||
|
||||
salden = l16_line._kz_salden([slip])
|
||||
|
||||
self.assertEqual(salden['kz210'], 0.0)
|
||||
self.assertEqual(salden['kz220'], 0.0)
|
||||
self.assertEqual(salden['lohnsteuer'], 720.0)
|
||||
|
||||
def test_02_arbeitsstaette(self):
|
||||
"""§ 34 Abs 6 ASVG: je AN ein Satz 45 (Länge 300) mit BKNR,
|
||||
VSNR, STNR und GKZ aus dem Dienstort-Partner; Warnung, wenn
|
||||
|
||||
@@ -108,6 +108,7 @@ class LohnsteuerTest(TransactionCase):
|
||||
slip._l10n_at_absetzbetraege(20000.0),
|
||||
496.0 + 828.0 + 273.0, places=2)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lohnsteuertage (user decision: 30 je vollem Monat)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
|
||||
'version': '19.0.12.0.0',
|
||||
'version': '19.0.13.0.0',
|
||||
'author': 'Florian Egger',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -117,7 +117,6 @@ gem360-Brücke).
|
||||
'data/salary_rules_endabrechnung_private.xml',
|
||||
'data/salary_rules_sv_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
'data/salary_rules_bvg_private.xml',
|
||||
'data/hr_payroll_account_private.xml',
|
||||
'data/rule_parameters_mbgm_private.xml',
|
||||
'data/rule_parameters_wien_private.xml',
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
<field name="name">Aufrechnung offener Vorschuss (Netto)</field>
|
||||
<field name="code">ATP_ABRECHNUNG_VORSCHUSS</field>
|
||||
</record>
|
||||
<record id="input_type_atp_abf_alt" model="hr.payslip.input.type">
|
||||
<field name="name">Gesetzliche Abfertigung Alt (Brutto)</field>
|
||||
<field name="code">ATP_ABF_ALT</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -12,6 +12,6 @@
|
||||
model="hr.rule.parameter.value">
|
||||
<field name="rule_parameter_id" ref="hr_rule_parameter_at_endabrechnung"/>
|
||||
<field name="date_from">2026-01-01</field>
|
||||
<field name="parameter_value">{'divisor_werktage': 26.0, 'divisor_arbeitstage': 22.0}</field>
|
||||
<field name="parameter_value">{'divisor_werktage': 26.0, 'divisor_arbeitstage': 22.0, 'abfertigung_alt_staffel': [[36, 2.0], [60, 3.0], [120, 4.0], [180, 6.0], [240, 9.0], [300, 12.0]], 'abfertigung_steuersatz': 6.0}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -72,6 +72,25 @@ result = inputs['ATP_ABGANG'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Gesetzliche Abfertigung Alt (§§ 23/23a AngG, § 67 Abs 3 EStG). -->
|
||||
<record id="rule_abf_alt" model="hr.salary.rule">
|
||||
<field name="name">Gesetzliche Abfertigung Alt (§ 67 Abs 3)</field>
|
||||
<field name="code">ABF_ALT</field>
|
||||
<field name="sequence" eval="30"/>
|
||||
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_brutto"/>
|
||||
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = 'ATP_ABF_ALT' in inputs and inputs['ATP_ABF_ALT'].amount</field>
|
||||
<field name="l10n_at_bemessung">neutral</field>
|
||||
<field name="l10n_at_sv_art">keine</field>
|
||||
<field name="l10n_at_s67_abs">abs3</field>
|
||||
<field name="l10n_at_pfandung_beendigung" eval="True"/>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = inputs['ATP_ABF_ALT'].amount
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Zeitguthaben an Normalarbeitszeit (§ 19e Abs 2 AZG: + 50 %). -->
|
||||
<record id="rule_zeit_guthaben" model="hr.salary.rule">
|
||||
<field name="name">Zeitguthaben Normalarbeitszeit + 50 % (§ 19e Abs 2 AZG)</field>
|
||||
|
||||
@@ -104,4 +104,19 @@ result = payslip._l10n_at_lst_laufend(result_rules)
|
||||
result = payslip._l10n_at_lst_sonstige(result_rules)
|
||||
]]></field>
|
||||
</record>
|
||||
<record id="rule_lst_abf" model="hr.salary.rule">
|
||||
<field name="name">Lohnsteuer Abfertigung Alt (§ 67 Abs 3)</field>
|
||||
<field name="code">LST_ABF</field>
|
||||
<field name="sequence" eval="72"/>
|
||||
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_abzug"/>
|
||||
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python">result = payslip._at_abf_alt_lohnsteuer(result_rules) > 0</field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="l10n_at_bemessung">neutral</field>
|
||||
<field name="l10n_at_sv_art">keine</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = payslip._at_abf_alt_lohnsteuer(result_rules)
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -56,4 +56,23 @@ result = (payslip._at_pfaendung_masse(result_rules, 'sz13')
|
||||
+ payslip._at_pfaendung_masse(result_rules, 'sz14'))
|
||||
]]></field>
|
||||
</record>
|
||||
|
||||
<record id="rule_pfaendung_end" model="hr.salary.rule">
|
||||
<field name="name">Lohnpfändung — Beendigungsansprüche (§ 291d EO)</field>
|
||||
<field name="code">PFAENDUNG_END</field>
|
||||
<field name="sequence" eval="77"/>
|
||||
<field name="category_id" ref="l10n_at_hr_payroll_private.category_atp_abzug"/>
|
||||
<field name="struct_id" ref="l10n_at_hr_payroll_private.structure_at_privat"/>
|
||||
<field name="condition_select">python</field>
|
||||
<field name="condition_python"><![CDATA[
|
||||
result = bool(payslip.employee_id._at_pfaendungen_aktiv(payslip.date_to)) \
|
||||
and payslip._at_pfaendung_masse(result_rules, 'beendigung') > 0
|
||||
]]></field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="l10n_at_bemessung">neutral</field>
|
||||
<field name="l10n_at_sv_art">keine</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
result = payslip._at_pfaendung_masse(result_rules, 'beendigung')
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Endabrechnungs-Zyklus — Rechenfälle AP14-A
|
||||
# Endabrechnungs-Zyklus — Rechenfälle AP14-A / AP15-A
|
||||
|
||||
Stand: 2026-09. AP14-A bildet die Beendigungs-Abrechnung auf der
|
||||
privaten Structure ab: Urlaubsersatzleistung (UEL), Abgangsentschädigung,
|
||||
@@ -80,12 +80,54 @@ Netto; LSt-, SV- und LNK-Basen unverändert.
|
||||
10 h Normalarbeitszeit-Guthaben × 1,5 + 5 h Zeitschulden × (−1):
|
||||
beides laufender Bezug, kein §-68-Freibetrag.
|
||||
|
||||
## Offene Punkte (dokumentiert, AP14-B/AP15)
|
||||
## RF-ABF-1 — Abfertigung Alt, Vervielfachung günstiger
|
||||
|
||||
21 Dienstjahre ergeben nach § 23 Abs 1 AngG 9 Monatsentgelte. Bei
|
||||
1.800 € laufendem Monatsarbeitslohn und 2.100 € arbeitsrechtlicher
|
||||
Bemessungsgrundlage einschließlich 2/12 Sonderzahlungen:
|
||||
|
||||
- Bruttoabfertigung: 2.100 × 9 = **18.900 €**;
|
||||
- steuerlicher Vervielfacher: 18.900 / 1.800 = **10,5** — nicht 9;
|
||||
- auf den maßgeblichen laufenden Bezug entfallende Tarif-LSt laut
|
||||
Ausgangsfall: **101,96 €**;
|
||||
- Vervielfachungssteuer: 101,96 × 10,5 = **1.070,58 €**;
|
||||
- Vergleich mit 18.900 × 6 % = **1.134 €**;
|
||||
- zwingend wird der niedrigere Steuerbetrag angesetzt.
|
||||
|
||||
`ABF_ALT` ist SV-, BV-, DB-, DZ- und KommSt-frei, verbraucht kein
|
||||
Jahressechstel und läuft als Beendigungsanspruch in den §-291d-EO-Topf.
|
||||
|
||||
## RF-ABF-2 — 6 % günstiger
|
||||
|
||||
Bei hohem laufendem Monatsarbeitslohn kann die vervielfachte Tarif-LSt
|
||||
den Fixsatz überschreiten. Für 9.333,34 € Abfertigung wird dann zwingend
|
||||
**560,00 €** Lohnsteuer (6 %) angesetzt.
|
||||
|
||||
## RF-ABF-3 — Staffel und Sonderfälle
|
||||
|
||||
- 3/5/10/15/20/25 Jahre → 2/3/4/6/9/12 Monatsentgelte;
|
||||
- Elternaustritt nach § 23a Abs 3/4: erst nach mindestens fünf Jahren,
|
||||
dann Hälfte, maximal 3 Monatsentgelte;
|
||||
- Tod nach § 23 Abs 6: Hälfte des normalen Anspruchs;
|
||||
- normale Arbeitnehmerkündigung, unberechtigter Austritt und
|
||||
verschuldete Entlassung begründen grundsätzlich keinen Anspruch;
|
||||
- Abfertigung-Neu-Verträge sind gesperrt, außer ein eingefrorener
|
||||
Altanspruch wird ausdrücklich nachgewiesen.
|
||||
|
||||
Der Wizard schlägt Dienstzeit, Faktor und eine Basis aus laufendem Entgelt
|
||||
plus aliquoten Sonderzahlungen vor. Die auf den maßgeblichen laufenden
|
||||
Monatsarbeitslohn entfallende Tarif-LSt wird getrennt bestätigt, weil sie
|
||||
von der §-62-Bemessung und den persönlichen Absetzbeträgen abhängt und nicht
|
||||
mit dem Bruttolohn gleichgesetzt werden darf. Wegen regelmäßiger Zulagen,
|
||||
Durchschnittsbezügen, Lehrzeiten, Teilübertritten und §-23a-Sonderfällen
|
||||
müssen Anspruch, Basis und Nachweis vor Anlage des Laufs bestätigt werden.
|
||||
Die geprüften Angaben werden am Payslip gespeichert.
|
||||
|
||||
## Offene Punkte (AP15-B/AP15-C)
|
||||
|
||||
- **Vergleich (§ 67 Abs 8 lit a)** und **freiwillige Abfertigung/
|
||||
Abfindung (§ 67 Abs 6 6 %-Bänder)** → AP15 mit der Abfertigung-Alt-
|
||||
Günstiger-Maschine (dort auch rollierende 12-Monats-Basis und
|
||||
Dienstzeit-Nachweise).
|
||||
Abfindung (§ 67 Abs 6 6 %-Bänder)** → AP15-B/C mit rollierender
|
||||
12-Monats-Basis, Dienstzeit-Nachweisen und Alt-/Neu-Aufteilung.
|
||||
- Prämien-Beendigung (§ 16 AngG-Aliquotierung, Rollung) — PRAM/SZ-Regeln
|
||||
vorhanden; Feinschliff AP14-B.
|
||||
- Meldewesen-Schnittstellen: „Ende des Entgeltanspruches" in mBGM/
|
||||
|
||||
@@ -68,6 +68,11 @@ L10N_AT_BUCHUNGSMATRIX = {
|
||||
'rule_svdn_sz': {'credit': ('3600',)},
|
||||
'rule_lst_lfd': {'credit': ('3540',)},
|
||||
'rule_lst_sz': {'credit': ('3540',)},
|
||||
'rule_lst_abf': {'credit': ('3540',)},
|
||||
# Gesetzliche Abfertigung Alt: bis zur Referenzbuchhaltungs-
|
||||
# Fixierung allgemeiner Gehalts-/Lohnaufwand, company-dependent
|
||||
# Regelkonto bleibt übersteuerbar.
|
||||
'rule_abf_alt': {'debit': ('6200',), 'debit_arbeiter': ('6000',)},
|
||||
# Nettoauszahlung (reconcilable, s. o.)
|
||||
'rule_netto': {'credit': ('3850',)},
|
||||
# DG-Beiträge (650–655 SV Arbeiter · 656–659 SV Angestellte)
|
||||
|
||||
@@ -56,6 +56,34 @@ class HrSalaryRule(models.Model):
|
||||
class HrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
l10n_at_abf_alt_anspruchsart = fields.Selection(
|
||||
[('normal', 'Normalanspruch (§ 23 AngG)'),
|
||||
('eltern', 'Elternaustritt (§ 23a Abs 3/4 AngG)'),
|
||||
('tod', 'Tod (§ 23 Abs 6 AngG)'),
|
||||
('pension', 'Pension/Invalidität (§ 23a Abs 1 AngG)'),
|
||||
('eingefroren', 'Eingefrorener Altanspruch (§ 47 BMSVG)'),
|
||||
('manuell', 'Sonstiger nachgewiesener Anspruch')],
|
||||
string='AT Abfertigung Alt — Anspruchsart', copy=False)
|
||||
l10n_at_abf_alt_dienstzeit_monate = fields.Integer(
|
||||
string='AT Abfertigung Alt — Dienstzeitmonate', copy=False)
|
||||
l10n_at_abf_alt_monatsentgelt = fields.Float(
|
||||
string='AT Abfertigung Alt — Monatsentgelt', copy=False,
|
||||
help='Bestätigte arbeitsrechtliche Bemessungsgrundlage inklusive '
|
||||
'regelmäßiger Bezugsbestandteile und aliquoter Sonderzahlungen.')
|
||||
l10n_at_abf_alt_laufender_arbeitslohn = fields.Float(
|
||||
string='AT Abfertigung Alt — laufender Arbeitslohn', copy=False,
|
||||
help='Laufender Monatsarbeitslohn für den steuerlichen '
|
||||
'Vervielfacher nach § 67 Abs 3 EStG.')
|
||||
l10n_at_abf_alt_monatslohnsteuer = fields.Float(
|
||||
string='AT Abfertigung Alt — tarifmäßige Monatslohnsteuer',
|
||||
copy=False,
|
||||
help='Auf den maßgeblichen laufenden Monatsarbeitslohn entfallende '
|
||||
'tarifmäßige Lohnsteuer nach den persönlichen Verhältnissen.')
|
||||
l10n_at_abf_alt_mehrfach = fields.Float(
|
||||
string='AT Abfertigung Alt — Anspruchsfaktor', copy=False)
|
||||
l10n_at_abf_alt_nachweis = fields.Text(
|
||||
string='AT Abfertigung Alt — Nachweis/Begründung', copy=False)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# SV-Tage-Umrechnung und Verlängerungsmonate (§ 11 Abs 2 ASVG)
|
||||
# ------------------------------------------------------------------
|
||||
@@ -135,6 +163,75 @@ class HrPayslip(models.Model):
|
||||
lambda i: i.input_type_id.code == code)
|
||||
return linie[:1].amount or 0.0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Abfertigung Alt — §§ 23/23a AngG, § 67 Abs 3 EStG
|
||||
# ------------------------------------------------------------------
|
||||
def _at_abf_alt_staffel(self, dienstzeit_monate=None):
|
||||
"""Arbeitsrechtlicher Grundfaktor nach § 23 Abs 1 AngG."""
|
||||
self.ensure_one()
|
||||
monate = dienstzeit_monate
|
||||
if monate is None:
|
||||
monate = self.l10n_at_abf_alt_dienstzeit_monate
|
||||
staffel = self._l10n_at_param('at_endabrechnung').get(
|
||||
'abfertigung_alt_staffel', [])
|
||||
mehrfach = 0.0
|
||||
for grenze, faktor in staffel:
|
||||
if monate >= int(grenze):
|
||||
mehrfach = float(faktor)
|
||||
return mehrfach
|
||||
|
||||
def _at_abf_alt_mehrfach_vorschlag(self, dienstzeit_monate=None,
|
||||
anspruchsart=None):
|
||||
"""§-23-Faktor mit Halb-/Deckelregeln für Elternaustritt/Tod."""
|
||||
self.ensure_one()
|
||||
art = anspruchsart or self.l10n_at_abf_alt_anspruchsart
|
||||
monate = dienstzeit_monate
|
||||
if monate is None:
|
||||
monate = self.l10n_at_abf_alt_dienstzeit_monate
|
||||
mehrfach = self._at_abf_alt_staffel(monate)
|
||||
if art == 'eltern':
|
||||
if monate < 60:
|
||||
return 0.0
|
||||
return min(mehrfach / 2.0, 3.0) if mehrfach else 0.0
|
||||
if art == 'tod':
|
||||
return mehrfach / 2.0
|
||||
return mehrfach
|
||||
|
||||
def _at_abf_alt_betrag(self, result_rules=None):
|
||||
"""Gesetzliche Alt-Abfertigung als eigene Bezugszeile."""
|
||||
self.ensure_one()
|
||||
return self._at_input_amount('ATP_ABF_ALT')
|
||||
|
||||
def _at_abf_alt_steuer_6(self, result_rules=None):
|
||||
self.ensure_one()
|
||||
satz = float(self._l10n_at_param(
|
||||
'at_endabrechnung')['abfertigung_steuersatz'])
|
||||
return max(self._at_abf_alt_betrag(result_rules), 0.0) * satz / 100.0
|
||||
|
||||
def _at_abf_alt_steuer_vervielfachung(self, result_rules=None):
|
||||
"""§ 67 Abs 3: Monats-Tarif-LSt × steuerlicher Vervielfacher.
|
||||
|
||||
Der steuerliche Vervielfacher ist Abfertigungsbetrag dividiert
|
||||
durch den laufenden Arbeitslohn. Er kann vom arbeitsrechtlichen
|
||||
Staffel-Faktor abweichen, weil die Abfertigungsbasis etwa aliquote
|
||||
Sonderzahlungen enthält (lb-end-01, RF 21 Dienstjahre: 10,5 statt 9).
|
||||
"""
|
||||
self.ensure_one()
|
||||
betrag = self._at_abf_alt_betrag(result_rules)
|
||||
laufend = self.l10n_at_abf_alt_laufender_arbeitslohn
|
||||
if betrag <= 0 or laufend <= 0:
|
||||
return self._at_abf_alt_steuer_6(result_rules)
|
||||
vervielfacher = betrag / laufend
|
||||
return self.l10n_at_abf_alt_monatslohnsteuer * vervielfacher
|
||||
|
||||
def _at_abf_alt_lohnsteuer(self, result_rules=None):
|
||||
"""Zwingend günstigere Steuer aus Vervielfachung und 6 %."""
|
||||
self.ensure_one()
|
||||
if self._at_abf_alt_betrag(result_rules) <= 0:
|
||||
return 0.0
|
||||
return min(self._at_abf_alt_steuer_vervielfachung(result_rules),
|
||||
self._at_abf_alt_steuer_6(result_rules))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Urlaubsersatzleistung — § 67 Abs 8 lit d, lb-url-15/16
|
||||
# ------------------------------------------------------------------
|
||||
@@ -238,7 +335,8 @@ class HrPayslip(models.Model):
|
||||
+ self._l10n_at_brutto('sonstig', result_rules)
|
||||
- self._l10n_at_st_frei_freie_laufen(result_rules))
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if rule.l10n_at_s67_abs in ('abs3', 'abs6'):
|
||||
if (rule.l10n_at_bemessung in ('laufend', 'sonstig')
|
||||
and rule.l10n_at_s67_abs in ('abs3', 'abs6')):
|
||||
if result_rules is not None and rule.code in result_rules:
|
||||
basis -= result_rules[rule.code]['total']
|
||||
elif result_rules is None:
|
||||
|
||||
@@ -351,7 +351,8 @@ class HrPayslip(models.Model):
|
||||
+ self._l10n_at_brutto('sonstig', result_rules)
|
||||
- self._l10n_at_st_frei_freie_laufen(result_rules))
|
||||
for rule in self.struct_id.rule_ids:
|
||||
if rule.l10n_at_s67_abs in ('abs3', 'abs6'):
|
||||
if (rule.l10n_at_bemessung in ('laufend', 'sonstig')
|
||||
and rule.l10n_at_s67_abs in ('abs3', 'abs6')):
|
||||
if result_rules is not None and rule.code in result_rules:
|
||||
basis -= result_rules[rule.code]['total']
|
||||
elif result_rules is None:
|
||||
|
||||
@@ -322,7 +322,7 @@ class HrPayslip(models.Model):
|
||||
# ------------------------------------------------------------------
|
||||
def _at_pfa_rule_amount(self, result_rules, code):
|
||||
if result_rules is not None:
|
||||
werte = result_rules.get(code)
|
||||
werte = result_rules[code] if code in result_rules else None
|
||||
# result_rules enthält ungerundete Rule-Summen; die
|
||||
# Bezugsart ist der gerundete Zeilenbetrag (Cents) —
|
||||
# mit diesem wird auch die Pfändungsberechnungsgrundlage
|
||||
@@ -378,14 +378,16 @@ class HrPayslip(models.Model):
|
||||
if not rule.l10n_at_pfandung_beendigung:
|
||||
continue
|
||||
if result_rules is not None:
|
||||
werte = result_rules.get(rule.code)
|
||||
werte = result_rules[rule.code] \
|
||||
if rule.code in result_rules else None
|
||||
if werte:
|
||||
total += float(werte['total'])
|
||||
else:
|
||||
linie = self.line_ids.filtered(
|
||||
lambda l, c=rule.code: l.code == c)
|
||||
total += sum(linie.mapped('total'))
|
||||
return round(max(0.0, total), 2)
|
||||
steuer = self._at_pfa_rule_amount(result_rules, 'LST_ABF')
|
||||
return round(max(0.0, total - steuer), 2)
|
||||
raise UserError('Unbekannter Pfändungstopf: %s' % topf)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -420,6 +422,7 @@ class HrPayslip(models.Model):
|
||||
total = self._at_pfaendung_masse(result_rules, 'laufend')
|
||||
total += self._at_pfaendung_masse(result_rules, 'sz13')
|
||||
total += self._at_pfaendung_masse(result_rules, 'sz14')
|
||||
total += self._at_pfaendung_masse(result_rules, 'beendigung')
|
||||
return round(total, 2)
|
||||
|
||||
def _at_pfaendung_kostenersatz(self, result_rules):
|
||||
|
||||
@@ -5,6 +5,7 @@ Abgangsentschädigung (§ 67 Abs 8 lit b Fünftel), Aufrechnung offener
|
||||
Vorschüsse (§ 293 Abs 3 EO) und Zeitguthaben (§ 19e Abs 2 AZG)."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@@ -278,7 +279,8 @@ class EndabrechnungPrivateTest(TransactionCase):
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': emp.id,
|
||||
'austrittsdatum': date(2026, 3, 15),
|
||||
'beendigungsart': 'einvernehmlich',
|
||||
'departure_reason_id': self.env.ref(
|
||||
'l10n_at_hr_payroll.departure_reason_einvernehmlich').id,
|
||||
'uel_tage': 11.0,
|
||||
'abgang_betrag': 9000.0,
|
||||
'abgang_ffrist_tage': 42.0,
|
||||
@@ -296,3 +298,184 @@ class EndabrechnungPrivateTest(TransactionCase):
|
||||
self._total(slip, 'URL_ERSATZ_LFD'), 1320.0, places=2)
|
||||
self.assertAlmostEqual(self._total(slip, 'ABGANG'), 9000.0,
|
||||
places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# AP15-A: gesetzliche Abfertigung Alt (§§ 23/23a, § 67 Abs 3)
|
||||
# ------------------------------------------------------------------
|
||||
def test_07_abfertigung_alt_staffel_und_sonderfaelle(self):
|
||||
slip = self._slip(self._employee())
|
||||
for monate, erwartet in (
|
||||
(35, 0.0), (36, 2.0), (60, 3.0), (120, 4.0),
|
||||
(180, 6.0), (240, 9.0), (300, 12.0)):
|
||||
self.assertEqual(slip._at_abf_alt_staffel(monate), erwartet)
|
||||
self.assertEqual(
|
||||
slip._at_abf_alt_mehrfach_vorschlag(59, 'eltern'), 0.0)
|
||||
self.assertEqual(
|
||||
slip._at_abf_alt_mehrfach_vorschlag(60, 'eltern'), 1.5)
|
||||
self.assertEqual(
|
||||
slip._at_abf_alt_mehrfach_vorschlag(180, 'eltern'), 3.0)
|
||||
self.assertEqual(
|
||||
slip._at_abf_alt_mehrfach_vorschlag(240, 'tod'), 4.5)
|
||||
|
||||
def test_08_abfertigung_alt_vervielfachung_guenstiger(self):
|
||||
"""21 Jahre: 9 Monatsentgelte inklusive 2/12 SZ ergeben den
|
||||
steuerlichen Vervielfacher 10,5; der Tarif ist günstiger als 6 %."""
|
||||
emp_mit = self._employee(ueberzahlung=1800.0)
|
||||
emp_ohne = self._employee(ueberzahlung=1800.0)
|
||||
slip_mit = self._slip(emp_mit)
|
||||
slip_ohne = self._slip(emp_ohne)
|
||||
slip_mit.write({
|
||||
'l10n_at_abf_alt_anspruchsart': 'normal',
|
||||
'l10n_at_abf_alt_dienstzeit_monate': 252,
|
||||
'l10n_at_abf_alt_monatsentgelt': 2100.0,
|
||||
'l10n_at_abf_alt_laufender_arbeitslohn': 1800.0,
|
||||
'l10n_at_abf_alt_monatslohnsteuer': 101.96,
|
||||
'l10n_at_abf_alt_mehrfach': 9.0,
|
||||
'l10n_at_abf_alt_nachweis': 'Testnachweis',
|
||||
})
|
||||
self._input(slip_mit, 'ATP_ABF_ALT', 18900.0)
|
||||
|
||||
erwartet = 101.96 * 10.5
|
||||
self.assertAlmostEqual(self._total(slip_mit, 'ABF_ALT'), 18900.0,
|
||||
places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_abf_alt_steuer_vervielfachung(), erwartet, places=2)
|
||||
self.assertLess(erwartet, 18900.0 * 0.06)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip_mit, 'LST_ABF'),
|
||||
self.env.company.currency_id.round(erwartet), places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sv_basis_roh()
|
||||
- slip_ohne._l10n_at_sv_basis_roh(), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_lnk_basis('flaf')
|
||||
- slip_ohne._at_lnk_basis('flaf'), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_bv_basis_sonst()
|
||||
- slip_ohne._at_bv_basis_sonst(), 0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._l10n_at_sechstel() - slip_ohne._l10n_at_sechstel(),
|
||||
0.0, places=2)
|
||||
self.assertAlmostEqual(
|
||||
slip_mit._at_pfaendung_basis(None, 'beendigung'),
|
||||
18900.0 - self._total(slip_mit, 'LST_ABF'), places=2)
|
||||
|
||||
def test_09_abfertigung_alt_sechs_prozent_guenstiger(self):
|
||||
emp = self._employee(ueberzahlung=4000.0)
|
||||
slip = self._slip(emp)
|
||||
slip.write({
|
||||
'l10n_at_abf_alt_laufender_arbeitslohn': 4000.0,
|
||||
'l10n_at_abf_alt_monatslohnsteuer': 300.0,
|
||||
'l10n_at_abf_alt_mehrfach': 2.0,
|
||||
})
|
||||
self._input(slip, 'ATP_ABF_ALT', 9333.34)
|
||||
|
||||
self.assertGreater(
|
||||
slip._at_abf_alt_steuer_vervielfachung(), 9333.34 * 0.06)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip, 'LST_ABF'), 9333.34 * 0.06, places=2)
|
||||
|
||||
def test_10_abfertigung_alt_wizard(self):
|
||||
emp = self._employee(ueberzahlung=3000.0)
|
||||
version = emp.version_id
|
||||
version.write({
|
||||
'structure_type_id': self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
|
||||
'l10n_at_abfertigung_neu': False,
|
||||
})
|
||||
reason = self.env.ref(
|
||||
'l10n_at_hr_payroll.departure_reason_einvernehmlich')
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': emp.id,
|
||||
'austrittsdatum': date(2026, 3, 15),
|
||||
'departure_reason_id': reason.id,
|
||||
'abf_alt_aktiv': True,
|
||||
'abf_alt_anspruch_bestaetigt': True,
|
||||
'abf_alt_anspruchsart': 'normal',
|
||||
'abf_alt_dienstzeit_monate': 74,
|
||||
'abf_alt_monatsentgelt': 3500.0,
|
||||
'abf_alt_laufender_arbeitslohn': 3000.0,
|
||||
'abf_alt_monatslohnsteuer': 200.0,
|
||||
'abf_alt_mehrfach': 3.0,
|
||||
'abf_alt_nachweis': 'Dienstzeiten und Altstatus geprüft',
|
||||
})
|
||||
ergebnis = wizard.action_payslip()
|
||||
slip = self.env['hr.payslip'].browse(ergebnis['res_id'])
|
||||
|
||||
self.assertAlmostEqual(self._total(slip, 'ABF_ALT'), 10500.0,
|
||||
places=2)
|
||||
self.assertGreater(self._total(slip, 'LST_ABF'), 0.0)
|
||||
self.assertEqual(slip.l10n_at_abf_alt_dienstzeit_monate, 74)
|
||||
self.assertEqual(slip.l10n_at_abf_alt_nachweis,
|
||||
'Dienstzeiten und Altstatus geprüft')
|
||||
self.assertEqual(version.departure_reason_id, reason)
|
||||
|
||||
def test_11_abfertigung_alt_neu_sperre(self):
|
||||
emp = self._employee()
|
||||
emp.version_id.write({
|
||||
'structure_type_id': self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
|
||||
'l10n_at_abfertigung_neu': True,
|
||||
})
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': emp.id,
|
||||
'austrittsdatum': date(2026, 3, 15),
|
||||
'departure_reason_id': self.env.ref(
|
||||
'l10n_at_hr_payroll.departure_reason_einvernehmlich').id,
|
||||
'abf_alt_aktiv': True,
|
||||
'abf_alt_anspruch_bestaetigt': True,
|
||||
'abf_alt_anspruchsart': 'normal',
|
||||
'abf_alt_dienstzeit_monate': 74,
|
||||
'abf_alt_monatsentgelt': 3000.0,
|
||||
'abf_alt_laufender_arbeitslohn': 2640.0,
|
||||
'abf_alt_monatslohnsteuer': 150.0,
|
||||
'abf_alt_mehrfach': 3.0,
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
wizard.action_payslip()
|
||||
|
||||
def test_12_abfertigung_alt_arbeitnehmerkuendigung_ohne_sonderfall(self):
|
||||
emp = self._employee()
|
||||
emp.version_id.write({
|
||||
'structure_type_id': self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
|
||||
'l10n_at_abfertigung_neu': False,
|
||||
})
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': emp.id,
|
||||
'austrittsdatum': date(2026, 3, 15),
|
||||
'departure_reason_id': self.env.ref('hr.departure_resigned').id,
|
||||
'abf_alt_aktiv': True,
|
||||
'abf_alt_anspruch_bestaetigt': True,
|
||||
'abf_alt_anspruchsart': 'normal',
|
||||
'abf_alt_dienstzeit_monate': 74,
|
||||
'abf_alt_monatsentgelt': 3000.0,
|
||||
'abf_alt_laufender_arbeitslohn': 2640.0,
|
||||
'abf_alt_monatslohnsteuer': 150.0,
|
||||
'abf_alt_mehrfach': 3.0,
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
wizard.action_payslip()
|
||||
|
||||
def test_13_abfertigung_alt_elternaustritt_unter_fuenf_jahren(self):
|
||||
emp = self._employee()
|
||||
emp.version_id.write({
|
||||
'structure_type_id': self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_type_at_privat').id,
|
||||
'l10n_at_abfertigung_neu': False,
|
||||
})
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': emp.id,
|
||||
'austrittsdatum': date(2026, 3, 15),
|
||||
'departure_reason_id': self.env.ref('hr.departure_resigned').id,
|
||||
'abf_alt_aktiv': True,
|
||||
'abf_alt_anspruch_bestaetigt': True,
|
||||
'abf_alt_anspruchsart': 'eltern',
|
||||
'abf_alt_dienstzeit_monate': 59,
|
||||
'abf_alt_monatsentgelt': 3000.0,
|
||||
'abf_alt_laufender_arbeitslohn': 2640.0,
|
||||
'abf_alt_monatslohnsteuer': 150.0,
|
||||
'abf_alt_mehrfach': 1.0,
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
wizard.action_payslip()
|
||||
|
||||
@@ -185,6 +185,30 @@ class PfaendungPrivateTest(TransactionCase):
|
||||
self.assertAlmostEqual(self._total(slip, 'PFAENDUNG'),
|
||||
pfaendbar, places=2)
|
||||
|
||||
def test_09b_pfaendung_regel_beendigung(self):
|
||||
"""AP15-A: Netto-Abfertigung läuft über den eigenen §-291d-Topf."""
|
||||
employee = self._employee(datetime.date(2020, 1, 1), unterhalt=0)
|
||||
self._pfaendung(employee, datetime.date(2026, 2, 1))
|
||||
slip = self._slip(employee, 3)
|
||||
slip.write({
|
||||
'l10n_at_abf_alt_laufender_arbeitslohn': 3000.0,
|
||||
'l10n_at_abf_alt_monatslohnsteuer': 200.0,
|
||||
'l10n_at_abf_alt_mehrfach': 3.0,
|
||||
})
|
||||
input_type = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.input_type_atp_abf_alt')
|
||||
self.env['hr.payslip.input'].create({
|
||||
'payslip_id': slip.id,
|
||||
'input_type_id': input_type.id,
|
||||
'amount': 10500.0,
|
||||
})
|
||||
slip.compute_sheet()
|
||||
|
||||
masse = slip._at_pfaendung_masse(None, 'beendigung')
|
||||
self.assertGreater(masse, 0.0)
|
||||
self.assertAlmostEqual(
|
||||
self._total(slip, 'PFAENDUNG_END'), masse, places=2)
|
||||
|
||||
def test_10_rang(self):
|
||||
"""Zwei Pfändungen → Rang 1 (früheres Einlangen) und Rang 2."""
|
||||
employee = self._employee(datetime.date(2026, 1, 1))
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<group string="Beendigung">
|
||||
<field name="employee_id"/>
|
||||
<field name="austrittsdatum"/>
|
||||
<field name="beendigungsart"/>
|
||||
<field name="departure_reason_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<group string="Offene Posten">
|
||||
<field name="uel_tage"/>
|
||||
@@ -24,6 +24,26 @@
|
||||
<field name="vorschuss_saldo"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Abfertigung Alt (§§ 23/23a AngG)">
|
||||
<field name="abf_alt_aktiv"/>
|
||||
<group invisible="not abf_alt_aktiv">
|
||||
<field name="abf_alt_anspruchsart"/>
|
||||
<field name="abf_alt_dienstzeit_monate"/>
|
||||
<field name="abf_alt_monatsentgelt"/>
|
||||
<field name="abf_alt_laufender_arbeitslohn"/>
|
||||
<field name="abf_alt_monatslohnsteuer"/>
|
||||
<field name="abf_alt_mehrfach"/>
|
||||
<field name="abf_alt_nachweis" placeholder="Dienstzeitnachweis, Alt-/Neu-Status und Anspruchsbegründung"/>
|
||||
<field name="abf_alt_anspruch_bestaetigt"/>
|
||||
</group>
|
||||
<group invisible="not abf_alt_aktiv">
|
||||
<field name="abf_alt_betrag_vorschau"/>
|
||||
<field name="abf_alt_steuer_vervielfachung_vorschau"/>
|
||||
<field name="abf_alt_steuer_6_vorschau"/>
|
||||
<field name="abf_alt_lohnsteuer_vorschau"/>
|
||||
<field name="abf_alt_netto_vorschau"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Vorschau (§ 10 UrlG, § 11 Abs 2 ASVG)">
|
||||
<group>
|
||||
<field name="uel_lfd_vorschau"/>
|
||||
@@ -44,6 +64,32 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_payslip_view_form_abf_alt" model="ir.ui.view">
|
||||
<field name="name">hr.payslip.form.abfertigung.alt</field>
|
||||
<field name="model">hr.payslip</field>
|
||||
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Abfertigung Alt" name="l10n_at_abfertigung_alt"
|
||||
invisible="not l10n_at_abf_alt_mehrfach">
|
||||
<group>
|
||||
<group>
|
||||
<field name="l10n_at_abf_alt_anspruchsart" readonly="state != 'draft'"/>
|
||||
<field name="l10n_at_abf_alt_dienstzeit_monate" readonly="state != 'draft'"/>
|
||||
<field name="l10n_at_abf_alt_monatsentgelt" readonly="state != 'draft'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="l10n_at_abf_alt_laufender_arbeitslohn" readonly="state != 'draft'"/>
|
||||
<field name="l10n_at_abf_alt_monatslohnsteuer" readonly="state != 'draft'"/>
|
||||
<field name="l10n_at_abf_alt_mehrfach" readonly="state != 'draft'"/>
|
||||
<field name="l10n_at_abf_alt_nachweis" readonly="state != 'draft'"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_endabrechnung_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Endabrechnung (Beendigung)</field>
|
||||
<field name="res_model">l10n.at.payroll.endabrechnung.wizard</field>
|
||||
|
||||
@@ -7,7 +7,10 @@ Sammel-/Anlageprozess; die BV-Anwartschaft läuft über den eigenen
|
||||
BV-Wizard (GP5), die Pfändung über die AP11-Maschine."""
|
||||
from datetime import timedelta
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
@@ -18,20 +21,11 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
'hr.employee', string='Dienstnehmer:in', required=True)
|
||||
austrittsdatum = fields.Date(
|
||||
string='Austritt (Ende des Arbeitsverhältnisses)', required=True)
|
||||
beendigungsart = fields.Selection(
|
||||
[('ag_kuendigung', 'Kündigung durch den Arbeitgeber'),
|
||||
('an_kuendigung', 'Kündigung durch den Dienstnehmer'),
|
||||
('einvernehmlich', 'Einvernehmliche Auflösung'),
|
||||
('berechtigter_austritt', 'Berechtigter vorzeitiger Austritt'),
|
||||
('unberechtigter_austritt',
|
||||
'Unberechtigter vorzeitiger Austritt '
|
||||
'(keine UEL für die 5./6. Woche, § 10 Abs 2 UrlG; '
|
||||
'kein Zeitguthaben-Zuschlag, § 19e Abs 2 AZG)'),
|
||||
('verschuldete_entlassung', 'Verschuldete Entlassung'),
|
||||
('unverschuldete_entlassung', 'Unverschuldete Entlassung'),
|
||||
('befristung', 'Zeitablauf (befristetes Dienstverhältnis)'),
|
||||
('tod', 'Tod (UEL an die Erben, § 10 Abs 5 UrlG)')],
|
||||
string='Beendigungsart', required=True)
|
||||
departure_reason_id = fields.Many2one(
|
||||
'hr.departure.reason', string='Abgangsgrund', required=True,
|
||||
domain="['|', ('country_id', '=', False), ('country_id.code', '=', 'AT')]",
|
||||
help='Odoo-Abgangsgrund des Beschäftigtendatensatzes; die '
|
||||
'Abfertigungsberechtigung wird zusätzlich ausdrücklich bestätigt.')
|
||||
uel_tage = fields.Float(
|
||||
string='Offene Urlaubstage (Arbeitstage)',
|
||||
help='Resturlaub früherer Urlaubsjahre zur Gänze plus aliquoter '
|
||||
@@ -68,6 +62,45 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
'Vorschusssaldo (lb-end-06) — Rückverrechnung vom Netto '
|
||||
'nach SV und Lohnsteuer, ohne Abgabenberührung '
|
||||
'(§ 293 Abs 3 EO).')
|
||||
abf_alt_aktiv = fields.Boolean(string='Gesetzliche Abfertigung Alt')
|
||||
abf_alt_anspruch_bestaetigt = fields.Boolean(
|
||||
string='Anspruch und Nachweise geprüft',
|
||||
help='Bestätigung, dass Alt-Anwartschaft, Beendigungsgrund, '
|
||||
'Dienstzeiten und Bemessungsgrundlage geprüft wurden.')
|
||||
abf_alt_anspruchsart = fields.Selection(
|
||||
[('normal', 'Normalanspruch (§ 23 AngG)'),
|
||||
('eltern', 'Elternaustritt (§ 23a Abs 3/4 AngG)'),
|
||||
('tod', 'Tod (§ 23 Abs 6 AngG)'),
|
||||
('pension', 'Pension/Invalidität (§ 23a Abs 1 AngG)'),
|
||||
('eingefroren', 'Eingefrorener Altanspruch (§ 47 BMSVG)'),
|
||||
('manuell', 'Sonstiger nachgewiesener Anspruch')],
|
||||
string='Anspruchsart', default='normal')
|
||||
abf_alt_dienstzeit_monate = fields.Integer(
|
||||
string='Nachgewiesene Abfertigungsdienstzeit (Monate)')
|
||||
abf_alt_monatsentgelt = fields.Monetary(
|
||||
string='Maßgebliches Monatsentgelt', currency_field='currency_id',
|
||||
help='Bestätigte arbeitsrechtliche Bemessungsgrundlage: letzter '
|
||||
'Monat einschließlich regelmäßiger Bezugsteile, Sachbezüge '
|
||||
'und aliquoter Sonderzahlungen.')
|
||||
abf_alt_laufender_arbeitslohn = fields.Monetary(
|
||||
string='Laufender Monatsarbeitslohn (§ 67 Abs 3)',
|
||||
currency_field='currency_id',
|
||||
help='Laufender Arbeitslohn für den steuerlichen Vervielfacher; '
|
||||
'aliquote Sonderzahlungen gehören hier nicht dazu.')
|
||||
abf_alt_monatslohnsteuer = fields.Monetary(
|
||||
string='Tarif-LSt des laufenden Monats', currency_field='currency_id',
|
||||
help='Die auf den maßgeblichen laufenden Monatsarbeitslohn '
|
||||
'entfallende tarifmäßige Lohnsteuer nach den persönlichen '
|
||||
'Verhältnissen. Bei steuerfreiem Monatslohn ausdrücklich 0 '
|
||||
'bestätigen.')
|
||||
abf_alt_mehrfach = fields.Float(
|
||||
string='Bestätigter Anspruchsfaktor',
|
||||
help='§-23-Staffel, gegebenenfalls halbiert/gedeckelt oder als '
|
||||
'eingefrorener beziehungsweise nachgewiesener Faktor.')
|
||||
abf_alt_nachweis = fields.Text(
|
||||
string='Nachweis/Begründung',
|
||||
help='Quelle der Dienstzeiten, Anspruchsprüfung, Alt-/Neu-Status '
|
||||
'und allfällige manuelle Abweichung dokumentieren.')
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', default=lambda self: self.env.company.currency_id)
|
||||
|
||||
@@ -80,21 +113,76 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
sv_tage_vorschau = fields.Integer(
|
||||
string='SV-Verlängerungstage (§ 11 Abs 2)',
|
||||
compute='_compute_vorschau')
|
||||
abf_alt_betrag_vorschau = fields.Monetary(
|
||||
string='Abfertigung Alt brutto', currency_field='currency_id',
|
||||
compute='_compute_vorschau')
|
||||
abf_alt_steuer_vervielfachung_vorschau = fields.Monetary(
|
||||
string='LSt Vervielfachungsmethode', currency_field='currency_id',
|
||||
compute='_compute_vorschau')
|
||||
abf_alt_steuer_6_vorschau = fields.Monetary(
|
||||
string='LSt 6 %', currency_field='currency_id',
|
||||
compute='_compute_vorschau')
|
||||
abf_alt_lohnsteuer_vorschau = fields.Monetary(
|
||||
string='Angewendete günstigere LSt', currency_field='currency_id',
|
||||
compute='_compute_vorschau')
|
||||
abf_alt_netto_vorschau = fields.Monetary(
|
||||
string='Abfertigung Alt netto', currency_field='currency_id',
|
||||
compute='_compute_vorschau')
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee(self):
|
||||
for wizard in self:
|
||||
if wizard.employee_id:
|
||||
if not wizard.employee_id:
|
||||
continue
|
||||
version = wizard.employee_id.version_id
|
||||
wizard.austrittsdatum = (
|
||||
wizard.employee_id.version_id.contract_date_end
|
||||
or fields.Date.today())
|
||||
version.contract_date_end or fields.Date.today())
|
||||
wizard.departure_reason_id = version.departure_reason_id
|
||||
wizard._at_abf_alt_vorschlag_setzen()
|
||||
|
||||
@api.depends('employee_id', 'austrittsdatum', 'uel_tage')
|
||||
@api.onchange('austrittsdatum', 'abf_alt_anspruchsart',
|
||||
'abf_alt_dienstzeit_monate')
|
||||
def _onchange_abf_alt(self):
|
||||
for wizard in self:
|
||||
wizard._at_abf_alt_vorschlag_setzen()
|
||||
|
||||
def _at_abf_alt_vorschlag_setzen(self):
|
||||
self.ensure_one()
|
||||
if not self.employee_id or not self.austrittsdatum:
|
||||
return
|
||||
version = self.employee_id.version_id
|
||||
start = version.contract_date_start
|
||||
if start and not self.abf_alt_dienstzeit_monate:
|
||||
dauer = relativedelta(
|
||||
self.austrittsdatum + timedelta(days=1), start)
|
||||
self.abf_alt_dienstzeit_monate = dauer.years * 12 + dauer.months
|
||||
slip = self._vorschau_payslip()
|
||||
if not self.abf_alt_laufender_arbeitslohn:
|
||||
self.abf_alt_laufender_arbeitslohn = slip._at_kv_gehalt_basis()
|
||||
if not self.abf_alt_monatsentgelt:
|
||||
self.abf_alt_monatsentgelt = (
|
||||
self.abf_alt_laufender_arbeitslohn
|
||||
+ 2.0 * slip._at_sz_basis(self.austrittsdatum) / 12.0)
|
||||
vorgeschlagen = slip._at_abf_alt_mehrfach_vorschlag(
|
||||
self.abf_alt_dienstzeit_monate, self.abf_alt_anspruchsart)
|
||||
if vorgeschlagen or not self.abf_alt_mehrfach:
|
||||
self.abf_alt_mehrfach = vorgeschlagen
|
||||
|
||||
@api.depends('employee_id', 'austrittsdatum', 'uel_tage',
|
||||
'abf_alt_aktiv', 'abf_alt_monatsentgelt',
|
||||
'abf_alt_laufender_arbeitslohn', 'abf_alt_monatslohnsteuer',
|
||||
'abf_alt_mehrfach',
|
||||
'abf_alt_dienstzeit_monate', 'abf_alt_anspruchsart')
|
||||
def _compute_vorschau(self):
|
||||
for wizard in self:
|
||||
wizard.uel_lfd_vorschau = 0.0
|
||||
wizard.uel_sz_vorschau = 0.0
|
||||
wizard.sv_tage_vorschau = 0
|
||||
wizard.abf_alt_betrag_vorschau = 0.0
|
||||
wizard.abf_alt_steuer_vervielfachung_vorschau = 0.0
|
||||
wizard.abf_alt_steuer_6_vorschau = 0.0
|
||||
wizard.abf_alt_lohnsteuer_vorschau = 0.0
|
||||
wizard.abf_alt_netto_vorschau = 0.0
|
||||
if not wizard.employee_id or not wizard.austrittsdatum:
|
||||
continue
|
||||
slip = wizard._vorschau_payslip()
|
||||
@@ -102,6 +190,18 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
wizard.uel_sz_vorschau = slip._at_uel_sz()
|
||||
wizard.sv_tage_vorschau = slip._at_end_sv_tage(
|
||||
wizard.uel_tage)
|
||||
if wizard.abf_alt_aktiv:
|
||||
betrag = slip._at_abf_alt_betrag()
|
||||
steuer_vervielfachung = \
|
||||
slip._at_abf_alt_steuer_vervielfachung()
|
||||
steuer_6 = slip._at_abf_alt_steuer_6()
|
||||
steuer = min(steuer_vervielfachung, steuer_6)
|
||||
wizard.abf_alt_betrag_vorschau = betrag
|
||||
wizard.abf_alt_steuer_vervielfachung_vorschau = \
|
||||
steuer_vervielfachung
|
||||
wizard.abf_alt_steuer_6_vorschau = steuer_6
|
||||
wizard.abf_alt_lohnsteuer_vorschau = steuer
|
||||
wizard.abf_alt_netto_vorschau = betrag - steuer
|
||||
|
||||
def _vorschau_payslip(self):
|
||||
"""Flüchtiger Payslip-Kontext für die Vorschau-Berechnung (kein
|
||||
@@ -119,8 +219,22 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
'struct_id': struct.id,
|
||||
'date_from': erster,
|
||||
'date_to': letzter,
|
||||
'l10n_at_abf_alt_anspruchsart': self.abf_alt_anspruchsart,
|
||||
'l10n_at_abf_alt_dienstzeit_monate':
|
||||
self.abf_alt_dienstzeit_monate,
|
||||
'l10n_at_abf_alt_monatsentgelt': self.abf_alt_monatsentgelt,
|
||||
'l10n_at_abf_alt_laufender_arbeitslohn':
|
||||
self.abf_alt_laufender_arbeitslohn,
|
||||
'l10n_at_abf_alt_monatslohnsteuer':
|
||||
self.abf_alt_monatslohnsteuer,
|
||||
'l10n_at_abf_alt_mehrfach': self.abf_alt_mehrfach,
|
||||
'l10n_at_abf_alt_nachweis': self.abf_alt_nachweis,
|
||||
})
|
||||
eingaben = [('ATP_UEL_TAGE', self.uel_tage)]
|
||||
eingaben = [
|
||||
('ATP_UEL_TAGE', self.uel_tage),
|
||||
('ATP_ABF_ALT', self.abf_alt_monatsentgelt
|
||||
* self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0),
|
||||
]
|
||||
slip.input_line_ids = [
|
||||
(0, 0, {
|
||||
'input_type_id': self.env['hr.payslip.input.type']
|
||||
@@ -140,16 +254,65 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
('ATP_ZEIT_GUTHABEN', self.zeit_guthaben_stunden),
|
||||
('ATP_ZEIT_SCHULDEN', self.zeit_schulden_stunden),
|
||||
('ATP_ABRECHNUNG_VORSCHUSS', self.vorschuss_saldo),
|
||||
('ATP_ABF_ALT', self.abf_alt_monatsentgelt
|
||||
* self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0),
|
||||
]
|
||||
|
||||
def _abf_alt_validieren(self):
|
||||
self.ensure_one()
|
||||
if not self.abf_alt_aktiv:
|
||||
return
|
||||
if not self.abf_alt_anspruch_bestaetigt:
|
||||
raise UserError(_('Bitte Anspruch und Nachweise der '
|
||||
'Abfertigung Alt bestätigen.'))
|
||||
if self.employee_id.version_id.l10n_at_abfertigung_neu \
|
||||
and self.abf_alt_anspruchsart != 'eingefroren':
|
||||
raise UserError(_(
|
||||
'Das Dienstverhältnis ist als Abfertigung Neu markiert. '
|
||||
'Eine Alt-Abfertigung ist nur als nachgewiesener '
|
||||
'eingefrorener Anspruch zulässig.'))
|
||||
if self.abf_alt_anspruchsart == 'eltern' \
|
||||
and self.abf_alt_dienstzeit_monate < 60:
|
||||
raise UserError(_(
|
||||
'Ein Elternaustritt nach § 23a Abs. 3/4 AngG setzt ein '
|
||||
'ununterbrochenes Dienstverhältnis von fünf Jahren voraus.'))
|
||||
if (self.abf_alt_dienstzeit_monate <= 0
|
||||
or self.abf_alt_monatsentgelt <= 0
|
||||
or self.abf_alt_laufender_arbeitslohn <= 0
|
||||
or self.abf_alt_monatslohnsteuer < 0
|
||||
or self.abf_alt_mehrfach <= 0):
|
||||
raise UserError(_('Dienstzeit, Monatsentgelt, laufender '
|
||||
'Arbeitslohn und Anspruchsfaktor müssen '
|
||||
'größer als null sein; die Tarif-LSt darf '
|
||||
'nicht negativ sein.'))
|
||||
agrd = self.departure_reason_id.l10n_at_agrd
|
||||
if agrd == '02' and self.abf_alt_anspruchsart not in (
|
||||
'eltern', 'pension', 'manuell'):
|
||||
raise UserError(_(
|
||||
'Eine gewöhnliche Arbeitnehmerkündigung begründet keinen '
|
||||
'Anspruch nach § 23 AngG. Bitte einen §-23a-Sonderfall '
|
||||
'wählen oder den Anspruch manuell nachweisen.'))
|
||||
if agrd == '06' and self.abf_alt_anspruchsart != 'manuell':
|
||||
raise UserError(_(
|
||||
'Der Abgangsgrund „Fristlose Entlassung“ unterscheidet '
|
||||
'nicht zwischen verschuldeter und unverschuldeter '
|
||||
'Entlassung. Bitte den Anspruch manuell bestätigen und '
|
||||
'begründen.'))
|
||||
|
||||
def action_payslip(self):
|
||||
"""Endabrechnungs-Lauf im Austrittsmonat anlegen und die
|
||||
gesammelten offenen Posten als Eingaben vorbefüllen."""
|
||||
self.ensure_one()
|
||||
self._abf_alt_validieren()
|
||||
erster = self.austrittsdatum.replace(day=1)
|
||||
letzter = (erster + timedelta(days=32)).replace(day=1) \
|
||||
- timedelta(days=1)
|
||||
struct = self.employee_id.version_id.structure_id \
|
||||
version = self.employee_id.version_id
|
||||
version.write({
|
||||
'contract_date_end': self.austrittsdatum,
|
||||
'departure_reason_id': self.departure_reason_id.id,
|
||||
})
|
||||
struct = version.structure_id \
|
||||
or self.env.ref('l10n_at_hr_payroll_private.structure_at_privat')
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': _('Endabrechnung %s') % self.employee_id.name,
|
||||
@@ -157,6 +320,21 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
'struct_id': struct.id,
|
||||
'date_from': erster,
|
||||
'date_to': letzter,
|
||||
'l10n_at_abf_alt_anspruchsart': self.abf_alt_anspruchsart
|
||||
if self.abf_alt_aktiv else False,
|
||||
'l10n_at_abf_alt_dienstzeit_monate':
|
||||
self.abf_alt_dienstzeit_monate if self.abf_alt_aktiv else 0,
|
||||
'l10n_at_abf_alt_monatsentgelt':
|
||||
self.abf_alt_monatsentgelt if self.abf_alt_aktiv else 0.0,
|
||||
'l10n_at_abf_alt_laufender_arbeitslohn':
|
||||
self.abf_alt_laufender_arbeitslohn
|
||||
if self.abf_alt_aktiv else 0.0,
|
||||
'l10n_at_abf_alt_monatslohnsteuer':
|
||||
self.abf_alt_monatslohnsteuer if self.abf_alt_aktiv else 0.0,
|
||||
'l10n_at_abf_alt_mehrfach':
|
||||
self.abf_alt_mehrfach if self.abf_alt_aktiv else 0.0,
|
||||
'l10n_at_abf_alt_nachweis':
|
||||
self.abf_alt_nachweis if self.abf_alt_aktiv else False,
|
||||
})
|
||||
typen = {code: self.env['hr.payslip.input.type'].search(
|
||||
[('code', '=', code)], limit=1)
|
||||
|
||||
Reference in New Issue
Block a user