Python code for advanced Office reports
Operating mode
Cloud Suite
|ON-PREMISES
Modules
Services & CRM
Budget & Phases
Purchases
Resource Planning
Business Intelligence
If you are working with Vertec version 6.5.0.20 or later, you should use the article Python code for advanced office reports.
The data logic of an extended Office report is based on a hierarchy of frames. A frame is similar to a table, it has rows of data fields on it. The fields of a frame are defined using field definitions.
The Python code for an extended Office report essentially consists of the declaration of the frames, starting with the main frame (main_frame
) of the report.
A minimum report definition must contain a frame declaration and the assignment of the main_frame
variable. Here, for example, an editor frame with 2 fields is defined:
# Beispiel eines minimalen Mitarbeiter-Reports class Bearbeiter(Frame): businessclass = "Projektbearbeiter" fields = [ OclTextField("name"), OclTextField("kuerzel"), ] main_frame = Bearbeiter
Each ribbon in Word points to a specific frame (bndXXXExp). The variables used within the ribbon point to fields declared in the frame (see the Fields section below). Fields not declared in the frame are not available in Word.
The main_frame
corresponds to the standard volume in Word, which encloses everything.
Frames can be assigned to other frames as sub-frames. This allows hierarchical structures to be mapped:
Fields are created on the frames. These fields can be referenced later in the report. There are two types of fields: OCL fields and simple fields. The OCL fields calculate their value via an OCL expression. For simple fields, the content must be filled manually or with a Python feature.
There are the following types of fields:
Fields | OCL fields | description |
---|---|---|
TextField | OclTextField | Represents a string value |
CurrencyField | OclCurrencyField | Represents a fixed-point number, usually used for monetary amounts. Formatting according to country setting. |
IntegerField | OclIntegerField | Represents an integer |
MinuteField | OclMinuteField | Represents an integer value as minutes. Formatting as set in Vertec. |
Boolean Field | OclBooleanField | Represents a True/False value |
DateField | OclDateField | Represents a date |
DateTimeField | OclDateTimeField | Represents a date with a time fraction |
ImageField | OclImageField | Represents an image |
FrameField | OclFrameField | Contains another frame as a value. Frame fields allow the representation of hierarchical data structures. |
OCL fields contain the field name as the first parameter. If this corresponds to the member to be used for the calculation, nothing else needs to be specified:
OclTextField("code")
Optionally, an OCL expression can be specified as a second argument:
OclTextField("name", "projektleiter.name")
OCL fields are only allowed on frames that can be assigned to a unique business class, which is specified in the declaration of the frame using businessclass=
.
Simple fields (without an OCL prefix) also contain the field name as the first parameter. If not specified, these are non-automatically calculated fields that can be filled with values when calculating the frame in the code.
CurrencyField("value1")
Optionally, a second argument can be a Python feature that is called to calculate the field. Example:
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
The structure of the frame fields is somewhat different from the other field types, i.e. the fields with which the sub-frames are specified.
These fields have three arguments:
FrameField("leistungen", "Leistungen", "calculate_leistungen")
OclFrameField
), the result of the expression must be a list of business objects corresponding to the frame declaration. OclFrameField("offeneLeistungen", "Leistung", "offeneLeistungen->ordermulti('datum;creationdatetime')If the OCL expression matches the name of the frame, it can be omitted. The report mechanism will then use the name as the OCL expression:
OclFrameField("offeneLeistungen", "Leistung")
If the computation is done using a Python feature (FrameField
), as shown in the example above, the feature must return a frame instance of the correct type.
Frames are structured as follows:
Syntax:
Class XY(Frame):
There is a main frame. It is called main_frame
and must be assigned a declared frame:
main_frame = XY
Frames can be assigned to other frames as sub-frames by using a frame field (see the Fields section above).
fields = [FrameField("leistungen", "Leistungen", "calculate_leistungen")]
For frames where the objects all belong to the same business class, the fields can be calculated via OCL:
class Leistungen(Frame): businessclass="OffeneLeistung" fields = [OclDateField("datum"), OclTextField("projekt", "projekt.code"), OclTextField("text"), OclMinuteField("minutenext"), OclCurrencyField("ansatzext"), OclCurrencyField("wertext"), ]
Frames can also be constructed in code. This is needed when different objects are to be displayed in a row, i.e. in a band, which do not belong to the same business class, and therefore cannot be calculated automatically.
For this purpose, a add_row
method is available on the frame object, which allows a row-by-row construction of the frame. The fields defined on the frame are available as properties of the Row object.
class DetailsFrame(Frame): fields = [TextField("code"), CurrencyField("value1"), CurrencyField("value2"), ] class Invoice(Frame): fields = [FrameField("details", DetailsFrame, calculate_details)]
The individual rows of such a frame are added via add_row()
. The fields of the frame are then populated during the calculation.
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
All frames except the main_frame are specified as a field in another frame and a calculation is included.
For the very first frame, the main_frame, this is not possible. If you want to calculate this first frame, you can do it with the calculate_main_frame
method.
You always need this if you want to calculate something in the main_frame (and not, for example, all projects or the object on which you start the report).
The method calculate_main_frame
is available in 2 variants:
calculate_main_frame
is a feature that gets a context object and should return a frame instance of the correct type.Signature of the feature is calculate_main_frame(context)
, return value is a frame instance.Example:The report is registered to a list of projects, but the main frame is a list of users. The main frame class is called 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
is a string. This is evaluated as an OCL expression and must return a list. The expression is evaluated on the original list of the report.Example, as above, but with expression only:main_frame = Bearbeiter calculate_main_frame = "bearbeiter"
A Python feature is declared as follows:
def Funktionsname(context): return XY
Features can be assigned within a frame and are then available in that frame. If features are to be available everywhere, they must be declared outside the frames.
Each calculation in the context of report generation is provided with the context object. The context object is looped through all calculation functions and has the following properties:
The following variables are defined in each report:
currentobject | The up-to-date object of the calculation.
|
currentdate | Today’s date, without time part. context.currentdate |
optarg | The optional address argument for reports. To continue working with the object, it can be accessed as follows: class Projekt(Frame): |
rootlist | The list on which the report was executed. Usually this is the list of entries of the container on which the report was executed, or a list of the object on which the report was executed. |
container | The container on which the report was run. |
var<frame> | For each parent frame a variable with its up-to-date object is defined. |
var<frame>List | For each parent frame, a variable with the list of objects of the frame is defined. |
Further variables can be defined and assigned to the context object.
firma = vtcapp.getpropertyvalue('Firma') if firma: context.firma = firma
The context variables can be output later on the report directly via a context expression.
From Vertec 6.5.0.15 onwards, via set_image(name, value)
an image can be read into a context variable:
logo = vtcapp.getpropertyvalue('CompanyLogo') context.set_image('logo', logo)
This can be output as a normal Context Expression as well as in Headers and footers of Word reports.
In addition, the context object has the evalocl(<expression>)
method, which evaluates an OCL expression on the up-to-date object (currentobject).
context.evalocl("offeneLeistungen.wertext->sum")
is equivalent to
context.currentobject.evalocl("offeneLeistungen.wertext->sum")
If the last argument to context.evalocl
is None or not, the context’s currentobject is used. You can also pass an object or list as an argument, in which case the expression is applied to the object or list passed.
context.evalocl("offeneLeistungen.wertext->sum", phasenliste)
Starting with Vertec 6.2.0.7, the context variables can be used directly in OCL. This greatly simplifies calculations of the following types:
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))
The ability to use the context variable in OCL reduces this code to:
class Bearbeiter(Frame): businessclass = "Projektbearbeiter" fields = [ OclMinuteField("balance", "self->getFerienSaldo(todate)"), ]
The following rules apply:
varXY
and varXYList
. The manually set variables on the context object may conflict with keywords in OCL (self, date, now). In this case, an error message will appear.context.evalocl
method. All other OCL calls, especially the evalocl
methods of business objects, use the global OCL evaluator and it does not know the local report variables. Instead of e.g. spesenlist.evalocl(..), it is written as follows: templist = context.evalocl("self->select(getbearbeiter=varBearbeiter)->ordermulti('datum;boldid')", spesenlist)
As of Vertec 6.2.0.8, other fields of the same frame can be accessed in the calculation method of a field. This means that calculations only have to be performed once and then the value can be worked on directly. This is not only clearer, but also a performance advantage.
For this, a method on the context returns context.get_fieldvalue(fieldname)
.
Example
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")
To be able to run Python code before running the report (for example, to show dialogs and set parameters on the context object), there is the method 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"]
References to frames and features can be specified in two ways:
The reference as a name was introduced because it makes the code more readable, so you can first see where and why something is referenced, and then what it is.
Each ribbon in Word refers to a specific frame. The variables used within the ribbon refer to fields declared in the frame. Fields not declared in the frame are not available as variables in Word.
For individual reports, a report is created for the up-to-date object. Here, the main_frame is the individual object.
There are two variants of list reports:
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
Sample report: Expenses to be reimburseed.
class Report(Frame): fields = [FrameField("projekte", "Projekt", "calculate_projekte")] def calculate_projekte(context): return Projekt(context, context.projekte)
Then this specially created frame is assigned as main_frame
. Since this frame has no object that can be displayed, a single row has to be created via add_row
– otherwise an error message will appear.
def calculate_main_frame(context): frame = Report(context) row = frame.add_row() return frame
The method for generating ZUGFeRD metadata in invoice reports is available from Vertec version 6.3.0.12 onwards
metadata_zugferd(context)
If this method is defined in the report code, the XML metadata generated with it is integrated into the PDF of the report. The exact procedure is described in the separate article Invoices according to ZUGFeRD 2.0 Standard (X-Invoice).