Python-Code für Office-Berichte Vorher - Nachher
Produktlinie
Standard
|Expert
Betriebsart
CLOUD ABO
|ON-PREMISES
Module
Leistung & CRM
Budget & Teilprojekt
Fremdkosten
Ressourcenplanung
Business Intelligence
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 .
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) |
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.
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:
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.
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.
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): |
Nachher |
---|
class Projekt(Table): |
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:
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).
Neu kann bei Feldberechnungen optional der Feldname mitgegeben werden. Dadurch ist es möglich, dieselbe Feldberechnung für mehrere Felder zu teilen:
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")
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")
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")
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.
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)
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)
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.
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.
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()
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="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.
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.
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.