Umstellungen im Python-Code für Office-Berichte ab Version 6.5.0.20

Python-Code für Office-Berichte Vorher - Nachher

Product line

Standard

|

Expert

Operating mode

CLOUD ABO

|

ON-PREMISES

Modules

Services & CRM

Budget & Phases

Purchases

Resource Planning

Business Intelligence

Created: 02.06.2022
Updated: 07.06.2023 | Terminologie aktualisiert.

Es gibt Verbesserungen bei den Office-Bericht Features. Vorab eine Information: Die Änderungen sind vollständig rückwärtskompatibel. Alle bisherigen Berichte laufen unverändert weiter.

Dieser Artikel hier soll Ihnen helfen, wenn Sie bestehende Berichte umbauen möchten.

Wie Office-Berichte ab der Version 6.5.0.20 aufgebaut werden, finden Sie im Artikel Python-Code für Office-Berichte .

Umbenennung von Frame in Table

Die Bezeichnung "Frame" verwirrt viele Benutzer. Deshalb heissen die Frames neu "Tables". Das ist den meisten Leuten geläufiger aus MS Access etc. Das kommt bei allen Verwendungen zum Zuge:

Vorher Nachher
class Projekt(Frame): class Projekt(Table):
main_frame = Projekt main_table = Projekt
def calculate_main_frame(context): def calculate_main_table(context):
OclFrameField("offeneleistungen", Leistung) OclTableField("offeneleistungen", Leistung)

Bestehenden Code umbauen

Will man bestehenden Code umbauen, ersetzt man zuerst Frame durch Table ("Gross-/Kleinschreibung beachten" einschalten!), danach frame durch table. Dann hat man alle Vorkommnisse im Code geändert.

Automatische Felder

Neu müssen OCL-Felder nicht mehr deklariert werden – vor allem dann nicht, wenn sie nur aus einem einzigen Begriff, nämlich dem Member, bestehen.

Vorher Nachher
class Projekt(Frame):
    businessclass = "Projekt"
    fields = [
        OclTextField("code"),
    ] 
class Projekt(Table):
    businessclass = "Projekt"

Wenn man die businessclass angibt, werden die Feldwerte mit dem richtigen Datentyp zurückgeliefert. Ist keine businessclass angegeben, wird der Wert als String zurückgegeben.

Wir empfehlen die Angabe der businessclass aus zwei Gründen:

  1. Will man im Bericht Berechnungen anstellen etc., geht das schief, wenn es nur String-Werte gibt.
  2. In der Table-Deklaration braucht es einen Inhalt. Die Angabe der businessclass ist sinnstiftender als pass.

Lieber:

class Projekt(Table):
    businessclass = "Projekt"

Als:

class Projekt(Table):
    pass

Es können auch längere OCL-Expressions direkt im Bericht ausgewertet werden statt im Code. Der Bericht kann dadurch aber schnell unübersichtlich werden, weil die OCL-Expression ja als Text hinterlegt werden muss, welcher dann durch einen Kommentar versehen wird (und nicht, wie früher, im Kommentar selber hinterlegt wird).

Deshalb empfehlen wir, längere OCL-Expressions nach wie vor direkt im Code zu hinterlegen und wie bisher über den Feldnamen im Bericht aufzurufen.

Beispiel

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

OCL-Expressions in Sub-Bändern gehen nicht. Die OclTableFields müssen nach wie vor deklariert werden.

Bestehenden Code umbauen

Einfach loswerden kann man die "simplen" OCL-Felder, indem man einfach alle Felder aus dem Code löscht, die nur aus einer OCL-Expression bestehen:

Vorher Nachher
class Leistung(Frame):
    businessclass = "OffeneLeistung"
    fields = [
        OclDateField("datum"),
        OclTextField("email", "bearbeiter.briefemail"),
        OclMinuteField("minutenext"),
        OclCurrencyField("ansatzext"),
        OclCurrencyField("wertext"),
    ] 
class Leistung(Table):
    businessclass = "OffeneLeistung"
    fields = [
        OclTextField("email", "bearbeiter.briefemail"),
    ]

Der Office-Bericht funktioniert so unverändert weiter, wenn folgendes beachtet wird:

