1df5fb19c4
Document generation module: templates with own text (structure reference and source stand documented per template, no licensed full text in the repo), fill fields resolved from hr data (company, employee, hr.version stand) plus wizard input, preview rendering and PDF/HTML attachment via the generic report layout. - templates: pilot set for the general-AT track formulated as own text after the KB muster structure (lb-bes-09, lb-bnd-55, lb-bnd-52/62, lb-end-35), legal status per template marked as draft - rendering via convert_inline_template_to_qweb + ir.qweb (t-out auto-escapes values) — deliberately not mail.render.mixin inline engine, whose expression whitelist blocks werte[...] placeholders for non-template-editors - wizard resolves hr.version by date_version at the reference date; PDF falls back to HTML attachment without wkhtmltopdf - security: ir.model.access (hr user read / manager write) + company rule; 8 tests, suite 236/236 green on a fresh -i database run
118 lines
4.7 KiB
Python
118 lines
4.7 KiB
Python
# 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.'),
|
|
] |