Changes in the Python code for Office reports from version 6.5.0.20

Python code for Office reports Before – After

Product line

Standard

|

Expert

Operating mode

CLOUD ABO

|

ON-PREMISES

Modules

Services & CRM

Budget & Phases

Purchases

Resource Planning

Business Intelligence

Created: 02.06.2022
Machine translated
Updated: 07.06.2023 | Terminology updated.

There are improvements to the Office report features. First of all, the changes are fully backward compatible. All previous reports continue to run unchanged.

This article here is intended to help you if you want to rebuild existing reports.

For information on how Office reports are structured starting with version 6.5.0.20, see Python code for office reports .

Renaming Frame to Table

The designation “Frame” confuses many users. That’s why the frames are renamed “Tables”. This is more familiar to most people from MS Access etc. This applies to all uses:

Before After
class project(Frame): class project(Table):
main_frame = project main_table = project
def calculate_main_frame(context): def calculate_main_table(context):
OclFrameField(“open services, service) OclTableField(“open services, service)

Rebuild existing code

If you want to rebuild existing code, you first replace frame with table (turn on case-sensitive!), then frame with table. Then you have changed all occurrences in the code.

Automatic fields

OCL fields no longer need to be declared, especially if they consist of only a single term, namely the member.

Before After
class project(Frame):
businessclass = “project”
fields = [
OclTextField(“code
),
]  
class project(Table):
businessclass = “project”

If businessclass is specified, the field values are returned with the correct data type. If no businessclass is specified, the value is returned as a string.

We recommend specifying the businessclass for two reasons:

  1. If you want to do calculations etc. in the report, it will go wrong if there are only String values.
  2. In the table declaration it needs a content. The indication of the businessclass is more meaningful than pass.

Preferably:

class Projekt(Table):
    businessclass = "Projekt"

As:

class Projekt(Table):
    pass

It is also possible to evaluate longer OCL expressions directly in the report instead of in the code. However, the report can quickly become confusing because the OCL expression has to be stored as text, which is then provided with a comment (and not, as before, stored in the comment itself).

Therefore, we recommend that longer OCL expressions should still be stored directly in the code and that they should be called using the field name in the report as before.

Example

OclTextField("text", "if typ->size>0 then typtext + ' ' + text else text endif")

OCL expressions in subbands are not supported. The Ocltablefields still need to be declared.

Rebuild existing code

You can easily get rid of the “simple” OCL fields by simply deleting all fields from the code that consist only of an OCL expression:

Before After
class Service(Frame):
businessclass = “OpenService”
fields = [
OclDateField(“date
),
OclTextField(“email
, “worker.briefemail”),

OclMinuteField(“minutext
),
OclCurrencyField(“valuetext
”),
]    
class Service(Table):
businessclass = “OpenService”
fields = [
OclTextField(“email
, “worker.briefemail”),
]

The Office Report will continue to function as it is if the following are observed:

If the fields in the report are used as OCL expressions, they become case-sensitive, so make sure that the field names are case-sensitive, or the way the expression is written in OCL .

Simpler conditional expressions

You no longer need a Boolean field to query whether a list or value is empty. You can query the value directly in the report.

Before

class project(Frame):
businessclass = “project”
fields = [
OclFrameField(“services
, service, “offenservices->orderby(date)”),
OclBooleanField(“has_services
, “offenservices->size>0”),
]

After

class project(Table):
businessclass = “project”
fields = [
OclTableField(“services
, service, “openservices->orderby(date)”),
]

For the negative case – you only want to show a tape if it has no list or value – you can not use directly in the report:

This works not only for lists, but for all values. In general:

  • Zero/Empty = False
  • not Null/not Empty = True
  • Empty strings: False
  • Empty date: False
  • Integer / Minutes / Currency: 0 = False

Rebuild existing code

You go through the corresponding OclBooleanFields. Whenever it concerns an entire list or a single field, you can delete the declaration. In the report, you have to adjust the expression at the Cond-Band accordingly (see example above).

Field Reference for Field Calculation Methods

It is now possible to specify the field name for field calculations. This makes it possible to divide the same field calculation for several fields:

Before
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")
After
class Projekt(Table):
    businessclass = "Projekt"
    fields = [
        CurrencyField("SummeOffeneLeistungen", "calc_summen"),
        CurrencyField("SummeOffeneSpesen", "calc_summen"),
    ]
    def calc_summen(context, fieldname):
        if fieldname == " SummeOffeneLeistungen ":
            return context.evalocl("offeneleistungen.wertext->sum")
        if fieldname == " SummeOffeneSpesen ":
            return context.evalocl("offenespesen.wertext->sum")

Init Method for Rows

Probably the biggest change is the implementation of an init method for rows:

def initialize_row(context, row):

