c3b9d6eb8e
Satzstrukturen PDF-verifiziert aus DM-ORG 42.7.0 (ODL, Pages 493-505; Positionsketten lückenlos): G.10 AEB-Krankengeld v12 (SART 70/71, BEST AK, 111 Felder, Satzlänge 1392) und G.11 AEB-Wochengeld v10 (SART 75/76, BEST AW, 64 Felder, 1048). Die .txt-Extraktion des DM-ORG ist unzuverlässig (Feldversatz). - Neues Modell l10n.at.payroll.aeb (models/aeb.py): Episode (verhinderung_von/bis), KZKU, Storno über storno_id (E.10.2.1: nur Fehler/BMGL-Änderung), REFN/Datei/Prüfprotokoll; Erzeugung auf dem ELDA-Bestands-Framework (Vorlauf/Schluss/Satzlängen- selbstkontrolle) - EFZ-Refaktur: Phasen-Karte nach hr.version verschoben (_l10n_at_efz_arbeitsjahr_start/_l10n_at_efz_phasen/ _l10n_at_efz_stufe_am) — Payslip-Status (AP4) und AEB teilen die Maschine; _at_efz_status-Verhalten durch Tests gepinnt - Fachdaten Krankengeld: AEFZ (Anspruch in Wochen), JAGU=1 (Arbeitsjahr), TAGU=2 (Kalendertage), Teilentgelt TPR/TVO/TBI je Phase, Vorerkrankungen ANV/ANB vor der Episode, VENT (Voll-Phase-Ende), Bezugsblöcke BVO/BBI/BBE + BSUM aus den vollen Monatsläufen vor der Episode, SZKZ datengetrieben - Fachdaten Wochengeld: AVER (Ø-Netto der drei Referenzmonate, § 162 Abs 3 ASVG — AP7-Logik), AVON/ABIS, ANGV=K (kein Fortbezug), AGRD-Validierung nach D.22 (SART 75/76 nur 02/03/04/05/06/10/22/24; unzulässige geleert + Protokoll) - Fund: L10nAtEldaSatz.setze(None) übernimmt die Grundstellung nicht — Felder nur per Leerstring aktiv leeren - 4 Tests: SART 70 mit 76-Tage-Episode (AEFZ 06, TPR1 50, TVO1 13.10.2026, VENT 12.10.2026, Bezugsblock), SART 75 mit AVER/AVON/ABIS, Storno 76 (REFN -ST, Kerndaten des Originals), AGRD-Validierung
561 lines
22 KiB
Python
561 lines
22 KiB
Python
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||
"""AP8: Arbeits- und Entgeltbestätigungen (AEB) — ELDA-Bestände
|
||
``AK`` (Krankengeld, SART 70/71, Version 12) und ``AW`` (Wochengeld,
|
||
SART 75/76, Version 10), DM-ORG 42.7.0 Kapitel E.10/E.11 + G.10/G.11
|
||
(Satzstrukturen PDF-verifiziert aus der lokalen DM-ORG-Extraktion
|
||
2026-09-12; Positionsketten lückenlos geprüft: Krankengeld 111 Felder
|
||
bis Stelle 1392, Wochengeld 64 Felder bis Stelle 1048).
|
||
|
||
Fachliche Rollen (lb-krs-02 / lb-msf-03): Krankengeld und Wochengeld
|
||
zahlt die KV; die AEB ist die Bemessungsdaten-Lieferung des
|
||
Dienstgebers nach Ausschöpfung des vollen EFZ-Anspruchs (Krankengeld)
|
||
bzw. bei Mutterschutz-Frist (Wochengeld; AGRD dort nur 02/03/04/05/06/
|
||
10/22/24, D.22). Storno (E.10.2.1) nur bei Fehler oder Änderung der
|
||
Bemessungsgrundlage — sonst Korrektur per Neumeldung.
|
||
|
||
Datenquellen: EFZ-Phasen-Karte (``hr.version._l10n_at_efz_phasen``,
|
||
AP4/AP8-Refaktur) für Anspruch (AEFZ/JAGU/TAGU), Teilentgelt-Phasen
|
||
(TPR/TVO/TBI), Vorerkrankungen (ANV/ANB) und volles-Entgelt-Ende
|
||
(VENT); Payslip-Historie für die Bezugsblöcke (BVO/BBI/BBE) bzw. den
|
||
Wochengeld-Arbeitsverdienst (AVER = Ø-Netto der drei Referenzmonate,
|
||
§ 162 Abs 3 ASVG — AP7-Logik).
|
||
|
||
Grundstellung ohne Modell (dokumentierter MVP-Rest): Kündigungs-
|
||
entschädigung/Urlaubsersatzleistung, Einarbeitungs-/Feiertagsfelder,
|
||
Vordienstzeiten (§ 2 Abs 3 EFZG), Prämien-/Provisionsausweis —
|
||
Prüfkatalog-Verifikation im Parallellauf (Prämisse P7)."""
|
||
import base64
|
||
from datetime import timedelta
|
||
|
||
from odoo import fields, models
|
||
from odoo.exceptions import UserError
|
||
|
||
from odoo.addons.l10n_at_hr_payroll.models.elda import (
|
||
L10N_AT_ELDA_IDTEIL,
|
||
L10nAtEldaBestand,
|
||
L10nAtEldaDatei,
|
||
L10nAtEldaSatz,
|
||
)
|
||
|
||
from odoo.addons.l10n_at_hr_payroll.models.lohnzettel_l16 import \
|
||
l10n_at_namen
|
||
|
||
# ------------------------------------------------------------------
|
||
# G.10 Arbeits- und Entgeltbestätigung für Krankengeld – Version 12
|
||
# (SART 70/71) — nach IDTEIL, Satzlänge 1392.
|
||
# ------------------------------------------------------------------
|
||
AEB_KRANKENGELD_SATZ = [
|
||
(2, 'BKNR', 21, 10, 'a'),
|
||
(3, 'DGNA', 31, 70, 'a'),
|
||
(4, 'DTEL', 101, 50, 'a'),
|
||
(5, 'WOBD', 151, 12, 'a'),
|
||
(6, 'ZOBD', 163, 15, 'a'),
|
||
(7, 'VSNR', 178, 10, 'n'),
|
||
(8, 'GEBD', 188, 8, 'n'),
|
||
(9, 'FANA', 196, 70, 'a'),
|
||
(10, 'VONA', 266, 70, 'a'),
|
||
(11, 'AKGR', 336, 30, 'a'),
|
||
(12, 'WFKZ', 366, 3, 'a'),
|
||
(13, 'PLZL', 369, 9, 'a'),
|
||
(14, 'WORT', 378, 40, 'a'),
|
||
(15, 'STRA', 418, 50, 'a'),
|
||
(16, 'BEAB', 468, 8, 'n'),
|
||
(17, 'KABE', 476, 1, 'n'),
|
||
(18, 'TAET', 477, 30, 'a'),
|
||
(19, 'BTAG', 507, 1, 'n'),
|
||
(20, 'TATU', 508, 1, 'a'),
|
||
(21, 'LTAG', 509, 8, 'n'),
|
||
(22, 'GRUN', 517, 30, 'a'),
|
||
(23, 'KZKU', 547, 1, 'a'),
|
||
(24, 'ARFT', 548, 7, 'a'),
|
||
(25, 'ARTX', 555, 25, 'a'),
|
||
(26, 'BLOE', 580, 1, 'a'),
|
||
(27, 'EBSV', 581, 8, 'n'),
|
||
(28, 'IBAN', 589, 34, 'a'),
|
||
(29, 'BIC', 623, 11, 'a'),
|
||
(30, 'BVO1', 634, 8, 'n'),
|
||
(31, 'BBI1', 642, 8, 'n'),
|
||
(32, 'BBE1', 650, 9, 'n'),
|
||
(33, 'BVO2', 659, 8, 'n'),
|
||
(34, 'BBI2', 667, 8, 'n'),
|
||
(35, 'BBE2', 675, 9, 'n'),
|
||
(36, 'BVO3', 684, 8, 'n'),
|
||
(37, 'BBI3', 692, 8, 'n'),
|
||
(38, 'BBE3', 700, 9, 'n'),
|
||
(39, 'BVO4', 709, 8, 'n'),
|
||
(40, 'BBI4', 717, 8, 'n'),
|
||
(41, 'BBE4', 725, 9, 'n'),
|
||
(42, 'BSUM', 734, 9, 'n'),
|
||
(43, 'SZKZ', 743, 1, 'a'),
|
||
(44, 'SBGB', 744, 1, 'a'),
|
||
(45, 'SBZT', 745, 24, 'a'),
|
||
(46, 'KEAB', 769, 8, 'n'),
|
||
(47, 'KEBI', 777, 8, 'n'),
|
||
(48, 'UEAB', 785, 8, 'n'),
|
||
(49, 'UEBI', 793, 8, 'n'),
|
||
(50, 'VENT', 801, 8, 'n'),
|
||
(51, 'AEFZ', 809, 2, 'n'),
|
||
(52, 'JAGU', 811, 1, 'n'),
|
||
(53, 'TAGU', 812, 1, 'n'),
|
||
(54, 'TPR1', 813, 2, 'n'),
|
||
(55, 'TVO1', 815, 8, 'n'),
|
||
(56, 'TBI1', 823, 8, 'n'),
|
||
(57, 'TPR2', 831, 2, 'n'),
|
||
(58, 'TVO2', 833, 8, 'n'),
|
||
(59, 'TBI2', 841, 8, 'n'),
|
||
(60, 'ANV1', 849, 8, 'n'),
|
||
(61, 'ANB1', 857, 8, 'n'),
|
||
(62, 'ANV2', 865, 8, 'n'),
|
||
(63, 'ANB2', 873, 8, 'n'),
|
||
(64, 'ANV3', 881, 8, 'n'),
|
||
(65, 'ANB3', 889, 8, 'n'),
|
||
(66, 'ANV4', 897, 8, 'n'),
|
||
(67, 'ANB4', 905, 8, 'n'),
|
||
(68, 'ANV5', 913, 8, 'n'),
|
||
(69, 'ANB5', 921, 8, 'n'),
|
||
(70, 'ANV6', 929, 8, 'n'),
|
||
(71, 'ANB6', 937, 8, 'n'),
|
||
(72, 'FAN2', 945, 70, 'a'),
|
||
(73, 'VON2', 1015, 70, 'a'),
|
||
(74, 'AKG2', 1085, 30, 'a'),
|
||
(75, 'AGRD', 1115, 2, 'a'),
|
||
(76, 'VOAB', 1117, 8, 'n'),
|
||
(77, 'VOBI', 1125, 8, 'n'),
|
||
(78, 'FEAB', 1133, 8, 'n'),
|
||
(79, 'FEBI', 1141, 8, 'n'),
|
||
(80, 'EIN1', 1149, 8, 'n'),
|
||
(81, 'EIN2', 1157, 8, 'n'),
|
||
(82, 'EIN3', 1165, 8, 'n'),
|
||
(83, 'EIN4', 1173, 8, 'n'),
|
||
(84, 'EIN5', 1181, 8, 'n'),
|
||
(85, 'EIN6', 1189, 8, 'n'),
|
||
(86, 'REFN', 1197, 30, 'a'),
|
||
(87, 'APFT1', 1227, 8, 'n'),
|
||
(88, 'APFT2', 1235, 8, 'n'),
|
||
(89, 'APFT3', 1243, 8, 'n'),
|
||
(90, 'APFT4', 1251, 8, 'n'),
|
||
(91, 'APFT5', 1259, 8, 'n'),
|
||
(92, 'APFT6', 1267, 8, 'n'),
|
||
(93, 'APFT7', 1275, 8, 'n'),
|
||
(94, 'APFT8', 1283, 8, 'n'),
|
||
(95, 'APFT9', 1291, 8, 'n'),
|
||
(96, 'APFT10', 1299, 8, 'n'),
|
||
(97, 'ANV7', 1307, 8, 'n'),
|
||
(98, 'ANB7', 1315, 8, 'n'),
|
||
(99, 'ANV8', 1323, 8, 'n'),
|
||
(100, 'ANB8', 1331, 8, 'n'),
|
||
(101, 'ANV9', 1339, 8, 'n'),
|
||
(102, 'ANB9', 1347, 8, 'n'),
|
||
(103, 'ANV10', 1355, 8, 'n'),
|
||
(104, 'ANB10', 1363, 8, 'n'),
|
||
(105, 'GEGC', 1371, 2, 'a'),
|
||
(106, 'GEGT', 1373, 8, 'a'),
|
||
(107, 'ARLO', 1381, 1, 'a'),
|
||
(108, 'SZUM', 1382, 1, 'a'),
|
||
(109, 'SBUM', 1383, 1, 'a'),
|
||
(110, 'SBKZ', 1384, 1, 'a'),
|
||
(111, 'ALBV', 1385, 8, 'n'),
|
||
]
|
||
|
||
# ------------------------------------------------------------------
|
||
# G.11 Arbeits- und Entgeltbestätigung für Wochengeld – Version 10
|
||
# (SART 75/76) — nach IDTEIL, Satzlänge 1048.
|
||
# ------------------------------------------------------------------
|
||
AEB_WOCHENGELD_SATZ = [
|
||
(2, 'BKNR', 21, 10, 'a'),
|
||
(3, 'DGNA', 31, 70, 'a'),
|
||
(4, 'DTEL', 101, 50, 'a'),
|
||
(5, 'WOBD', 151, 12, 'a'),
|
||
(6, 'ZOBD', 163, 15, 'a'),
|
||
(7, 'VSNR', 178, 10, 'n'),
|
||
(8, 'GEBD', 188, 8, 'n'),
|
||
(9, 'FANA', 196, 70, 'a'),
|
||
(10, 'VONA', 266, 70, 'a'),
|
||
(11, 'AKGR', 336, 30, 'a'),
|
||
(12, 'WKFZ', 366, 3, 'a'),
|
||
(13, 'PLZL', 369, 9, 'a'),
|
||
(14, 'WORT', 378, 40, 'a'),
|
||
(15, 'STRA', 418, 50, 'a'),
|
||
(16, 'BEAB', 468, 8, 'n'),
|
||
(17, 'KABE', 476, 1, 'n'),
|
||
(18, 'TAET', 477, 30, 'a'),
|
||
(19, 'BTAG', 507, 1, 'n'),
|
||
(20, 'TATU', 508, 1, 'a'),
|
||
(21, 'LTAG', 509, 8, 'n'),
|
||
(22, 'GRUN', 517, 30, 'a'),
|
||
(23, 'BLOE', 547, 1, 'a'),
|
||
(24, 'AGRD', 548, 2, 'a'),
|
||
(25, 'EBSV', 550, 8, 'n'),
|
||
(26, 'RESE', 558, 8, 'a'),
|
||
(27, 'PRAB', 566, 8, 'n'),
|
||
(28, 'URLV', 574, 8, 'n'),
|
||
(29, 'URLB', 582, 8, 'n'),
|
||
(30, 'IBAN', 590, 34, 'a'),
|
||
(31, 'BIC', 624, 11, 'a'),
|
||
(32, 'AVER', 635, 9, 'n'),
|
||
(33, 'AVON', 644, 8, 'n'),
|
||
(34, 'ABIS', 652, 8, 'n'),
|
||
(35, 'SBZT', 660, 24, 'a'),
|
||
(36, 'UNV1', 684, 8, 'n'),
|
||
(37, 'UNB1', 692, 8, 'n'),
|
||
(38, 'UNV2', 700, 8, 'n'),
|
||
(39, 'UNB2', 708, 8, 'n'),
|
||
(40, 'UNV3', 716, 8, 'n'),
|
||
(41, 'UNB3', 724, 8, 'n'),
|
||
(42, 'UNV4', 732, 8, 'n'),
|
||
(43, 'UNB4', 740, 8, 'n'),
|
||
(44, 'SZKZ', 748, 1, 'a'),
|
||
(45, 'ANGV', 749, 1, 'a'),
|
||
(46, 'AHEB', 750, 8, 'n'),
|
||
(47, 'AMEB', 758, 8, 'n'),
|
||
(48, 'FAN2', 766, 70, 'a'),
|
||
(49, 'VON2', 836, 70, 'a'),
|
||
(50, 'AKG2', 906, 30, 'a'),
|
||
(51, 'SZMO', 936, 4, 'n'),
|
||
(52, 'SZWO', 940, 4, 'n'),
|
||
(53, 'SBZW', 944, 1, 'a'),
|
||
(54, 'SBZE', 945, 1, 'a'),
|
||
(55, 'UNV5', 946, 8, 'n'),
|
||
(56, 'UNB5', 954, 8, 'n'),
|
||
(57, 'UNV6', 962, 8, 'n'),
|
||
(58, 'UNB6', 970, 8, 'n'),
|
||
(59, 'UNV7', 978, 8, 'n'),
|
||
(60, 'UNB7', 986, 8, 'n'),
|
||
(61, 'UNV8', 994, 8, 'n'),
|
||
(62, 'UNB8', 1002, 8, 'n'),
|
||
(63, 'AVER1', 1010, 9, 'n'),
|
||
(64, 'REFN', 1019, 30, 'a'),
|
||
]
|
||
|
||
# SART 75/76: zulaessige Abmeldegruende (DM-ORG D.22).
|
||
_WOCHENGELD_AGRD = ('02', '03', '04', '05', '06', '10', '22', '24')
|
||
|
||
|
||
def _monats_laenge(monat):
|
||
"""Letzter Kalendertag des Monats."""
|
||
naechster = (monat.replace(day=28) + timedelta(days=4)).replace(
|
||
day=1)
|
||
return (naechster - timedelta(days=1)).day
|
||
|
||
|
||
class L10nAtPayrollAeb(models.Model):
|
||
_name = 'l10n.at.payroll.aeb'
|
||
_description = 'Arbeits- und Entgeltbestätigung (Krankengeld/Wochengeld)'
|
||
_inherit = ['mail.thread']
|
||
_order = 'id desc'
|
||
|
||
company_id = fields.Many2one(
|
||
'res.company', required=True,
|
||
default=lambda self: self.env.company, index=True)
|
||
art = fields.Selection(
|
||
[('krankengeld', 'Krankengeld (BEST AK, SART 70/71)'),
|
||
('wochengeld', 'Wochengeld (BEST AW, SART 75/76)')],
|
||
required=True, tracking=True)
|
||
employee_id = fields.Many2one('hr.employee', required=True)
|
||
kzku = fields.Selection(
|
||
[('1', 'Krankheit/Unglücksfall'),
|
||
('2', 'Arbeitsunfall/Berufskrankheit')],
|
||
string='Kennzeichen (KZKU)', default='1')
|
||
verhinderung_von = fields.Date(
|
||
string='Verhinderung ab (Episode)', required=True,
|
||
help='Beginn der zusammenhängenden Arbeitsunfähigkeit '
|
||
'(Krankengeld) bzw. der Mutterschutz-Frist (Wochengeld) — '
|
||
'Bezugszeitraum der Bestätigung.')
|
||
verhinderung_bis = fields.Date(
|
||
string='Verhinderung bis (bekannt)',
|
||
help='Letzter bekannter Verhinderungstag; leer = laufend.')
|
||
storno_id = fields.Many2one(
|
||
'l10n.at.payroll.aeb', string='Stornierte Bestätigung',
|
||
help='Storno (SART 71/76) — nur bei Fehler oder Änderung der '
|
||
'Bemessungsgrundlage (E.10.2.1); sonst Korrektur per '
|
||
'Neumeldung (SART 70/75).')
|
||
refn = fields.Char(string='Referenznummer (REFN)', readonly=True,
|
||
copy=False)
|
||
datei = fields.Binary(attachment=True, copy=False)
|
||
datei_name = fields.Char(copy=False)
|
||
protokoll = fields.Text(string='Prüfprotokoll')
|
||
state = fields.Selection(
|
||
[('draft', 'Entwurf'), ('done', 'Erzeugt')],
|
||
default='draft', required=True, tracking=True)
|
||
|
||
# ------------------------------------------------------------------
|
||
# Datenaufbereitung
|
||
# ------------------------------------------------------------------
|
||
|
||
def _aeb_version(self):
|
||
return self.employee_id.version_id
|
||
|
||
def _aeb_referenz_slips(self, bis, monate):
|
||
"""Die bis zu ``monate`` jüngsten vollständigen validierten
|
||
Monatsläufe vor ``bis`` (älteste zuerst)."""
|
||
self.ensure_one()
|
||
slips = self.env['hr.payslip'].search([
|
||
('employee_id', '=', self.employee_id.id),
|
||
('state', 'in', ('validated', 'paid')),
|
||
('date_to', '<', bis),
|
||
], order='date_from desc', limit=monate).filtered(
|
||
lambda s: s.date_from.day == 1
|
||
and s.date_to.day == _monats_laenge(s.date_from))
|
||
return slips.sorted('date_from')
|
||
|
||
def _aeb_monats_brutto(self, slip):
|
||
"""Monats-Bezug (Brutto inkl. SZ-Anteil) eines Laufs."""
|
||
return slip._l10n_at_brutto('laufend') \
|
||
+ slip._l10n_at_brutto('sonstig')
|
||
|
||
def _aeb_szkz(self):
|
||
"""SZKZ: Anspruch auf Sonderzahlung — datengetrieben aus den
|
||
sonstigen (§-67-)Bezügen irgendeines validierten Laufs des
|
||
AN (B13/14-Struktur des Referenz-KVs; die reinen
|
||
Referenzmonate können sz-frei sein)."""
|
||
self.ensure_one()
|
||
slips = self.env['hr.payslip'].search([
|
||
('employee_id', '=', self.employee_id.id),
|
||
('state', 'in', ('validated', 'paid')),
|
||
], limit=24)
|
||
return 'J' if any(s._l10n_at_brutto('sonstig') for s in slips) \
|
||
else 'N'
|
||
|
||
def _aeb_wochenplan(self):
|
||
"""ARFT: 'A' je Arbeitstag, 'F' sonst (Montag–Sonntag)."""
|
||
self.ensure_one()
|
||
kalender = self._aeb_version().resource_calendar_id
|
||
tage = {int(att.dayofweek)
|
||
for att in kalender.attendance_ids} \
|
||
if kalender else {0, 1, 2, 3, 4}
|
||
return ''.join('A' if d in tage else 'F' for d in range(7))
|
||
|
||
def _aeb_ident_setzen(self, satz, wfkz_feld):
|
||
"""Identifikations- und Beschäftigungsfelder beider
|
||
Satzarten. ``wfkz_feld``: 'WFKZ' (Krankengeld) bzw. 'WKFZ'
|
||
(Wochengeld) — die Feldnamen weichen ab (G.10/G.11)."""
|
||
self.ensure_one()
|
||
version = self._aeb_version()
|
||
company = self.company_id
|
||
employee = self.employee_id
|
||
familienname, vorname = l10n_at_namen(employee)
|
||
satz.setze('BKNR', company.l10n_at_beitragskontonummer)
|
||
satz.setze('DGNA', (company.name or '')[:70])
|
||
satz.setze('DTEL', (company.partner_id.phone or '')[:50])
|
||
satz.setze('VSNR', version.ssnid)
|
||
satz.setze_datum('GEBD', employee.birthday)
|
||
satz.setze('FANA', familienname[:70])
|
||
satz.setze('VONA', (vorname or '')[:70])
|
||
satz.setze_datum('BEAB', version.contract_date_start)
|
||
satz.setze('KABE', '4' if version.l10n_at_lehrling else (
|
||
'1' if getattr(version, 'l10n_at_personalgruppe', '')
|
||
== 'arbeiter' else '2'))
|
||
satz.setze('TAET', (employee.job_id.name or '')[:30])
|
||
plan = self._aeb_wochenplan()
|
||
satz.setze('BTAG', str(plan.count('A')))
|
||
satz.setze('TATU', str(min(plan.count('A'), 6)))
|
||
# Wohnort (D.12): A = Österreich
|
||
satz.setze(wfkz_feld, 'A')
|
||
satz.setze('PLZL', (version.private_zip or '')[:9])
|
||
satz.setze('WORT', (version.private_city or '')[:40])
|
||
satz.setze('STRA', (version.private_street or '')[:50])
|
||
if version.contract_date_end:
|
||
satz.setze('BLOE', 'G')
|
||
satz.setze_datum('EBSV', version.contract_date_end)
|
||
agrd = getattr(version.departure_reason_id, 'l10n_at_agrd',
|
||
False)
|
||
if agrd:
|
||
satz.setze('AGRD', agrd)
|
||
satz.setze_datum('LTAG', version.contract_date_end)
|
||
else:
|
||
satz.setze('BLOE', 'N')
|
||
bank = employee.primary_bank_account_id
|
||
if bank:
|
||
satz.setze('IBAN', (bank.acc_number or '')[:34])
|
||
if bank.bank_id:
|
||
satz.setze('BIC', (bank.bank_id.bic or '')[:11])
|
||
|
||
# ------------------------------------------------------------------
|
||
# Fachdaten
|
||
# ------------------------------------------------------------------
|
||
|
||
def _aeb_fuelle_krankengeld(self, satz, fehler):
|
||
"""SART 70: EFZ-Anspruch (AEFZ/JAGU/TAGU), Teilentgelt-Phase,
|
||
Vorerkrankungen, Bezugsblöcke, VENT (Phasen-Karte AP4)."""
|
||
self.ensure_one()
|
||
version = self._aeb_version()
|
||
episode_von = self.verhinderung_von
|
||
aj_start = version._l10n_at_efz_arbeitsjahr_start(episode_von)
|
||
if not aj_start:
|
||
fehler.append('Kein Vertragsbeginn — EFZ-Daten leer.')
|
||
return
|
||
endtag = self.verhinderung_bis or fields.Date.context_today(self)
|
||
kontext = version._l10n_at_efz_phasen(
|
||
aj_start, max(endtag, episode_von))
|
||
phasen = kontext['phasen']
|
||
satz.setze('KZKU', self.kzku or '1')
|
||
satz.setze('ARFT', self._aeb_wochenplan())
|
||
wochen = version._l10n_at_efz_stufe_am(episode_von)
|
||
if wochen:
|
||
satz.setze('AEFZ', '%02d' % wochen)
|
||
satz.setze('JAGU', '1') # Arbeitsjahr (lb-krs-08)
|
||
satz.setze('TAGU', '2') # Kalendertage
|
||
satz.setze('SBKZ', 'N') # kein Sachbezug modelliert
|
||
|
||
def bloecke(phase=None, filter_ab=None):
|
||
"""Zusammenhängende Kalendertag-Blöcke einer Phase."""
|
||
ergebnis = []
|
||
vorher = None
|
||
for tag in sorted(phasen):
|
||
if filter_ab is not None and tag >= filter_ab:
|
||
break
|
||
if phase is not None and phasen[tag] != phase:
|
||
vorher = None
|
||
continue
|
||
if vorher is not None and (tag - vorher).days == 1:
|
||
ergebnis[-1][1] = tag
|
||
else:
|
||
ergebnis.append([tag, tag])
|
||
vorher = tag
|
||
return ergebnis
|
||
|
||
# VENT: Ende der zusammenhängenden Voll-Phase ab Episode.
|
||
voll = bloecke(phase='voll')
|
||
voll_der_episode = [b for b in voll if b[0] >= episode_von]
|
||
if voll_der_episode:
|
||
satz.setze_datum('VENT', voll_der_episode[0][1])
|
||
# Teilentgelt: bis zu zwei Halb-Phasen (TPR/TVO/TBI).
|
||
for index, (von, bis) in enumerate(bloecke(phase='halb')[:2]):
|
||
satz.setze('TPR%d' % (index + 1), 50)
|
||
satz.setze_datum('TVO%d' % (index + 1), von)
|
||
satz.setze_datum('TBI%d' % (index + 1), bis)
|
||
# Vorerkrankungen (ANV/ANB): Blöcke vor der Episode
|
||
# (vorangehende Arbeitsunfähigkeiten, E.10.2.7).
|
||
for index, (von, bis) in enumerate(bloecke(filter_ab=episode_von)[:10]):
|
||
satz.setze_datum('ANV%d' % (index + 1), von)
|
||
satz.setze_datum('ANB%d' % (index + 1), bis)
|
||
# Bezugsblöcke: bis zu 4 volle Monatsläufe vor Episode.
|
||
slips = self._aeb_referenz_slips(episode_von, 4)[:4]
|
||
summe = 0.0
|
||
for index, slip in enumerate(slips):
|
||
betrag = self._aeb_monats_brutto(slip)
|
||
summe += betrag
|
||
satz.setze_datum('BVO%d' % (index + 1), slip.date_from)
|
||
satz.setze_datum('BBI%d' % (index + 1), slip.date_to)
|
||
satz.setze('BBE%d' % (index + 1), betrag, cent=True)
|
||
if summe:
|
||
satz.setze('BSUM', summe, cent=True)
|
||
satz.setze('SZKZ', self._aeb_szkz())
|
||
|
||
def _aeb_fuelle_wochengeld(self, satz, fehler):
|
||
"""SART 75: Arbeitsverdienst netto der letzten drei
|
||
Kalendermonate vor der Frist (§ 162 Abs 3 ASVG — AP7-Logik),
|
||
Fortbezug, AGRD-Kontrolle (D.22)."""
|
||
self.ensure_one()
|
||
von = self.verhinderung_von
|
||
referenz = self._aeb_referenz_slips(von, 3)
|
||
if referenz:
|
||
netto = sum(sum(s.line_ids.filtered(
|
||
lambda l: l.code == 'NET').mapped('total'))
|
||
for s in referenz)
|
||
satz.setze('AVER', netto, cent=True)
|
||
satz.setze_datum('AVON', referenz[0].date_from)
|
||
satz.setze_datum('ABIS', referenz[-1].date_to)
|
||
else:
|
||
fehler.append(
|
||
'Keine vollständigen Referenzläufe für den '
|
||
'Arbeitsverdienst (§ 162 Abs 3 ASVG).')
|
||
satz.setze('SZKZ', self._aeb_szkz())
|
||
satz.setze('ANGV', 'K') # kein Fortbezug des Entgelts (AP7)
|
||
agrd = satz.wert('AGRD')
|
||
if agrd and agrd not in _WOCHENGELD_AGRD:
|
||
fehler.append(
|
||
'Abmeldegrund %s ist für SART 75/76 unzulässig '
|
||
'(D.22: %s) — Feld geleert.' % (
|
||
agrd, ', '.join(_WOCHENGELD_AGRD)))
|
||
# setze(None) übernähme die Grundstellung nicht — Feld
|
||
# aktiv mit Blank leeren.
|
||
satz.setze('AGRD', '')
|
||
|
||
# ------------------------------------------------------------------
|
||
# Dateierzeugung
|
||
# ------------------------------------------------------------------
|
||
|
||
def action_erzeugen(self):
|
||
import datetime as datetime_modul
|
||
for aeb in self:
|
||
fehler = []
|
||
company = aeb.company_id
|
||
bknr = company.l10n_at_beitragskontonummer
|
||
if not bknr:
|
||
raise UserError('Beitragskontonummer (BKNR) fehlt.')
|
||
version = aeb._aeb_version()
|
||
if not version.ssnid:
|
||
raise UserError(
|
||
'Mitarbeiter %s ohne Versicherungsnummer.'
|
||
% aeb.employee_id.name)
|
||
storno = bool(aeb.storno_id)
|
||
if aeb.art == 'krankengeld':
|
||
satzart = '71' if storno else '70'
|
||
struktur = L10N_AT_ELDA_IDTEIL + AEB_KRANKENGELD_SATZ
|
||
laenge, bestand, vers = 1392, 'AK', '12'
|
||
praefix, wfkz_feld = 'AEBK', 'WFKZ'
|
||
else:
|
||
satzart = '76' if storno else '75'
|
||
struktur = L10N_AT_ELDA_IDTEIL + AEB_WOCHENGELD_SATZ
|
||
laenge, bestand, vers = 1048, 'AW', '10'
|
||
praefix, wfkz_feld = 'AEBW', 'WKFZ'
|
||
satz = L10nAtEldaSatz(
|
||
struktur, laenge,
|
||
bezeichnung='AEB %s' % aeb.employee_id.name)
|
||
satz.setze('SART', satzart)
|
||
stichtag = aeb.verhinderung_bis \
|
||
or datetime_modul.date.today()
|
||
aeb._aeb_ident_setzen(satz, wfkz_feld)
|
||
if storno:
|
||
# Storno (E.10.2.1): Kerndaten der stornierten
|
||
# Bestätigung übernehmen, REFN unten neu setzen.
|
||
original = aeb.storno_id
|
||
satz.setze('VSNR',
|
||
original._aeb_version().ssnid)
|
||
satz.setze_datum('GEBD', original.employee_id.birthday)
|
||
familienname, vorname = l10n_at_namen(
|
||
original.employee_id)
|
||
satz.setze('FANA', familienname[:70])
|
||
satz.setze('VONA', (vorname or '')[:70])
|
||
satz.setze_datum('BEAB',
|
||
original._aeb_version()
|
||
.contract_date_start)
|
||
elif aeb.art == 'krankengeld':
|
||
aeb._aeb_fuelle_krankengeld(satz, fehler)
|
||
else:
|
||
aeb._aeb_fuelle_wochengeld(satz, fehler)
|
||
if not aeb.refn:
|
||
aeb.refn = '%s-%d-%s-%d%s' % (
|
||
praefix, company.id,
|
||
aeb.verhinderung_von.strftime('%Y%m%d'),
|
||
aeb.employee_id.id, '-ST' if storno else '')
|
||
satz.setze('REFN', aeb.refn[:30])
|
||
vstr = (bknr or '')[:2]
|
||
bestands_objekt = L10nAtEldaBestand(
|
||
bestand, vstr, vers,
|
||
hersteller={'name': 'odoo-at-payroll', 'kfz': 'A',
|
||
'plz': company.partner_id.zip,
|
||
'ort': company.partner_id.city,
|
||
'strasse': company.partner_id.street},
|
||
datensaetze=[satz])
|
||
datei = L10nAtEldaDatei([bestands_objekt])
|
||
roh = datei.render()
|
||
# Selbstkontrolle: Datensatz-Satzlaenge (nach Header,
|
||
# Vorlauf; vor Schluss).
|
||
datensaetze = roh.decode('ISO-8859-15') \
|
||
.strip('\r\n').split('\r\n')
|
||
if len(datensaetze[2]) != laenge:
|
||
fehler.append(
|
||
'Satzlängenkontrolle: %d != %d'
|
||
% (len(datensaetze[2]), laenge))
|
||
aeb.write({
|
||
'datei': base64.b64encode(roh).decode(),
|
||
'datei_name': '%s_%s.txt' % (
|
||
praefix.lower(),
|
||
aeb.verhinderung_von.strftime('%Y%m%d')),
|
||
'protokoll': 'WARNUNGEN:\n%s' % (
|
||
'\n'.join(fehler) or '—'),
|
||
'state': 'done',
|
||
})
|
||
return True |