Beiträge von Tobias Häusler (Vertec Gruppe)

    Hallo David,

    danke für deine Rückfragen, die ich nachfolgend versuche bestmöglich zu beantworten.

    wie oft bzw. wann wird denn ein ListController instanziert bzw. dessen initialize()-Methode aufgerufen?

    Der List Controller existiert einmal je Liste / Container. Es ist korrekt, dass die initialize Methode beispielsweise bei SQL-Ordnern mehrfach aufgerufen wird, theoretisch immer, wenn der Suchen-Button geklickt wird. Das Problem, dass sie beim ersten Öffnen doppelt aufgerufen wird, ist uns bereits bekannt, aber wir haben noch keine gute Lösung dafür gefunden. Das Thema hat im Moment auch keinen Fokus. Wenn das stört, ist ein gangbarer Weg die Methode gleich zu verlassen, wenn self.get_context_entries() leer ist.

    Sie wird ausserdem neu aufgerufen, wenn Sie in der Methode auf Daten subscriben (passiert automatisch bei Verwendung von self.evalocl) und sich diese Daten ändern. Ein Beispiel: Sie verwenden den List Controller auf einer Projektliste und führen für jedes Projekt self.evalocl("phasen", project) aus. Dann wird die Methode neu aufgerufen, wenn bei einem der Projekte eine Phase hinzugefügt oder entfernt wird. Möchten Sie dies vermeiden, verwenden Sie stattdessen den direkten Zugriff per Python (project.phasen), vtcapp.evalocl oder die evalocl Methode des Projekts (project.evalocl("phasen")).


    wie kann ich dann Daten vorberechnen, wenn der ListController nicht pro Zeileneintrag initialisiert wird?

    Sie verwenden genau die initialize Methode zur Vorberechnung und können die Daten auf Instanzvariablen speichern (self.data = ...). Diese haben Sie dann überall im Zugriff (auch in Custom Renderern mit self.controller.data). Wenn die Vorberechnung nicht von den geladenen Daten abhängt, kann es sinnvoll sein, das wirklich nur einmal durchzuführen (zum Beispiel durch Verwendung von if not self.data:).


    wenn ich dann über die Zeileneinträge iteriere, wo kann ich dort Daten ablegen?

    Verwenden Sie dazu eine Datenstruktur auf dem List Controller, zum Beispiel ein Dictionary. Sie können unseren Businessobjekte als Key verwenden. Ein einfaches Beispiel zur Veranschaulichung:

    Code
    self.data = {}
    for project in self.get_context_entries():
          self.data[project] = calculate_expensive_stuff(project)

    Das hat auch den Vorteil, dass sie einfach mit dem rowobj aus einem Custom Renderer auf die Daten zugreifen können, direkt oder mit einer Hilfsmethode:

    Code
    def get_value(self, rowobj, expression, subscriber):
        return self.controller.data[rowobj]


    wann wird der Renderer instanziert? Darf ich in der Instanz Daten zw. get_value und get_background_color zwischenspeichern?

    Das ist tatsächlich mit gutem Grund nicht in der KB dokumentiert, da wir hier keine Garantien geben. Es ist nicht definiert, wie lange eine Custom Renderer Instanz existiert. Die Lebensdauer kann sich zudem in der Zukunft ändern. Speichern Sie daher, anders als beim List Controller, keine Daten auf der Custom Renderer Instanz. Verwenden Sie stattdessen den List Controller wie oben aufgezeigt. Auch die Reihenfolge, in der die Methoden aufgerufen werden, ist nicht definiert. Versuchen Sie also bitte NICHT so etwas:

    Code
    def get_value(self, rowobj, expression, subscriber):
        self.value = 5
        return self.value
    
    def get_background_color(self, rowobj, expression, subscriber):
        # don't do that
        return "clRed" if self.value is 5 else "clGreen"

    Das wird aus diversen Gründen (Lebensdauer des Custom Renderers, mehrfaches Aufrufen von get_value wegen Subscriptions, fehlende Subscription in get_background_color usw) nicht so funktionieren wie Sie es vielleicht erwarten würden.

    Wenn Sie in einer Methode den Rückgabewert der anderen Methode brauchen, ist es normalerweise immer problemlos möglich, einfach die andere Methode aufzurufen. Die korrekte Implementierung für das Beispiel wäre daher:

    Code
    def get_value(self, rowobj, expression, subscriber):
        return 5
    
    def get_background_color(self, rowobj, expression, subscriber):
        value = self.get_value(rowobj, expression, subscriber)
        return "clRed" if value is 5 else "clGreen"


    wenn ich in der Value-Expression etwas komplexeres wie groupLeistungen oder getSollzeit verwende, und dann in der Methode get_background_color(...) noch self.get_value(...) aufrufe, wird dann ein gecachter Wert verwendet oder diese potentiell aufwändige Operation 2x ausgeführt?

    Das wäre genau der Fall, in dem Sie die aufwändige Berechnung im initialize des List Controllers ausführen und aus dem Custom Renderer auf den vorberechneten Wert zugreifen.


    oder anders gefragt: wenn zwei Spalten in der Value-Expression projektleiter.adresse verwenden, ist das nach dem 1. Mal gecacht und danach schnell berechnet. Wenn jedoch mehrere Spalten getSollzeit(...) mit gleichen Datumsgrenzen abfragen, könnte das bei X Spalten zu einem Performanceproblem führen?

    "Darunter" steckt natürlich immer unser Objektsystem, dass heisst ein einmal geladenes Businessobjekt ist im Arbeitsspeicher und auch aufwändigere berechnete Attribute oder Listen (siehe oben das project.phasen Beispiel) sind natürlich geladen und der Zugriff erfolgt sehr schnell.


    wie hole ich die vorberechneten Daten aus dem ListController? weil self.controller.someData ja nicht bezogen auf meine Zeile ist

    Dazu habe ich oben ein Beispiel gemacht (Dictionary mit Businessobjekt als Key).


    wie kann ich die Darstellung eines "bestehenden" Renderers wie zB dbmTim.MinuteRenderer weiterverwenden, und nur die Hintergrundfarbe selber berechnen?

    Da für Custom Renderer das Renderer Feld der Spalte blockiert ist, unterstützen unsere Custom Renderer einen "Converter".


    Grob gesagt fehlt mir ein praktisches Beispiel, bei dem ListController + CustomRenderer in einem Expression/SqlOrdner verwendet werden. Bisher gibt es so ein Beispiel nur für einen Wrapper-Link (oder habe ich etwas übersehen?).

    Wir verwenden in vielen unserer Zusatzfunktionen mittlerweile mehr oder weniger komplexe List Controller und Custom Renderer. Vielleicht möchten Sie sich einige dieser Beispiele in einer Testdatenbank einmal anschauen? Zu nennen wären unter anderem

    Falls Sie das Modul "Ressourcenplanung" einsetzen: Auch hier liefern wir umfangreiche List Controller und Custom Renderer aus, deren Code Sie aus Vertec laden können.

    Hallo David,

    eingebaut haben wir eine solche Funktionalität nicht. Wenn Pythoncode ausgeführt wird (zum Beispiel bei Office-Berichten im before_report oder bei Listen mit List Controller im initialize) könnte man sich natürlich mit vtcapp.log behelfen und die Logdateien (oder Loggly) analysieren oder sonst eine Funktionalität implementieren, die einen Counter hochzählt oder die Verwendung loggt.

    Wenn solche möglichen Einsprungpunkte nicht vorliegen wird es denke ich schwierig.

    Beste Grüsse Tobias

    Hallo Herr Ritter,

    nein, das hilft an der Stelle nicht weiter. Es gibt hier verschiedene Punkte, die beachten sind, aber die hauptsächliche Schwierigkeit ist, dass wir eine OCL-Variable immer nur im Kontext eines OCL-Evaluators (das ist das Objekt, welches das OCL im aktuellen Kontext auswertet) definieren können. Der OCL-Evaluator existiert aber nicht je Zelle, sondern für die gesamte Tabelle. Wir können also nicht je Zelle die Variable mit einem anderen Wert definieren, sondern nur für die gesamte Tabelle. Das System ist dafür auch nicht gemacht und ein Umbau wäre sehr viel komplexer als nur einen Wert als OCL Variable zu setzen. Es gibt dazu auch hier bereits eine Diskussion mit recht ähnlichem Inhalt:

    Claudio Pietra (Vertec Gruppe)
    8. August 2023 um 16:07

    Zu Ihrer zweiten Frage: Ja, diese Variablen sind so umgesetzt, dass sie in der ganzen Applikation (in der aktuellen Session) zur Verfügung stehen. Im Kontext eines List Controllers benötigen Sie das aber gar nicht, weil Sie Werte einfach auf dem List Controller zwischenspeichern (zum Beispiel self.value = <value>) und auf diesen dann von einem Custom Renderer zugreifen können (zum Beispiel self.controller.value).

    Statt <value> können Sie auch ein Dictionary, eine Liste oder eben ein beliebiges Python Objekt verwenden.

    Die "Zielgruppe" für die Standardschnittstelle zu Bexio sind Vertec-Kunden, die zusätzlich Bexio rein als Finanzbuchhaltungssystem verwenden. Es geht hier also um die Übernahme von Rechnungsdaten in das Buchhaltungssystem und umgekehrt um die Übernahme von erfassten Zahlungen aus dem Buchhaltungssystem nach Vertec. Das entspricht dem Funktionsumfang all unserer (Standard-)Debitorenschnittstellen.

    Hallo Herr Ritter,

    vielen Dank für Ihre Vorschläge. Als Softwareentwickler verstehe ich den Wunsch sehr gut und auch, dass das das oben beschriebene Bemerkungsfeld nicht immer ganz die Anforderung abdeckt.

    Da die Interpretation des OCL auf unserem Businessmodell basiert verwenden wir keinen Standardinterpreter, der Kommentare eventuell unterstützen würde. Den Interpreter anzupassen ist kritisch - daher haben wir auch bereits über die von Ihnen vorgeschlagene Variante mit der Bereinigung VOR der Übergabe an den Interpreter diskutiert, haben uns aber aufgrund der Komplexität der OCL-Integration dagegen entschieden.

    Kurz gesagt: Aktuell gibt es keine Pläne, unser OCL um eine Kommentarfunktion erweitern. Generell empfehlen wir, das OCL so einfach und selbsterklärend wie möglich zu halten.

    Zusätzlich zu dem oben genannten Vorschlag der Call Operatoren kann ich Ihnen mittlerweile auch noch eine weitere, in diesem Zusammenhang mächtigere Alternative anbieten: Seit Version 6.6.0.1 unterstützen wir sogenannte Custom Renderer. Dabei handelt es sich um in Python umgesetzte, wiederverwendbare Logik für die Anzeige in der Oberfläche. Eventuell ist es eine Alternative, für komplexe OCL-Abfragen stattdessen einen Custom Renderer zu schreiben? In Python haben Sie viel mehr Möglichkeiten - zum Beispiel Kommentare, Reduzierung von Codeduplikation mit Methoden, Vererbung, um nur einige zu nennen. Zusammen mit den ebenfalls in Python umgesetzten List Controllern können Sie zudem an vielen Stellen einen Performancegewinn durch das Preloading respektive Caching von Daten erreichen.

    Wir konnten das Problem eingrenzen und haben bereits eine Lösung in Arbeit. Die Verbindungsprobleme entstehen, wenn gleichzeitig mit der Vertec Outlook App auch das Addin Microsoft Viva respektive Viva Insights aktiviert ist. Als Workaround können betroffene User dieses Addin deaktivieren. Eine Korrektur, so dass beide Addins parallel verwendet werden können, erfolgt mit der nächsten Minor Version 6.7.0.7.

    Derzeit werden die Mails immer in der Vertec-Datenbank abgespeichert und es bestehen derzeit keine Pläne, dies zu ändern. Aber selbstverständlich können Sie auf den gespeicherten Inhalt zugreifen und so sollte beispielsweise mit einem Eventscript gut machbar sein, die Email zusätzlich noch an anderen Stellen zu speichern. Ausgehend von einer Aktivität können Sie per OCL auf den Emailcontent zugreifen mit documentdata.data.

    Wir sind weiter an diesem Thema dran, konnten die Ursache aber bisher noch immer nicht finden. Wir können mittlerweile ausschliessen, dass die Outlook App auf unserer Seite in einen Fehler läuft und untersuchen daher die betroffenen Systemumgebungen genauer. Des Weiteren prüfen wir, welche weiterführenden Loggingmöglichkeiten wir noch mit Outlook und Exchange aktivieren können, um dem Problem auf die Spur zu kommen.

    Falls Sie ein Pattern erkennen, welches die betroffenen von den nicht betroffenen Usern unterscheidet, kann uns auch dies helfen.