littleyoda / hibiscus.depotviewer

Depotviewer Plugin für Hibiscus
32 stars 15 forks source link

Depot-Umsätze mit Hibiscus synchronisieren #92

Closed faiteanu closed 3 years ago

faiteanu commented 3 years ago

Depot-Umsätze beim Import durch Jobs und beim manuellen Ändern mit Hibiscus-Umsätzen synchronisieren. Dadurch zeigen die Berichte "Einnahmen/Ausgaben" sowie "Sparquote" in Hibiscus auch die entsprechenden Umsätze auf den Depot-Konten an.

Damit im DepotViewer angelegte oder geänderte Orderbuch-Einträge korrekt zugeordnet werden können, erhalten die Hibiscus-Umsätze im Feld "EndToEndID" den Wert "DVID"+UmsatzID. -> DVID = DepotViewerID

adamsjo commented 3 years ago

Hallo!

Schon mal Danke an faiteanu dafür, dass ich es jetzt mal geschafft habe, die Kursdaten von ariva herunterzuladen. 👍 Ich habe mich auch schon immer an der nicht korrekten Anzeige des Depotwertes im Saldenverlauf gestört. Es sieht so aus, als arbeitest du auch daran, richtig? Kann man das schon ausprobieren (ohne sich selbst das plugin zu compilen?)

Ich kenne mich weder mit github noch mit Java aus und habe bisher nur bisschen PHP geschrieben. Ich entschuldige mich daher, wenn ich irgendwelche Konventionen hier bei github verletzt habe oder meine Frage dumm scheint.

faiteanu commented 3 years ago

Hallo @adamsjo, so lange die Änderungen an Hibiscus / DepotViewer nicht veröffentlicht sind, kann man sie leider auch nicht auf einfache Weise ausprobieren. Die Alternative besteht - wie von dir befürchtet - darin, alles selber zu kompilieren, was einen gewissen Aufwand bedeutet.

Zwar wüsste ich auch gerne, wann es eine nächste Version gibt (@willuhn), aber da alle Beteiligten lediglich in ihrer Freizeit an den Projekten arbeiten, muss man etwas Geduld mitbringen.

willuhn commented 3 years ago

Da sich im Nightly-Build von Hibiscus schon wieder einiges gesammelt hat, ist in der Tat mal wieder Zeit für ein neues Release. Ich denke mal, so in 2-3 Wochen.

Damit im DepotViewer angelegte oder geänderte Orderbuch-Einträge korrekt zugeordnet werden können, erhalten die Hibiscus-Umsätze im Feld "EndToEndID" den Wert "DVID"+UmsatzID. -> DVID = DepotViewerID

Ich würde eher davon abraten, Felder in Hibiscus für sowas "zweck-zu-entfremden". Wenn das Feld bei dem Umsatz dann doch irgendwann mal gebraucht wird, kann es nicht mehr genutzt werden. Hinzu kommt, dass der User den Wert ja einfach ändern und damit Inkonsistenzen erzeugen kann. Wäre es nicht möglich, die Zuordung von Depot-Umsatz zu Hibiscus-Umsatz umzukehren? Sprich: Nicht die ID des Depot-Umsatzes im Hibiscus-Umsatz speichern sondern stattdessen die Hibiscus-Umsatz-ID im Depo-Umsatz? Mein SynTAX-Plugin hat auch eine Verknüpfung von Buchungen aus SynTAX zu Umsätzen aus Hibiscus. Hierfür existiert in der SynTAX-Tabelle "buchung" eine Spalte "hb_umsatz_id" (Hibiscus Umsatz-ID), mit der ich speichern kann, welcher Hibiscus-Umsatz zugeordnet ist. Hat den Vorteil, dass das Feld rein intern bleiben kann und der Benutzer es nicht bearbeiten kann.

Alternativ gäbe es noch die generischen Meta-Daten. Jede Entity in Hibiscus kann beliebige Meta-Daten haben. Beim Umsatz z.Bsp.:

