[IMP] l10n_at_hr_payroll_private: add unpaid care/parental leave family (AP16)
Close the parental/care-leave block after the pilot-client scope release (8.13): six unpaid full-release kinds join the generic parental-leave work entry — Bildungskarenz (AVRAG § 11, two to twelve months by agreement with the AMS-financed Weiterbildungsbeihilfe), Familienhospizkarenz for terminal accompaniment (AVRAG § 14a, three to six months), accompaniment of seriously ill children (§ 14b, five to nine months), children's rehabilitation leave (§ 14e, four weeks per year), Pflegekarenz (§ 14c, one to three months, undivided) and the Papa-Monat (VKG § 1a, one month, creditable toward seniority under § 1a Abs 7 VKG iVm § 15f MSchG while Bildungs-/Pflegekarenz is not). All are unpaid at the employer — the external bodies pay the replacement income (AK, AMS, Bundesamt, KV-Träger) — so the new negative KARENZ_ENTGELT rule stops the employer wage per working day through the shared Tagesatz helper whose exclusion list now covers EFZG, Mutterschutz and Karenz corrections. The Versichertenmeldung run emits M4 deregistrations at the end of the pay entitlement (the day before the release begins) and M3 re-registrations at the return date — omitted when the employment itself ends — with the eSV Karenzart strings logged as protocol information pending the special DM-ORG formats (GP7 track). The fictive BMSVG basis gains the § 7 Abs 4 Z 1–3 bridge: when a new maternity prohibition follows a parental-leave stretch directly or after employment of less than three months, the reference months fall before the preceding prohibition, and a re-founded employment within three months of the ended karenziert employment uses the single last full calendar month; contributions during the KBG period are FLAF-financed (§ 7 Abs 5) and during hospice/care leave Bund-financed (§ 7 Abs 6) — no employer rule, verified by test. Pflegeteilzeit and comparable reduced-hour arrangements pay the BV contribution on the pre-reduction wage via a new version field consumed by the BMSVG basis (§ 6 Abs 4). The endabrechnung wizard gains the § 23 Abs 8 AngG full-time re-rating preview (monthly salary ÷ work_time_rate, confirmable) with § 23a Abs 4a and UEL § 10 Abs 4 UrlG guidance. The Mutterschutz status now detects the § 163 ASVG Sonderwochengeld case (prohibition inside a Karenz — the KV-Träger pays the increased sickness benefit) and warns on § 166 ASVG suspension (full for >50 % continued pay, half at 50 %, activity and Pflegekarenzgeld hints). Verified against the original statute text in .ris/ (AVRAG §§ 11/11a/14a–14e, VKG § 1a, MSchG § 15f, BMSVG § 7 Abs 4–6, ASVG §§ 163/166) and the knowledge-base sources lb-kar/elt/kzs/pap/kbg; the vacation proration of leave stretches (§ 15f Abs 2 MSchG on the hr_holidays allocation side), the eSV special formats for the Karenz art block, the full Sonderwochengeld computation and the leave-overlap policy decision remain documented open points. Version 19.0.17.0.0 (private)
This commit is contained in:
@@ -2,6 +2,45 @@
|
||||
|
||||
## Current focus
|
||||
|
||||
**AP16 Karenz-/Familien-Reste umgesetzt (8.13 freigegeben) — Kern 73/73, Privat 199/199 Tests grün**
|
||||
(2026-09-15, uncommittet bis Nutzerfreigabe; Privat **19.0.17.0.0**):
|
||||
|
||||
- **8.13 komplett freigegeben** (Nutzer, 2026-09-15) — Mutterschutz-/Karenz-
|
||||
Block im R1-Scope; §-Verifikation am Originalwortlaut (AVRAG §§ 11/11a/
|
||||
14a–14e/15a, VKG § 1a, MSchG § 15f, BMSVG § 7 Abs 4–6, ASVG §§ 163/166).
|
||||
KB-Synthesen (3 Subagenten): lb-kar/elt/kzs/pap/kbg — arbeitsrechtliche
|
||||
Kerne + Abrechnungstypologie (alle Voll-Freistellungen entgeltlos,
|
||||
Leistungen extern: AK/AMS/BASB/KV/Gesundheitskasse; elt-SV/LSt/BMSVG-Lücken
|
||||
verifizieren = erledigt über RIS/Bestandsmaschinerie; ⚠ lb-elt-06 §-8/§-8a-
|
||||
VKG-Zuordnung dokumentiert).
|
||||
- **Privat (19.0.17.0.0):** 6 neue WETs/Urlaubsarten (ATP_BILDUNGSKARENZ,
|
||||
ATP_HOSPIZKARENZ, ATP_KINDERKRANK, ATP_KINDERREHAB, ATP_PFLEGEKARENZ,
|
||||
ATP_PAPAMONAT); Regel `KARENZ_ENTGELT` (seq 48, negatives Tagesatz-Muster
|
||||
wie MSCH_ENTGELT, Ausschlüsse im geteilten `_at_efz_tagesatz`); Modell
|
||||
`models/karenz.py` (Status/Tage/Karenzarten-Map); vr.event-An-/Abmeldungen
|
||||
aus Karenz (M4 Tag vor Beginn, M3 Rückkehr, entfällt bei DV-Ende; Karenz-
|
||||
art-Strings im Protokoll, eSV-Sonderformate GP7); fiktive BV mit §-7-Abs-4-
|
||||
**Z-1–3-Brücke** (`_at_msch_referenz_stichtag_und_anzahl` in mutterschutz.py);
|
||||
BV-Vor-Entgelt-Feld `l10n_at_bv_vor_entgelt` (§ 6 Abs 4 BMSVG, Pflegeteilzeit)
|
||||
in `_at_bv_basis_laufen`; §-23-Abs-8-Hochrechnungs-Vorschau im Wizard
|
||||
(`abf_alt_hochrechnung_vorschau`); Sonderwochengeld-Erkennung (§ 163 ASVG)
|
||||
+ Ruhen-Warnungen (§ 166 ASVG) im msch_info.
|
||||
- **Tests:** neu `tests/test_karenz_private.py` 6 (Entgelt-Stopp, VR-Events
|
||||
inkl. Idempotenz + Protokoll, Z-1-Brücke, §-6-Abs-4-BV, § 163/§ 166,
|
||||
Wizard-Hochrechnung); Suite **214/214** (73 Kern + 199 Privat). Test-Details:
|
||||
hr.leave-Quere-Überlappung nur mit `leave_skip_date_check`+`leave_skip_state_check`
|
||||
+ direktem state='validate' umgehbar (Krankheit während Karenz);
|
||||
EFZ-Phasen lesen Leaves, `voll_arbeitstage` Work-Entries — überlappte Tage
|
||||
brauchen direkte hr.work.entry-Records; `hr.leave` hat KEIN
|
||||
work_entry_type_id (nur der Leave-Typ).
|
||||
- **Offen AP16-Reste (docs/rechenfaelle/karenz.md):** Urlaubs-Aliquotierung aus
|
||||
Karenz (§ 15f Abs 2 MSchG, hr_holidays-Allokationsseite), Leave-Überlappungs-
|
||||
Policy (allow_request_on_top/time_type — Mandanten-Entscheid), eSV-Sonderformate
|
||||
(Karenzart-Block, GP7), Sonderwochengeld-Vollrechnung (§ 141/§ 125/§ 108f =
|
||||
Anzeige-Stufe), lb-kar-02-13-Wochen-Über-/§ 2i-AVRAG-Konflikt (dokumentiert).
|
||||
- **Offen gesamt:** AP17 Meldewesen-Reste (L17/§ 109a/§ 109b, Geringfügigen-
|
||||
Jahreszahlung), AP18+ (Gates). Nicht committen ohne Nutzeranforderung.
|
||||
|
||||
**AP14-B Prämien bei Beendigung (§ 16 AngG) umgesetzt — Kern 73/73, Privat 191/191 Tests grün**
|
||||
(2026-09-15, uncommittet bis Nutzerfreigabe; Privat **19.0.16.0.0**):
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
{
|
||||
'name': 'Österreich - Privatwirtschaft Personalverrechnung (General-AT)',
|
||||
'version': '19.0.16.0.0',
|
||||
'version': '19.0.17.0.0',
|
||||
'author': 'Florian Egger',
|
||||
'website': 'https://gem360.at',
|
||||
'category': 'Human Resources/Payroll',
|
||||
@@ -109,6 +109,7 @@ gem360-Brücke).
|
||||
'data/salary_rules_lst_private.xml',
|
||||
'data/salary_rules_efzg_private.xml',
|
||||
'data/salary_rules_msch_private.xml',
|
||||
'data/salary_rules_karenz_private.xml',
|
||||
|
||||
'data/salary_rules_pfaendung_private.xml',
|
||||
'data/salary_rules_sb_private.xml',
|
||||
|
||||
@@ -113,4 +113,117 @@
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_karenz"/>
|
||||
</record>
|
||||
|
||||
<!-- AP16 (8.13): unbezahlte Karenz-Arten — alle entgeltlos
|
||||
(Entgelt-Stopp über KARENZ_ENTGELT), je Art eigene eSV-Events;
|
||||
Dienstzeit: Papamonat anrechnen (§ 1a Abs 7 VKG), Bildungs-/
|
||||
Pflegekarenz nicht (§ 11 Abs 2, § 14c/14d AVRAG). -->
|
||||
<record id="wett_atp_bildungskarenz" model="hr.work.entry.type">
|
||||
<field name="name">Bildungskarenz (§ 11 AVRAG)</field>
|
||||
<field name="color">4</field>
|
||||
<field name="display_code">BLK</field>
|
||||
<field name="code">ATP_BILDUNGSKARENZ</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<record id="wett_atp_hospizkarenz" model="hr.work.entry.type">
|
||||
<field name="name">Familienhospizkarenz — Sterbebegleitung (§ 14a AVRAG)</field>
|
||||
<field name="color">5</field>
|
||||
<field name="display_code">HSP</field>
|
||||
<field name="code">ATP_HOSPIZKARENZ</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<record id="wett_atp_kinderkrank" model="hr.work.entry.type">
|
||||
<field name="name">Begleitung schwerst erkrankter Kinder (§ 14b AVRAG)</field>
|
||||
<field name="color">7</field>
|
||||
<field name="display_code">KKB</field>
|
||||
<field name="code">ATP_KINDERKRANK</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<record id="wett_atp_kinderrehab" model="hr.work.entry.type">
|
||||
<field name="name">Freistellung Kinderrehabilitation (§ 14e AVRAG)</field>
|
||||
<field name="color">10</field>
|
||||
<field name="display_code">KRE</field>
|
||||
<field name="code">ATP_KINDERREHAB</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<record id="wett_atp_pflegekarenz" model="hr.work.entry.type">
|
||||
<field name="name">Pflegekarenz (§ 14c AVRAG)</field>
|
||||
<field name="color">1</field>
|
||||
<field name="display_code">PFG</field>
|
||||
<field name="code">ATP_PFLEGEKARENZ</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<record id="wett_atp_papamonat" model="hr.work.entry.type">
|
||||
<field name="name">Papa-Monat (§ 1a VKG)</field>
|
||||
<field name="color">0</field>
|
||||
<field name="display_code">PAP</field>
|
||||
<field name="code">ATP_PAPAMONAT</field>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="is_leave" eval="True"/>
|
||||
</record>
|
||||
<record id="leave_type_at_bildungskarenz" model="hr.leave.type">
|
||||
<field name="name">Bildungskarenz (§ 11 AVRAG)</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_bildungskarenz"/>
|
||||
</record>
|
||||
<record id="leave_type_at_hospizkarenz" model="hr.leave.type">
|
||||
<field name="name">Familienhospizkarenz — Sterbebegleitung</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_hospizkarenz"/>
|
||||
</record>
|
||||
<record id="leave_type_at_kinderkrank" model="hr.leave.type">
|
||||
<field name="name">Begleitung schwerst erkrankter Kinder</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_kinderkrank"/>
|
||||
</record>
|
||||
<record id="leave_type_at_kinderrehab" model="hr.leave.type">
|
||||
<field name="name">Freistellung Kinderrehabilitation</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_kinderrehab"/>
|
||||
</record>
|
||||
<record id="leave_type_at_pflegekarenz" model="hr.leave.type">
|
||||
<field name="name">Pflegekarenz (§ 14c AVRAG)</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_pflegekarenz"/>
|
||||
</record>
|
||||
<record id="leave_type_at_papamonat" model="hr.leave.type">
|
||||
<field name="name">Papa-Monat (§ 1a VKG)</field>
|
||||
<field name="requires_allocation" eval="False"/>
|
||||
<field name="leave_validation_type">hr</field>
|
||||
<field name="request_unit">day</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
<field name="country_id" ref="base.at"/>
|
||||
<field name="work_entry_type_id"
|
||||
ref="l10n_at_hr_payroll_private.wett_atp_papamonat"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- AP16 (8.13): Karenz-Entgelt-Stopp — entgeltlose Voll- -->
|
||||
<!-- Freistellungen (AVRAG §§ 11/14a/14b/14e/14c, VKG § 1a): -->
|
||||
<!-- das Karenzgeld/Pflegekarenzgeld zahlen AK/Bund (nicht der -->
|
||||
<!-- AG), der Dienstgeber schuldet für die Freistellungs-Tage -->
|
||||
<!-- kein Entgelt. Negative Brutto-Zeile je Arbeitstag aus dem -->
|
||||
<!-- Arbeitseintragssatz (Muster MSCH_ENTGELT, AP7); der Tagesatz -->
|
||||
<!-- teilt die Ausschlüsse mit EFZG/MSCH (_at_efz_tagesatz). -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<record id="rule_karenz_entgelt" model="hr.salary.rule">
|
||||
<field name="name">Karenz-Freistellung (kein Dienstgeber-Entgelt; Leistungen extern)</field>
|
||||
<field name="code">KARENZ_ENTGELT</field>
|
||||
<field name="sequence" eval="48"/>
|
||||
<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"><![CDATA[
|
||||
result = payslip._at_karenz_entgelt_tage() > 0
|
||||
]]></field>
|
||||
<field name="amount_select">code</field>
|
||||
<field name="l10n_at_bemessung">laufend</field>
|
||||
<field name="amount_python_compute"><![CDATA[
|
||||
# Freistellung je Arbeitstag: volle Tages-Entgelt-Reduktion
|
||||
# (Tagesatz teilt die Ausschlüsse mit EFZG/MSCH/KARENZ).
|
||||
result = -payslip._at_efz_tagesatz(result_rules) \
|
||||
* payslip._at_karenz_entgelt_tage()
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,113 @@
|
||||
# Karenz-/Familien-Freistellungen — Rechenfälle AP16 (8.13 freigegeben)
|
||||
|
||||
Karenz-Familie im Privatmodul: unbezahlte Voll-Freistellungen (Eltern-
|
||||
karenz generisch, Bildungs-, Familienhospiz-/Sterbebegleitungs-,
|
||||
Kinderkranken-Begleitungs-, Kinderrehabilitations- und Pflegekarenz,
|
||||
Papa-Monat) mit Entgelt-Stopp, eSV-An-/Abmeldungen, fiktiver BV mit
|
||||
§-7-Abs-4-Brücke, §-6-Abs-4-BV bei Teilzeit, Sonderwochengeld (§ 163
|
||||
ASVG) und Ruhen (§ 166 ASVG). Grundlagen: KB lb-kar-01…06, lb-elt-
|
||||
01…06, lb-kzs-01…07, lb-pap-01/02, lb-kbg-06 (Stand 2026-07) und die
|
||||
verifizierten Originaltexte in `.ris/` (AVRAG, VKG, MSchG, KBGG,
|
||||
FamZeitbG, BMSVG § 7, ASVG §§ 162/163/166).
|
||||
|
||||
## RF-KAR-1 — Entgelt-Stopp je Karenz-Art (test_01)
|
||||
|
||||
Bildungskarenz 1.–15. 11. 2026 (§ 11 AVRAG: 2–12 Monate, teilbar ≥ 2,
|
||||
Rahmenfrist 4 Jahre, kein Rechtsanspruch — Einvernehmen): das
|
||||
Karenzgeld zahlt die **AK** (nicht der AG) — `KARENZ_ENTGELT` stoppt
|
||||
das Dienstgeber-Entgelt je Arbeitstag (negatives Tagesatz-Muster von
|
||||
`MSCH_ENTGELT`, geteilter Tagesatz-Helfer `_at_efz_tagesatz`, der
|
||||
Ausschlüsse mit EFZG/MSCH teilt). 10 AT → −(2.059,41/21) × 10.
|
||||
Gleiches Muster für Hospizkarenz (§ 14a, 3/6 Monate), Begleitung
|
||||
schwerst erkrankter Kinder (§ 14b, 5/9 Monate), Kinderrehabilitation
|
||||
(§ 14e, 4 Wochen/Jahr), Pflegekarenz (§ 14c, 1–3 Monate, unteilbar)
|
||||
und Papa-Monat (§ 1a VKG, 1 Monat).
|
||||
|
||||
## RF-KAR-2 — eSV-An-/Abmeldungen (vr.event)
|
||||
|
||||
- Abmeldung zum **Ende des Entgeltanspruchs** = Tag vor dem Karenz-
|
||||
beginn; Anmeldung zum ersten Arbeitstag nach dem Karenzende —
|
||||
entfällt, wenn das DV mit der Karenz endet (normale Vertrags-
|
||||
Abmeldung).
|
||||
- **Papa-Monat/Familienzeit tagesgenau:** Abmeldung am Tag **vor**
|
||||
Beginn der Familienzeit (§ 11 Abs 3 lit a ASVG — unbezahlter Urlaub
|
||||
hält die Pflichtversicherung nur bis 1 Monat, nicht bei Familien-
|
||||
zeit; lb-pap-01), Anmeldung vor Arbeitsantritt.
|
||||
- Karenzart-Strings („Familienhospizkarenz/Pflegekarenz An-/Abmeldung",
|
||||
lb-kzs-05) laufen als Protokoll-Info; erweiterte eSV-Sonderformate
|
||||
= GP7-Rest. Idempotenz über die Registry (employee, art, adatum).
|
||||
|
||||
## RF-KAR-3 — Fiktive BV mit Karenz-Brücke (§ 7 Abs 4 Z 1–3 BMSVG)
|
||||
|
||||
Mutterschutz-Frist 1.–15. 11. 2026 (Verbot 1) → Elternkarenz
|
||||
16. 11. 2026–28. 2. 2027 → neuerliches Beschäftigungsverbot 1. 3. 2027
|
||||
(Z 1, unmittelbar nach der Karenz): die fiktive BV-Basis des
|
||||
neuerlichen Verbots bemisst sich nach dem **Monatsentgelt des
|
||||
Kalendermonats vor dem Verbot 1** — Referenzmonate Aug/Sep/Okt 2026
|
||||
(statt Dez 2026–Feb 2027, die karenzbedingt leer wären). Z 2
|
||||
(Beschäftigung < 3 Monate zwischen Karenz und Verbot): gleicher
|
||||
Stichtag. Z 3 (neues DV binnen 3 Monaten nach Beendigung des
|
||||
karenzierten DV mit Karenz): der letzte volle Kalendermonat vor dem
|
||||
neuerlichen Verbot als EINZELNER Referenzmonat („volles Monatsentgelt").
|
||||
|
||||
## RF-KAR-4 — BV-Vor-Entgelt bei Pflegeteilzeit (§ 6 Abs 4 BMSVG)
|
||||
|
||||
20-h-Teilzeit (work_time_rate 0,5) mit Vor-Reduzierungs-Entgelt
|
||||
2.059,41 €: die BV-Basis läuft während der Maßnahme auf dem
|
||||
Vor-Reduzierungs-Entgelt (§ 6 Abs 4 BMSVG), nicht auf der reduzierten
|
||||
laufenden Basis (1.029,71 €). Bildungs-/Hospizteilzeit analog über
|
||||
das Feld `l10n_at_bv_vor_entgelt` am Vertrag.
|
||||
|
||||
## RF-KAR-5 — Sonderwochengeld (§ 163 ASVG) und Ruhen (§ 166 ASVG)
|
||||
|
||||
- Mutterschutz-Frist **innerhalb** einer Karenz: kein Wochengeld
|
||||
(§ 162), sondern **Sonderwochengeld** — Zahlung durch die ÖGK
|
||||
(erhöhtes Krankengeld § 141 Abs 2, Bemessungsgrundlage § 125 aus
|
||||
dem Arbeitsverdienst vor dem Ende des letzten Entgeltanspruchs,
|
||||
§-108f-Anpassung); Ausweis im Mutterschutz-Status.
|
||||
- Ruhen (§ 166 Abs 1): Fortbezug > 50 % (Entgeltfortzahlung 100 %-
|
||||
Phase) → Ruhen zur Gänze, = 50 % (Halbphase) → hälftig; Zeiten der
|
||||
Urlaubsersatzleistung/UEL/KE gelten nicht als weitergeleistete
|
||||
Bezüge; Z 3 Erwerbstätigkeit (in Höhe des Einkommens) und Z 4
|
||||
Pflegekarenzgeld (in dessen Höhe) = Bezugszeitraum-Daten der ÖGK
|
||||
(Hinweis).
|
||||
|
||||
## RF-KAR-6 — Elternteilzeit: Entgelt und Beendigungs-Basen
|
||||
|
||||
- Entgelt: letzter voller Lohn/Gehalt, aliquotiert nach der
|
||||
herabgesetzten Wochenstundenzahl (lb-elt-02) — maschinell über
|
||||
`work_time_rate` der Version (Versionskalender ÷ Firmenzalender).
|
||||
- SZ-Mischberechnung (§ 15j Abs 7 MSchG / § 8b Abs 7 VKG): B13/B14
|
||||
bemessen je Monat aus dem jeweiligen (reduzierten) Monatsentgelt ×
|
||||
Dienstzeitfaktor — VZ-/TZ-Anteile im Kalenderjahr ergeben sich
|
||||
automatisch.
|
||||
- Endabrechnungs-Wizard: §-23-Abs-8-Hochrechnung (DV während
|
||||
MSchG-/VKG-Teilzeit: frühere Normalarbeitszeit = Monatsentgelt ÷
|
||||
work_time_rate, Vorschau); für Arbeitnehmerkündigung § 23a Abs 4a
|
||||
(5-Jahres-Ø der geleisteten Arbeitszeit ohne Karenz, manuell);
|
||||
UEL § 10 Abs 4 UrlG (überwiegende Arbeitszeit des Urlaubsjahres).
|
||||
Dienstzeitanrechnung: Karenzzeiten je Kind in vollem Umfang
|
||||
(§ 15f Abs 1 MSchG, Geburten ab 1. 8. 2019, max. 22./24. Lebens-
|
||||
monat); Papa-Monat anrechnen (§ 1a Abs 7 VKG).
|
||||
|
||||
## Offene Punkte (AP16-Reste)
|
||||
|
||||
- **Urlaubs-Aliquotierung aus Karenz** (§ 15f Abs 2 MSchG: 30 WT/25 AT
|
||||
: 365 × aktive KT, Teiltage aufrunden) greift in die hr_holidays-
|
||||
Allokationsseite — eigener Folgeschritt; Urlaubsverjährung
|
||||
verlängert sich um die Karenz (§ 4 Abs 5 UrlG).
|
||||
- **Krankheit während Karenz/Mutterschutz** ist als gleichzeitige
|
||||
Abwesenheit in hr.leave nur mit einer Überlappungs-Policy-
|
||||
Entscheidung umsetzbar (Odoo sperrt Quere-Überlappungen;
|
||||
`allow_request_on_top` erfordert time_type „other") —
|
||||
Mandanten-Entscheid; der Test umgeht mit dem offiziellen
|
||||
leave_skip_date_check-Bypass.
|
||||
- Fristen der AG↔AN-Kommunikation (3-/2-Monats-Bekanntgaben,
|
||||
5/10-Arbeitstage, Begründungspflichten 5 Kalendertage) = Help-Texte;
|
||||
keine Fristen-Automatik.
|
||||
- eSV-Sonderformate (Karenzart-Block „Familienhospizkarenz/Pflege-
|
||||
karenz") und mBGM-Feinschliff = GP7; Sonderwochengeld-Vollrechnung
|
||||
(§ 141 Abs 2/§ 125/§ 108f) = Anzeige-Stufe, kein Eigenrechenpfad.
|
||||
- lb-kar-02: Folgen der 13-Wochen-Überschreitung + § 2i-AVRAG-Konflikt
|
||||
und lb-elt-06 §-8-/§-8a-VKG-Zuordnung — keine Implementierung auf
|
||||
kontroversen Literaturansichten (dokumentiert ⚠).
|
||||
@@ -14,6 +14,7 @@ from . import buchungsbeleg
|
||||
from . import wien_dag
|
||||
from . import efzg
|
||||
from . import mutterschutz
|
||||
from . import karenz
|
||||
from . import aeb
|
||||
from . import pfaendung
|
||||
from . import pfaendung_report
|
||||
|
||||
@@ -206,12 +206,18 @@ class HrPayslip(models.Model):
|
||||
Erstfenster — 30-Tage-Monat (KB-Beispiel 4.500/30 × 17 =
|
||||
2.550); Teilmonate über die Lohnsteuertage, um Doppelabzüge
|
||||
zu vermeiden (das Entgelt ist für die Teilzeit bereits
|
||||
aliquotiert)."""
|
||||
aliquotiert). AP16 (§ 6 Abs 4 BMSVG): während einer
|
||||
Arbeitszeit-Reduzierung mit gesetztem Feld
|
||||
l10n_at_bv_vor_entgelt (z. B. Pflegeteilzeit § 14d AVRAG)
|
||||
läuft der Beitrag auf dem Vor-Reduzierungs-Entgelt."""
|
||||
self.ensure_one()
|
||||
pflichtig_ab, kurz_av = self._at_bv_fenster()
|
||||
if kurz_av:
|
||||
return 0.0
|
||||
vor_entgelt = self.version_id.l10n_at_bv_vor_entgelt
|
||||
basis = self._l10n_at_sv_basis_roh(result_rules)
|
||||
if vor_entgelt:
|
||||
basis = max(basis, float(vor_entgelt))
|
||||
if pflichtig_ab is None or pflichtig_ab <= self.date_from:
|
||||
# AP4: fiktive BG während des Krankengeldbezugs (§ 7 Abs 3
|
||||
# BMSVG) und des Wochengeldbezugs (§ 7 Abs 4, AP7) — nur
|
||||
|
||||
@@ -275,7 +275,7 @@ class HrPayslip(models.Model):
|
||||
self.ensure_one()
|
||||
basis = self._l10n_at_sv_basis_roh(result_rules)
|
||||
for code in ('TEIL_ENTGELT_HALB', 'KRANKENTGELT_PHASE',
|
||||
'MSCH_ENTGELT'):
|
||||
'MSCH_ENTGELT', 'KARENZ_ENTGELT'):
|
||||
if result_rules is not None and code in result_rules \
|
||||
and result_rules[code]['total']:
|
||||
basis -= result_rules[code]['total']
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP16: Karenz-/Familien-Freistellungen — unbezahlte Karenz-Arten
|
||||
(Elternkarenz generisch, Bildungs-, Familienhospiz-, Kinderrehabilita-
|
||||
tions- und Pflegekarenz, Papa-Monat) mit Entgelt-Stopp, eSV-An- und
|
||||
Abmeldeereignissen und der BMSVG-§-7-Brücke.
|
||||
|
||||
Am Originalwortlaut verifiziert (.ris/, 8.13 freigegeben 2026-09-15):
|
||||
- Alle Voll-Freistellungen sind entgeltlos (AVRAG §§ 11, 14a, 14b,
|
||||
14e, 14c; VKG § 1a) — Entgelt-Stopp je Arbeitstag über die
|
||||
negative Regel ``KARENZ_ENTGELT`` (Muster MSCH_ENTGELT, AP7).
|
||||
- eSV: Abmeldung zum Ende des Entgeltanspruchs (Tag vor dem
|
||||
Karenzbeginn), Anmeldung zum ersten Arbeitstag nach dem Ende;
|
||||
Papamonat/Familienzeit: Abmeldung am Tag vor Beginn (§ 11 Abs 3
|
||||
lit a ASVG, lb-pap-01). Karenzart-Strings der Sonderformate
|
||||
„Familienhospizkarenz/Pflegekarenz An-/Abmeldung" (lb-kzs-05)
|
||||
laufen als Protokoll-Info — erweiterte eSV-Sonderformate = GP7-Rest.
|
||||
- BV: Elternkarenz/KBG zu Lasten FLAF (§ 7 Abs 5 BMSVG), Familien-
|
||||
hospiz-/Pflegekarenz zu Lasten des Bundes (§ 7 Abs 6 BMSVG,
|
||||
Fiktivgrundlage KBG § 5b Abs 1 KBGG idF vor BGBl I 53/2016);
|
||||
Pflegeteilzeit: AG-Beitrag auf Vor-Reduzierungs-Entgelt (§ 6 Abs 4
|
||||
BMSVG, Feld l10n_at_bv_vor_entgelt).
|
||||
- Dienstzeit: Papamonat ist ANZURECHNEN (§ 1a Abs 7 VKG iVm § 15f
|
||||
MSchG), Bildungs-/Pflegekarenz grundsätzlich NICHT (§ 11 Abs 2,
|
||||
§ 14c/14d AVRAG) — Wizard-bestätigte Dienstzeiten, dokumentiert.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
# Unbezahlte Karenz-WETs (generisch + AP16-Spezies)
|
||||
_KARENZ_WET = 'ATP_KARENZ'
|
||||
_BILDUNGSKARENZ_WET = 'ATP_BILDUNGSKARENZ'
|
||||
_HOSPIZKARENZ_WET = 'ATP_HOSPIZKARENZ'
|
||||
_KINDER_KRANK_WET = 'ATP_KINDERKRANK'
|
||||
_KINDER_REHAB_WET = 'ATP_KINDERREHAB'
|
||||
_PFLEGEKARENZ_WET = 'ATP_PFLEGEKARENZ'
|
||||
_PAPAMONAT_WET = 'ATP_PAPAMONAT'
|
||||
|
||||
KARENZ_ARTEN = {
|
||||
_KARENZ_WET: 'Elternkarenz (MSchG/VKG)',
|
||||
_BILDUNGSKARENZ_WET: 'Bildungskarenz (§ 11 AVRAG)',
|
||||
_HOSPIZKARENZ_WET: 'Familienhospizkarenz — Sterbebegleitung '
|
||||
'(§ 14a AVRAG)',
|
||||
_KINDER_KRANK_WET: 'Familienhospizkarenz — Begleitung schwerst '
|
||||
'erkrankter Kinder (§ 14b AVRAG)',
|
||||
_KINDER_REHAB_WET: 'Freistellung Kinderrehabilitation '
|
||||
'(§ 14e AVRAG)',
|
||||
_PFLEGEKARENZ_WET: 'Pflegekarenz (§ 14c AVRAG)',
|
||||
_PAPAMONAT_WET: 'Papa-Monat/Familienzeit (§ 1a VKG / FamZeitbG)',
|
||||
}
|
||||
|
||||
|
||||
class HrPayslip(models.Model):
|
||||
_inherit = 'hr.payslip'
|
||||
|
||||
def _at_karenz_wet_codes(self):
|
||||
"""Codes der unbezahlten Karenz-Freistellungen (inkl. generisch
|
||||
und AP16-Spezies)."""
|
||||
self.ensure_one()
|
||||
return list(KARENZ_ARTEN)
|
||||
|
||||
def _at_karenz_leaves(self):
|
||||
"""Validierte Karenz-Freistellungen: [(von, bis, wett_code)]
|
||||
chronologisch."""
|
||||
self.ensure_one()
|
||||
typen = self.env['hr.leave.type'].search([
|
||||
('work_entry_type_id.code', 'in', self._at_karenz_wet_codes())
|
||||
])
|
||||
leaves = self.env['hr.leave'].search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('holiday_status_id', 'in', typen.ids),
|
||||
('state', 'in', ('validate', 'validate1')),
|
||||
])
|
||||
ergebnis = []
|
||||
for leave in leaves:
|
||||
wett = leave.holiday_status_id.work_entry_type_id.code
|
||||
ergebnis.append((leave.request_date_from,
|
||||
leave.request_date_to, wett))
|
||||
return [(von, bis, wett) for von, bis, wett in ergebnis
|
||||
if von and bis]
|
||||
|
||||
def _at_karenz_status(self):
|
||||
"""Karenz-Tage je Abrechnungsperiode über alle unbezahlten
|
||||
Karenz-Arten: {'kalendertage', 'arbeitstage', 'arten'} — None
|
||||
ohne Freistellung im Zeitraum (Muster _at_msch_status)."""
|
||||
self.ensure_one()
|
||||
codes = set(self._at_karenz_wet_codes())
|
||||
tage = {}
|
||||
tag_art = {}
|
||||
for von, bis, wett in self._at_karenz_leaves():
|
||||
tag = fields.Date.to_date(von)
|
||||
ende = fields.Date.to_date(bis)
|
||||
while tag <= ende:
|
||||
tage[tag] = True
|
||||
tag_art[tag] = wett
|
||||
tag += timedelta(days=1)
|
||||
in_periode = sorted(
|
||||
tag for tag in tage
|
||||
if self.date_from <= tag <= self.date_to)
|
||||
if not in_periode:
|
||||
return None
|
||||
eintraege = self.env['hr.work.entry'].search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('date', '>=', self.date_from),
|
||||
('date', '<=', self.date_to),
|
||||
('state', '!=', 'cancelled'),
|
||||
('work_entry_type_id.code', 'in', list(codes)),
|
||||
])
|
||||
arten_aktuell = {tag_art[tag] for tag in in_periode}
|
||||
return {
|
||||
'kalendertage': in_periode,
|
||||
'arbeitstage': sorted({e.date for e in eintraege}),
|
||||
'arten': sorted(KARENZ_ARTEN[c] for c in arten_aktuell),
|
||||
}
|
||||
|
||||
def _at_karenz_entgelt_tage(self, result_rules=None):
|
||||
"""Arbeitstage der unbezahlten Karenz im Abrechnungszeitraum
|
||||
(Entgelt-Stopp-Basis der Regel KARENZ_ENTGELT)."""
|
||||
self.ensure_one()
|
||||
status = self._at_karenz_status()
|
||||
if not status:
|
||||
return 0
|
||||
return len(status['arbeitstage'])
|
||||
|
||||
|
||||
class HrVersion(models.Model):
|
||||
_inherit = 'hr.version'
|
||||
|
||||
l10n_at_bv_vor_entgelt = fields.Monetary(
|
||||
string='BV-Basis vor Arbeitszeit-Reduktion (§ 6 Abs 4 BMSVG)',
|
||||
currency_field='currency_id',
|
||||
help='Monatsentgelt vor der Reduzierung (z. B. Pflegeteilzeit '
|
||||
'§ 14d AVRAG): der BV-Beitrag läuft während der Maßnahme '
|
||||
'auf Grundlage der Arbeitszeit vor Herabsetzung '
|
||||
'(§ 6 Abs 4 BMSVG). Leer = laufende Basis.')
|
||||
@@ -36,11 +36,27 @@ Fristberechnung aus dem ärztlichen Zeugnis bleibt beim Mandanten.
|
||||
Urlaubsanspruch erwächst während des Wochengeldbezugs (lb-msf-03);
|
||||
Sonderwochengeld (§ 163 ASVG, Günstigkeitsvergleich § 162 Abs 3b) =
|
||||
dokumentierte Option.
|
||||
|
||||
AP16 (8.13): Karenz-Brücke nach § 7 Abs 4 Z 1–3 BMSVG — folgt das
|
||||
neuerliche Beschäftigungsverbot unmittelbar auf eine Karenz (Z 1) oder
|
||||
nach einer kürzeren als dreimonatigen Beschäftigung (Z 2), ist die
|
||||
Bemessungsgrundlage die des Kalendermonats vor dem der Karenz
|
||||
unmittelbar vorangegangenen Beschäftigungsverbots; Z 3 (neues DV nach
|
||||
Beendigung des karenzierten DV, < 3 Monate): der letzte volle
|
||||
Kalendermonat vor dem neuerlichen Verbot. BV während des KBG-Bezugs
|
||||
läuft zu Lasten des FLAF (§ 7 Abs 5), in der Familienhospiz-/Pflege-
|
||||
karenz zu Lasten des Bundes (§ 7 Abs 6) — keine AG-Regeln; das Ruhen
|
||||
des Wochengeldes (§ 166 ASVG) und das Sonderwochengeld während
|
||||
einer Karenz (§ 163 ASVG) werden als Info/Warnung ausgewiesen.
|
||||
"""
|
||||
from datetime import date, timedelta
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
from .karenz import KARENZ_ARTEN
|
||||
|
||||
_MSCH_WET = 'ATP_MSCH'
|
||||
|
||||
|
||||
@@ -61,13 +77,21 @@ class HrPayslip(models.Model):
|
||||
slip.l10n_at_msch_info = ''
|
||||
continue
|
||||
wochengeld = slip._at_wochengeld()
|
||||
slip.l10n_at_msch_info = (
|
||||
info = (
|
||||
'%d Kalendertage Bezug · WG %.2f/Kalendertag '
|
||||
'(Ø-Netto %.2f + %.0f %%)' % (
|
||||
len(msch['kalendertage']),
|
||||
wochengeld['tagesatz'],
|
||||
wochengeld['netto_drei_monate'],
|
||||
wochengeld['sz_prozent']))
|
||||
if slip._at_msch_sonderwochengeld():
|
||||
info += (' · Sonderwochengeld (§ 163 ASVG) statt '
|
||||
'Wochengeld — ÖGK, erhöhtes Krankengeld '
|
||||
'(§ 141 Abs 2, § 125 iVm § 108f)')
|
||||
warnungen = slip._at_msch_ruhen_warnungen()
|
||||
if warnungen:
|
||||
info += ' · Ruhen: ' + '; '.join(warnungen)
|
||||
slip.l10n_at_msch_info = info
|
||||
|
||||
def _at_msch_leaves(self):
|
||||
"""Validierte Mutterschutz-Frist-Abwesenheiten (ATP_MSCH)."""
|
||||
@@ -120,17 +144,109 @@ class HrPayslip(models.Model):
|
||||
'episode_beginn': block,
|
||||
}
|
||||
|
||||
def _at_msch_referenz_monate(self, episode_beginn):
|
||||
"""Die drei (vollen) Kalendermonate vor dem Versicherungsfall
|
||||
der Mutterschaft (§ 162 Abs 3: letzte 13 Wochen; § 7 Abs 4
|
||||
BMSVG: letzte drei Kalendermonate). Versicherungsfall im Monat
|
||||
X → Monate X-3, X-2, X-1 (aufsteigend)."""
|
||||
def _at_msch_sonderwochengeld(self):
|
||||
"""Sonderwochengeld statt Wochengeld (§ 163 ASVG, verifiziert):
|
||||
die Mutterschutz-Frist fällt in eine Karenz nach dem MSchG —
|
||||
kein Anspruch auf Wochengeld (§ 162), das Sonderwochengeld
|
||||
zahlt der Krankenversicherungsträger (erhöhtes Krankengeld
|
||||
§ 141 Abs 2, Bemessungsgrundlage § 125 aus dem Arbeits-
|
||||
verdienst vor dem Ende des letzten Entgeltanspruchs, mit
|
||||
§ 108f-Anpassung)."""
|
||||
self.ensure_one()
|
||||
msch = self._at_msch_status()
|
||||
if not msch:
|
||||
return False
|
||||
for von, bis, _wett in self._at_karenz_leaves():
|
||||
if any(von <= tag <= bis for tag in msch['kalendertage']):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _at_msch_ruhen_warnungen(self):
|
||||
"""Ruhen des Wochengeldes (§ 166 ASVG, verifiziert) —
|
||||
Warnungen zu Fortbezug während der Frist:
|
||||
Z 2 Entgeltfortzahlung 100 % → Ruhen zur Gänze, 50 % →
|
||||
hälftig (Urlaubs-/UEL-/KE-Zeiten gelten nicht als weiterge-
|
||||
leistete Bezüge); Z 3 Erwerbstätigkeit und Z 4 Pflegekarenz-
|
||||
geld sind Bezugszeitraum-Daten (AEB/ÖGK-Seite, Hinweis)."""
|
||||
self.ensure_one()
|
||||
msch = self._at_msch_status()
|
||||
if not msch:
|
||||
return []
|
||||
tage = set(msch['kalendertage'])
|
||||
efz = self._at_efz_status()
|
||||
warnungen = []
|
||||
if efz:
|
||||
if any(tag in tage for tag in efz.get('voll_arbeitstage', [])):
|
||||
warnungen.append('Fortbezug > 50 % (Entgeltfortzahlung) '
|
||||
'— Ruhen zur Gänze (§ 166 Abs 1 Z 2)')
|
||||
elif any(tag in tage for tag in efz.get('halb_arbeitstage', [])):
|
||||
warnungen.append('Fortbezug = 50 % — Ruhen hälftig '
|
||||
'(§ 166 Abs 1 Z 2)')
|
||||
warnungen.append('Erwerbstätigkeit/Pflegekarenzgeld während der '
|
||||
'Frist prüfen (§ 166 Abs 1 Z 3/4)')
|
||||
return warnungen
|
||||
|
||||
def _at_msch_referenz_stichtag_und_anzahl(self, episode_beginn):
|
||||
"""(Stichtag, Referenz-Monatsanzahl) nach § 7 Abs 4 BMSVG mit
|
||||
Karenz-Brücke Z 1–3 (AP16):
|
||||
- Z 1/Z 2: endete eine Karenz innerhalb von drei Monaten vor
|
||||
dem neuerlichen Beschäftigungsverbot, gilt das der Karenz
|
||||
unmittelbar vorangegangene Beschäftigungsverbot als
|
||||
Bemessungsstichtag (Referenzmonate davor);
|
||||
- Z 3: neues DV binnen drei Monaten nach Beendigung des
|
||||
karenzierten DV (mit Karenz) → der letzte volle Kalendermonat
|
||||
vor dem neuerlichen Verbot als EINZELNER Referenzmonat
|
||||
(„volles Monatsentgelt");
|
||||
- sonst: Versicherungsfall selbst, drei Monate."""
|
||||
self.ensure_one()
|
||||
stichtag = episode_beginn or self.date_from
|
||||
grenze = stichtag - relativedelta(months=3)
|
||||
karenz_kandidat = None
|
||||
for von, bis, _wett in sorted(self._at_karenz_leaves(),
|
||||
key=lambda k: k[1], reverse=True):
|
||||
if grenze <= bis < stichtag:
|
||||
karenz_kandidat = (von, bis)
|
||||
break
|
||||
if karenz_kandidat:
|
||||
karenz_von = karenz_kandidat[0]
|
||||
frist = max(
|
||||
(m for m in self._at_msch_leaves()
|
||||
if m[1] <= karenz_von),
|
||||
key=lambda m: m[1], default=None)
|
||||
if frist:
|
||||
return frist[0], 3
|
||||
version = self.version_id
|
||||
vorher = self.env['hr.version'].search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('contract_date_end', '!=', False),
|
||||
('id', '!=', version.id),
|
||||
], order='contract_date_end desc', limit=1)
|
||||
if (vorher and vorher.contract_date_end
|
||||
and version.contract_date_start
|
||||
and vorher.contract_date_end
|
||||
<= version.contract_date_start
|
||||
and (version.contract_date_start
|
||||
- vorher.contract_date_end).days < 92
|
||||
and any(k[1] <= vorher.contract_date_end
|
||||
for k in self._at_karenz_leaves())):
|
||||
erster = date(stichtag.year, stichtag.month, 1)
|
||||
erster = (erster - timedelta(days=1)).replace(day=1)
|
||||
return erster, 1
|
||||
return stichtag, 3
|
||||
|
||||
def _at_msch_referenz_monate(self, episode_beginn):
|
||||
"""Referenzkalendermonate der fiktiven BV (§ 7 Abs 4 BMSVG:
|
||||
letzte drei Monate; Karenz-Brücke Z 1–3, AP16 — s.
|
||||
_at_msch_referenz_stichtag_und_anzahl; § 162 Abs 3: letzte
|
||||
13 Wochen). Versicherungsfall im Monat X → Monate X-3, X-2,
|
||||
X-1 (aufsteigend)."""
|
||||
self.ensure_one()
|
||||
stichtag, anzahl = self._at_msch_referenz_stichtag_und_anzahl(
|
||||
episode_beginn)
|
||||
erster = date(stichtag.year, stichtag.month, 1)
|
||||
monate = []
|
||||
hilf = erster
|
||||
for _ in range(3):
|
||||
for _ in range(anzahl):
|
||||
hilf = (hilf - timedelta(days=1)).replace(day=1)
|
||||
monate.append(hilf)
|
||||
return list(reversed(monate))
|
||||
|
||||
@@ -23,12 +23,15 @@ BVAB/BVJN aus dem GP5-BMSVG-Flag, VWAZ vereinbarte Wochenarbeitszeit
|
||||
(Stunden ×100, neu für Meldedaten ab 01.01.2026).
|
||||
|
||||
Dateibasiert (Prämisse P7); Bestätigungen/Übertragung beim Mandanten."""
|
||||
from datetime import date
|
||||
from datetime import date, timedelta
|
||||
import base64
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.addons.l10n_at_hr_payroll_private.models.karenz import \
|
||||
KARENZ_ARTEN
|
||||
|
||||
from odoo.addons.l10n_at_hr_payroll.models.elda import (
|
||||
L10N_AT_ELDA_IDTEIL,
|
||||
L10nAtEldaBestand,
|
||||
@@ -199,6 +202,58 @@ class L10nAtPayrollVr(models.Model):
|
||||
abmeldungen.append(version)
|
||||
return sorted(abmeldungen, key=lambda v: v.contract_date_end)
|
||||
|
||||
def _l10n_at_offene_karenz_events(self):
|
||||
"""AP16 (8.13): An-/Abmeldungen aus validierten Karenz-
|
||||
Freistellungen: M4 zum Ende des Entgeltanspruchs (Tag vor dem
|
||||
Karenzbeginn; Familienzeit am Tag vor Beginn, § 11 Abs 3 lit a
|
||||
ASVG, lb-pap-01), M3 zum ersten Arbeitstag nach dem Karenzende
|
||||
— entfällt, wenn das DV mit der Karenz endet (normale M4-
|
||||
Vertragsabmeldung deckt das Ende). Karenzart-Strings für die
|
||||
eSV-Sonderformate „Familienhospizkarenz/Pflegekarenz An-/
|
||||
Abmeldung" (lb-kzs-05) laufen als Protokoll-Info; erweiterte
|
||||
eSV-Sonderformate = GP7-Rest. Dedup über die Registry
|
||||
(employee, art, adatum); keine Events nach dem Stichtag."""
|
||||
self.ensure_one()
|
||||
typen = self.env['hr.leave.type'].search([
|
||||
('work_entry_type_id.code', 'in', list(KARENZ_ARTEN))])
|
||||
if not typen:
|
||||
return []
|
||||
leaves = self.env['hr.leave'].search([
|
||||
('company_id', 'in', self.company_id.ids),
|
||||
('holiday_status_id', 'in', typen.ids),
|
||||
('state', 'in', ('validate', 'validate1')),
|
||||
]).sorted('request_date_from')
|
||||
registry = self.env['l10n.at.payroll.vr.event'].search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
])
|
||||
gesehen = {(e.employee_id.id, e.art, e.adatum) for e in registry}
|
||||
ereignisse = []
|
||||
for leave in leaves:
|
||||
von = leave.request_date_from
|
||||
bis = leave.request_date_to
|
||||
if not von or not bis:
|
||||
continue
|
||||
version = leave.employee_id.version_id
|
||||
if not version:
|
||||
continue
|
||||
wett_code = leave.holiday_status_id \
|
||||
.work_entry_type_id.code
|
||||
karenzart = KARENZ_ARTEN.get(wett_code, 'Karenz')
|
||||
abmeldetag = von - timedelta(days=1)
|
||||
if abmeldetag > self.stichtag:
|
||||
continue
|
||||
if (leave.employee_id.id, 'M4', abmeldetag) not in gesehen:
|
||||
ereignisse.append((version, 'M4', abmeldetag, karenzart))
|
||||
rueckkehr = bis + timedelta(days=1)
|
||||
if (version.contract_date_end
|
||||
and rueckkehr >= version.contract_date_end):
|
||||
continue
|
||||
if rueckkehr > self.stichtag:
|
||||
continue
|
||||
if (leave.employee_id.id, 'M3', rueckkehr) not in gesehen:
|
||||
ereignisse.append((version, 'M3', rueckkehr, karenzart))
|
||||
return ereignisse
|
||||
|
||||
def _l10n_at_bber(self, version):
|
||||
if version.l10n_at_lehrling:
|
||||
return '03' if getattr(
|
||||
@@ -234,6 +289,7 @@ class L10nAtPayrollVr(models.Model):
|
||||
raise UserError('Beitragskontonummer (BKNR) fehlt.')
|
||||
datensaetze = []
|
||||
ereignisse = []
|
||||
karenz_info = []
|
||||
vstr = bknr[:2]
|
||||
if lauf.nur_fallweise:
|
||||
versionen = self.env['hr.version'].search([
|
||||
@@ -366,6 +422,51 @@ class L10nAtPayrollVr(models.Model):
|
||||
ereignisse.append((version, 'M4',
|
||||
version.contract_date_end,
|
||||
refw))
|
||||
for version, art, adatum, karenzart in \
|
||||
lauf._l10n_at_offene_karenz_events():
|
||||
if not version.ssnid:
|
||||
fehler.append(
|
||||
'Karenz-Meldung %s ohne Versicherungsnummer.'
|
||||
% version.employee_id.name)
|
||||
continue
|
||||
satz = L10nAtEldaSatz(
|
||||
L10N_AT_ELDA_IDTEIL + VR_SATZ, 772,
|
||||
bezeichnung='%s Karenz %s' % (
|
||||
art, version.employee_id.name))
|
||||
refw = '%s-K%d-%s-%d' % (
|
||||
art, company.id,
|
||||
adatum.strftime('%Y%m%d'),
|
||||
version.employee_id.id)
|
||||
satz.setze('SART', art)
|
||||
satz.setze('VSTR', vstr)
|
||||
satz.setze('REFW', refw)
|
||||
satz.setze('BKNR', bknr)
|
||||
satz.setze('DGNA', company.name[:70])
|
||||
satz.setze('VSNR', version.ssnid)
|
||||
satz.setze_datum(
|
||||
'GEBD', version.employee_id.birthday)
|
||||
familienname, vorname = l10n_at_namen(
|
||||
version.employee_id)
|
||||
satz.setze('FANA', familienname[:70])
|
||||
satz.setze('VONA', (vorname or '')[:70])
|
||||
satz.setze_datum('ADAT', adatum)
|
||||
if art == 'M3':
|
||||
satz.setze('BBER', lauf._l10n_at_bber(version))
|
||||
satz.setze('GERF', 'N')
|
||||
satz.setze('FRDV', 'N')
|
||||
else:
|
||||
satz.setze_datum('EBSV', adatum)
|
||||
satz.setze('AGRD', '00')
|
||||
kalender = version.resource_calendar_id
|
||||
if kalender:
|
||||
satz.setze('VWAZ', kalender.hours_per_week,
|
||||
skala=2)
|
||||
datensaetze.append(satz)
|
||||
ereignisse.append((version, art, adatum, refw))
|
||||
karenz_info.append(
|
||||
'%s (ADAT %s) — %s' % (
|
||||
version.employee_id.name,
|
||||
adatum.strftime('%d.%m.%Y'), karenzart))
|
||||
if fehler:
|
||||
lauf.protokoll = 'FEHLER:\n' + '\n'.join(fehler)
|
||||
raise UserError('Versichertenmeldung mit Fehlern '
|
||||
@@ -395,12 +496,17 @@ class L10nAtPayrollVr(models.Model):
|
||||
ereignisse = self.env['l10n.at.payroll.vr.event'].create(
|
||||
ereignis_werte) if ereignis_werte else \
|
||||
self.env['l10n.at.payroll.vr.event']
|
||||
protokoll = 'Meldungen: %d' % len(datensaetze)
|
||||
if karenz_info:
|
||||
protokoll += ("\nKarenz (eSV-Sonderformate GP7-Rest; "
|
||||
"Karenzart zur Übergabe):\n"
|
||||
+ "\n".join(karenz_info))
|
||||
lauf.write({
|
||||
'datei': base64.b64encode(datei.render()),
|
||||
'datei_name': 'versichertenmeldung_%s.txt' % (
|
||||
fields.Date.today().strftime('%Y%m%d'),),
|
||||
'event_ids': [(6, 0, ereignisse.ids)],
|
||||
'state': 'done',
|
||||
'protokoll': 'Meldungen: %d' % len(datensaetze),
|
||||
'protokoll': protokoll,
|
||||
})
|
||||
return True
|
||||
@@ -18,3 +18,4 @@ from . import test_sachbezuege2_private
|
||||
from . import test_reisekosten_private
|
||||
from . import test_pendler_private
|
||||
from . import test_endabrechnung_private
|
||||
from . import test_karenz_private
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||
"""AP16 (8.13): Karenz-/Familien-Freistellungen — Entgelt-Stopp je
|
||||
unbezahlter Karenz-Art (KARENZ_ENTGELT, Muster MSCH_ENTGELT), eSV-An-/
|
||||
Abmeldungen aus der vr.event-Registry (Papa-Monat tagesgenau, Karenz-
|
||||
Art-Protokoll), fiktive BV mit §-7-Abs-4-Z-1–3-BMSVG-Karenz-Brücke,
|
||||
BV-Vor-Entgelt bei Pflegeteilzeit (§ 6 Abs 4 BMSVG), Sonderwochengeld
|
||||
(§ 163 ASVG) und Ruhen-Warnungen (§ 166 ASVG), §-23-Abs-8-ETZ-
|
||||
Hochrechnung im Endabrechnungs-Wizard."""
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class KarenzPrivateTest(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.company = self.env.company
|
||||
self.structure = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.structure_at_privat')
|
||||
self.kv2203 = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv_si_2203')
|
||||
self.gruppe_ii = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.kv2203_grp_ii')
|
||||
self.calendar = self.env['resource.calendar'].create({
|
||||
'name': 'AT Karenz 40h',
|
||||
'tz': 'Europe/Vienna',
|
||||
'hours_per_day': 8,
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Tag %d' % wd, 'dayofweek': str(wd),
|
||||
'hour_from': 8.0, 'hour_to': 16.0})
|
||||
for wd in range(5)
|
||||
],
|
||||
})
|
||||
self.kalender_teilzeit = self.env['resource.calendar'].create({
|
||||
'name': 'AT Karenz 20h (Teilzeit)',
|
||||
'tz': 'Europe/Vienna',
|
||||
'hours_per_day': 4,
|
||||
'attendance_ids': [
|
||||
(0, 0, {'name': 'Tag %d' % wd, 'dayofweek': str(wd),
|
||||
'hour_from': 8.0, 'hour_to': 12.0})
|
||||
for wd in range(5)
|
||||
],
|
||||
})
|
||||
self.company.resource_calendar_id = self.calendar
|
||||
self.typ_bildung = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.leave_type_at_bildungskarenz')
|
||||
self.typ_pflege = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.leave_type_at_pflegekarenz')
|
||||
self.typ_karenz = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.leave_type_at_karenz')
|
||||
self.typ_msch = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.leave_type_at_mutterschutz')
|
||||
self.typ_krank = self.env.ref(
|
||||
'l10n_at_hr_payroll_private.leave_type_at_krankenstand')
|
||||
|
||||
def _employee(self, teilzeit=False, ssnid=False, ende=None):
|
||||
employee = self.env['hr.employee'].create({
|
||||
'name': 'Karenz Test', 'sex': 'female',
|
||||
'birthday': '1990-05-15'})
|
||||
version = self.env['hr.version'].search(
|
||||
[('employee_id', '=', employee.id)], limit=1)
|
||||
version.write({
|
||||
'contract_date_start': datetime.date(2020, 7, 1),
|
||||
'kv_id': self.kv2203.id,
|
||||
'kv_gruppe_id': self.gruppe_ii.id,
|
||||
'erfahrungsstufe': 1,
|
||||
'ueberzahlung': 2059.41,
|
||||
'resource_calendar_id': self.calendar.id,
|
||||
})
|
||||
if ssnid:
|
||||
version.write({'ssnid': '1234150590',
|
||||
'sex': 'female'})
|
||||
if ende:
|
||||
version.write({'contract_date_end': ende})
|
||||
version.generate_work_entries(
|
||||
datetime.date(2026, 8, 1), datetime.date(2027, 3, 31))
|
||||
return employee
|
||||
|
||||
def _slip(self, employee, monat, jahr=2026, confirm=False):
|
||||
erster = datetime.date(jahr, monat, 1)
|
||||
letzter = (erster.replace(day=28)
|
||||
+ datetime.timedelta(days=4)).replace(day=1) \
|
||||
- datetime.timedelta(days=1)
|
||||
slip = self.env['hr.payslip'].create({
|
||||
'name': 'Karenz %d/%d' % (monat, jahr),
|
||||
'employee_id': employee.id,
|
||||
'struct_id': self.structure.id,
|
||||
'date_from': erster,
|
||||
'date_to': letzter,
|
||||
})
|
||||
slip.compute_sheet()
|
||||
if confirm:
|
||||
slip.action_payslip_done()
|
||||
return slip
|
||||
|
||||
def _total(self, slip, code):
|
||||
return sum(slip.line_ids.filtered(
|
||||
lambda l: l.code == code).mapped('total'))
|
||||
|
||||
def _leave(self, employee, typ, von, bis, skip_check=False):
|
||||
vals = {
|
||||
'name': '%s %s' % (typ.name, von),
|
||||
'employee_id': employee.id,
|
||||
'holiday_status_id': typ.id,
|
||||
'request_date_from': von,
|
||||
'request_date_to': bis,
|
||||
}
|
||||
if skip_check:
|
||||
# Überlappende Freistellungen (Krankheit während Karenz/MSchG):
|
||||
# direkt auf state='validate' — _action_validate würde an
|
||||
# number_of_days == 0 (vom Karenz-Typ belegte Tage) scheitern.
|
||||
leave = self.env['hr.leave'].with_context(
|
||||
leave_skip_date_check=True,
|
||||
leave_skip_state_check=True).create(
|
||||
dict(vals, state='validate'))
|
||||
return leave
|
||||
leave = self.env['hr.leave'].create(vals)
|
||||
leave.with_user(SUPERUSER_ID)._action_validate()
|
||||
return leave
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-KAR-1: Entgelt-Stopp je Karenz-Art (KARENZ_ENTGELT)
|
||||
# ------------------------------------------------------------------
|
||||
def test_01_entgelt_stopp_bildungskarenz(self):
|
||||
"""Bildungskarenz 1.-15. 11. (§ 11 AVRAG, 15 KT/10 AT): das
|
||||
Karenzgeld zahlt die AK — KARENZ_ENTGELT stoppt das
|
||||
Dienstgeber-Entgelt je Arbeitstag (Muster MSCH_ENTGELT)."""
|
||||
emp_mit = self._employee()
|
||||
emp_ohne = self._employee()
|
||||
slip_mit = self._slip(emp_mit, 11)
|
||||
slip_ohne = self._slip(emp_ohne, 11)
|
||||
self._leave(emp_mit, self.typ_bildung,
|
||||
datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 15))
|
||||
slip_mit.compute_sheet()
|
||||
|
||||
status = slip_mit._at_karenz_status()
|
||||
self.assertEqual(len(status['kalendertage']), 15)
|
||||
self.assertEqual(len(status['arbeitstage']), 10)
|
||||
self.assertTrue(any('Bildungskarenz' in a
|
||||
for a in status['arten']))
|
||||
tagesatz = 2059.41 / 21
|
||||
self.assertAlmostEqual(self._total(slip_mit, 'KARENZ_ENTGELT'),
|
||||
-tagesatz * 10, places=2)
|
||||
# Keine MSCH-Wirkung; SV-Basis durch die negative Zeile reduziert
|
||||
self.assertEqual(self._total(slip_mit, 'MSCH_ENTGELT'), 0.0)
|
||||
basis_diff = (slip_ohne._l10n_at_sv_basis_roh()
|
||||
- slip_mit._l10n_at_sv_basis_roh())
|
||||
self.assertAlmostEqual(basis_diff, tagesatz * 10, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-KAR-2: eSV-An-/Abmeldungen aus Karenz (vr.event)
|
||||
# ------------------------------------------------------------------
|
||||
def test_02_vr_events_karenz(self):
|
||||
"""Pflegekarenz 1.-15. 11.: Abmeldung zum Tag vor Beginn
|
||||
(Ende des Entgeltanspruchs, § 163/§ 11 ASVG-Rahmen) und
|
||||
Anmeldung zur Rückkehr (16. 11.); Karenzart im Protokoll;
|
||||
erneuter Lauf idempotent (Registry-Dedup)."""
|
||||
self.company.write({
|
||||
'l10n_at_beitragskontonummer': '1412345678',
|
||||
})
|
||||
employee = self._employee(ssnid=True)
|
||||
self._leave(employee, self.typ_pflege,
|
||||
datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 15))
|
||||
lauf = self.env['l10n.at.payroll.vr'].create({
|
||||
'company_id': self.company.id,
|
||||
'stichtag': datetime.date(2026, 12, 31),
|
||||
})
|
||||
lauf.action_erzeugen()
|
||||
self.assertEqual(lauf.state, 'done')
|
||||
events = self.env['l10n.at.payroll.vr.event'].search([
|
||||
('company_id', '=', self.company.id),
|
||||
('employee_id', '=', employee.id),
|
||||
])
|
||||
adate = {(e.art, e.adatum) for e in events}
|
||||
self.assertIn(('M4', datetime.date(2026, 10, 31)), adate)
|
||||
self.assertIn(('M3', datetime.date(2026, 11, 16)), adate)
|
||||
self.assertIn('Pflegekarenz', lauf.protokoll)
|
||||
roh = base64.b64decode(lauf.datei).decode('ISO-8859-15')
|
||||
self.assertIn('M4-K', roh)
|
||||
self.assertIn('M3-K', roh)
|
||||
# Idempotenz: zweite Meldungen sind registriert → Lauf leer
|
||||
lauf2 = self.env['l10n.at.payroll.vr'].create({
|
||||
'company_id': self.company.id,
|
||||
'stichtag': datetime.date(2026, 12, 31),
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
lauf2.action_erzeugen()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-KAR-3: BMSVG-Karenz-Brücke (§ 7 Abs 4 Z 1–3)
|
||||
# ------------------------------------------------------------------
|
||||
def test_03_bmsvg_karenz_bruecke(self):
|
||||
"""Mutterschutz-Frist Nov 2026, Karenz Dez 2026–Feb 2027,
|
||||
neuerliches Verbot 1. 3. 2027 (Z 1): der Bemessungsstichtag der
|
||||
fiktiven BV ist das der Karenz unmittelbar vorangegangene
|
||||
Beschäftigungsverbot — Referenzmonate Aug/Sep/Okt 2026."""
|
||||
employee = self._employee()
|
||||
self._leave(employee, self.typ_msch,
|
||||
datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 15))
|
||||
self._leave(employee, self.typ_karenz,
|
||||
datetime.date(2026, 11, 16),
|
||||
datetime.date(2027, 2, 28))
|
||||
self._leave(employee, self.typ_msch,
|
||||
datetime.date(2027, 3, 1),
|
||||
datetime.date(2027, 3, 15))
|
||||
slip = self._slip(employee, 11, jahr=2026)
|
||||
stichtag, anzahl = slip._at_msch_referenz_stichtag_und_anzahl(
|
||||
datetime.date(2027, 3, 1))
|
||||
self.assertEqual(stichtag, datetime.date(2026, 11, 1))
|
||||
self.assertEqual(anzahl, 3)
|
||||
monate = slip._at_msch_referenz_monate(datetime.date(2027, 3, 1))
|
||||
self.assertEqual(
|
||||
monate,
|
||||
[datetime.date(2026, 8, 1), datetime.date(2026, 9, 1),
|
||||
datetime.date(2026, 10, 1)])
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-KAR-4: BV-Vor-Entgelt bei Pflegeteilzeit (§ 6 Abs 4 BMSVG)
|
||||
# ------------------------------------------------------------------
|
||||
def test_04_bv_vor_entgelt_pflegeteilzeit(self):
|
||||
"""20-h-Teilzeit (work_time_rate 0,5) mit gesetztem
|
||||
Vor-Reduzierungs-Entgelt: die BV-Basis läuft auf dem
|
||||
Vor-Reduzierungs-Entgelt (§ 6 Abs 4 BMSVG), nicht auf der
|
||||
reduzierten laufenden Basis."""
|
||||
employee = self._employee()
|
||||
version = employee.version_id
|
||||
version.write({
|
||||
'l10n_at_abfertigung_neu': True,
|
||||
'l10n_at_bv_vor_entgelt': 2059.41,
|
||||
})
|
||||
version.resource_calendar_id = self.kalender_teilzeit
|
||||
version.generate_work_entries(
|
||||
datetime.date(2026, 8, 1), datetime.date(2026, 11, 30))
|
||||
slip = self._slip(employee, 11)
|
||||
self.assertAlmostEqual(slip._at_bv_basis_laufen(), 2059.41,
|
||||
places=2)
|
||||
# OHNE §-6-Abs-4-Feld: reduzierte laufende Basis
|
||||
version.write({'l10n_at_bv_vor_entgelt': 0.0})
|
||||
slip.compute_sheet()
|
||||
self.assertAlmostEqual(slip._at_bv_basis_laufen(), 1029.71,
|
||||
places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-KAR-5: Sonderwochengeld (§ 163) und Ruhen (§ 166)
|
||||
# ------------------------------------------------------------------
|
||||
def test_05_sonderwochengeld_und_ruhen(self):
|
||||
"""Mutterschutz-Frist 1.-15. 11. innerhalb einer Pflegekarenz:
|
||||
Sonderwochengeld statt Wochengeld (§ 163 ASVG); Krankenstand
|
||||
1.-10. 11. (Entgeltfortzahlung 100 %) → Ruhen-Warnung
|
||||
(§ 166 Abs 1 Z 2)."""
|
||||
employee = self._employee()
|
||||
# Karenz + Krankenstand + Mutterschutz überlappen real (Krankheit
|
||||
# während der Frist); Odos Quere-Overlap-Sperre wird im Test mit
|
||||
# dem offiziellen leave_skip_date_check-Bypass umgangen — die
|
||||
# Leave-Überlappungs-Policy ist Mandanten-Entscheid (offener
|
||||
# Punkt in docs/rechenfaelle/karenz.md).
|
||||
self._leave(employee, self.typ_pflege,
|
||||
datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 12, 15), skip_check=True)
|
||||
self._leave(employee, self.typ_krank,
|
||||
datetime.date(2026, 11, 1),
|
||||
datetime.date(2026, 11, 10), skip_check=True)
|
||||
self._leave(employee, self.typ_msch,
|
||||
datetime.date(2026, 11, 5),
|
||||
datetime.date(2026, 11, 15), skip_check=True)
|
||||
# Krankenstand Nov 1–10 als direkte Work Entries (ein Leave auf
|
||||
# denselben Tagen erzeugt der Work-Entry-Generator nicht — die
|
||||
# Karenz belegt die Tage bereits); die EFZ-Maschine liest die
|
||||
# KRANK-Entries für die Fortzahlungs-Phase.
|
||||
krank_typ = self.env['hr.work.entry.type'].search(
|
||||
[('code', '=', 'ATP_KRANK')], limit=1)
|
||||
arbeitstage = [datetime.date(2026, 11, d) for d in
|
||||
(2, 3, 4, 5, 6, 9, 10)]
|
||||
for tag in arbeitstage:
|
||||
self.env['hr.work.entry'].create({
|
||||
'name': 'Krankenstand %s' % tag,
|
||||
'employee_id': employee.id,
|
||||
'work_entry_type_id': krank_typ.id,
|
||||
'date': tag,
|
||||
'duration': 8.0,
|
||||
})
|
||||
slip = self._slip(employee, 11)
|
||||
slip.compute_sheet()
|
||||
self.assertTrue(slip._at_msch_sonderwochengeld())
|
||||
warnungen = slip._at_msch_ruhen_warnungen()
|
||||
self.assertTrue(any('Ruhen zur Gänze' in w for w in warnungen))
|
||||
self.assertIn('Sonderwochengeld', slip.l10n_at_msch_info)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# RF-KAR-6: §-23-Abs-8-Hochrechnung im Wizard (Elternteilzeit)
|
||||
# ------------------------------------------------------------------
|
||||
def test_06_etz_hochrechnung_wizard(self):
|
||||
"""20-h-Versionskalender (work_time_rate 0,5): der §-23-Abs-8-
|
||||
Hochrechnungs-Vorschlag = Monatsentgelt ÷ work_time_rate; die
|
||||
Übernahme bleibt bestätigt (kein Auto-Write)."""
|
||||
employee = self._employee()
|
||||
version = employee.version_id
|
||||
version.resource_calendar_id = self.kalender_teilzeit
|
||||
version.generate_work_entries(
|
||||
datetime.date(2026, 8, 1), datetime.date(2026, 11, 30))
|
||||
wizard = self.env['l10n.at.payroll.endabrechnung.wizard'].create({
|
||||
'employee_id': employee.id,
|
||||
'austrittsdatum': datetime.date(2026, 11, 15),
|
||||
'departure_reason_id': self.env.ref(
|
||||
'l10n_at_hr_payroll'
|
||||
'.departure_reason_einvernehmlich').id,
|
||||
'abf_alt_aktiv': True,
|
||||
'abf_alt_monatsentgelt': 1029.71,
|
||||
'abf_alt_laufender_arbeitslohn': 1029.71,
|
||||
'abf_alt_monatslohnsteuer': 0.0,
|
||||
'abf_alt_mehrfach': 2.0,
|
||||
'abf_alt_anspruchsart': 'normal',
|
||||
})
|
||||
self.assertAlmostEqual(wizard.abf_alt_hochrechnung_vorschau,
|
||||
2059.42, places=2)
|
||||
@@ -38,6 +38,8 @@
|
||||
</group>
|
||||
<group invisible="not abf_alt_aktiv">
|
||||
<field name="abf_alt_betrag_vorschau"/>
|
||||
<field name="abf_alt_hochrechnung_vorschau"
|
||||
invisible="not abf_alt_monatsentgelt"/>
|
||||
<field name="abf_alt_steuer_vervielfachung_vorschau"/>
|
||||
<field name="abf_alt_steuer_6_vorschau"/>
|
||||
<field name="abf_alt_lohnsteuer_vorschau"/>
|
||||
|
||||
@@ -95,6 +95,17 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
'entfallende tarifmäßige Lohnsteuer nach den persönlichen '
|
||||
'Verhältnissen. Bei steuerfreiem Monatslohn ausdrücklich 0 '
|
||||
'bestätigen.')
|
||||
abf_alt_hochrechnung_vorschau = fields.Monetary(
|
||||
string='§ 23 Abs 8 AngG — Hochrechnung Vollzeit (Vorschau)',
|
||||
currency_field='currency_id', compute='_compute_vorschau',
|
||||
help='Endet das DV während einer Teilzeitbeschäftigung nach '
|
||||
'MSchG/VKG (z. B. Elternteilzeit), ist die frühere '
|
||||
'Normalarbeitszeit zugrunde zu legen (§ 23 Abs 8 AngG; für '
|
||||
'Arbeitnehmerkündigung § 23a Abs 4a — 5-Jahres-Ø ohne '
|
||||
'Karenz, manuell einzugeben). Vorschlag: Monatsentgelt ÷ '
|
||||
'aktuelles work_time_rate; die Übernahme bleibt '
|
||||
'bestätigt (bei UEL-Basen gilt § 10 Abs 4 UrlG — '
|
||||
'überwiegende Arbeitszeit des Urlaubsjahres).')
|
||||
abf_alt_mehrfach = fields.Float(
|
||||
string='Bestätigter Anspruchsfaktor',
|
||||
help='§-23-Staffel, gegebenenfalls halbiert/gedeckelt oder als '
|
||||
@@ -404,6 +415,7 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
wizard.abf_alt_steuer_6_vorschau = 0.0
|
||||
wizard.abf_alt_lohnsteuer_vorschau = 0.0
|
||||
wizard.abf_alt_netto_vorschau = 0.0
|
||||
wizard.abf_alt_hochrechnung_vorschau = 0.0
|
||||
wizard.abf_frei_z1_vorschau = 0.0
|
||||
wizard.abf_frei_z2_vorschau = 0.0
|
||||
wizard.abf_frei_kuerzung_vorschau = 0.0
|
||||
@@ -434,6 +446,12 @@ class L10nAtEndabrechnungWizard(models.TransientModel):
|
||||
wizard.abf_alt_steuer_6_vorschau = steuer_6
|
||||
wizard.abf_alt_lohnsteuer_vorschau = steuer
|
||||
wizard.abf_alt_netto_vorschau = betrag - steuer
|
||||
rate = wizard.employee_id.version_id.work_time_rate
|
||||
if rate and rate < 1.0 and wizard.abf_alt_monatsentgelt:
|
||||
# § 23 Abs 8 AngG: frühere Normalarbeitszeit —
|
||||
# Hochrechnungs-Vorschlag, bestätigte Übernahme.
|
||||
wizard.abf_alt_hochrechnung_vorschau = \
|
||||
wizard.abf_alt_monatsentgelt / rate
|
||||
if wizard.abf_frei_aktiv:
|
||||
split = slip._at_abf_frei_split()
|
||||
satz = float(slip._l10n_at_param(
|
||||
|
||||
Reference in New Issue
Block a user