4b86bcb790
Split the voluntary severance into its privileged part and a tariff remainder on the exit run: the privileged part combines the quarter rule (1/4 of the last twelve months' running pay, capped at nine times the monthly HBG) with the twelfth rule (2/12 to 12/12 by proven service, capped at n × 3 × HBG), reduced per Z 3 by severance already received and the statutory claim due at this termination, all taxed at the flat 6 % rate. The excess runs as its own rule flagged l10n_at_s67_abs6_ueberhang: taxed with the running tariff of the payment month but excluded from the annual sixth, since Z 5 exempts only the Z 1/Z 2 amounts and the excess is a special payment, not running pay. The twelve-month basis aggregates validated prior runs plus own lines, excluding the flagged excess and § 26 Z 4 travel overhang via a dedicated rule flag. Free of SV (§ 49 Abs 3 Z 7 ASVG), payroll levies regardless of the 6 %/tarif split (§ 41 Abs 4 lit b FLAG, § 122 Abs 8 WKG, § 5 Abs 2 lit b KommStG) and BV contributions; the former BV-Anwartschaft exclusion Z 7 is repealed (BGBl I 118/2015). The wizard proposes the rolling basis and the statutory claim, previews Z 1/Z 2/reduction/privileged/excess/tax and requires explicit confirmation against claim-conversion risk. The § 291d EO termination pot nets both severance wage-tax rules; key 260 now includes LST_ABF_FREI. Verified against the original statute text in .ris/ (EStG § 67 Abs 6) and the knowledge-base sources lb-end-12/13/15; KZ positioning of Abs-6 amounts and the excess's § 291d pot allocation remain documented verification points against LStR Rz 1087 ff. Version 19.0.14.0.0 (private), 19.0.10.0.0 (core)
451 lines
20 KiB
Python
451 lines
20 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""AP11: Lohnpfändung/Exekution — Pfändungsvormerkung (Drittschuldner),
|
||
Existenzminimum-Maschine (§ 291a EO) je Pfändungstopf und Kostenersatz
|
||
(§ 292h/§ 302 EO).
|
||
|
||
Verifizierte Grundlagen (RIS-Gate, 2026-09-13):
|
||
- **§ 291a EO** (Unpfändbarer Freibetrag/Existenzminimum) — exakter
|
||
Wortlaut `.additional_docs/Lexis360_291a_eo.pdf` (NOR40233792, idF
|
||
1.7.2021): (1) allgemeiner Grundbetrag = AZR; (2) +1/6 bei keinen
|
||
§-290b-Leistungen (erhöhter allgemeiner Grundbetrag) und +20 % je
|
||
Unterhaltsperson (max 5, Unterhaltsgrundbetrag); (3) 30 % des
|
||
Mehrbetrags + 10 % je Unterhaltsperson (max 5); der Teil über dem
|
||
Vierfachen des AZR (Höchstberechnungsgrundlage) ist jedenfalls
|
||
pfändbar; (5) Grundbeträge auf volle Euro abrunden, Höchstteil nach
|
||
§ 291 Abs 2 (20-€-Schritte).
|
||
- **§ 292 EO** (Zusammenrechnung/Sachleistungen) —
|
||
`.additional_docs/Lexis360_292_eo.pdf` (NOR40187912): Abs 4 absolutes
|
||
Geldexistenzminimum = halber allgemeiner Grundbetrag.
|
||
- **AZR** (ASVG § 293 Abs 1 lit a sublit bb) am RIS belegt
|
||
(KB-KONFLIKTE A9, 3. RIS-Runde 2026-09-11): 2025 1.273,99 € (BGBl II
|
||
417/2024), 2026 1.308,39 € (BGBl II 263/2025).
|
||
- **KB** lb-pfa-01…14 (Stand 2026-07), wk-pfa-01: Einlangen/Rang,
|
||
Durchführung/§ 304-Zahlungssperre, Kostenersatz, Beendigungsansprüche
|
||
(§ 291d EO), Vorschüsse (§ 290c EO), Sachbezüge.
|
||
|
||
Formel (§ 291a, gegen KB-Rechenbeispiel lb-pfa-03 verifiziert:
|
||
Abfertigung 6×6.000 €, 6 % LSt → BG 33.840 €, 2 Unterhaltspflichten,
|
||
HöBG 6×5.220 € → EX 16.684 € / pfändbar 17.156 € — exakt):
|
||
|
||
azr = Parameter at_eo_azr (versioniert)
|
||
allg = floor(azr) [allg. Grundbetrag]
|
||
erh = floor(azr * 7/6) [erhöhter allg.]
|
||
unterhalt = floor(azr * 0.20) [je Person, max 5]
|
||
grund = (erh|allg) + n * unterhalt
|
||
hoechst_j = floor_to_20(4 * azr) [HöBG/Jahr]
|
||
hoechst = hoechst_j * bezugsmonate
|
||
mehr = max(0, min(basis, hoechst) - grund)
|
||
ex = min(basis, grund + (0.30 + 0.10*n) * mehr)
|
||
pfaendbar = basis - ex
|
||
"""
|
||
import math
|
||
from datetime import timedelta
|
||
|
||
from odoo import api, fields, models
|
||
from odoo.exceptions import UserError
|
||
|
||
|
||
def _floor_to(x, step):
|
||
"""Abbruch auf das Vielfache von ``step`` (20-€-Schritte § 291 Abs 2)."""
|
||
return math.floor(round(x, 6) / step) * step
|
||
|
||
|
||
class L10nAtPayrollPfaendung(models.Model):
|
||
_name = 'l10n.at.payroll.pfaendung'
|
||
_description = 'Lohnpfändung / Exekution (Drittschuldner)'
|
||
_order = 'zustellungsdatum, id'
|
||
_rec_name = 'glaubiger'
|
||
|
||
active = fields.Boolean(default=True)
|
||
employee_id = fields.Many2one(
|
||
'hr.employee', string='Dienstnehmer:in', required=True, index=True)
|
||
company_id = fields.Many2one(
|
||
'res.company', string='Company',
|
||
related='employee_id.company_id', readonly=True, required=True)
|
||
currency_id = fields.Many2one(
|
||
'res.currency', related='company_id.currency_id', readonly=True)
|
||
glaubiger = fields.Char(
|
||
string='Gläubiger', required=True,
|
||
help='Betreibende Partei (Name des Gläubigers bzw. der '
|
||
'Vollstreckungsbehörde).')
|
||
vollstreckung_traeger = fields.Selection(
|
||
[('gericht', 'Gericht (Exekutionsbewilligung)'),
|
||
('finanz', 'Finanzamt (Abgabenexekution)'),
|
||
('sv', 'Sozialversicherungsträger'),
|
||
('verwaltung', 'Verwaltungsbehörde'),
|
||
('privat', 'Privatrechtlich (Verpfändung/Zession)')],
|
||
string='Vollstreckungsträger', default='gericht', required=True,
|
||
help='Steuert Pflichtenmatrix (Drittschuldnererklärung, '
|
||
'Wartefrist) — lb-pfa-04/06/07.')
|
||
pfandung_art = fields.Selection(
|
||
[('gewoehnlich', 'Gewöhnliche Exekution'),
|
||
('unterhalt', 'Unterhaltsexekution'),
|
||
('verpfandung', 'Vertragliche Verpfändung'),
|
||
('zession', 'Gehaltsabtretung (Zession)')],
|
||
string='Pfändungsart', default='gewoehnlich', required=True,
|
||
help='Unterhalts- vs. gewöhnliche Pfändung steuert Tabellenwahl '
|
||
'und Unterhaltsmasse (lb-pfa-04/05).')
|
||
zustellungsdatum = fields.Date(
|
||
string='Zustellungs-/Einlangedatum', required=True, index=True,
|
||
help='Einlangen der Pfändung beim Drittschuldner; bestimmt Rang '
|
||
'und Lauf der Fristen (lb-pfa-01/07).')
|
||
forderungshoehe = fields.Monetary(
|
||
string='Forderungshöhe', currency_field='currency_id',
|
||
help='Höhe der gepfändeten Forderung (Ranganmerkung).')
|
||
status = fields.Selection(
|
||
[('aktiv', 'Aktiv'),
|
||
('gesetzt', 'Gesetzt (ausgesetzt)'),
|
||
('geloest', 'Gelöst / vollstreckt')],
|
||
string='Status', default='aktiv', required=True,
|
||
help='Aktiv: Einbehalt läuft; Gelöst: keine weiteren Einbehalte '
|
||
'(lb-pfa-08 Pfändungsende).')
|
||
loesungsdatum = fields.Date(
|
||
string='Löse-/Enddatum',
|
||
help='Datum der Aufhebung/Vollstreckung (lb-pfa-08).')
|
||
verwalter = fields.Boolean(
|
||
string='Verwalter bestellt (§ 301 Abs 1 Z 6 EO)',
|
||
help='Bestellter Verwalter: Erklärung/Überweisung an ihn; '
|
||
'Berechnung des Freibetrags anregbar (lb-pfa-01/04).')
|
||
verwalter_name = fields.Char(
|
||
string='Verwalter (Name)',
|
||
help='Name des bestellten Verwalters.')
|
||
zahlungssperre = fields.Boolean(
|
||
string='4-Wochen-Zahlungssperre (§ 304 EO)',
|
||
help='Exekutionsbewilligung ordnet Wartefrist an: Einbehalt '
|
||
'gesperrt, Auszahlung an Gläubiger frühestens 4 Wochen ab '
|
||
'Zustellung (lb-pfa-07).')
|
||
kostenersatz_variante = fields.Selection(
|
||
[('einbehalt', 'Kosteneinbehalt vom Bezug'),
|
||
('verzicht', 'Verzicht (kein Kostenersatz)'),
|
||
('ueberweisung', 'Überweisung durch den Gläubiger')],
|
||
string='Kostenersatz-Variante', default='einbehalt',
|
||
help='Geltendmachung des pauschalen Kostenersatzes (§ 292h/§ 302 '
|
||
'EO) — lb-pfa-09.')
|
||
dse_frist = fields.Date(
|
||
string='Drittschuldnererklärung-Frist', compute='_compute_fristen',
|
||
store=True, readonly=False,
|
||
help='IdR 4 Wochen ab Zustellung (gerichtliche Exekution, § 301 '
|
||
'EO) — lb-pfa-06.')
|
||
auszahlung_frist = fields.Date(
|
||
string='Spätester Auszahlungstermin', compute='_compute_fristen',
|
||
store=True, readonly=False,
|
||
help='Nächster Bezugstermin, spätestens 8 Wochen nach Einlangen '
|
||
'(lb-pfa-07).')
|
||
rang = fields.Integer(
|
||
string='Rang', compute='_compute_rang', store=True, readonly=True,
|
||
help='Rangfolge nach Einlangedatum (lb-pfa-01/10).')
|
||
note = fields.Text(string='Bemerkung')
|
||
|
||
@api.constrains('loesungsdatum', 'zustellungsdatum')
|
||
def _check_loesungsdatum(self):
|
||
for rec in self:
|
||
if rec.loesungsdatum and rec.zustellungsdatum \
|
||
and rec.loesungsdatum < rec.zustellungsdatum:
|
||
raise UserError(_(
|
||
"Das Löse-/Enddatum (%(loes)s) darf nicht vor dem "
|
||
"Zustellungsdatum (%(zust)s) liegen.",
|
||
loes=rec.loesungsdatum,
|
||
zust=rec.zustellungsdatum))
|
||
|
||
@api.depends('zustellungsdatum')
|
||
def _compute_fristen(self):
|
||
for rec in self:
|
||
if rec.zustellungsdatum:
|
||
rec.dse_frist = rec.zustellungsdatum + timedelta(weeks=4)
|
||
rec.auszahlung_frist = rec.zustellungsdatum + timedelta(
|
||
weeks=8)
|
||
else:
|
||
rec.dse_frist = None
|
||
rec.auszahlung_frist = None
|
||
|
||
@api.depends('employee_id', 'zustellungsdatum', 'active')
|
||
def _compute_rang(self):
|
||
for rec in self:
|
||
if not (rec.employee_id and rec.zustellungsdatum):
|
||
rec.rang = 0
|
||
continue
|
||
voher = self.search_count([
|
||
('employee_id', '=', rec.employee_id.id),
|
||
('active', '=', True),
|
||
('zustellungsdatum', '<', rec.zustellungsdatum),
|
||
('id', '!=', rec.id),
|
||
])
|
||
rec.rang = voher + 1
|
||
|
||
def name_get(self):
|
||
return [
|
||
(rec.id, '%s — %s (%s)' % (
|
||
rec.glaubiger, rec.employee_id.name or '',
|
||
rec.zustellungsdatum or ''))
|
||
for rec in self]
|
||
|
||
def action_geloest(self):
|
||
"""Pfändung als gelöst/vollstreckt markieren (lb-pfa-08)."""
|
||
today = fields.Date.context_today(self)
|
||
for rec in self:
|
||
rec.write({'status': 'geloest',
|
||
'loesungsdatum': rec.loesungsdatum or today})
|
||
return True
|
||
|
||
def action_aktiv(self):
|
||
for rec in self:
|
||
rec.write({'status': 'aktiv'})
|
||
return True
|
||
|
||
def action_gesetzt(self):
|
||
for rec in self:
|
||
rec.write({'status': 'gesetzt'})
|
||
return True
|
||
|
||
|
||
class HrVersion(models.Model):
|
||
_inherit = 'hr.version'
|
||
|
||
l10n_at_unterhaltspflichten = fields.Integer(
|
||
string='AT gesetzliche Unterhaltspflichten', default=0,
|
||
groups="hr_payroll.group_hr_payroll_user", tracking=True,
|
||
help='Anzahl gesetzlicher Unterhaltspflichten (max 5 wirksam, '
|
||
'§ 291a Abs 2 Z 2 EO) — wirkt auf Grund- und '
|
||
'Steigerungsbeträge (lb-pfa-11, Vereinfachungsregel '
|
||
'§ 292f Abs 2 EO: Angaben des DN genügen).')
|
||
l10n_at_sz_anspruch = fields.Boolean(
|
||
string='AT Sonderzahlungsanspruch (13./14.)', default=True,
|
||
groups="hr_payroll.group_hr_payroll_user", tracking=True,
|
||
help='Besteht ein Anspruch auf 13./14. Sonderbezug? Ohne SZ-'
|
||
'Anspruch gilt der erhöhte allgemeine Grundbetrag '
|
||
'(§ 291a Abs 2 Z 1 EO, Faktor 7/6) — lb-pfa-05.')
|
||
|
||
|
||
class HrEmployee(models.Model):
|
||
_inherit = 'hr.employee'
|
||
|
||
l10n_at_unterhaltspflichten = fields.Integer(
|
||
related='version_id.l10n_at_unterhaltspflichten', inherited=True,
|
||
readonly=False, groups="hr_payroll.group_hr_payroll_user")
|
||
l10n_at_sz_anspruch = fields.Boolean(
|
||
related='version_id.l10n_at_sz_anspruch', inherited=True,
|
||
readonly=False, default=True,
|
||
groups="hr_payroll.group_hr_payroll_user")
|
||
|
||
def _at_pfaendungen_aktiv(self, date_to):
|
||
"""Aktive Pfändungen zum Stichtag (sortiert nach Einlangedatum)."""
|
||
self.ensure_one()
|
||
return self.env['l10n.at.payroll.pfaendung'].search([
|
||
('employee_id', '=', self.id),
|
||
('active', '=', True),
|
||
('status', '=', 'aktiv'),
|
||
('zustellungsdatum', '<=', date_to),
|
||
], order='zustellungsdatum, id')
|
||
|
||
def _at_unterhaltspflichten(self):
|
||
"""Wirksame gesetzliche Unterhaltspflichten (max 5, § 291a
|
||
Abs 2 Z 2) — Vereinfachungsregel § 292f Abs 2 EO (lb-pfa-11)."""
|
||
self.ensure_one()
|
||
return min(self.l10n_at_unterhaltspflichten or 0, 5)
|
||
|
||
def _at_sz_anspruch(self):
|
||
"""Sonderzahlungsanspruch (13./14.) — bestimmt allg. vs. erhöhten
|
||
Grundbetrag (§ 291a Abs 2 Z 1: erhöht nur bei KEINEN §-290b-
|
||
Leistungen)."""
|
||
self.ensure_one()
|
||
return bool(self.l10n_at_sz_anspruch)
|
||
|
||
|
||
class HrSalaryRule(models.Model):
|
||
_inherit = 'hr.salary.rule'
|
||
|
||
l10n_at_pfandung_unpfandbar = fields.Boolean(
|
||
string='AT unpfändbarer Bezug (§ 291 EO)',
|
||
help='Bezugsart, die nicht pfändbar ist und aus der '
|
||
'Pfändungsberechnungsgrundlage ausfällt (zB '
|
||
'Reisekostenentschädigung) — lb-pfa-05. Schnittstelle '
|
||
'AP12 Sachbezüge.')
|
||
l10n_at_pfandung_beendigung = fields.Boolean(
|
||
string='AT Beendigungsanspruch (§ 291d EO)',
|
||
help='Einmalige Beendigungsleistung (Abfertigung, '
|
||
'Urlaubsersatzleistung, Abgangsentschädigung, '
|
||
'Pensionsabfindung) — eigener Pfändungsberechnungskreis '
|
||
'(lb-pfa-03). Schnittstelle AP14/15.')
|
||
|
||
|
||
class HrPayslip(models.Model):
|
||
_inherit = 'hr.payslip'
|
||
|
||
# ------------------------------------------------------------------
|
||
# Werte (versioniert, Parameter at_eo_azr)
|
||
# ------------------------------------------------------------------
|
||
def _at_pfaendung_werte(self, datum=None):
|
||
"""AZR + abgeleitete Grund-/Höchstbeträge (§ 291a EO) zum
|
||
Stichtag — aus dem versionierten Parameter ``at_eo_azr``."""
|
||
self.ensure_one()
|
||
datum = datum or self.date_to
|
||
werte = self._l10n_at_param('at_eo_azr', datum)
|
||
azr = float(werte['azr'])
|
||
return {
|
||
'azr': azr,
|
||
'allg': math.floor(azr),
|
||
'erh': math.floor(azr * 7.0 / 6.0),
|
||
'unterhalt': math.floor(azr * 0.20),
|
||
'hoechst_j': _floor_to(4.0 * azr, 20),
|
||
'abs_minimum': math.floor(azr / 2.0),
|
||
}
|
||
|
||
# ------------------------------------------------------------------
|
||
# Existenzminimum (§ 291a EO)
|
||
# ------------------------------------------------------------------
|
||
def _at_pfaendung_existenzminimum(self, basis, unterhaltspflichten,
|
||
erhoht, bezugsmonate=1):
|
||
"""Unpfändbarer Freibetrag (Existenzminimum) nach § 291a EO.
|
||
|
||
:param basis: Pfändungsberechnungsgrundlage (Netto, > 0)
|
||
:param unterhaltspflichten: n, gesetzliche Unterhaltspflichten
|
||
:param erhoht: erhöhter allgemeiner Grundbetrag (kein §-290b /
|
||
Beendigungsleistungen)
|
||
:param bezugsmonate: Vervielfachung der HöBG (Beendigungs-
|
||
ansprüche, lb-pfa-03)
|
||
"""
|
||
self.ensure_one()
|
||
w = self._at_pfaendung_werte()
|
||
n = min(unterhaltspflichten or 0, 5)
|
||
bezugsmonate = max(1, int(bezugsmonate or 1))
|
||
grund = (w['erh'] if erhoht else w['allg']) + n * w['unterhalt']
|
||
hoechst = w['hoechst_j'] * bezugsmonate
|
||
basis = max(0.0, basis)
|
||
if basis <= grund:
|
||
return basis
|
||
mehr = min(basis, hoechst) - grund
|
||
ex = grund + (0.30 + 0.10 * n) * mehr
|
||
return min(basis, round(ex, 2))
|
||
|
||
# ------------------------------------------------------------------
|
||
# Pfändungsberechnungsgrundlage (§ 291 EO) je Topf
|
||
# ------------------------------------------------------------------
|
||
def _at_pfa_rule_amount(self, result_rules, code):
|
||
if result_rules is not None:
|
||
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
|
||
# (§ 291 EO) bestimmt.
|
||
return round(float(werte['total']), 2) if werte else 0.0
|
||
linie = self.line_ids.filtered(lambda l: l.code == code)
|
||
return sum(linie.mapped('total')) if linie else 0.0
|
||
|
||
def _at_pfa_bruto(self, result_rules, unpfandbar=None):
|
||
"""Summe laufender Brutto-Bezüge (Bemessung 'laufend'); optional
|
||
nur die unpfändbaren (Flag l10n_at_pfandung_unpfandbar)."""
|
||
self.ensure_one()
|
||
total = 0.0
|
||
for rule in self.struct_id.rule_ids:
|
||
if (rule.category_id.code != 'ATP_BRUTTO'
|
||
or rule.l10n_at_bemessung != 'laufend'):
|
||
continue
|
||
if (unpfandbar is not None
|
||
and bool(rule.l10n_at_pfandung_unpfandbar) != unpfandbar):
|
||
continue
|
||
if result_rules is not None:
|
||
werte = result_rules.get(rule.code)
|
||
if werte:
|
||
total += round(float(werte['total']), 2)
|
||
else:
|
||
linie = self.line_ids.filtered(
|
||
lambda l, c=rule.code: l.code == c)
|
||
total += sum(linie.mapped('total'))
|
||
return total
|
||
|
||
def _at_pfaendung_basis(self, result_rules, topf='laufend'):
|
||
"""Pfändungsberechnungsgrundlage (§ 291 EO) je Topf.
|
||
|
||
- ``laufend``: laufende Bezüge − unpfändbar − SV-DN(lfd) − LSt(lfd)
|
||
- ``sz13``/``sz14``: 13./14. Bezug (eigener Berechnungskreis)
|
||
- ``beendigung``: einmalige Beendigungsansprüche (§ 291d EO)
|
||
"""
|
||
self.ensure_one()
|
||
if topf == 'laufend':
|
||
bruto = self._at_pfa_bruto(result_rules)
|
||
unpf = self._at_pfa_bruto(result_rules, unpfandbar=True)
|
||
svdn = sum(self._at_pfa_rule_amount(
|
||
result_rules, c) for c in (
|
||
'SVDN_KV', 'SVDN_PV', 'SVDN_AV', 'SVDN_AK', 'SVDN_WF'))
|
||
lst = self._at_pfa_rule_amount(result_rules, 'LST_LFD')
|
||
return round(max(0.0, bruto - unpf - svdn - lst), 2)
|
||
if topf in ('sz13', 'sz14'):
|
||
code = {'sz13': 'B13', 'sz14': 'B14'}[topf]
|
||
return round(max(0.0, self._at_pfa_rule_amount(result_rules, code)), 2)
|
||
if topf == 'beendigung':
|
||
total = 0.0
|
||
for rule in self.struct_id.rule_ids:
|
||
if not rule.l10n_at_pfandung_beendigung:
|
||
continue
|
||
if result_rules is not None:
|
||
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'))
|
||
steuer = sum(self._at_pfa_rule_amount(result_rules, c)
|
||
for c in ('LST_ABF', 'LST_ABF_FREI'))
|
||
return round(max(0.0, total - steuer), 2)
|
||
raise UserError('Unbekannter Pfändungstopf: %s' % topf)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Pfändbare Masse je Topf (Basis − Existenzminimum)
|
||
# ------------------------------------------------------------------
|
||
def _at_pfaendung_masse(self, result_rules, topf):
|
||
"""Pfändbarer Betrag (= Berechnungsgrundlage − Existenzminimum)
|
||
je Topf. SZ-Töpfe: allgemeiner Grundbetrag (SZ = §-290b).
|
||
Beendigungsansprüche: erhöhter Grundbetrag (lb-pfa-03)."""
|
||
self.ensure_one()
|
||
basis = self._at_pfaendung_basis(result_rules, topf)
|
||
if basis <= 0:
|
||
return 0.0
|
||
n = self.employee_id._at_unterhaltspflichten()
|
||
if topf in ('sz13', 'sz14'):
|
||
erhoht = False
|
||
elif topf == 'beendigung':
|
||
erhoht = True
|
||
else:
|
||
erhoht = not self.employee_id._at_sz_anspruch()
|
||
ex = self._at_pfaendung_existenzminimum(basis, n, erhoht)
|
||
return max(0.0, round(basis - ex, 2))
|
||
|
||
# ------------------------------------------------------------------
|
||
# Gesamt-Einbehalt (laufend + SZ) und Kostenersatz (§ 292h/§ 302)
|
||
# ------------------------------------------------------------------
|
||
def _at_pfaendung_einbehalt(self, result_rules):
|
||
"""Gesamter einbehaltener pfändbarer Betrag (laufend + SZ-Töpfe)."""
|
||
self.ensure_one()
|
||
if not self.employee_id._at_pfaendungen_aktiv(self.date_to):
|
||
return 0.0
|
||
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):
|
||
"""Pauschaler Kostenersatz (§ 292h EO): erste Zahlung 2 %/max 8 €,
|
||
weitere 1 %/max 4 € — nur vom pfändbaren Bezug, nie vom EX
|
||
(lb-pfa-09). Variante 'verzicht' → 0."""
|
||
self.ensure_one()
|
||
pfaendungen = self.employee_id._at_pfaendungen_aktiv(self.date_to)
|
||
if not pfaendungen:
|
||
return 0.0
|
||
if all(p.kostenersatz_variante == 'verzicht' for p in pfaendungen):
|
||
return 0.0
|
||
masse = self._at_pfaendung_einbehalt(result_rules)
|
||
if masse <= 0:
|
||
return 0.0
|
||
p = self._l10n_at_param('at_eo_kostenersatz')
|
||
erste_max = float(p['erste_max'])
|
||
weitere_max = float(p['weitere_max'])
|
||
# Erste Zahlung (erstrangig) 2 %/max 8 €, weitere 1 %/max 4 €.
|
||
kosten = min(masse * float(p['erste_rate']) / 100.0, erste_max)
|
||
if masse > kosten:
|
||
kosten += min((masse - kosten) * float(p['weitere_rate'])
|
||
/ 100.0, weitere_max)
|
||
return round(min(kosten, masse), 2)
|