String dvId = umsatz.getMeta("dvid",null);
umsatz.setMeta("dvid",dvId");

Die Daten werden bei "setMeta" sofort in der DB gespeichert. Die Daten werden beim Laden auch gecached, sodass es hier nicht zu unnötig vielen Selects kommen sollte - selbst wenn man über eine Liste von Depot-Umsätzen iteriert und für jeden einzeln ermittelt, ob ein Hibiscus-Umsatz zugeordnet ist. Beim ersten Zugriff auf die Meta-Daten für eine Tabelle werden diese automatisch in einen Cache geladen. Das sollte eigentlich auch schneller gehen als die bisherige Lösung mit dem "liste.addFilter("endtoendid=?", "DVID" + u.getID())". Denn "endtoendid" hat in der DB keinen Index, da normalerweise nicht nach diesem Kritererium gesucht wird.

faiteanu commented 3 years ago

Ich würde eher davon abraten, Felder in Hibiscus für sowas "zweck-zu-entfremden". Wenn das Feld bei dem Umsatz dann doch irgendwann mal gebraucht wird, kann es nicht mehr genutzt werden. Hinzu kommt, dass der User den Wert ja einfach ändern und damit Inkonsistenzen erzeugen kann. Wäre es nicht möglich, die Zuordung von Depot-Umsatz zu Hibiscus-Umsatz umzukehren? Sprich: Nicht die ID des Depot-Umsatzes im Hibiscus-Umsatz speichern sondern stattdessen die Hibiscus-Umsatz-ID im Depo-Umsatz?

Bin ganz deiner Meinung, dass Zweckentfremdung nicht gut ist. Eine Umkehrung halte ich an dieser Stelle jedoch nicht für möglich, da es sich nicht um eine 1:1 Zuordnung handelt. Das ist mir letztens erst beim Scalable Capital Broker aufgefallen: für einen Kauf, der einem Eintrag im DepotViewer-Orderbuch entspricht, sind gleich 3 Kontobuchungen aufgetreten:

Die ersten beiden Buchungen kommen über Hibiscus-HBCI beim Kontoabruf, die letzte wird vom DepotViewer erzeugt. Ein einzelnes Feld hb_umsatz_id würde daher wohl nicht ausreichen.

Ein Punkt, bei dem ich mir nicht sicher bin: soll der Benutzer die Zuordnung bearbeiten können oder nicht? In Hibiscus und auch im DepotViewer kann der Benutzer bisher grundsätzlich alles bearbeiten. Er kann Buchungen erzeugen, löschen oder jedes Feld ändern. Es wäre ja schön, wenn der Benutzer im oben genannten Beispiel die beiden Girobuchungen auch dem gleichen DepotViewer-Eintrag zuordnen könnte.

An welcher Stelle wäre die Zuordnung sinnvoll unterzubringen, eher in Hibiscus oder im DepotViewer? Kann ich die Hibiscus-Buchungsseite aus einem Plugin erweitern? Die Frage ist auch für ein weiteres Plugin relevant an, dem ich gerade arbeite, welches Bankdokumente abruft: einem Wertpapierkauf wird ein PDF zugeordnet, welches ich gerne aus der Hibiscus-Buchungsseite aufrufen könnte.

Die Metadaten kannte ich damals noch nicht, werde sie mir aber ansehen. Verstehe ich es richtig, dass ich dann die Metadaten zu allen Umsätzen laden und selber iterieren muss, um das Äquivalent von "liste.addFilter("endtoendid=?", "DVID" + u.getID())" zu erhalten?

Apropos iterieren und Liste... Könntest du den GenericIterator um die entsprechenden Java-Interfaces Iterator und Iterable ergänzen, so dass man die normale for-Schleife damit verwenden kann? (Genau genommen sollten die beiden Interfaces nicht von der gleichen Klasse implementiert werden, aber das würde wohl zu viel Aufwand erzeugen.)

DBIterator<Umsatz> liste = Settings.getDBService().createList(Umsatz.class);
for(Umsatz u : liste){ ...}

Ich finde zumindest aktuell kein öffentliches Repository, welches den Code enthält, sonst würde ich auch da einen PR schreiben :-)

willuhn commented 3 years ago

Die Metadaten kannte ich damals noch nicht, werde sie mir aber ansehen. Verstehe ich es richtig, dass ich dann die Metadaten zu allen Umsätzen laden und selber iterieren muss, um das Äquivalent von "liste.addFilter("endtoendid=?", "DVID" + u.getID())" zu erhalten?

Nein. Mit dem Satz wollte ich nur sagen, dass Hibiscus für die Metadaten einen internen Cache hat. Sobald für einen Datensatz einer Tabelle die Metadaten abgerufen werden, cached Hibiscus automatisch die Metadaten für alle anderen Datensätze in der Tabelle.

Heisst: Die Meta-Daten können auch für performance-kritische Sachen verwendet werden. Wenn man also z.Bsp. über 1000 Umsätze iteriert und bei jedem einzeln dann u.getMeta() aufruft, ist das kein Problem.

Apropos iterieren und Liste... Könntest du den GenericIterator um die entsprechenden Java-Interfaces Iterator und Iterable ergänzen, so dass man die normale for-Schleife damit verwenden kann? (Genau genommen sollten die beiden Interfaces nicht von der gleichen Klasse implementiert werden, aber das würde wohl zu viel Aufwand erzeugen.)

