Python code for advanced Office reports before version 6.5.0.20

Python code for advanced Office reports

Operating mode

Cloud Suite

|

ON-PREMISES

Modules

Services & CRM

Budget & Phases

Purchases

Resource Planning

Business Intelligence

Created: 12.04.2018
Machine translated
Updated: 26.07.2022 | This article is only relevant for versions prior to 6.5.0.20.

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

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.

Field Types

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

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

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

Frame Fields (Subframes)

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")
  1. Name of the field: As always, the first argument contains the name with which this subset can be accessed in the report, for example as a band expression.
  2. Name of the frame: The second argument passes the corresponding frame declaration:
  3. OCL expression: The third argument specifies how the content of the subframe is calculated.
    If the OCL expression is used (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:

Frames

Declaring a Frame

Syntax:

Class XY(Frame):

Assign the main frame (main_frame)

There is a main frame. It is called main_frame and must be assigned a declared frame:

main_frame = XY

Assign sub-frames (FrameField)

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")]

Build frames

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"),
    ]

Building frames in code

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

calculate_main_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
    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
    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"

Python Feature

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 Context Object

Variables

The following variables are defined in each report:

currentobject The up-to-date object of the calculation.

context.currentobject

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): 
    fields = [OclTextField("code"),
OclTextField("beschrieb"),
OclTextField("creationdatetime"),
TextField('adresstext', 'calcadresse'),
]

    def calcadresse (context):
        return context.optarg.adresstext

main_frame = Projekt
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.

Define Images as Context Variables

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.

Method evalocl(expression)

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)

Using context variables in ocl

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:

  • Naming: The names of the automatically set variables are the same as on the context object, namely 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.
  • The Reporting OCL variables are created on the private OCL evaluator of the executed report. This means that the variables can only be used in OCL expressions of OCL fields or in the 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)
  • Only OCL compatible values are supported as variables (Vertec objects and lists, simple values). Incompatible values (e.g. Python Dictionaries, Python lists, etc) will cause an error.

Access to other fields in field calculation

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")

Before Report Logic

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

References to frames and features can be specified in two ways:

  • Reference as name: If the reference name is written in quotation marks, it is a reference as name. In this case, the referenced declaration may be somewhere in the code, the order does not play a role.
  • Direct reference: If the reference name is written without quotation marks, it is a direct reference. In this case, the referenced declaration must be above the code, i.e. before it is referenced.

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.

Interaction Frames – Bands (Word)

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.

Individual reports

For individual reports, a report is created for the up-to-date object. Here, the main_frame is the individual object.

List reports

There are two variants of list reports:

  1. For each object in the list, e.g. for each user, a new page should appear in the report. In this case the report has the same structure as the individual report. Here the main_frame is simply the list:
    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.

  2. The list should appear as a list in the report, but around it there is still a frame such as a title or texts to be translated, a deadline etc. In this case you need a frame frame that is called exactly once (and not even per object in the list). This can be done as follows: First, a frame is created, which represents the actual list as a sub-frame (which in the first example would be the main_frame):
    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

Generate zugferd metadata

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).