Werden die Felder im Bericht als OCL-Expressions verwendet, werden sie Case-Sensitiv. Es muss also darauf geachtet werden, dass die Feldnamen klein geschrieben werden bzw. so, wie die Expression auch in OCL geschrieben wird.

Einfachere Conditional Expressions

Für die Abfrage, ob eine Liste oder ein Wert leer ist, braucht es nun kein Boolean-Feld mehr. Man kann den Wert direkt im Bericht nachfragen.

Vorher

class Projekt(Frame):
    businessclass = "Projekt"
    fields = [
        OclFrameField("leistungen", Leistung, "offeneleistungen->orderby(datum)"),
        OclBooleanField("has_leistungen", "offeneleistungen->size>0"),   
    ]

Nachher

class Projekt(Table):
    businessclass = "Projekt"
    fields = [
        OclTableField("leistungen", Leistung, "offeneleistungen->orderby(datum)"),
    ]

Für den verneinenden Fall – man will ein Band nur anzeigen, wenn es keine Liste bzw. keinen Wert hat – kann man not direkt im Bericht verwenden:

Das funktioniert nicht nur für Listen, sondern für alle Werte. Generell gilt:

  • Null/Empty = False
  • not Null/not Empty = True
  • Leere Strings: False
  • Leeres Datum: False
  • Integer / Minuten / Currency: 0 = False

Bestehenden Code umbauen

Man geht die entsprechenden OclBooleanFields durch. Immer wenn es sich um eine gesamte Liste oder ein einzelnes Feld handelt, kann man die Deklaration löschen. Im Bericht muss dann die Expression beim Cond-Band entsprechend angepasst werden (siehe Beispiel oben).

Feld-Referenz für Feld-Berechnungsmethoden

Neu kann bei Feldberechnungen optional der Feldname mitgegeben werden. Dadurch ist es möglich, dieselbe Feldberechnung für mehrere Felder zu teilen:

Vorher
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")
Nachher
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 Methode für Rows

Die wohl grösste Veränderung ist die Einführung einer Init-Methode für Rows:

def initialize_row(context, row):

Diese wird beim Anlegen jeder neuen Row aufgerufen. Sie wird vor den automatischen Feldberechnungen (per OCL oder Berechnungsfunktion) aufgerufen, kann also z.B. Werte berechnen und im context Objekt ablegen, welche dann von Feld-Berechnungs Funktionen verwendet werden können.

Auch kann auf alle Felder zugegriffen werden. Falls es sich dabei um berechnete Felder handelt, werden sie beim Zugriff berechnet.

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

So könnte man das Beispiel im Abschnitt Feld-Referenz für Feld-Berechnungsmethoden auch so formulieren:

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

Bestehenden Code umbauen

Es lohnt sich, bestehenden Code umzubauen, wenn man verschiedene Feldberechnungen hat, die auf die gleichen Listen zugreifen und diese immer z.B. auf das aktuelle Objekt filtern müssen. Statt:

Kann das neu so gemacht werden:


Das ist nicht nur übersichtlicher, sondern natürlich auch schneller.

Subtabellen müssen nur noch erzeugt werden, wenn sie auch wirklich benötigt werden bzw. den geforderten Kriterien entsprechen. Dies muss so nicht mehr in den Bands oder mit Boolean-Expressions gemacht werden.

Der Umbau ist auch dann interessant, wenn man vorher im Code die Tabellen irgendwo von Hand erzeugt hat. Da ist es nun oft möglich, den Code für die Untertabellen im initialize_row der Untertabellen unterzubringen – was den Code leserlicher und gegliederter macht.

Verbesserungen für add_row Verwendung

Das manuelle Hinzufügen von Rows via add_row wurde vereinfacht.

Statt:

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

reicht neu ein:

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

Zugriff auf die Table in calculate-Methoden

In Berechnungsmethoden von Table-Feldern kann via table. auf die entsprechende Table zugegriffen werden.

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)

Verzicht auf main_table Variable

Die Variable main_table zur Identifizierung der Top-Level Table muss nur noch angegeben werden, wenn nicht klar ist, welches diese Haupttabelle ist.

Diese wird dadurch identifiziert, dass sie keine Übertabelle hat.

Gibt es mehrere Tabellen ohne Übertabelle, muss die Haupttabelle nach wie vor angegeben werden.

Bestehenden Code umbauen

Normalerweise können die Zeilen der Art

main_table = Projekt

einfach gelöscht werden.

Werden mehrere Tabellen parallel geführt (und nicht hierarchisch), muss die Zeile bestehen bleiben.

Vereinfachung für Top-Level Table

Oft muss man bei Listen Office-Berichten eine Bericht-Table definieren, welche genau 1 Row hat, um darauf Felder zur Anzeige im Basisbereich des Berichts oder die Objekte als Liste anzeigen zu können. Bisher benötigte man folgendes Konstrukt, um diese Row zu erzeugen:

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

Neu gibt es dafür die SingleRowTable. Diese enthält genau eine Row, welche automatisch erzeugt wird. Es reicht also folgender Code:

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

Wichtig ist zu wissen, dass diese Row kein Objekt hat. Darauf Felder via OCL zu berechnen ist deshalb nicht möglich. Auch ein context.evalocl() geht schief, da der context eben kein Objekt hat.

Möglich ist hingegen das Berechnen von Feldern wie oben dargestellt. Feldwerte können auch in einem initialize_row gesetzt werden.

Was auch funktioniert, sind OCL-Berechnungen auf konkreten Objekten der Art context.rootlist.evalocl()

Unterstützung von Bericht-Sprache

Bei Registrierungen von Office-Berichten kann die Übersetzung in Projektsprache aktiviert werden. Dies beeinflusst die zur Übersetzung verwendete Sprache (mit translation markierte Texte in der Berichtsvorlage): Anstelle der Oberflächensprache wird die Projektsprache verwendet.

Damit diese Sprache im Bericht-Code verwendet werden kann bzw. der Bericht auch in einer abweichenden Sprache ausgegeben werden kann, gibt es neu folgende Methoden:

  • context.language: Dieses Attribut enthält die Bericht-Sprache gemäss oben genannter Logik. Dieses Attribut kann auch gesetzt werden, um den Bericht in einer anderen Sprache auszugeben:

context.language="IT"

Es kennt folgende Werte:

–    DE: Deutsch (Schweiz)
–    DD: Deutsch (Deutschland)
–    EN: Englisch
–    FR: Französisch
–    IT: Italienisch
Soll ein Jargon berücksichtig werden (Projekt- oder Mandatssprache), dann kann zusätzlich der Suffix 0 (für Projektsprache) bzw. 1 (für Mandatssprache) angegeben werden: DE0, DE1, etc.

–    context.translate(text): Diese Methode steht im Bericht-Code zur Verfügung, um Texte gemäss Bericht-Sprache (context.language) zu übersetzen.

Der im Code zuletzt gesetzte Wert von context.language wird zur Übersetzung der Texte in der Berichtsvorlage verwendet.

Wird ein unbekannter Sprachcode eingegeben, dann werden die originalen Texte angezeigt. Der Bericht wird also einfach nicht übersetzt.

Übersetzungen von MLString-Feldern

Dieses Übersetzungssystem funktioniert nicht für MLString-Felder, da diese einen anderen Übersetzungsmechanismus haben. Welches MLString-Felder sind und wie diese übersetzt werden, finden Sie im Artikel über die Mehrsprachigkeit mit Vertec beschrieben.

Möchte man solche Begriffe in Office-Berichten übersetzen, muss man auf die OCL-Methode .asstringbylanguage zurückgreifen:

context.evalocl("typ.text.asStringByLanguage(language)")

Mögliche Sprachen/Parameter sind 'DE', 'DD' (ab 6.5.0.9), 'FR', 'IT', 'EN' oder 'NV'. Eine Unterscheidung nach Jargon ist bei MLString-Feldern nicht möglich.

Beispiele
class Leistung(Table):
    businessclass = "OffeneLeistung"
    fields = [
        OclTextField("text", "typ.text.asStringByLanguage(language)"),
    ]
    def initialize_row(context, row):
        context.language = "FR"

bzw.

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

Mit context.language[:2] eruiert man die ersten zwei Zeichen des bestehenden Language-Attributs. Da in Berichten die Sprache standardmässig immer mit dem Jargon-Suffix hinterlegt ist (DE0..), und dies bei MLString-Feldern nicht möglich ist (siehe oben), erreicht man so die Übersetzung in die aktuelle Berichts-Sprache.

Bitte wählen Sie Ihren Standort