Das geht leider nicht. Es gibt im Umfeld von Jameica etliche Implementierungen von GenericIterator. Auch in anderen Plugins oder gar inline. Wenn ich da ein Interface hinzufüge, zerbrechen all die Implementierungen.

Ich finde zumindest aktuell kein öffentliches Repository, welches den Code enthält, sonst würde ich auch da einen PR schreiben :-)

Du musst nicht GenericIterator/DBIterator verwenden. Man kann durchaus auch komplett eigene Persistenz machen. Die UI-Controls von Jameica akzeptieren auch generische Beans oder Lists.

faiteanu commented 3 years ago

Du musst nicht GenericIterator/DBIterator verwenden. Man kann durchaus auch komplett eigene Persistenz machen. Die UI-Controls von Jameica akzeptieren auch generische Beans oder Lists.

Klar, aber nur für die Bequemlichkeit der for-Schleife lohnt es sich nicht, einen komplett neuen Persistenzlayer zu schreiben. Ich finde es einfach schade, dass GenericIterator nicht den Iterator implementiert, obwohl man es dem Namen nach erwarten würde, und auch die Methoden hasNext und next bereits vorhanden sind.

willuhn commented 3 years ago

Ich finde es einfach schade, dass GenericIterator nicht den Iterator implementiert, obwohl man es dem Namen nach erwarten würde, und auch die Methoden hasNext und next bereits vorhanden sind.

Es geht aber leider technisch nicht, da die Methoden-Signaturen nicht kompatibel sind. Man muesste dazu das "throws RemoteException" entfernen. Damit zerbricht man aber alle Plugins.

faiteanu commented 3 years ago

Habe den Code umgestellt, so dass die EndToEndID nicht mehr zweckentfremdet wird, sondern die Verknüpfung über Metadaten erfolgt. Im Verwendungszweck der Gegenbuchung werden Beträge nun mit Tausendertrennzeichen und Dezimal-Komma dargestellt.

willuhn commented 3 years ago

Oh. Sorry. Habe mir gerade das letzte Diff angeschaut. Ich nahm an, dass ohnehin über die Liste der Umsätze in Hibiscus iteriert werden muss. Bei der Gelegenheit ist es kein extra Aufwand, die Meta-Daten mit zu laden. Für diesen Use-Case ist das optimiert. Das Patch sieht aber eher so aus, als würde extra zum Finden eines einzelnen passenden Depot-Umsatzes über die ganze Liste aller Umsätze iteriert. Wenn das beim Abruf der Depot-Umsätze schnell hintereinander für mehrere abgerufene Umsätze gemacht wird, dann kann das ziemlich aufwändig.

Wenn die Hibiscus-Umsätze vom Depot-Plugin selbst angelegt wurden und daher eine vorher von Hibiscus dort gespeicherte End2End-ID nicht versehentlich überschrieben werden kann (das wäre der Fall, wenn die Hibiscus-Umsätze bereits durch Hibiscus selbst angelegt wurden und lediglich zu einem Depot-Umsatz zugeordnet werden sollen), dann wäre es in dem Fall vermutlich in der Tat deutlich effizienter, die Zuordnung in der End2End-ID zu speichern, weil man dann ein Query per "WHERE end2endid=?" über alle Umsätze machen kann. Auch wenn es den Nachteil hätte, dass der User die Zuordnung beim Bearbeiten des Hibiscus-Umsatzes potentiell wieder kaputt machen kann, das das Feld End2End-ID ja bearbeitbar ist.

Die Alternative wäre, dass ich die Klasse DBPropertyUtil in Hibiscus erweitere. Dort gibt es eine private statische Funktion "Map<String, DBProperty> getScope(Prefix prefix, String scope)". Damit kann man die kompletten "rohen" Meta-Daten einer Domain (also z.B. aller Umsätze) laden. Die könnte ich public machen. Oder ich könnte eine neue Funktion in der Art einbauen: "public static Map<Integer,Map> get(Prefix prefix, String scope)", die für eine Domain die Meta-Daten jedes Datensatzes liefert. Als Key der Map die ID des Datensatzes und als Value eine Map mit den Meta-Daten. Das Depot-Plugin wäre dann nicht mehr abwärtskompatibel zu älteren Hibiscus-Versionen.

Oder Moment, weitere Alternative: Du greifst direkt selbst auf die Meta-Daten zu. Ist zwar ziemlich lowlevel aber geht ohne, dass ich in Hibiscus was ändern müsste (ungetestet):

