Python-Code für erweiterte Office-Berichte
Produktlinie
Standard
|Expert
Betriebsart
CLOUD ABO
|ON-PREMISES
Module
Leistung & CRM
Budget & Teilprojekt
Fremdkosten
Ressourcenplanung
Business Intelligence
Falls Sie mit einer Vertec Version ab 6.5.0.20 arbeiten, sollten Sie den Artikel Python-Code für erweiterte Office-Berichte verwenden.
Die Datenlogik eines erweiterten Office-Berichts basiert auf einer Hierarchie von sogenannten Frames. Ein Frame ist vergleichbar mit einer Tabelle, es hat Zeilen mit Datenfeldern drauf. Die Felder eines Frames werden mit Hilfe von Feld-Definitionen festgelegt.
Der Python Code für einen erweiterten Office-Bericht besteht im Wesentlichen aus der Deklaration der Frames, startend mit dem Haupt-Frame (main_frame
) des Reports.
Eine minimale Bericht-Definition muss eine Frame-Deklaration sowie die Zuweisung der main_frame
Variable enthalten. Hier wird beispielsweise ein Bearbeiter-Frame mit 2 Feldern definiert:
# Beispiel eines minimalen Mitarbeiter-Reports class Bearbeiter(Frame): businessclass = "Projektbearbeiter" fields = [ OclTextField("name"), OclTextField("kuerzel"), ] main_frame = Bearbeiter
Jedes Band im Word verweist auf ein bestimmtes Frame (bndXXXExp). Die innerhalb des Bands verwendeten Variablen verweisen auf Felder, die im Frame deklariert wurden (siehe Abschnitt Felder weiter unten). Felder, die im Frame nicht deklariert wurden, sind im Word nicht verfügbar.
Das main_frame
entspricht dem Standard-Band im Word, welches alles umschliesst.
Frames können anderen Frames als Sub-Frames zugewiesen werden. Dadurch können hierarchische Strukturen abgebildet werden:
Auf den Frames werden Felder erzeugt. Auf diese Felder kann später im Report referenziert werden. Es gibt zwei Arten von Feldern: OCL-Felder und einfache Felder. Die OCL-Felder berechnen ihren Wert über eine OCL-Expression. Bei den einfachen Feldern muss der Inhalt manuell bzw. mit einer Python Funktion abgefüllt werden.
Es gibt folgende Arten von Feldern:
Felder | OCL-Felder | Beschreibung |
---|---|---|
TextField | OclTextField | Stellt einen String-Wert dar |
CurrencyField | OclCurrencyField | Stellt eine Fixkomma Zahl dar, wird üblicherweise für Geld-Beträge verwendet. Formatierung gemäss Ländereinstellung. |
IntegerField | OclIntegerField | Stellt eine Ganzzahl dar |
MinuteField | OclMinuteField | Stellt einen Integer-Wert als Minuten dar. Formatierung gemäss Einstellung in Vertec. |
BooleanField | OclBooleanField | Stellt einen Wahr/Falsch Wert dar |
DateField | OclDateField | Stellt ein Datum dar |
DateTimeField | OclDateTimeField | Stellt ein Datum mit Zeitanteil dar |
ImageField | OclImageField | Stellt ein Bild dar |
FrameField | OclFrameField | Enthält als Wert ein weiteres Frame. Frame-Fields erlauben die Darstellung von hierarchischen Datenstrukturen. |
OCL Felder enthalten als ersten Parameter den Feldnamen. Entspricht dieser dem Member, welches für die Berechnung verwendet werden soll, muss nichts weiter angegeben werden:
OclTextField("code")
Optional kann als zweites Argument eine OCL Expression angegeben werden:
OclTextField("name", "projektleiter.name")
OCL-Felder sind nur auf Frames zulässig, welche einer eindeutigen Businessklasse zugeordnet werden können. Diese wird in der Deklaration des Frames mittels businessclass=
angegeben.
Einfache Felder (ohne OCL-Präfix) enthalten ebenfalls als ersten Parameter den Feldnamen. Ohne weitere Angaben stellen diese nicht automatisch berechnete Felder dar, welche beim Berechnen des Frames im Code mit Werten gefüllt werden können.
CurrencyField("value1")
Optional kann als zweites Argument eine Python Funktion angegeben werden, die zur Berechnung des Feldes aufgerufen wird. Beispiel:
class Rechnnung(Frame): fields = [CurrencyField("summe", "calc_summe")] def calc_summe(context): rechnung = context.currentobject sum = 0.0 for leist in rechnung.leistungen: sum += leist.wertext return sum
Etwas anders als die anderen Feld-Typen sind die Frame-Felder aufgebaut, also die Felder, mit welchen die Sub-Frames angegeben werden.
Diese Felder haben drei Argumente:
FrameField("leistungen", "Leistungen", "calculate_leistungen")
OclFrameField
), muss das Ergebnis der Expression eine Liste von Businessobjekten sein, welche der Frame Deklaration entsprechen.
OclFrameField("offeneLeistungen", "Leistung", "offeneLeistungen->ordermulti('datum;creationdatetime')Falls die OCL Expression dem Namen des Frames entspricht, so kann diese weggelassen werden. Der Report Mechanismus wird dann den Namen als OCL Expression verwenden:
OclFrameField("offeneLeistungen", "Leistung")
Falls die Berechnung per Python Funktion erfolgt (FrameField
), wie im obigen Beispiel dargestellt, muss die Funktion eine Frame-Instanz des richtigen Typs zurückgeben.
Frames werden wie folgt aufgebaut:
Syntax:
Class XY(Frame):
Es gibt ein Haupt-Frame. Dieses heisst main_frame
und muss ein deklariertes Frame zugewiesen bekommen:
main_frame = XY
Frames können anderen Frames als Sub-Frames zugewiesen werden. Dies geschieht mit einem Frame-Feld (siehe Abschnitt Felder weiter oben).
fields = [FrameField("leistungen", "Leistungen", "calculate_leistungen")]
Bei Frames, bei welchem die Objekte alle zur gleichen Businessklasse gehören, können die Felder via OCL berechnet werden:
class Leistungen(Frame): businessclass="OffeneLeistung" fields = [OclDateField("datum"), OclTextField("projekt", "projekt.code"), OclTextField("text"), OclMinuteField("minutenext"), OclCurrencyField("ansatzext"), OclCurrencyField("wertext"), ]
Frames können auch im Code aufgebaut werden. Dies wird dann benötigt, wenn in einer Zeile, also in einem Band, verschiedene Objekte dargestellt werden sollen, welche nicht zur gleichen Businessklasse gehören, und also nicht automatisch berechnet werden können.
Dafür steht auf dem Frame Objekt eine add_row
Methode zur Verfügung, welche ein zeilenweises Aufbauen des Frames erlaubt. Die auf dem Frame definierten Felder stehen als Eigenschaften des Row-Objektes zur Verfügung.
class DetailsFrame(Frame): fields = [TextField("code"), CurrencyField("value1"), CurrencyField("value2"), ] class Invoice(Frame): fields = [FrameField("details", DetailsFrame, calculate_details)]
Die einzelnen Zeilen eines solchen Frames werden via add_row()
hinzugefügt. Bei der Berechnung werden dann die Felder des Frames gefüllt.
def calculate_details(context): phasen = context.evalocl("phasenaufrechnung") frame = DetailsFrame(context) for ph in phasen: row = frame.add_row(ph) row.value1 = ph.evalocl("sumMinutenInt") row.value2 = ph.evalocl("sumWertExt") return frame
Alle Frames ausser dem main_frame werden als Feld in einem anderen Frame angegeben und dabei eine Berechnung mitgegeben.
Beim allerersten Frame, dem main_frame, ist das nicht möglich. Möchte man dieses erste Frame berechnen, kann dies mit der Methode calculate_main_frame
geschehen.
Dies braucht man immer dann, wenn man im main_frame etwas berechnen möchte (und nicht beispielsweise alle Projekte bzw. das Objekt, auf welchem man den Report startet) haben möchte.
Die Methode calculate_main_frame
gibt es in 2 Varianten:
calculate_main_frame
ist eine Funktion, welche ein Context-Objekt erhält und eine Frame-Instanz des richtigen Typs zurückgeben soll.Signatur der Funktion ist calculate_main_frame(context)
, Rückgabewert ist eine Frame Instanz.Beispiel:Der Report ist auf einer Liste von Projekten registriert, das Main-Frame stellt aber eine Liste von Bearbeitern dar. Die Main-Frame Klasse heisst Bearbeiter
.
main_frame=Bearbeiter def calculate_main_frame(context): bearbeiter_list = context.evalocl("bearbeiter") frame = Bearbeiter(context) for b in bearbeiter_list: row = frame.add_row(b) row.specialvalue = calculate_some_value() return frame
calculate_main_frame
ist ein String. Dieser wird als OCL Expression ausgewertet und muss eine Liste ergeben. Die Expression wird auf der ursprünglichen Liste des Reports ausgewertet.Beispiel, wie oben, aber nur mit Expression:
main_frame = Bearbeiter calculate_main_frame = "bearbeiter"
Eine Python Funktion wird wie folgt deklariert:
def Funktionsname(context): return XY
Funktionen können innerhalb eines Frames zugewiesen werden und sind dann in diesem Frame verfügbar. Sollen Funktionen überall verfügbar sein, müssen Sie ausserhalb der Frames deklariert werden.
Jeder Berechnung im Rahmen der Report-Generierung wird das Context-Objekt mitgegeben. Dieses wird so durch alle Berechnungsfunktionen durchgeschlauft und hat folgende Eigenschaften:
Die folgenden Variablen sind in jedem Report definiert:
currentobject | Das jeweils aktuelle Objekt der Berechnung.
|
currentdate | Das heutige Datum, ohne Zeitteil. context.currentdate |
optarg | Das optionale Adressen-Argument bei Berichten. Um mit dem Objekt weiterzuarbeiten, kann wie folgt darauf zugegriffen werden:
class Projekt(Frame): |
rootlist | Die Liste, auf der der Report ausgeführt wurde. Normalerweise ist das die eintraege-Liste des Containers, auf dem der Report ausgeführt wurde, beziehungsweise eine Liste mit dem Objekt drin, auf dem der Report ausgeführt wurde. |
container | Der Container, auf dem der Report ausgeführt wurde. |
var<frame> | Für jedes übergeordnete Frame ist eine Variable mit dessen aktuellem Objekt definiert. |
var<frame>List | Für jedes übergeordnete Frame ist eine Variable mit der Liste der Objekte des Frames definiert. |
Weitere Variablen können selbst definiert und dem Context-Objekt zugewiesen werden.
firma = vtcapp.getpropertyvalue('Firma') if firma: context.firma = firma
Die Context-Variablen können später auf dem Report direkt über eine Context-Expression ausgegeben werden.
Ab Vertec 6.5.0.15 kann via set_image(name, value)
ein Bild in eine Context-Variable eingelesen werden:
logo = vtcapp.getpropertyvalue('CompanyLogo') context.set_image('logo', logo)
Diese kann als normale Context-Expression sowie auch in Kopf- und Fusszeilen von Word-Reports ausgegeben werden.
Ausserdem verfügt das Context-Objekt über die Methode evalocl(<expression>)
, welche eine OCL-Expression auf dem aktuellen Objekt (currentobject) auswertet.
context.evalocl("offeneLeistungen.wertext->sum")
ist äquivalent zu
context.currentobject.evalocl("offeneLeistungen.wertext->sum")
Wenn das letzte Argument von context.evalocl
None ist oder nicht angegeben wird, dann wird das currentobject des contexts verwendet. Man kann aber auch ein Objekt oder eine Liste als Argument übergeben; in diesem Fall wird die Expression auf das übergebene Objekt bzw. Liste angewendet.
context.evalocl("offeneLeistungen.wertext->sum", phasenliste)
Ab Vertec 6.2.0.7 können die Context-Variablen direkt in OCL verwendet werden. Berechnungen der folgenden Art können dadurch stark vereinfacht werden:
class Bearbeiter(Frame): businessclass = "Projektbearbeiter" fields = [ MinuteField("balance", "calc_balance"), ] def calc_balance(context): return context.currentobject.evalocl("self->getFerienSaldo(%s)" % vtcapp.ocldate(context.todate))
Durch die Möglichkeit der Verwendung der Context-Variable in OCL reduziert sich dieser Code auf:
class Bearbeiter(Frame): businessclass = "Projektbearbeiter" fields = [ OclMinuteField("balance", "self->getFerienSaldo(todate)"), ]
Es gelten folgende Regeln:
varXY
und varXYList
. Die manuell auf dem Contextobjekt gesetzten Variablen können mit Keywords in OCL kollidieren (self, date, now). In diesem Fall erscheint eine Fehlermeldung.context.evalocl
Methode verwendet werden können. Alle anderen OCL Aufrufe, insbesondere die evalocl
Methoden von Businessobjekten, verwenden den globalen OCL Evaluator und der kennt die lokalen Report-Variablen nicht. Statt z.B. spesenlist.evalocl(..) wird das wie folgt geschrieben:
templist = context.evalocl("self->select(getbearbeiter=varBearbeiter)->ordermulti('datum;boldid')", spesenlist)
Ab Vertec 6.2.0.8 kann in in der Berechnungsmethode eines Fields auf andere Fields des selben Frames zugegriffen werden. Dadurch müssen Berechnungen nur noch einmal durchgeführt und danach kann direkt mit dem Wert weitergearbeitet werden. Das ist nicht nur übersichtlicher, sondern auch ein Performance-Vorteil.
Dafür gibt auf dem Context eine Methode context.get_fieldvalue(fieldname)
.
Beispiel
class Projekt(Frame): businessclass = "Projekt" fields = [ OclMinuteField("bdgaufwand", "if (phasen->size>0) then phasen.bdgvalue('planminutenint', duedate, -1)->sum else planminutenint endif"), OclMinuteField("effaufwand", "leistsums->select(projekt=varProjekt)->collect(minutenintoffen+minutenintverrechnet)->sum"), MinuteField("restaufwand", "calculate_restaufwand"), def calculate_restaufwand(context): return context.get_fieldvalue("bdgaufwand")-context.get_fieldvalue("effaufwand")
Um vor der Ausführung des Reports Python Code ausführen zu können (um beispielsweise Dialoge anzuzeigen und Parameter auf dem Context-Objekt zu setzen), gibt es die Methode before_report(context)
:
def before_report(context): """Frage den User nach dem Datum ab.""" initValues = {} initValues["Stichdatum"] = vtcapp.currentdate() dlgDefinition=""" <Dialog Title="{Translate 'Choose date'}" Width="400"> <Group Orientation="Vertical"> <DatePicker Name="Stichdatum" Label="Stichdatum" /> </Group> <Dialog.Buttons> <Button Text="OK" IsAccept="True" Command="{Binding OkCommand}" /> <Button Text="Cancel" IsCancel="True" Command="{Binding CancelCommand}" /> </Dialog.Buttons> </Dialog> """ ok, values = vtcapp.showcustomdialog(dlgDefinition, initValues) if not ok: return False context.stichdatum = values["Stichdatum"]
Referenzen auf Frames und Funktionen können auf zwei Arten angegeben werden:
Die Referenz als Name wurde eingeführt, weil es zu leserlicherem Code führt. So sieht man zuerst, wo und warum etwas referenziert wird, und dann, was es ist.
Jedes Band im Word verweist auf ein bestimmtes Frame. Die innerhalb des Bands verwendeten Variablen verweisen auf Felder, die im Frame deklariert wurden. Felder, die im Frame nicht deklariert wurden, sind im Word nicht als Variablen verfügbar.
Bei Einzelreports wird für das aktuelle Objekt ein Report erzeugt. Hier ist das main_frame das einzelne Objekt.
Bei Listenreports gibt es zwei Varianten:
class Bearbeiter(Frame): businessclass = "Projektbearbeiter" fields = [OclTextField("name"), OclTextField("kuerzel"), FrameField("leistungen", "Leistungen", "calculate_leistungen"), ] def calculate_leistungen(context): leistungen = context.evalocl("offeneleistungen->orderby(datum)") return Leistungen(context, leistungen) main_frame = Bearbeiter
Beispielreport: Rückzuerstattende Spesen.
class Report(Frame): fields = [FrameField("projekte", "Projekt", "calculate_projekte")] def calculate_projekte(context): return Projekt(context, context.projekte)
Dann wird dieses extra erstellte Frame als main_frame
zugewiesen. Da dieses Frame kein Objekt hat, welches dargestellt werden kann, muss via add_row
eine einzelne Zeile erzeugt werden – sonst erscheint eine Fehlermeldung.
def calculate_main_frame(context): frame = Report(context) row = frame.add_row() return frame
Für die Generierung von ZUGFeRD Metadaten in Rechnungsreports gibt es ab Vertec Version 6.3.0.12 die Methode
metadata_zugferd(context)
Wird diese Methode im Report-Code definiert, werden die damit generierten XML-Metadaten in das PDF des Reports integriert. Das genaue Vorgehen ist im separaten Artikel Rechnungen nach ZUGFeRD 2.0 Standard (X-Rechnung) beschrieben.