Python code for Office reports
Operating mode
Cloud Suite
|ON-PREMISES
Modules
Services & CRM
Budget & Phases
Purchases
Resource Planning
Business Intelligence
This article applies to Vertec versions from 6.5.0.20. A description for older versions can be found here. What has changed compared to previous versions can be found in the article Python code for office reports before – after.
The data content of a Office Reports is calculated by Python code, which is stored as a report definition:
The exact structure of this code is described in detail below.
The data logic of a Office Reports is based on a hierarchy of so-called tables.
The individual objects are then the “rows” of such a table: for each object (individually or in the list on which the report is executed), a row (e.g. row
) was introduced.
Each Band in word or Excel (Disambiguation) refers to a table. The variables used within the band refer to the Fields.
A table is declared as follows:
class Name(Table):
The name is optional, but must be unique within the report definition.
# Beispiel eines minimalen Projekt-Reports class Projekt(Table): businessclass = "Projekt"
On this table no own have been (yet) Fields defined. For these objects, all normal Vertec members are available, as they are also available via OCL.
In order for the Vertec member call to work in the report, the table must be assigned to a unique business class (project, project user, address entry, etc.). This is specified in the declaration of the table using businessclass=
.
Note: If no business class is specified, the values are simply returned as strings. This works for simple ads, but is not expected. We therefore recommend that you always specify the business class.
If several tables are used that do not have a parent table, one of them must be marked as the parent table. This is done with the variable main_table
:
class Projekt(Table): businessclass = "Projekt" main_table = Projekt
The assignment of a table as a sub-table is done via a field declaration. The exact procedure is described in section Table Fields (Subtables) described.
All tables except the main table (main_table) are specified as fields in another table and a calculation is given. The main table, the main_table, is not the case. There, the list (rootlist
) on which the report is executed (executed on a single object is a list with one entry).
If you want to pre-calculate the main table (for example, to filter or sort a list), you can do this with the method calculate_main_table
. There are 2 variants of this method:
calculate_main_table
is a feature that Context Object contains and should return a Table instance of the correct type. The signature of the feature is calculate_main_table(context)
, the return value is a Table instance.def calculate_main_table(context): projektlist = context.rootlist.evalocl("self->orderby(code)") # Hier können Berechnungen gemacht werden return Projekt(context, projektlist)
calculate_main_table
is a string. This will be evaluated as an OCL expression and must return a list. The expression will be evaluated on the original list of the report. Example, as above, but as an expression:calculate_main_table = "self.oclAsType(Projekt)->orderby(code)->asSet"
self
references the given rootlist. The rootlist itself does not know the type of its objects, so it must be specified (.oclAsType(Klasse)
).
If the objects of a list are not to be shown per object, but in a list, a parent report table must be created, which provides the frame for the list, but has no object itself.
For this purpose, the main table can be used as SingeRowTable
be declared. In such a table, the system will automatically generate exactly one row
created, which the list can then contain as a subtable.
# Projektliste mit Reporttable class Projekt(Table): fields = [] class Report(SingleRowTable): fields = [ TableField("projekte", Projekt), ] def initialize_row(context, row): row.projekte = Projekt(context, context.rootlist)
The SingleRowTable can also be used via calculate_main_table
It has the same properties as the normal table, but does not evaluate OCL. So you cannot create OCL fields on it and also the Context Variables are not used via OCL, since there is no object on which the expressions could be evaluated.
For all objects for which the report is executed, a “row” of the table is created. A row is called row
.
On the table there is the method initialize_row to initialize the objects. In it you can do calculations, fill fields and set context variables. The syntax is as follows:
def initialize_row(context, row):
This method is called automatically for each individual object.
# Projektliste mit Reporttable class Projekt(Table): fields = [] class Report(SingleRowTable): fields = [ TableField("projekte", Projekt), ] def initialize_row(context, row): row.projekte = Projekt(context, context.rootlist)
Tables can also be built in code. This is needed when different objects are to be displayed that 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 table, which allows a row-by-row structure of the table. The fields defined on the table are available as properties of the row object.
class Details(Table): fields = [ TextField("code"), CurrencyField("value1"), CurrencyField("value2"), ] class Invoice(Table): fields = [ TableField("details", Details, "calculate_details") ] def calculate_details(context): phasen = context.evalocl("phasentotale.phase->asSet") for ph in phasen: row = context.table.add_row(ph) row.value1 = ph.evalocl("sumMinutenInt") row.value2 = ph.evalocl("sumWertExt")
The quantity of objects in a table can be determined by len(table)
can be determined.
The standard Vertec members are used as data fields. These can be easily referenced in the report template without the need for a field declaration in the code (see Office Reports – Fields).
In addition, further fields can be declared and filled with calculations, for example.
There are two types of fields: OCL fields and simple fields. The OCL fields calculate their value via an OCL expression. The simple fields have to be filled manually or with a Python feature.
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 according to Settings 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 |
TableField |
OclTableField | Contains a subtable as a value. Table fields allow the representation of hierarchical data structures. |
OCL fields contain the field name (referenced in the report template) as the first parameter, and an OCL expression for the calculation as the second parameter:
OclTextField("projektleiter", "projektleiter.kuerzel")
The result of the expression must be of the data type for which the field was declared (OclTextField = String, OclIntegerField = Integer, etc.).
The second parameter is optional. If it corresponds to the first parameter (e.g. OclTextField("code", "code")
, it can be omitted or the field directly referenced in the report without having to declare a field at all.
Simple fields (without Ocl
-prefix) also contain the field name as the first parameter:
CurrencyField("offeneLeistungen")
Without further information, these are not automatically calculated fields, which can be manually filled with values when calculating the table in the code, for example in a initialize_row:
class Projekt(Table): businessclass = "Projekt" fields = [ CurrencyField("offeneLeistungen"), CurrencyField("offeneSpesen"), ] def initialize_row(context, row): row.offeneLeistungen = context.evalocl("offeneleistungen.wertext->sum") row.offeneSpesen = context.evalocl("offeneSpesen.wertext->sum")
Optionally, a second argument can be given as a Python feature, which is called automatically to calculate the field. Example:
class Projekt(Table): businessclass = "Projekt" fields = [ CurrencyField("offeneLeistungen", "calc_leistungen"), CurrencyField("offeneSpesen", "calc_spesen"), ] def calc_leistungen(context): return context.evalocl("offeneleistungen.wertext->sum") def calc_spesen(context): return context.evalocl("offenespesen.wertext->sum")
It is also possible to use the same feature in several fields, by passing the fieldnames
:
class Projekt(Table): businessclass = "Projekt" fields = [ CurrencyField("offeneLeistungen", "calc_summen"), CurrencyField("offeneSpesen", "calc_summen"), ] def calc_summen(context, fieldname): if fieldname == "offeneLeistungen": return context.evalocl("offeneleistungen.wertext->sum") if fieldname == "offeneSpesen": return context.evalocl("offenespesen.wertext->sum")
The structure of the table fields is somewhat different from the other field types, i.e. the fields with which the sub-tables are specified.
These fields have three arguments:
TableField("leistungen", "Leistungen", "calculate_leistungen")
Calculation. The third argument is optional and can be used as follows:
OclTableField
), the result of the expression must be a list of business objects corresponding to the Table declaration. OclTableField("leistungen", "Leistung", "offeneLeistungen->orderby(datum)")If the OCL expression matches the name of the table, it can be omitted. The report mechanism uses the name as OCL expression:
OclTableField("offeneLeistungen", "Leistung")
Python feature: If the calculation is performed using a Python feature (declaration without Ocl prefix: TableField
), the feature must return a list of business objects corresponding to the Table declaration.
In the calculation function of a Table Field (3rd argument as a Python function), a resulting Table object is context.table
This allows access to an already created table, which allows the creation of a row via context.table.add_row()
.
# Projektliste mit Leistungen class Leistung(Table): fields = [ IntegerField("einwert"), ] class Projekt(Table): fields = [ TableField("leistungen", Leistung, "calc_leistungen"), ] def calc_leistungen(context): for l in context.evalocl("offeneleistungen"): row = context.table.add_row(l) row.einwert = 1234
TableField("offeneLeistungen", "Leistung")
Each calculation in the context of report generation is given the context object. The context object is looped through all calculation functions and has the following properties:
The context object has the method evalocl(<expression>)
, which evaluates an OCL expression on the up-to-date object.
context.evalocl("offeneLeistungen.wertext->sum")
An object or list can be passed as an optional second argument beside the expression. In this case, the expression is applied to the passed object or list.
context.evalocl("offeneLeistungen.wertext->sum", phasenliste)
With the method context.translate(text)
may produce texts in accordance with the language of the report (context.language) to be translated.
The value last set in the code of context.language
is used to translate the texts in the report template.
If an unknown language code is entered, the original texts are shown. The report is simply not translated.
This translation system does not work for MLString fields because they have a different translation mechanism. See the article on multilingualism with Vertec for more information on what MLString fields are and how they are translated.
If you want to translate such terms in Office reports, you have to use the OCL method .asstringbylanguage:
context.evalocl("typ.text.asStringByLanguage(language)")
Possible languages/parameters are 'DE’, 'DD’ (from 6.5.0.9), 'FR’, 'IT’, 'EN’ or 'NV’. Jargon discrimination is not possible for MLString fields.
class Leistung(Table): businessclass = "OffeneLeistung" fields = [ OclTextField("text", "typ.text.asStringByLanguage(language)"), ] def initialize_row(context, row): context.language = "FR"
or
class Leistung(Table): businessclass = "OffeneLeistung" fields = [ TextField("text"), ] def initialize_row(context, row): context.language = context.language[:2] row.text = context.evalocl("typ.text.asStringByLanguage(language)")
With context.language[:2]
you get the first two symbols of the existing language attribute. Since the default language in reports is always defined with the jargon suffix (DE0..), and this is not possible for MLString fields (see above), you get the translation to the current report language.
The following variables are defined in each report:
container |
The container on which the report was run. |
comment | From version 6.6.0.9. Contains the comment from the Print Dialog. |
currentobject |
The up-to-date object of the calculation.
|
currentdate |
Today’s date, without time part.context.currentdate |
Linguistic |
This variable contains the report language (interface language or project language if translation is activated). It can also be set to output the report in a different language:
It has the following values: – This affects the texts marked with translation in the report template. To translate text in Python code, the context.translate() method can be used. |
optarg |
The optional address argument for reports. To continue working with the object, it can be accessed as follows: class Projekt(Table): fields = [ TextField('adresstext', 'calcadresse'), ] def calcadresse (context): return context.optarg.adresstext |
opportunity | From version 6.7.0.6. For use in Outlook App: Email Templates. Contains the opportunity of the email pre-selected in the Outlook app. If no opportunity is pre-selected or if the report is called outside the Outlook app, the variable is |
phase | From version 6.7.0.6. For use in Outlook App: Email Templates. Contains the pre-selected phase of the email in the Outlook app. If no phase is preselected or if the report is called outside the Outlook app, the variable is |
project | From version 6.7.0.6. For use in Outlook App: Email Templates. Contains the project of the email pre-selected in the Outlook app. If no project is pre-selected or if the report is called outside the Outlook app, the variable is |
reportdef |
As of version 6.6.0.3. Contains the report object that executes the report. This is mainly for Centralized code useful when several reports are accessing the same code. |
rootlist |
The list on which the report was run. Usually this is the list of entries of the container on which the report was run, or a list of the object on which the report was run. |
subject | From version 6.6.0.9. Contains the subject of the Report Registration or from the Print Dialog. |
var<Table> |
For each parent table a variable with its up-to-date object is defined. |
var<Table>List |
For each parent table, a variable with the list of objects of the table is defined. |
Further variables can be defined and assigned to the context object.
firma = vtcapp.getpropertyvalue('Firma') if firma: context.firma = firma
Via set_image(name, value)
an image can be read into a context variable:
logo = vtcapp.getpropertyvalue('CompanyLogo') context.set_image('logo', logo)
The context variables can be output on the report via a context expression.
Context variables can also be used in OCL. The call is made via the name of the variable (without context.
), here in the example todate
:
class Bearbeiter(Table): fields = [ OclMinuteField("balance", "self->getFerienSaldo(todate)"), ]
The variables are only available in Ocl fields as well as in the context.evalocl method available. Other OCL calls such as vtcapp.evalocl()
or the evalocl
Methods on lists and objects use a global OCL evaluator and it does not know the context variables.
For example, if a context variable is to be applied to a list, evalocl must still be called on the context and the corresponding list must be passed as an argument. spesenlist.evalocl(..)
So you write:
context.evalocl("self->select(getProjekt=varProjekt)->orderby(code))", spesenlist)
OCL only supports calling OCL-compatible values (Vertec objects and lists, strings, integers, etc.). Incompatible values (e.g. Python Dictionaries, Python lists, etc.) an error message appears.
If manually set variables on the context object collide with keywords in OCL (self, date, now), an error message also appears.
In the calculation method of a field, other fields of the same table can be accessed. For this purpose, a method context.get_fieldvalue(fieldname)
is given on the context.
class Projekt(Table): fields = [ OclMinuteField("bdgaufwand", "planminutenint"), 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, Dialogues to show and variables on the Context Object to set), there is the method before_report(context)
. If this method exists, it is automatically called first.
def before_report(context): # Frage den User nach dem Datum 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"]
Returns the method False
the implementation of the report is interrupted. Otherwise, it will continue (True
is standard and does not need to be specified separately).
A Python feature is declared as follows:
def Funktionsname(context): return XY
Features can be defined within a table and are then available to that table.
Features that should be available everywhere must be declared outside the tables.
References to tables and features can be specified in two ways: