Compare commits
2 Commits
27f3b4b7f9
...
1df5fb19c4
| Author | SHA1 | Date | |
|---|---|---|---|
| 1df5fb19c4 | |||
| efe76e6b3d |
@@ -2,6 +2,77 @@
|
|||||||
|
|
||||||
## Current focus
|
## Current focus
|
||||||
|
|
||||||
|
**Modul l10n_at_payroll_dokumente (Dokumentgenerierung) umgesetzt — 236/236 Tests grün**
|
||||||
|
(2026-09-16, uncommittet bis Nutzerfreigabe; Modul **19.0.1.0.0**):
|
||||||
|
|
||||||
|
- **Anforderung (Nutzer):** Muster → Dokumente aus Stammdaten (Dienstvertrag,
|
||||||
|
Kündigung, DSE …). Nutzer-Entscheidungen: neues Modul; DSE = vorhandene
|
||||||
|
Pfändungs-DSE (kein Neubau); **eigene Texte nach Muster-Struktur** (kb_ref/
|
||||||
|
quell_stand je Vorlage dokumentiert, kein Lexis-Volltext im Repo —
|
||||||
|
Lizenzthema damit fürs Produkt gelöst; Muster werden laufend ergänzt,
|
||||||
|
kein Warten nötig: neue Vorlagen = Datenarbeit).
|
||||||
|
- **Bau:** Modell `l10n.at.payroll.dokument.vorlage` (+ feld): eigener Text
|
||||||
|
`inhalt_html` (Html, sanitize=False), Feldliste (auto/manuell, pflicht),
|
||||||
|
kb_ref/quell_stand/rechtsstatus (entwurf|geprueft), company + Rule.
|
||||||
|
Wizard `l10n.at.payroll.dokument.wizard` (+ Zeilen-Transient): Stichtag →
|
||||||
|
hr.version (date_version <= Stichtag!), Auto-Resolver (firma/firma_strasse/
|
||||||
|
firma_plz/firma_ort/mitarbeiter/mitarbeiter_strasse/plz/ort/ead/funktion/
|
||||||
|
geboren), Manuell-Eingaben, Pflicht-UserError, Vorschau, PDF via
|
||||||
|
`ir.actions.report._render_qweb_pdf` **mit HTML-Fallback** ohne
|
||||||
|
wkhtmltopdf (Anhang am hr.employee). Generisches QWeb-Print-Layout
|
||||||
|
(AbstractModel + data['body'] als Markup, wie DSE-Muster).
|
||||||
|
- **⚠️ Rendering-Weg (wichtig, abweichend vom Plan):** NICHT
|
||||||
|
`mail.render.mixin._render_template` (engine inline_template) — deren
|
||||||
|
Ausdrucks-Whitelist (`mail_allowed_qweb_expressions`, nur object.name u.ä.)
|
||||||
|
sperrt `werte['…']`-Ausdrücke für Nicht-Template-Editoren (AccessError).
|
||||||
|
Stattdessen: `rendering_tools.convert_inline_template_to_qweb` →
|
||||||
|
`ir.qweb._render(lxml html.fragment_fromstring(src, create_parent='div'), …)`
|
||||||
|
+ Wrapper-[5:-6]-Strip (Muster `_render_template_qweb`). Autor-Syntax
|
||||||
|
bleibt identisch; **t-out escapt Werte automatisch** (kein manuelles
|
||||||
|
Escaping). t-if-Blöcke für optionale Absätze sind möglich (QWeb direkt).
|
||||||
|
- **Pilotvorlagen (eigen formuliert, Rechtsstatus 'entwurf'):** DV
|
||||||
|
Angestellte (lb-bes-09), Kündigung DG (lb-bnd-55), einvernehmliche
|
||||||
|
Auflösung (lb-bnd-52/62), Vordienstzeitenbestätigung (lb-end-35).
|
||||||
|
§-Verifikation: § 27 AngG (wichtiger Grund) RIS ✅; **§ 34 AngG =
|
||||||
|
Verjährung** (nicht Auflösung!) — Auflösungstexte zitieren bewusst keine §§.
|
||||||
|
- **Tests 8** (Auto-Werte, Platzhalter-Ersetzung, Pflicht-UserError,
|
||||||
|
Stichtags-Versionen, Anhang PDF/HTML, generisches Layout via
|
||||||
|
`_render_qweb_html` ohne wkhtmltopdf, ACL hr.group_hr_user vs. base,
|
||||||
|
Pilotvorlagen E2E). Suite **236/236** (73 Kern + 18 GemBG + 199 Privat
|
||||||
|
+ 10 Dokumente) frischer -i-Lauf.
|
||||||
|
- Offen: DSE-Pfändung-Link ins Dokumente-Menü (kosmetisch), mehr Vorlagen
|
||||||
|
nach Nutzermustererweiterung, Produktivsetzung erst nach rechtlicher
|
||||||
|
Prüfung (rechtsstatus je Vorlage).
|
||||||
|
|
||||||
|
**Erneuter Abgleich Modul ↔ Wissensbasis (2026-09-16, Read-only, Grep-Verifikation am Code):**
|
||||||
|
Basis PLAN-Restluecken-AP11 (Abgleich 2026-09-12, 601 Einträge) + Zwischenstand AP8/AP11–AP16.
|
||||||
|
Befund:
|
||||||
|
- **Geplante Reste unverändert offen (0 Code-Treffer je Thema):** AP17 (L17, § 109a/
|
||||||
|
§ 109b-Meldung, mBGM Geringfügigen-Jahreszahlung 2,5 %), AP18 (NSchG-Monatszähler,
|
||||||
|
Schwerarbeitsmeldung — ASchV/NSchG-Beschaffungsgate), AP19 (GF/Vorstand-Flag,
|
||||||
|
Ferialpraktikanten, DVH-Sonderfälle, Präsenzdienst-BV-Fiktion), AP20+-Optionen
|
||||||
|
(ATZ, Kurzarbeit, Betriebspension/Beteiligung, Entsendung), Quer-LSt: FB+/ANV-
|
||||||
|
Automatik, Prämien-Anspruchsprinzip (A3-blockiert), 2027-Werte.
|
||||||
|
- **Zwischenzeitlich ✅ (Code-verifiziert):** All-in § 2g AVRAG (GP2-Deckungsrechnung,
|
||||||
|
payslip_private `_at_allin_deckung`), Fallweise-Felder + mBGM G3/G4 tagesgenau,
|
||||||
|
AVAB/AVB-Selection, Nachzahlungspfade (SV/LSt/Endabrechnung), Tod des DN =
|
||||||
|
Abgangsursache 13, §§ 23f/§ 47-Übertritte in abfertigung_private.
|
||||||
|
- **1 NEUER Restpunkt aus Batch 10:** **Ausbildungskostenrückersatz** (lb-end-27/28,
|
||||||
|
Checkliste + Muster) — 0 Code-Treffer, auch nicht im AP11-Plan erfasst →
|
||||||
|
als Zusatzpunkt in AP14/Endabrechnung (Scope-Gate Pilotmandant) aufnehmen.
|
||||||
|
- Batch 10 sonst: keine neuen Cluster, keine neuen Berechnungslogiken (Muster/
|
||||||
|
Checklisten/Übersichten); lb-end-30/31 (freiwillige Abf./Voll-Übertritt) und
|
||||||
|
lb-end-29 (Zeitschulden) abgedeckt; lb-grz-09..11/lb-swa-06 stützen AP18/AP20.
|
||||||
|
- KB-interne offene Punkte unverändert: A3 „Formel 7“, A4 All-In-Oberwert,
|
||||||
|
A7-2026-GFG, C7-2027-Werte, B3 Pensionisten (VwGH), ASchV/NSchG-Beschaffung.
|
||||||
|
- **Nachgetragen 2026-09-16 (Nutzer):** Ausbildungskostenrückersatz in
|
||||||
|
`PLAN-Restluecken-AP11.md` (AP14-Umsetzung/Rechtsanker + Lücken-Liste 2.4).
|
||||||
|
- **Neue Anfrage (2026-09-16, ERLEDIGT — siehe Modul-Block oben):**
|
||||||
|
`.lexis360/muster/` für Dokumente in Odoo nutzen → Modul
|
||||||
|
`l10n_at_payroll_dokumente` umgesetzt; Nutzer entschieden: eigene Texte
|
||||||
|
nach Muster-Struktur (kein Lexis-Volltext im Repo), Lizenz-Redistribution
|
||||||
|
damit fürs Produkt gelöst; Sammlung wird laufend ergänzt (kein Warten).
|
||||||
|
|
||||||
**Wissensbasis Batch 10 (3 neue Lexis-Subordner) ABGESCHLOSSEN — 64/64 kuratiert, `--registry` + `--check` grün**
|
**Wissensbasis Batch 10 (3 neue Lexis-Subordner) ABGESCHLOSSEN — 64/64 kuratiert, `--registry` + `--check` grün**
|
||||||
(2026-09-16, Nutzerfreigabe "bitte zur wissensbasis hinzufuegen"):
|
(2026-09-16, Nutzerfreigabe "bitte zur wissensbasis hinzufuegen"):
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ seit 2026-09-09 — Überführung aus `gem360-git` per History-Import; siehe
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `l10n_at_hr_payroll` | **Bundes-Kern** (sektor-neutral): SV-Werte 2026 als `hr.rule.parameter`-Seed, TASY-Import-Wizard; folgen: Lohnsteuer (AP3), Meldewesen L16/eSV (AP5), Belege (AP6) |
|
| `l10n_at_hr_payroll` | **Bundes-Kern** (sektor-neutral): SV-Werte 2026 als `hr.rule.parameter`-Seed, TASY-Import-Wizard; folgen: Lohnsteuer (AP3), Meldewesen L16/eSV (AP5), Belege (AP6) |
|
||||||
| `l10n_at_gemeinde_payroll` | **Bgld. GemBG 2014**: Katalogmodell (AP1), Entgelt-Engine (AP2); AP3+ gemäß `personalverrechnung/IMPLEMENTIERUNGSPLAN-Bgld.md` |
|
| `l10n_at_gemeinde_payroll` | **Bgld. GemBG 2014**: Katalogmodell (AP1), Entgelt-Engine (AP2); AP3+ gemäß `personalverrechnung/IMPLEMENTIERUNGSPLAN-Bgld.md` |
|
||||||
|
| `l10n_at_payroll_dokumente` | **Dokumente aus Vorlagen**: Dokumentgenerierung (Dienstvertrag, Kündigung, Bescheinigungen …) aus hr-Stammdaten; eigene Texte, Struktur-Referenz (kb_ref) dokumentiert; Wizard mit Auto-/Manuellfeldern, Vorschau, PDF/HTML-Ablage |
|
||||||
| `l10n_at_hr_payroll_private` | **General-AT** (KV-basierte Privatwirtschaft) — Gerüst; Implementierungsplan freigegeben ([`IMPLEMENTIERUNGSPLAN-Privat.md`](personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md)), Umsetzung ab M1 (GP0) |
|
| `l10n_at_hr_payroll_private` | **General-AT** (KV-basierte Privatwirtschaft) — Gerüst; Implementierungsplan freigegeben ([`IMPLEMENTIERUNGSPLAN-Privat.md`](personalverrechnung/IMPLEMENTIERUNGSPLAN-Privat.md)), Umsetzung ab M1 (GP0) |
|
||||||
|
|
||||||
VRV-Buchung für Gemeinden (Anlage 3b, Ansatz/MVAG, EHH/FHH) läuft über
|
VRV-Buchung für Gemeinden (Anlage 3b, Ansatz/MVAG, EHH/FHH) läuft über
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
from . import models
|
||||||
|
from . import tests
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
{
|
||||||
|
'name': 'AT Payroll: Dokumente aus Vorlagen',
|
||||||
|
'version': '19.0.1.0.0',
|
||||||
|
'category': 'Human Resources/Payroll',
|
||||||
|
'summary': 'Dokumentgenerierung aus Vorlagen mit hr-Stammdaten (eigene Texte)',
|
||||||
|
'description': """
|
||||||
|
Dokumente (Dienstvertrag, Kündigung, Bescheinigungen, ...) aus Vorlagen,
|
||||||
|
befüllt aus hr-Stammdaten und Wizard-Eingaben.
|
||||||
|
|
||||||
|
- Vorlagen enthalten ausschließlich eigenen Text; Struktur-Referenz und
|
||||||
|
Quell-Stand sind pro Vorlage dokumentiert (kb_ref/quell_stand).
|
||||||
|
- Platzhalter-Syntax des Standard-Inline-Renderers von mail.render.mixin:
|
||||||
|
{{ werte['key'] }}, {{ object.feld }}, {{ format_date(...) }}.
|
||||||
|
- PDF-Erzeugung über ir.actions.report (wkhtmltopdf); ohne PDF-Engine
|
||||||
|
wird das gerenderte HTML abgelegt.
|
||||||
|
""",
|
||||||
|
'author': 'odoo-at-payroll',
|
||||||
|
'website': 'https://example.com/odoo-at-payroll',
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
'depends': ['hr', 'mail'],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'security/security_rules.xml',
|
||||||
|
'data/vorlagen_pilot.xml',
|
||||||
|
'views/dokument_vorlage_views.xml',
|
||||||
|
'views/wizard_views.xml',
|
||||||
|
'report/report_dokument.xml',
|
||||||
|
],
|
||||||
|
'demo': [],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
<!-- Pilot-Vorlagen: EIGENER Text, strukturiert nach den -->
|
||||||
|
<!-- Wissensbasis-Mustern (kb_ref = Herkunftsnachweis der -->
|
||||||
|
<!-- Struktur, nie lizenzierter Volltext). Rechtsstatus 'entwurf' -->
|
||||||
|
<!-- — rechtliche Prüfung vor Produktivsetzung erforderlich. -->
|
||||||
|
<!-- ============================================================ -->
|
||||||
|
|
||||||
|
<!-- ================= Dienstvertrag (Angestellte) ============== -->
|
||||||
|
<record id="vorlage_dienstvertrag_angestellte" model="l10n.at.payroll.dokument.vorlage">
|
||||||
|
<field name="name">Dienstvertrag (Angestellte)</field>
|
||||||
|
<field name="thema">dienstvertrag</field>
|
||||||
|
<field name="kb_ref">lb-bes-09</field>
|
||||||
|
<field name="quell_stand">2025-06</field>
|
||||||
|
<field name="rechtsstatus">entwurf</field>
|
||||||
|
<field name="beschreibung">
|
||||||
|
Rahmen-Dienstvertrag für Angestellte (privatrechtlich). Struktur nach dem
|
||||||
|
Lexis-Muster lb-bes-09 (Stand 2025-06), Text eigen formuliert.
|
||||||
|
Anzupassen je Mandant/KV: Einstufung, Entgelt, Überstundenregelung,
|
||||||
|
Kündigungsfristen; optionale Blöcke (Geheimhaltung, Konkurrenzklausel)
|
||||||
|
erscheinen nur, wenn befüllt. Vertragswerte (Fristen, Tage) sind
|
||||||
|
Platzhalter — keine Gesetz/KV-Werte im Template verankert.
|
||||||
|
</field>
|
||||||
|
<field name="inhalt_html"><![CDATA[
|
||||||
|
<div>
|
||||||
|
<p>{{ werte['firma'] }}<br/>{{ werte['firma_strasse'] }}<br/>{{ werte['firma_plz'] }} {{ werte['firma_ort'] }}</p>
|
||||||
|
<h2>Dienstvertrag</h2>
|
||||||
|
<p>zwischen {{ werte['firma'] }}, {{ werte['firma_strasse'] }}, {{ werte['firma_plz'] }} {{ werte['firma_ort'] }} — im Folgenden „der Dienstgeber“ — und {{ werte['mitarbeiter'] }}, {{ werte['mitarbeiter_strasse'] }}, {{ werte['mitarbeiter_plz'] }} {{ werte['mitarbeiter_ort'] }} — im Folgenden „der Dienstnehmer“.</p>
|
||||||
|
<h3>I. Tätigkeitsbereich</h3>
|
||||||
|
<p>1. Der Dienstnehmer wird ab {{ werte['ead'] }} als {{ werte['funktion'] }} angestellt. Übertragen werden ihm insbesondere folgende Tätigkeiten:<br/>{{ werte['taetigkeit'] }}</p>
|
||||||
|
<p>2. Der Dienstnehmer hat die übertragenen Tätigkeiten mit der Sorgfalt eines ordentlichen Dienstnehmers auszuüben; im Bedarfsfall sind auch verwandte Tätigkeiten nach Weisung des Dienstgebers zu verrichten.</p>
|
||||||
|
<h3>II. Dienstort</h3>
|
||||||
|
<p>Dienstort ist {{ werte['dienstort'] }}. Der Dienstnehmer verpflichtet sich, im Interesse des Dienstgebers auch außerhalb des Dienstortes vorübergehend tätig zu werden, wenn dies die Tätigkeit erfordert.</p>
|
||||||
|
<h3>III. Einstufung</h3>
|
||||||
|
<p>Die Einstufung erfolgt gemäß den anwendbaren kollektivvertraglichen Bestimmungen: {{ werte['einstufung'] }}.</p>
|
||||||
|
<h3>IV. Entgelt</h3>
|
||||||
|
<p>1. Für die geleisteten Dienste gebührt dem Dienstnehmer ein Monatsentgelt von EUR {{ werte['entgelt'] }} brutto, zahlbar monatlich nach Maßgabe der kollektivvertraglichen Bestimmungen ({{ werte['kv_verweis'] }}).</p>
|
||||||
|
<p>2. Sonderzahlungen gebühren gemäß den anwendbaren kollektivvertraglichen Bestimmungen; die Abrechnung erfolgt nach den jeweils geltenden lohnsteuerlichen Vorgaben.</p>
|
||||||
|
<h3>V. Arbeitszeit, Mehr- und Überstunden</h3>
|
||||||
|
<p>1. Die regelmäßige Dienstzeit beträgt {{ werte['arbeitszeit'] }} je Woche, verteilt auf fünf Arbeitstage von Montag bis Freitag. Die Lage der Tagesarbeitszeit wird mit dem Dienstgeber vereinbart.</p>
|
||||||
|
<p>2. Mehr- und Überstunden sind nach den Bestimmungen des Arbeitszeitgesetzes zu leisten; der Abgeltung liegen die anwendbaren kollektivvertraglichen Bestimmungen zugrunde.</p>
|
||||||
|
<h3>VI. Spesen</h3>
|
||||||
|
<p>Spesen im Zusammenhang mit der Dienstausübung werden nach den geltenden Regelungen des Dienstgebers abgerechnet.</p>
|
||||||
|
<h3>VII. Urlaub</h3>
|
||||||
|
<p>Der Dienstnehmer hat Anspruch auf bezahlten Urlaub im Ausmaß von {{ werte['urlaub_tage'] }} Arbeitstagen je Dienstjahr; im Übrigen gelten die Bestimmungen des Urlaubs- und Abfertigungsgesetzes.</p>
|
||||||
|
<h3>VIII. Entgeltfortzahlung</h3>
|
||||||
|
<p>Bei Dienstverhinderung gelten die gesetzlichen Bestimmungen über die Entgeltfortzahlung.</p>
|
||||||
|
<h3>IX. Dauer</h3>
|
||||||
|
<p>1. Das Dienstverhältnis beginnt am {{ werte['ead'] }} und wird auf unbestimmte Zeit geschlossen.</p>
|
||||||
|
<p>2. Das Dienstverhältnis kann von beiden Vertragsparteien unter Einhaltung der gesetzlichen bzw. kollektivvertraglich vereinbarten Kündigungsfristen ordentlich gekündigt werden; geltend ist die Frist {{ werte['kuendigung_frist'] }}.</p>
|
||||||
|
<div t-if="werte['geheimhaltung']">
|
||||||
|
<h3>X. Geheimhaltung</h3>
|
||||||
|
<p>{{ werte['geheimhaltung'] }}</p>
|
||||||
|
</div>
|
||||||
|
<div t-if="werte['konkurrenzklausel']">
|
||||||
|
<h3>XI. Nebentätigkeit/Konkurrenzklausel</h3>
|
||||||
|
<p>{{ werte['konkurrenzklausel'] }}</p>
|
||||||
|
</div>
|
||||||
|
<h3>XII. Schlussbestimmungen</h3>
|
||||||
|
<p>1. Abfertigungsansprüche richten sich nach den Bestimmungen des BMSVG.</p>
|
||||||
|
<p>2. Nebenabreden und Änderungen dieses Vertrages bedürfen der Schriftform; mündliche Nebenabreden bestehen nicht.</p>
|
||||||
|
<p>3. Sollte eine Bestimmung dieses Vertrages unwirksam sein, bleibt die Wirksamkeit der übrigen Bestimmungen unberührt; an deren Stelle tritt eine wirksame Bestimmung mit gleichem wirtschaftlichem Gehalt.</p>
|
||||||
|
<p>Ort, Datum: {{ werte['ort_datum'] }}<br/><br/>Dienstgeber: ________________________<br/><br/>Dienstnehmer: ______________________</p>
|
||||||
|
</div>
|
||||||
|
]]></field>
|
||||||
|
<field name="feld_ids" eval="[
|
||||||
|
Command.create({'sequence': 1, 'key': 'taetigkeit', 'bezeichnung': 'Tätigkeiten (mehrere Zeilen möglich)', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 2, 'key': 'dienstort', 'bezeichnung': 'Dienstort', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'einstufung', 'bezeichnung': 'Einstufung (KV/Gruppe/Stufe)', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'entgelt', 'bezeichnung': 'Monatsentgelt brutto (EUR)', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 4, 'key': 'kv_verweis', 'bezeichnung': 'KV-Verweis (z. B. KV Angestellte, Ausgabe ...)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 5, 'key': 'arbeitszeit', 'bezeichnung': 'Wöchentliche Dienstzeit (z. B. 40 Stunden)', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 6, 'key': 'urlaub_tage', 'bezeichnung': 'Urlaubstage je Dienstjahr', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 6, 'key': 'kuendigung_frist', 'bezeichnung': 'Kündigungsfrist', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 7, 'key': 'geheimhaltung', 'bezeichnung': 'Geheimhaltungsblock (leer = Block entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 8, 'key': 'konkurrenzklausel', 'bezeichnung': 'Nebentätigkeit/Konkurrenzklausel (leer = Block entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 9, 'key': 'ort_datum', 'bezeichnung': 'Ort, Datum', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma', 'bezeichnung': 'Firmenname', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 11, 'key': 'firma_strasse', 'bezeichnung': 'Firma: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 11, 'key': 'firma_plz', 'bezeichnung': 'Firma: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 12, 'key': 'firma_ort', 'bezeichnung': 'Firma: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 13, 'key': 'mitarbeiter', 'bezeichnung': 'Dienstnehmer:in (Name)', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 14, 'key': 'mitarbeiter_strasse', 'bezeichnung': 'Dienstnehmer:in: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 15, 'key': 'mitarbeiter_plz', 'bezeichnung': 'Dienstnehmer:in: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 16, 'key': 'mitarbeiter_ort', 'bezeichnung': 'Dienstnehmer:in: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 17, 'key': 'ead', 'bezeichnung': 'Dienstantritt (EAD)', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 18, 'key': 'funktion', 'bezeichnung': 'Funktion (Job)', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ================= Kündigung (Dienstgeber) ================== -->
|
||||||
|
<record id="vorlage_kuendigung_dienstgeber" model="l10n.at.payroll.dokument.vorlage">
|
||||||
|
<field name="name">Kündigung durch den Dienstgeber</field>
|
||||||
|
<field name="thema">beendigung</field>
|
||||||
|
<field name="kb_ref">lb-bnd-55</field>
|
||||||
|
<field name="quell_stand">2026-07</field>
|
||||||
|
<field name="rechtsstatus">entwurf</field>
|
||||||
|
<field name="beschreibung">
|
||||||
|
Ordentliche Kündigung durch den Dienstgeber (Rahmen). Struktur nach dem
|
||||||
|
Lexis-Muster lb-bnd-55 (Stand 2026-07). Betriebsrat-Verweis und
|
||||||
|
Resturlaubs-/Freistellungsblöcke sind optional (leer = entfällt);
|
||||||
|
Personalvertretungsfälle (Gemeinden) sind gesondert zu ergänzen.
|
||||||
|
</field>
|
||||||
|
<field name="inhalt_html"><![CDATA[
|
||||||
|
<div>
|
||||||
|
<p>{{ werte['firma'] }}<br/>{{ werte['firma_strasse'] }}<br/>{{ werte['firma_plz'] }} {{ werte['firma_ort'] }}</p>
|
||||||
|
<p>An {{ werte['anrede'] }} {{ werte['mitarbeiter'] }}<br/>{{ werte['mitarbeiter_strasse'] }}<br/>{{ werte['mitarbeiter_plz'] }} {{ werte['mitarbeiter_ort'] }}</p>
|
||||||
|
<h2>Kündigung des Dienstverhältnisses</h2>
|
||||||
|
<p>{{ werte['ort_datum'] }}</p>
|
||||||
|
<p>{{ werte['anrede'] }},</p>
|
||||||
|
<p>leider sehen wir uns gezwungen, das mit Ihnen bestehende Dienstverhältnis ordentlich, fristgerecht aufzukündigen. Es endet demnach am {{ werte['beendigungstermin'] }}.</p>
|
||||||
|
<p>Die Kündigung erfolgt unter Einhaltung der für Ihr Dienstverhältnis geltenden Kündigungsfrist von {{ werte['kuendigung_frist'] }}.</p>
|
||||||
|
<p t-if="werte['urlaub_rest']">Hinsichtlich Ihres bestehenden Resturlaubs bieten wir an, diesen in der Zeit von {{ werte['urlaub_rest'] }} zu verbrauchen.</p>
|
||||||
|
<p t-if="werte['freistellung']">Weiters teilen wir mit, dass wir Sie in der Zeit von {{ werte['freistellung'] }} bezahlt vom Dienst freistellen.</p>
|
||||||
|
<p>Um den Erhalt dieses Schreibens zu bestätigen, ersuchen wir Sie, die beiliegende Kopie zu unterfertigen und zurückzusenden.</p>
|
||||||
|
<p>{{ werte['firma'] }}<br/><br/>______________________<br/>Dienstgeber</p>
|
||||||
|
<p>Erhalten am: ____________<br/>Unterschrift Dienstnehmer:in: ______________________</p>
|
||||||
|
</div>
|
||||||
|
]]></field>
|
||||||
|
<field name="feld_ids" eval="[
|
||||||
|
Command.create({'sequence': 1, 'key': 'anrede', 'bezeichnung': 'Anrede (Herr/Frau/...)', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 2, 'key': 'kuendigung_frist', 'bezeichnung': 'Kündigungsfrist', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'beendigungstermin', 'bezeichnung': 'Endtermin', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 4, 'key': 'urlaub_rest', 'bezeichnung': 'Resturlaub-Verbrauch (Zeitraum, leer = entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 5, 'key': 'freistellung', 'bezeichnung': 'Bezahlte Freistellung (Zeitraum, leer = entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 6, 'key': 'ort_datum', 'bezeichnung': 'Ort, Datum', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma', 'bezeichnung': 'Firmenname', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_strasse', 'bezeichnung': 'Firma: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_plz', 'bezeichnung': 'Firma: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_ort', 'bezeichnung': 'Firma: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter', 'bezeichnung': 'Dienstnehmer:in (Name)', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_strasse', 'bezeichnung': 'Dienstnehmer:in: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_plz', 'bezeichnung': 'Dienstnehmer:in: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_ort', 'bezeichnung': 'Dienstnehmer:in: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ============ Einvernehmliche Auflösung ==================== -->
|
||||||
|
<record id="vorlage_einvernehmliche_aufloesung" model="l10n.at.payroll.dokument.vorlage">
|
||||||
|
<field name="name">Einvernehmliche Auflösung</field>
|
||||||
|
<field name="thema">beendigung</field>
|
||||||
|
<field name="kb_ref">lb-bnd-52, lb-bnd-62</field>
|
||||||
|
<field name="quell_stand">2026-06</field>
|
||||||
|
<field name="rechtsstatus">entwurf</field>
|
||||||
|
<field name="beschreibung">
|
||||||
|
Vereinbarung der einvernehmlichen Auflösung (Rahmen). Struktur nach dem
|
||||||
|
Lexis-Muster lb-bnd-62 (Stand 2026-06). Hinweis-Konvention des Musters:
|
||||||
|
Endtermin = letzter Arbeitstag, außer es wird Verbrauch von Zeitguthaben
|
||||||
|
oder Urlaub vereinbart; optionale Blöcke leer lassen = entfällt.
|
||||||
|
</field>
|
||||||
|
<field name="inhalt_html"><![CDATA[
|
||||||
|
<div>
|
||||||
|
<p>{{ werte['firma'] }}, {{ werte['firma_strasse'] }}, {{ werte['firma_plz'] }} {{ werte['firma_ort'] }} — im Folgenden „der Dienstgeber“ —</p>
|
||||||
|
<p>und</p>
|
||||||
|
<p>{{ werte['mitarbeiter'] }}, {{ werte['mitarbeiter_strasse'] }}, {{ werte['mitarbeiter_plz'] }} {{ werte['mitarbeiter_ort'] }} — im Folgenden „die Dienstnehmerin/der Dienstnehmer“ —</p>
|
||||||
|
<h2>Vereinbarung über die einvernehmliche Auflösung des Dienstverhältnisses</h2>
|
||||||
|
<p>Die Parteien lösen das seit {{ werte['seit'] }} bestehende Dienstverhältnis einvernehmlich zum {{ werte['beendigungstermin'] }} auf. Mit diesem Tag endet das Dienstverhältnis; dieser Tag ist auch der letzte Arbeitstag.</p>
|
||||||
|
<p t-if="werte['zeitguthaben']">Ein offenes Zeitguthaben von {{ werte['zeitguthaben'] }} wird in der Zeit von {{ werte['zeitguthaben_zeit'] }} verbraucht.</p>
|
||||||
|
<p t-if="werte['urlaub_rest']">Offener Urlaub im Ausmaß von {{ werte['urlaub_rest'] }} wird in der Zeit von {{ werte['urlaub_zeit'] }} aufgebraucht.</p>
|
||||||
|
<p t-if="werte['freistellung']">Die Dienstnehmerin/der Dienstnehmer wird in der Zeit von {{ werte['freistellung'] }} bezahlt vom Dienst freigestellt.</p>
|
||||||
|
<p>Die letzte Entgeltabrechnung erfolgt zur gebührenden Ausfolgung mit der Beendigung; Urlaubsersatzleistungen und ausständige Beträge werden mit der Endabrechnung ausgewiesen.</p>
|
||||||
|
<p>Die Dienstnehmerin/der Dienstnehmer verpflichtet sich, sämtliche Betriebsgegenstände, Dokumente und Zugänge bis zum Ende des Dienstverhältnisses zurückzustellen.</p>
|
||||||
|
<p>Sämtliche gegenseitigen Ansprüche aus dem Dienstverhältnis — soweit gesetzlich zulässig und abweichend nicht geregelt — gelten mit diesem Tag als abgegolten.</p>
|
||||||
|
<p>Änderungen dieser Vereinbarung bedürfen der Schriftform.</p>
|
||||||
|
<p>Ort, Datum: {{ werte['ort_datum'] }}<br/><br/>Dienstgeber: ________________________<br/><br/>Dienstnehmer:in: ______________________</p>
|
||||||
|
</div>
|
||||||
|
]]></field>
|
||||||
|
<field name="feld_ids" eval="[
|
||||||
|
Command.create({'sequence': 1, 'key': 'seit', 'bezeichnung': 'Besteht seit (Dienstbeginn)', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 2, 'key': 'beendigungstermin', 'bezeichnung': 'Beendigungstermin', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'zeitguthaben', 'bezeichnung': 'Zeitguthaben (Stunden, leer = entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'zeitguthaben_zeit', 'bezeichnung': 'Zeitguthaben-Verbrauch (Zeitraum)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 4, 'key': 'urlaub_rest', 'bezeichnung': 'Resturlaub (Tage, leer = entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 4, 'key': 'urlaub_zeit', 'bezeichnung': 'Urlaubsverbrauch (Zeitraum)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 5, 'key': 'freistellung', 'bezeichnung': 'Bezahlte Freistellung (Zeitraum, leer = entfällt)', 'quelle': 'manuell', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 6, 'key': 'ort_datum', 'bezeichnung': 'Ort, Datum', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma', 'bezeichnung': 'Firmenname', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_strasse', 'bezeichnung': 'Firma: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_plz', 'bezeichnung': 'Firma: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_ort', 'bezeichnung': 'Firma: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter', 'bezeichnung': 'Dienstnehmer:in (Name)', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_strasse', 'bezeichnung': 'Dienstnehmer:in: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_plz', 'bezeichnung': 'Dienstnehmer:in: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_ort', 'bezeichnung': 'Dienstnehmer:in: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
]"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- ============ Vordienstzeitenbestätigung =================== -->
|
||||||
|
<record id="vorlage_vordienstzeiten" model="l10n.at.payroll.dokument.vorlage">
|
||||||
|
<field name="name">Vordienstzeitenbestätigung</field>
|
||||||
|
<field name="thema">bescheinigung</field>
|
||||||
|
<field name="kb_ref">lb-end-35</field>
|
||||||
|
<field name="quell_stand">2026-07</field>
|
||||||
|
<field name="rechtsstatus">entwurf</field>
|
||||||
|
<field name="beschreibung">
|
||||||
|
Bestätigung des Vor-Dienstgebers über Beschäftigungsdauer und Abfertigungs-
|
||||||
|
Status anlässlich der Beendigung (Anrechnung von Vordienstzeiten beim
|
||||||
|
neuen Dienstgeber, lb-end-35, Stand 2026-07). Abfertigungs-Auswahl im
|
||||||
|
Wizard befüllen (keine/gesetzliche/KV/freiwillige — nicht zutreffende
|
||||||
|
Angaben entfallen).
|
||||||
|
</field>
|
||||||
|
<field name="inhalt_html"><![CDATA[
|
||||||
|
<div>
|
||||||
|
<p>{{ werte['firma'] }}, {{ werte['firma_strasse'] }}, {{ werte['firma_plz'] }} {{ werte['firma_ort'] }}</p>
|
||||||
|
<h2>Bestätigung über Vordienstzeiten</h2>
|
||||||
|
<p>Wir, {{ werte['firma'] }}, bestätigen, dass {{ werte['mitarbeiter'] }}, geboren am {{ werte['geboren'] }}, in der Zeit von {{ werte['von'] }} bis {{ werte['bis'] }} bei uns beschäftigt war und anlässlich der Beendigung des Dienstverhältnisses:</p>
|
||||||
|
<ul>
|
||||||
|
<li>{{ werte['abfertigung_art'] }}</li>
|
||||||
|
</ul>
|
||||||
|
<p>Die Bestätigung dient der Anrechnung von Vordienstzeiten für die Einstufung beim neuen Dienstgeber.</p>
|
||||||
|
<p>Datum: {{ werte['ort_datum'] }}</p>
|
||||||
|
<p>firmenmäßige Zeichnung: ________________________</p>
|
||||||
|
</div>
|
||||||
|
]]></field>
|
||||||
|
<field name="feld_ids" eval="[
|
||||||
|
Command.create({'sequence': 1, 'key': 'von', 'bezeichnung': 'Beschäftigt von', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 2, 'key': 'bis', 'bezeichnung': 'Beschäftigt bis', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'abfertigung_art', 'bezeichnung': 'Abfertigungsstatus (z. B. weder eine ... noch eine ... Abfertigung erhalten)', 'quelle': 'manuell', 'pflicht': True, 'hinweis': 'Text je Fall; Muster-Varianten: keine / gesetzliche / kollektivvertragliche / freiwillige Abfertigung samt Höhe'}),
|
||||||
|
Command.create({'sequence': 3, 'key': 'ort_datum', 'bezeichnung': 'Ort, Datum', 'quelle': 'manuell', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma', 'bezeichnung': 'Firmenname', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_strasse', 'bezeichnung': 'Firma: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_plz', 'bezeichnung': 'Firma: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'firma_ort', 'bezeichnung': 'Firma: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter', 'bezeichnung': 'Dienstnehmer:in (Name)', 'quelle': 'auto', 'pflicht': True}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_strasse', 'bezeichnung': 'Dienstnehmer:in: Straße', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_plz', 'bezeichnung': 'Dienstnehmer:in: PLZ', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'mitarbeiter_ort', 'bezeichnung': 'Dienstnehmer:in: Ort', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
Command.create({'sequence': 10, 'key': 'geboren', 'bezeichnung': 'Geburtsdatum', 'quelle': 'auto', 'pflicht': False}),
|
||||||
|
]"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
from . import dokument_vorlage
|
||||||
|
from . import dokument_wizard
|
||||||
|
from . import dokument_report
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
"""Generisches Druck-Template: nimmt das vom Wizard gerenderte
|
||||||
|
body-HTML entgegen und bettet es in die PDF-Seite ein."""
|
||||||
|
from markupsafe import Markup
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class ReportDokumentGenerisch(models.AbstractModel):
|
||||||
|
_name = 'report.l10n_at_payroll_dokumente.report_dokument_generisch'
|
||||||
|
_description = 'Dokument aus Vorlage (generisches Drucklayout)'
|
||||||
|
|
||||||
|
def _get_report_values(self, docids, data=None):
|
||||||
|
data = data or {}
|
||||||
|
return {
|
||||||
|
'titel': data.get('titel', ''),
|
||||||
|
'body': Markup(data.get('body', '')),
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
"""Dokumentgenerierung: Vorlagen mit eigenem Text und Platzhaltern.
|
||||||
|
|
||||||
|
Vorlagen enthalten ausschließlich eigenen Text; die Struktur-Referenz zu
|
||||||
|
den Lexis-Mustern ist pro Vorlage dokumentiert (``kb_ref``/``quell_stand``),
|
||||||
|
nie der lizenzierte Volltext.
|
||||||
|
|
||||||
|
Rendering: eigene Ausführung über ``ir.qweb`` mit den Framework-Bausteinen
|
||||||
|
``rendering_tools.convert_inline_template_to_qweb`` — Autor-Syntax bleibt
|
||||||
|
``{{ werte['key'] }}`` (identisch zu mail.render.mixin), t-out escapt die
|
||||||
|
Werte automatisch. Bewusst NICHT ``mail.render.mixin._render_template``
|
||||||
|
mit engine='inline_template': deren Ausdrucks-Whitelist
|
||||||
|
(``mail_allowed_qweb_expressions``) lässt nur ``object.*``-Basistreffer zu
|
||||||
|
und würde die ``werte['...']``-Platzhalter für Nicht-Template-Editoren
|
||||||
|
sperren (AccessError).
|
||||||
|
"""
|
||||||
|
from odoo import fields, models
|
||||||
|
from odoo.tools.rendering_tools import convert_inline_template_to_qweb
|
||||||
|
|
||||||
|
from lxml import html as lxml_html
|
||||||
|
|
||||||
|
|
||||||
|
class L10nAtPayrollDokumentVorlage(models.Model):
|
||||||
|
_name = 'l10n.at.payroll.dokument.vorlage'
|
||||||
|
_description = 'Dokumentvorlage (eigener Text)'
|
||||||
|
_order = 'thema, name'
|
||||||
|
|
||||||
|
name = fields.Char(required=True)
|
||||||
|
thema = fields.Selection(
|
||||||
|
[('dienstvertrag', 'Dienstvertrag'),
|
||||||
|
('beendigung', 'Beendigung/Kündigung'),
|
||||||
|
('bescheinigung', 'Bescheinigung'),
|
||||||
|
('vereinbarung', 'Vereinbarung'),
|
||||||
|
('exekution', 'Exekution/Pfändung'),
|
||||||
|
('sonstiges', 'Sonstiges')],
|
||||||
|
required=True, default='sonstiges')
|
||||||
|
beschreibung = fields.Text(
|
||||||
|
help="Zweck des Dokuments und Hinweise zum Rechtsanker; auch "
|
||||||
|
"Platzhalter-Dokumentation für nicht in der Feldliste "
|
||||||
|
"geführte Ausdrücke (object.*, version.*).")
|
||||||
|
inhalt_html = fields.Html(
|
||||||
|
string='Vorlagentext',
|
||||||
|
sanitize=False,
|
||||||
|
help="Eigener Vorlagentext; Platzhalter {{ werte['key'] }} werden "
|
||||||
|
"im Generierungs-Wizard befüllt.")
|
||||||
|
feld_ids = fields.One2many(
|
||||||
|
'l10n.at.payroll.dokument.feld', 'vorlage_id',
|
||||||
|
string='Befüllfelder')
|
||||||
|
kb_ref = fields.Char(
|
||||||
|
string='Struktur-Referenz',
|
||||||
|
help="Wissensbasis-/Muster-ID, nach deren Struktur der eigene "
|
||||||
|
"Text formuliert wurde (Herkunftsnachweis, kein Volltext).")
|
||||||
|
quell_stand = fields.Char(
|
||||||
|
string='Quell-Stand',
|
||||||
|
help="Stand der Struktur-Referenz, z. B. '2025-06'.")
|
||||||
|
rechtsstatus = fields.Selection(
|
||||||
|
[('entwurf', 'Entwurf — rechtliche Prüfung offen'),
|
||||||
|
('geprueft', 'Rechtlich geprüft')],
|
||||||
|
default='entwurf', required=True, string='Rechtsstatus')
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
'res.company', default=lambda self: self.env.company)
|
||||||
|
|
||||||
|
def _at_renderen(self, employee, version, werte, datum):
|
||||||
|
"""Vorlagentext auf den Mitarbeiter rendern (HTML-String)."""
|
||||||
|
self.ensure_one()
|
||||||
|
variablen = self.env['mail.render.mixin']._render_eval_context()
|
||||||
|
variablen.update({
|
||||||
|
'werte': werte,
|
||||||
|
'version': version,
|
||||||
|
'datum': datum,
|
||||||
|
'object': employee,
|
||||||
|
})
|
||||||
|
qweb_src = convert_inline_template_to_qweb(self.inhalt_html)
|
||||||
|
element = lxml_html.fragment_fromstring(
|
||||||
|
str(qweb_src), create_parent='div')
|
||||||
|
html = self.env['ir.qweb']._render(
|
||||||
|
element, variablen, minimal_qcontext=True)
|
||||||
|
# Ummantelungs-<div> von fragment_fromstring entfernen
|
||||||
|
# (Muster mail.render.mixin._render_template_qweb).
|
||||||
|
return str(html)[5:-6]
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('name_uniq', 'unique(name, company_id)',
|
||||||
|
'Vorlagenname je Company eindeutig.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class L10nAtPayrollDokumentFeld(models.Model):
|
||||||
|
_name = 'l10n.at.payroll.dokument.feld'
|
||||||
|
_description = 'Dokumentvorlage-Befüllfeld'
|
||||||
|
_order = 'sequence, id'
|
||||||
|
_rec_name = 'key'
|
||||||
|
|
||||||
|
sequence = fields.Integer(default=10)
|
||||||
|
vorlage_id = fields.Many2one(
|
||||||
|
'l10n.at.payroll.dokument.vorlage', required=True,
|
||||||
|
ondelete='cascade', index=True)
|
||||||
|
key = fields.Char(
|
||||||
|
required=True,
|
||||||
|
help="Platzhalter-Schlüssel; im Vorlagentext als "
|
||||||
|
"{{ werte['key'] }} zu verwenden.")
|
||||||
|
bezeichnung = fields.Char()
|
||||||
|
quelle = fields.Selection(
|
||||||
|
[('auto', 'Automatisch (hr-Stammdaten)'),
|
||||||
|
('manuell', 'Manuelle Eingabe im Wizard')],
|
||||||
|
default='manuell', required=True)
|
||||||
|
pflicht = fields.Boolean(
|
||||||
|
default=True,
|
||||||
|
help="Pflichtfelder müssen im Wizard befüllt sein, bevor das "
|
||||||
|
"Dokument erzeugt wird.")
|
||||||
|
hinweis = fields.Char(
|
||||||
|
help="Erklärung für die Wizard-Eingabe (z. B. KV-Bezug).")
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('key_vorlage_uniq', 'unique(vorlage_id, key)',
|
||||||
|
'Platzhalter-Schlüssel je Vorlage eindeutig.'),
|
||||||
|
]
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
"""Wizard: Dokument aus Vorlage generieren.
|
||||||
|
|
||||||
|
Befüllt die Platzhalter der Vorlage aus hr-Stammdaten (automatische
|
||||||
|
Felder) und Wizard-Eingaben (manuelle Felder), rendert eine Vorschau und
|
||||||
|
erzeugt PDF (ir.actions.report, wkhtmltopdf) bzw. — ohne PDF-Engine —
|
||||||
|
das gerenderte HTML als Anhang.
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from markupsafe import Markup
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools.misc import format_date
|
||||||
|
|
||||||
|
|
||||||
|
class L10nAtPayrollDokumentWizard(models.TransientModel):
|
||||||
|
_name = 'l10n.at.payroll.dokument.wizard'
|
||||||
|
_description = 'Dokument aus Vorlage generieren'
|
||||||
|
|
||||||
|
vorlage_id = fields.Many2one(
|
||||||
|
'l10n.at.payroll.dokument.vorlage', required=True,
|
||||||
|
domain=[('active', '=', True)])
|
||||||
|
employee_id = fields.Many2one('hr.employee', required=True)
|
||||||
|
datum = fields.Date(
|
||||||
|
string='Stichtag',
|
||||||
|
default=fields.Date.context_today,
|
||||||
|
help="Stichtag für den hr.version-Stand (Vertragsdaten).")
|
||||||
|
version_id = fields.Many2one(
|
||||||
|
'hr.version', compute='_compute_version_id',
|
||||||
|
string='Vertragsstand (hr.version)')
|
||||||
|
feld_wert_ids = fields.One2many(
|
||||||
|
'l10n.at.payroll.dokument.wizard.feld', 'wizard_id',
|
||||||
|
string='Befüllung')
|
||||||
|
vorschau_html = fields.Html(
|
||||||
|
string='Vorschau', readonly=True, sanitize=False)
|
||||||
|
|
||||||
|
@api.depends('employee_id', 'datum')
|
||||||
|
def _compute_version_id(self):
|
||||||
|
for wizard in self:
|
||||||
|
wizard.version_id = wizard._at_version_am()
|
||||||
|
|
||||||
|
def _at_version_am(self):
|
||||||
|
"""hr.version gültig am Stichtag (date_version <= Stichtag)."""
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.datum or not self.employee_id:
|
||||||
|
return self.env['hr.version']
|
||||||
|
return self.env['hr.version'].search(
|
||||||
|
[('employee_id', '=', self.employee_id.id),
|
||||||
|
('date_version', '<=', self.datum)],
|
||||||
|
order='date_version desc, id desc', limit=1)
|
||||||
|
|
||||||
|
@api.onchange('vorlage_id')
|
||||||
|
def _onchange_vorlage_id(self):
|
||||||
|
self.vorschau_html = False
|
||||||
|
self.feld_wert_ids = self._at_linien()
|
||||||
|
|
||||||
|
def action_befuellen(self):
|
||||||
|
"""Linien aus der Vorlage neu aufbauen (auch bei Wechsel)."""
|
||||||
|
self.ensure_one()
|
||||||
|
self.vorschau_html = False
|
||||||
|
self.feld_wert_ids = self._at_linien()
|
||||||
|
|
||||||
|
def _at_linien(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.vorlage_id:
|
||||||
|
return [(5, 0, 0)]
|
||||||
|
return [(5, 0, 0)] + [
|
||||||
|
(0, 0, {
|
||||||
|
'key': feld.key,
|
||||||
|
'bezeichnung': feld.bezeichnung,
|
||||||
|
'quelle': feld.quelle,
|
||||||
|
'pflicht': feld.pflicht,
|
||||||
|
'hinweis': feld.hinweis,
|
||||||
|
})
|
||||||
|
for feld in self.vorlage_id.feld_ids.sorted('sequence')
|
||||||
|
]
|
||||||
|
|
||||||
|
def _at_werte(self):
|
||||||
|
"""Befüllwerte: automatisch aufgelöst + manuelle Eingaben."""
|
||||||
|
self.ensure_one()
|
||||||
|
werte = {}
|
||||||
|
fehlen = []
|
||||||
|
for feld in self.vorlage_id.feld_ids:
|
||||||
|
line = self.feld_wert_ids.filtered(
|
||||||
|
lambda l, k=feld.key: l.key == k)[:1]
|
||||||
|
if feld.quelle == 'auto':
|
||||||
|
werte[feld.key] = self._at_auto_wert(feld.key)
|
||||||
|
else:
|
||||||
|
wert = (line.wert or '').strip()
|
||||||
|
if not wert and feld.pflicht:
|
||||||
|
fehlen.append(feld.bezeichnung or feld.key)
|
||||||
|
werte[feld.key] = wert
|
||||||
|
if fehlen:
|
||||||
|
raise UserError(_(
|
||||||
|
"Pflichtfelder nicht befüllt: %s", ', '.join(fehlen)))
|
||||||
|
return werte
|
||||||
|
|
||||||
|
def _at_auto_wert(self, key):
|
||||||
|
self.ensure_one()
|
||||||
|
employee = self.employee_id
|
||||||
|
version = self.version_id
|
||||||
|
company = employee.company_id
|
||||||
|
if key == 'firma':
|
||||||
|
return company.name or ''
|
||||||
|
if key == 'firma_strasse':
|
||||||
|
return company.partner_id.street or ''
|
||||||
|
if key == 'firma_plz':
|
||||||
|
return company.partner_id.zip or ''
|
||||||
|
if key == 'firma_ort':
|
||||||
|
return company.partner_id.city or ''
|
||||||
|
if key == 'mitarbeiter':
|
||||||
|
return employee.name or ''
|
||||||
|
if key == 'mitarbeiter_strasse':
|
||||||
|
return version.private_street or ''
|
||||||
|
if key == 'mitarbeiter_plz':
|
||||||
|
return version.private_zip or ''
|
||||||
|
if key == 'mitarbeiter_ort':
|
||||||
|
return version.private_city or ''
|
||||||
|
if key == 'ead':
|
||||||
|
datum = version.date_version or version.contract_date_start
|
||||||
|
return format_date(self.env, datum) if datum else ''
|
||||||
|
if key == 'funktion':
|
||||||
|
return version.job_title or version.job_id.name or ''
|
||||||
|
if key == 'geboren':
|
||||||
|
return format_date(self.env, employee.birthday) if employee.birthday else ''
|
||||||
|
raise UserError(_(
|
||||||
|
"Kein automatischer Wert für Platzhalter '%s' definiert.", key))
|
||||||
|
|
||||||
|
def action_vorschau(self):
|
||||||
|
self.ensure_one()
|
||||||
|
werte = self._at_werte()
|
||||||
|
self.vorschau_html = Markup(self.vorlage_id._at_renderen(
|
||||||
|
self.employee_id, self.version_id, werte, self.datum))
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': self._name,
|
||||||
|
'res_id': self.id,
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_pdf(self):
|
||||||
|
self.ensure_one()
|
||||||
|
werte = self._at_werte()
|
||||||
|
body = Markup(self.vorlage_id._at_renderen(
|
||||||
|
self.employee_id, self.version_id, werte, self.datum))
|
||||||
|
name = '%s — %s (%s)' % (
|
||||||
|
self.vorlage_id.name, self.employee_id.name, self.datum)
|
||||||
|
if self.env['ir.actions.report'].get_wkhtmltopdf_state() == 'ok':
|
||||||
|
pdf_content, _typ = self.env['ir.actions.report']._render_qweb_pdf(
|
||||||
|
'l10n_at_payroll_dokumente.action_report_dokument_generisch',
|
||||||
|
self.ids, data={'titel': name, 'body': body})
|
||||||
|
datas = base64.b64encode(pdf_content)
|
||||||
|
mimetype = 'application/pdf'
|
||||||
|
suffix = '.pdf'
|
||||||
|
else:
|
||||||
|
datas = base64.b64encode(str(body).encode('utf-8'))
|
||||||
|
mimetype = 'text/html'
|
||||||
|
suffix = '.html'
|
||||||
|
self.env['ir.attachment'].create({
|
||||||
|
'name': name + suffix,
|
||||||
|
'datas': datas,
|
||||||
|
'mimetype': mimetype,
|
||||||
|
'res_model': 'hr.employee',
|
||||||
|
'res_id': self.employee_id.id,
|
||||||
|
'description': 'Dokument aus Vorlage %s' % self.vorlage_id.name,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window_close',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class L10nAtPayrollDokumentWizardFeld(models.TransientModel):
|
||||||
|
_name = 'l10n.at.payroll.dokument.wizard.feld'
|
||||||
|
_description = 'Dokument-Wizard Befüllzeile'
|
||||||
|
|
||||||
|
wizard_id = fields.Many2one(
|
||||||
|
'l10n.at.payroll.dokument.wizard', required=True,
|
||||||
|
ondelete='cascade')
|
||||||
|
key = fields.Char(readonly=True, required=True)
|
||||||
|
bezeichnung = fields.Char(readonly=True)
|
||||||
|
quelle = fields.Selection(
|
||||||
|
[('auto', 'Automatisch (hr-Stammdaten)'),
|
||||||
|
('manuell', 'Manuelle Eingabe im Wizard')], readonly=True)
|
||||||
|
pflicht = fields.Boolean(readonly=True)
|
||||||
|
hinweis = fields.Char(readonly=True)
|
||||||
|
wert = fields.Char()
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Generisches Drucklayout für Dokumente aus Vorlagen. -->
|
||||||
|
<!-- Aufgerufen vom Wizard (ir.actions.report._render_qweb_pdf); -->
|
||||||
|
<!-- kein Menü-Binding, da der Inhalt je Generierung variiert. -->
|
||||||
|
<record id="action_report_dokument_generisch" model="ir.actions.report">
|
||||||
|
<field name="name">Dokument aus Vorlage</field>
|
||||||
|
<field name="model">l10n.at.payroll.dokument.wizard</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">l10n_at_payroll_dokumente.report_dokument_generisch</field>
|
||||||
|
<field name="report_file">l10n_at_payroll_dokumente.report_dokument_generisch</field>
|
||||||
|
<field name="paperformat_id" ref="base.paperformat_euro"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<template id="report_dokument_generisch">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<div class="page">
|
||||||
|
<h1 class="d-none"><t t-out="titel"/></h1>
|
||||||
|
<div t-out="body"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_dokument_vorlage_hr_user,l10n.at.payroll.dokument.vorlage hr.user,model_l10n_at_payroll_dokument_vorlage,hr.group_hr_user,1,0,0,0
|
||||||
|
access_dokument_vorlage_hr_manager,l10n.at.payroll.dokument.vorlage hr.manager,model_l10n_at_payroll_dokument_vorlage,hr.group_hr_manager,1,1,1,1
|
||||||
|
access_dokument_feld_hr_user,l10n.at.payroll.dokument.feld hr.user,model_l10n_at_payroll_dokument_feld,hr.group_hr_user,1,0,0,0
|
||||||
|
access_dokument_feld_hr_manager,l10n.at.payroll.dokument.feld hr.manager,model_l10n_at_payroll_dokument_feld,hr.group_hr_manager,1,1,1,1
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="rule_dokument_vorlage_company" model="ir.rule">
|
||||||
|
<field name="name">Dokumentvorlage: eigene Company</field>
|
||||||
|
<field name="model_id" ref="model_l10n_at_payroll_dokument_vorlage"/>
|
||||||
|
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
from . import test_dokumente
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
# Part of the odoo-at-payroll project. License: LGPL-3.
|
||||||
|
"""Tests: Dokumentgenerierung aus Vorlagen.
|
||||||
|
|
||||||
|
Rendering der eigenen Vorlagentexte (werte['key']-Platzhalter), Auto-Werte
|
||||||
|
aus hr-Stammdaten, Pflichtfeld-Validierung, hr.version-Stichtagsauflösung,
|
||||||
|
generisches Druck-Template und Anhang-Ablage (PDF wenn wkhtmltopdf
|
||||||
|
verfügbar, sonst HTML-Fallback)."""
|
||||||
|
import base64
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from odoo.exceptions import AccessError, UserError
|
||||||
|
from odoo.tests import TransactionCase, tagged
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class DokumenteTest(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.company = self.env.company
|
||||||
|
self.company.write({'name': 'Musterwerkstatt GmbH'})
|
||||||
|
employee = self.env['hr.employee'].create({
|
||||||
|
'name': 'Dokument Test'})
|
||||||
|
version = self.env['hr.version'].search(
|
||||||
|
[('employee_id', '=', employee.id)], limit=1)
|
||||||
|
version.write({
|
||||||
|
'date_version': datetime.date(2020, 1, 1),
|
||||||
|
'contract_date_start': datetime.date(2020, 1, 1),
|
||||||
|
'job_title': 'Techniker',
|
||||||
|
'private_street': 'Testgasse 1',
|
||||||
|
'private_zip': '1010',
|
||||||
|
'private_city': 'Wien',
|
||||||
|
})
|
||||||
|
self.employee = employee
|
||||||
|
self.version = version
|
||||||
|
self.vorlage = self.env['l10n.at.payroll.dokument.vorlage'].create({
|
||||||
|
'name': 'Testbescheinigung',
|
||||||
|
'thema': 'bescheinigung',
|
||||||
|
'kb_ref': 'lb-test',
|
||||||
|
'rechtsstatus': 'entwurf',
|
||||||
|
'inhalt_html':
|
||||||
|
'<p>Firma: {{ werte[\'firma\'] }}</p>'
|
||||||
|
'<p>Mitarbeiter: {{ werte[\'mitarbeiter\'] }}</p>'
|
||||||
|
'<p>Funktion: {{ werte[\'funktion\'] }}</p>'
|
||||||
|
'<p>EAD: {{ werte[\'ead\'] }}</p>'
|
||||||
|
'<p>Zusatz: {{ werte[\'zusatz\'] }}</p>',
|
||||||
|
'feld_ids': [
|
||||||
|
(0, 0, {'sequence': 1, 'key': 'zusatz',
|
||||||
|
'quelle': 'manuell', 'pflicht': True,
|
||||||
|
'bezeichnung': 'Zusatzangabe'}),
|
||||||
|
(0, 0, {'sequence': 2, 'key': 'firma', 'quelle': 'auto',
|
||||||
|
'pflicht': True}),
|
||||||
|
(0, 0, {'sequence': 3, 'key': 'mitarbeiter',
|
||||||
|
'quelle': 'auto', 'pflicht': True}),
|
||||||
|
(0, 0, {'sequence': 4, 'key': 'funktion', 'quelle': 'auto',
|
||||||
|
'pflicht': False}),
|
||||||
|
(0, 0, {'sequence': 5, 'key': 'ead', 'quelle': 'auto',
|
||||||
|
'pflicht': False}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
def _wizard(self):
|
||||||
|
wizard = self.env['l10n.at.payroll.dokument.wizard'].create({
|
||||||
|
'vorlage_id': self.vorlage.id,
|
||||||
|
'employee_id': self.employee.id,
|
||||||
|
'datum': datetime.date(2026, 1, 15),
|
||||||
|
})
|
||||||
|
wizard.action_befuellen()
|
||||||
|
return wizard
|
||||||
|
|
||||||
|
def test_01_auto_werte(self):
|
||||||
|
wizard = self._wizard()
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'zusatz').wert = 'X'
|
||||||
|
werte = wizard._at_werte()
|
||||||
|
self.assertEqual(werte['firma'], 'Musterwerkstatt GmbH')
|
||||||
|
self.assertEqual(werte['mitarbeiter'], 'Dokument Test')
|
||||||
|
self.assertEqual(werte['funktion'], 'Techniker')
|
||||||
|
self.assertTrue(werte['ead'])
|
||||||
|
|
||||||
|
def test_02_vorschau_ersetzt_platzhalter(self):
|
||||||
|
wizard = self._wizard()
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'zusatz').wert = 'Projekt Alpha'
|
||||||
|
wizard.action_vorschau()
|
||||||
|
vorschau = wizard.vorschau_html
|
||||||
|
self.assertIn('Musterwerkstatt GmbH', vorschau)
|
||||||
|
self.assertIn('Dokument Test', vorschau)
|
||||||
|
self.assertIn('Projekt Alpha', vorschau)
|
||||||
|
self.assertNotIn('{{', vorschau)
|
||||||
|
|
||||||
|
def test_03_pflichtfeld_leist_user_error(self):
|
||||||
|
wizard = self._wizard()
|
||||||
|
with self.assertRaises(UserError):
|
||||||
|
wizard._at_werte()
|
||||||
|
|
||||||
|
def test_04_version_am_stichtag(self):
|
||||||
|
self.env['hr.version'].create({
|
||||||
|
'employee_id': self.employee.id,
|
||||||
|
'date_version': datetime.date(2026, 3, 1),
|
||||||
|
'job_title': 'Senior Techniker',
|
||||||
|
})
|
||||||
|
wizard = self._wizard()
|
||||||
|
self.assertEqual(wizard.version_id.job_title, 'Techniker')
|
||||||
|
wizard.datum = datetime.date(2026, 6, 1)
|
||||||
|
wizard._compute_version_id()
|
||||||
|
self.assertEqual(wizard.version_id.job_title, 'Senior Techniker')
|
||||||
|
|
||||||
|
def test_05_pdf_ablegt_anhang(self):
|
||||||
|
wizard = self._wizard()
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'zusatz').wert = 'Fall 5'
|
||||||
|
wizard.action_pdf()
|
||||||
|
anh = self.env['ir.attachment'].search(
|
||||||
|
[('res_model', '=', 'hr.employee'),
|
||||||
|
('res_id', '=', self.employee.id)])
|
||||||
|
self.assertEqual(len(anh), 1)
|
||||||
|
self.assertIn(anh.mimetype, ('application/pdf', 'text/html'))
|
||||||
|
if anh.mimetype == 'text/html':
|
||||||
|
html = base64.b64decode(anh.datas).decode('utf-8')
|
||||||
|
self.assertIn('Fall 5', html)
|
||||||
|
|
||||||
|
def test_06_report_values_generic(self):
|
||||||
|
wizard = self._wizard()
|
||||||
|
report = self.env[
|
||||||
|
'report.l10n_at_payroll_dokumente'
|
||||||
|
'.report_dokument_generisch']
|
||||||
|
values = report._get_report_values(
|
||||||
|
wizard.ids, data={'titel': 'T', 'body': '<p>Body</p>'})
|
||||||
|
self.assertEqual(str(values['body']), '<p>Body</p>')
|
||||||
|
html_content, _typ = self.env['ir.actions.report']._render_qweb_html(
|
||||||
|
'l10n_at_payroll_dokumente.action_report_dokument_generisch',
|
||||||
|
wizard.ids, data={'titel': 'T', 'body': values['body']})
|
||||||
|
self.assertIn('Body', str(html_content))
|
||||||
|
|
||||||
|
def test_07_access_hr_user_lesen_manager_schreiben(self):
|
||||||
|
hr_user = self.env['res.users'].create({
|
||||||
|
'name': 'Dokument Lesetest',
|
||||||
|
'login': 'dok_lesetest',
|
||||||
|
'group_ids': [(4, self.env.ref('hr.group_hr_user').id)],
|
||||||
|
})
|
||||||
|
umgebung = self.env(user=hr_user.id)
|
||||||
|
self.assertTrue(
|
||||||
|
umgebung['l10n.at.payroll.dokument.vorlage'].browse(
|
||||||
|
self.vorlage.id).name)
|
||||||
|
kein_hr = self.env['res.users'].create({
|
||||||
|
'name': 'Dokument Ohne HR',
|
||||||
|
'login': 'dok_ohne_hr',
|
||||||
|
'group_ids': [(4, self.env.ref('base.group_user').id)],
|
||||||
|
})
|
||||||
|
ohne = self.env(user=kein_hr.id)
|
||||||
|
with self.assertRaises(AccessError):
|
||||||
|
ohne['l10n.at.payroll.dokument.vorlage'].browse(
|
||||||
|
self.vorlage.id).name
|
||||||
|
|
||||||
|
def test_08_pilot_vorlagen_installiert(self):
|
||||||
|
for xmlid in ('vorlage_dienstvertrag_angestellte',
|
||||||
|
'vorlage_kuendigung_dienstgeber',
|
||||||
|
'vorlage_einvernehmliche_aufloesung',
|
||||||
|
'vorlage_vordienstzeiten'):
|
||||||
|
vorlage = self.env.ref(
|
||||||
|
'l10n_at_payroll_dokumente.%s' % xmlid)
|
||||||
|
self.assertTrue(vorlage.inhalt_html)
|
||||||
|
self.assertEqual(vorlage.rechtsstatus, 'entwurf')
|
||||||
|
kuendigung = self.env.ref(
|
||||||
|
'l10n_at_payroll_dokumente.vorlage_kuendigung_dienstgeber')
|
||||||
|
wizard = self.env['l10n.at.payroll.dokument.wizard'].create({
|
||||||
|
'vorlage_id': kuendigung.id,
|
||||||
|
'employee_id': self.employee.id,
|
||||||
|
'datum': datetime.date(2026, 9, 1),
|
||||||
|
})
|
||||||
|
wizard.action_befuellen()
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'anrede').wert = 'Sehr geehrter Herr'
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'kuendigung_frist').wert = '6 Wochen'
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'beendigungstermin').wert = '31.10.2026'
|
||||||
|
wizard.feld_wert_ids.filtered(
|
||||||
|
lambda l: l.key == 'ort_datum').wert = 'Wien, 1.9.2026'
|
||||||
|
wizard.action_vorschau()
|
||||||
|
self.assertIn('31.10.2026', wizard.vorschau_html)
|
||||||
|
self.assertIn('Musterwerkstatt GmbH', wizard.vorschau_html)
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_dokument_vorlage_list" model="ir.ui.view">
|
||||||
|
<field name="name">l10n.at.payroll.dokument.vorlage.list</field>
|
||||||
|
<field name="model">l10n.at.payroll.dokument.vorlage</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="thema"/>
|
||||||
|
<field name="kb_ref"/>
|
||||||
|
<field name="quell_stand"/>
|
||||||
|
<field name="rechtsstatus"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_dokument_vorlage_form" model="ir.ui.view">
|
||||||
|
<field name="name">l10n.at.payroll.dokument.vorlage.form</field>
|
||||||
|
<field name="model">l10n.at.payroll.dokument.vorlage</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<header>
|
||||||
|
<field name="rechtsstatus" widget="statusbar"
|
||||||
|
options="{'clickable': true}"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_title">
|
||||||
|
<label for="name"/>
|
||||||
|
<h1><field name="name" placeholder="z. B. Dienstvertrag (Angestellte)"/></h1>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="thema"/>
|
||||||
|
<field name="active"/>
|
||||||
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="kb_ref"/>
|
||||||
|
<field name="quell_stand"/>
|
||||||
|
<field name="rechtsstatus"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="beschreibung"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Vorlagentext" name="text">
|
||||||
|
<field name="inhalt_html"/>
|
||||||
|
</page>
|
||||||
|
<page string="Befüllfelder" name="felder">
|
||||||
|
<field name="feld_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="key"/>
|
||||||
|
<field name="bezeichnung"/>
|
||||||
|
<field name="quelle"/>
|
||||||
|
<field name="pflicht"/>
|
||||||
|
<field name="hinweis"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_dokument_vorlage" model="ir.actions.act_window">
|
||||||
|
<field name="name">Dokumentvorlagen</field>
|
||||||
|
<field name="res_model">l10n.at.payroll.dokument.vorlage</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="context">{'default_active': True}</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Erste Dokumentvorlage anlegen
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Eigener Text mit Platzhaltern {{ werte['key'] }}; die
|
||||||
|
Befüllfelder steuern, was der Generierungs-Wizard abfragt.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_dokumente_root"
|
||||||
|
name="AT Dokumente"
|
||||||
|
sequence="50"/>
|
||||||
|
<menuitem id="menu_dokument_vorlage"
|
||||||
|
action="action_dokument_vorlage"
|
||||||
|
parent="menu_dokumente_root"
|
||||||
|
sequence="10"/>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_dokument_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">l10n.at.payroll.dokument.wizard.form</field>
|
||||||
|
<field name="model">l10n.at.payroll.dokument.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Dokument aus Vorlage generieren">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="vorlage_id" options="{'no_create': True}"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="datum"/>
|
||||||
|
<field name="version_id"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Befüllung" name="befuellung">
|
||||||
|
<field name="feld_wert_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="key" optional="show"/>
|
||||||
|
<field name="bezeichnung" optional="show"/>
|
||||||
|
<field name="hinweis" optional="hide"/>
|
||||||
|
<field name="quelle" optional="show"/>
|
||||||
|
<field name="pflicht" optional="hide"/>
|
||||||
|
<field name="wert"
|
||||||
|
readonly="key and quelle == 'auto'"
|
||||||
|
required="pflicht"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Vorschau" name="vorschau">
|
||||||
|
<field name="vorschau_html" nolabel="1"/>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
<footer>
|
||||||
|
<button name="action_vorschau" type="object"
|
||||||
|
string="Vorschau erzeugen" class="btn-primary"/>
|
||||||
|
<button name="action_pdf" type="object"
|
||||||
|
string="PDF erzeugen & ablegen"
|
||||||
|
class="btn-primary"/>
|
||||||
|
<button special="cancel" string="Abbrechen"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_dokument_generieren" model="ir.actions.act_window">
|
||||||
|
<field name="name">Dokument aus Vorlage generieren</field>
|
||||||
|
<field name="res_model">l10n.at.payroll.dokument.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_dokument_generieren_employee" model="ir.actions.act_window">
|
||||||
|
<field name="name">Dokument aus Vorlage generieren</field>
|
||||||
|
<field name="res_model">l10n.at.payroll.dokument.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
<field name="context">{'default_employee_id': active_id}</field>
|
||||||
|
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||||
|
<field name="binding_view_types">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_dokument_generieren"
|
||||||
|
action="action_dokument_generieren"
|
||||||
|
parent="menu_dokumente_root"
|
||||||
|
sequence="20"/>
|
||||||
|
</odoo>
|
||||||
@@ -60,7 +60,9 @@ Absetzbeträgen — kein Pendlerpauschalen-Feature).
|
|||||||
4. **Endabrechnungs-Zyklus**: Urlaubsersatzleistung (lb-url-09/15/16),
|
4. **Endabrechnungs-Zyklus**: Urlaubsersatzleistung (lb-url-09/15/16),
|
||||||
Abgangsentschädigung/Vergleich/Prämien bei Beendigung
|
Abgangsentschädigung/Vergleich/Prämien bei Beendigung
|
||||||
(lb-end-04/05/06/08…22), Aufrechnung Vorschüsse/Darlehen
|
(lb-end-04/05/06/08…22), Aufrechnung Vorschüsse/Darlehen
|
||||||
(lb-lvr-01/02), Zeitschulden/Zeitguthaben (lb-end-09/10)
|
(lb-lvr-01/02), Zeitschulden/Zeitguthaben (lb-end-09/10),
|
||||||
|
Ausbildungskostenrückersatz (lb-end-27/28 — **Nachtrag 2026-09-16**
|
||||||
|
aus Batch 10; nicht im Abgleich-Stand 2026-09-12)
|
||||||
5. **Abfertigung Alt**-Bezugszeile (lb-end-01/02, lb-kar-01)
|
5. **Abfertigung Alt**-Bezugszeile (lb-end-01/02, lb-kar-01)
|
||||||
6. **Karenz-/Familien-Reste**: SV-An/-Abmeldung Karenz, Elternteilzeit
|
6. **Karenz-/Familien-Reste**: SV-An/-Abmeldung Karenz, Elternteilzeit
|
||||||
(lb-elt-03), Bildungs-/Pflege-/Hospizkarenz (lb-kzs-01/02/06/07),
|
(lb-elt-03), Bildungs-/Pflege-/Hospizkarenz (lb-kzs-01/02/06/07),
|
||||||
@@ -191,8 +193,14 @@ Test-Integration.
|
|||||||
`l10n_at_s67_abs` ✅), Prämien bei Beendigung, Aufrechnung
|
`l10n_at_s67_abs` ✅), Prämien bei Beendigung, Aufrechnung
|
||||||
Vorschüsse/Darlehen (Rückzahlungsmodus lb-end-06, lb-lvr-01/02),
|
Vorschüsse/Darlehen (Rückzahlungsmodus lb-end-06, lb-lvr-01/02),
|
||||||
Zeitguthaben/-schulden-Abrechnung.
|
Zeitguthaben/-schulden-Abrechnung.
|
||||||
|
- **Nachtrag (2026-09-16, Batch 10):** **Ausbildungskostenrückersatz**
|
||||||
|
bei vorzeitigem Austritt (lb-end-27 Checkliste, lb-end-28 Muster)
|
||||||
|
als Endabrechnungs-Komponente im Wizard — Rechtsgrundlage vor Bau
|
||||||
|
RIS-verifizieren (§ 32 AngG-Kontext/RL-VA); Scope-Gate Pilotmandant.
|
||||||
- **Rechtsanker:** lb-url-09/15/16, lb-son-05, lb-end-04/05/06/08…22;
|
- **Rechtsanker:** lb-url-09/15/16, lb-son-05, lb-end-04/05/06/08…22;
|
||||||
§ 67 Abs 8 lit d EStG + § 11 Abs 2 ASVG RIS-Gate.
|
§ 67 Abs 8 lit d EStG + § 11 Abs 2 ASVG RIS-Gate; **lb-end-27/28**
|
||||||
|
(Ausbildungskostenrückersatz, Nachtrag 2026-09-16 — RIS-Gate vor
|
||||||
|
Bau).
|
||||||
- **Tests:** Rechenfälle `endabrechnung.md` — Urlaubsersatzleistung
|
- **Tests:** Rechenfälle `endabrechnung.md` — Urlaubsersatzleistung
|
||||||
2 Komponenten, §-11-Abs-2-SV-Fall, Vergleich Günstiger § 67 Abs 3,
|
2 Komponenten, §-11-Abs-2-SV-Fall, Vergleich Günstiger § 67 Abs 3,
|
||||||
Aufrechnung gegen SZ (pfändungsfest? — Schnittstelle AP11),
|
Aufrechnung gegen SZ (pfändungsfest? — Schnittstelle AP11),
|
||||||
|
|||||||
Reference in New Issue
Block a user