This is called when each new row is created. It is called before the automatic field calculations (via OCL or calculation function), i.e. it can calculate values and store them in the context object, which can then be used by field calculation features.

All fields can also be accessed. If they are calculated fields, they will be calculated when accessed.

class Leistung(Table):
    businessclass = "OffeneLeistung"
    
class Projekt(Table):
    businessclass = "Projekt"
    fields = [
        TableField("leistungen", Leistung),
    ]
    def initialize_row(context, row):
      row.leistungen = Leistung(context, context.evalocl("offeneLeistungen->orderby(datum)"))

For example, the example in the Field Reference for Field Calculation Methods section could be worded as follows:

class Projekt(Table):
    businessclass = "Projekt"
    fields = [
        CurrencyField("summeOffeneLeistungen"),
        CurrencyField("summeOffeneSpesen"),
    ]
    def initialize_row(context, row):
        row.summeOffeneLeistungen = context.evalocl("offeneleistungen.wertext->sum")
        row.summeOffeneSpesen = context.evalocl("offenespesen.wertext->sum")

Rebuild existing code

It is worthwhile to rebuild existing code if you have different field calculations that access the same lists and always have to filter them, e.g. to the up-to-date object. Instead:

Can this be done as follows:


This is not only clearer, but of course also faster.

Subtables only need to be created if they are really needed or meet the required criteria. This no longer has to be done in the bands or with Boolean expressions.

The conversion is also interesting if you have previously created the tables by hand somewhere in the code. It is now often possible to place the code for the subtables in the initialize_row of the subtables – which makes the code more readable and structured.

Improvements for add_row usage

Adding rows manually via add_row has been simplified.

Instead of:

phasenTable = Projektphase(context)
    projectRow.phasen = phasenTable
    for phase in projekt.phasen:
        phasenRow = phasenTable.add_row(phase)

new submits:

    for phase in project.phasen:
        phasenRow = projectRow.phasen.add_row(phase)

Accessing the table in calculate methods

In calculation methods of table fields, the corresponding table can be accessed via table.

class Leistung(Table):
    businessclass = "OffeneLeistung"
    
class Projekt(Table):
    businessclass = "Projekt"
    fields = [
        TableField("leistungen", Leistung, "calculate_leistungen"),
    ]
    def calculate_leistungen(context):
        leistlist = context.evalocl("offeneleistungen->orderby(datum)")
        for leist in leistlist:
            if leist.wertext > 20:
                leistrow = context.table.add_row(leist)

Waiver of main_table Variable

The variable main_table to identify the top-level table only need to be specified if it is not clear which is this main table.

This is identified by the fact that it does not have a supertable.

If there are several tables without a parent table, the parent table must still be specified.

Rebuild existing code

Normally, the rows of the type

main_table = Projekt

can simply be deleted.

If several tables are run in parallel (and not hierarchically), the row must remain.

Simplification for Top-Level Table

Often, in List Office reports, you have to define a report table that has exactly 1 row in order to be able to display fields on it for display in the base area of the report or the objects as a list. Previously, you needed the following construct to create this row:

class Report(Frame):
    fields = [
        FrameField('projekte', 'Projekt', 'calculate_projekte'),
        ]
    def calculate_projekte(context):
        return Projekt(context, context.rootlist)

def calculate_main_frame(context):
    frame = Report(context)
    frame.add_row()
    return frame

main_frame = Report

New for this is the SingleRowTable. This contains exactly one row, which is automatically created. So the following code is sufficient:

class Report(SingleRowTable):
    fields = [
        TableField('projekte', 'Projekt', 'calculate_projekte'),
        ]
    def calculate_projekte(context):
        return Projekt(context, context.rootlist)

It is important to know that this row has no object. It is therefore not possible to compute fields on it via OCL. context.evalocl() goes wrong, because the context has no object.

However, it is possible to calculate fields as shown above. Field values can also be set in an initialize_row .

What also works are OCL calculations on concrete objects of the type context.rootlist.evalocl()

Support of report language

When Registrations of office reports , translation into project language can be activated. This affects the language used for translation (texts marked with translation in the report template): the project language is used instead of the interface language.

In order that this language can be used in the report code or that the report can also be output in a different language, the following new methods are available:

  • context.language: This attribute contains the report language according to the above logic. This attribute can also be set to output the report in another language:

context.language="IT"

It has the following values:

– DE: German (Switzerland)
– DD: German (Germany)
– EN: English
– FR: French
– IT: Italian
If a jargon is to be taken into account (project or mandate language), the suffix can also be used 0(for project language) or 1(for mandate language): DE0, DE1, etc.

context.translate(text): This method is available in the report code to translate texts according to the report language (context.language).

The last value of context.language set in the code 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.

Translations of MLString fields

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.

Examples
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] find 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), the translation to the current report language is achieved.