public static de.willuhn.jameica.hbci.rmi.Umsatz getHibiscusUmsatzByDepotViewerId(String depotViewerId) throws RemoteException
{
  String query = Prefix.META.value() + ".umsatz.%.depotviewer_id";
  DBService service = Settings.getDBService();
  DBIterator i = service.createList(DBProperty.class);
  i.addFilter("name like " + query); // Achtung - query nicht als Parameter übergeben - sonst wird das "%" escaped
  while (i.hasNext())
  {
    DBProperty p = (DBProperty) i.next();
    if (p.getValue().equals(depotViewerId)
    {
      // ist der passende Meta-Datensatz mit der ID des Depot-Umsatzes.
      // ID des Hibiscus-Umsatzes aus dem Key extrahieren
      // Key sieht typischerweise so aus: "meta.umsatz.1234.depoviewer_id"
      // "1234" ist die ID des Hibiscus-Umsatzes
      String[] parts = org.apache.commons.lang.StringUtils.split(p.getKey(),'.');
      if (parts.length != 4)
        continue; // Hu? Ungültiger Key?
      String id = parts[2];
      return service.createObject(Umsatz.class,id);
    }
  }
  return null;
}
faiteanu commented 3 years ago

Verstehe ich es richtig, dass ich dann die Metadaten zu allen Umsätzen laden und selber iterieren muss, um das Äquivalent von "liste.addFilter("endtoendid=?", "DVID" + u.getID())" zu erhalten?

Nein. Mit dem Satz wollte ich nur sagen, dass Hibiscus für die Metadaten einen internen Cache hat. Sobald für einen Datensatz einer Tabelle die Metadaten abgerufen werden, cached Hibiscus automatisch die Metadaten für alle anderen Datensätze in der Tabelle.

Deshalb hatte ich ja extra nachgefragt, ob es sinnvoll ist, über die Metadaten zu gehen. Aber nun hast du deine Meinung ja wieder geändert :-)

Aktuell läuft es bei mir mit folgendem Code:

public static de.willuhn.jameica.hbci.rmi.Umsatz getHibiscusUmsatzByDepotViewerId(String depotViewerId) throws RemoteException{
    String query = Prefix.META.value() + ".umsatz.%.depotviewer_id";
    DBService service = Settings.getDBService();
    DBIterator<DBProperty> i = service.createList(DBProperty.class);
    i.addFilter("name like ?", query);
    i.addFilter("content=?", depotViewerId);

    if (i.hasNext())
    {
        DBProperty p = i.next();
        // ist der passende Meta-Datensatz mit der ID des Depot-Umsatzes.
        // ID des Hibiscus-Umsatzes aus dem Key extrahieren
        // Key sieht typischerweise so aus: "meta.umsatz.1234.depoviewer_id"
        // "1234" ist die ID des Hibiscus-Umsatzes
        String[] parts = org.apache.commons.lang.StringUtils.split(p.getName(), '.');
        if (parts.length == 4) {
            String id = parts[2];
            return service.createObject(de.willuhn.jameica.hbci.rmi.Umsatz.class, id);
        }
    }
    return null;
}

Ich sehe keinen Grund, eine Liste von Metadaten zu laden, wenn ich direkt den passenden Datensatz ermitteln kann, falls vorhanden. Den Kommentar zum Prozent-Zeichen kann ich leider nicht nachvollziehen. Hängt das an einer bestimmten Datenbank? Mit der Standard-H2DB funktioniert die Übergabe als Parameter, ohne dass da etwas escaped wird.

willuhn commented 3 years ago

Deshalb hatte ich ja extra nachgefragt, ob es sinnvoll ist, über die Metadaten zu gehen. Aber nun hast du deine Meinung ja wieder geändert :-)

Ich habe meine Meinung nicht geändert sondern lediglich nicht realisiert, dass hier wirklich über alle Umsätze iteriert werden muss. Sorry, dass ich deine Worte nicht auf die Goldwaage gelegt habe.

Ich sehe keinen Grund, eine Liste von Metadaten zu laden, wenn ich direkt den passenden Datensatz ermitteln kann, falls vorhanden. Den Kommentar zum Prozent-Zeichen kann ich leider nicht nachvollziehen. Hängt das an einer bestimmten Datenbank?

Weiss ich nicht. Bei meinen Tests vor einigen Jahren war es jedenfalls so, dass das Prozent-Zeichen bei Übergabe als Parameter in einem PreparedStatement nicht mehr im Sinne eine Like-Query evaluiert wurde.

littleyoda commented 3 years ago

Bist du noch so nett und löst den Merge Conflict. Dann Merge ich den Teil.

faiteanu commented 3 years ago

Ich bin jetzt etwas irritiert, weil der Merge von master nach umsatz angezeigt wird. Das hätte ich anders herum erwartet. Letztens stand da noch die Meldung, ich hätte keine Schreibrechte auf master.