Flow in Computing

Entwicklung von FlowgencyTM und anderer Software. Arbeit im Verein

TreasureDB. Ein elektronisches, transparentes Vereinskassenbuch: 2. Migration

(Dieser öffentliche ENTWURF wurde zuletzt am 5. März geändert, um den aktuellen Stand widerzuspiegeln. Sobald dieser Hinweis verschwindet, gilt die vorliegende Version als final und zitierbar.)

Im hypothetischen Verein ist es passiert: Der amtierende Kassierer möchte von seinem bisherigen System auf TreasureDB umsteigen, oder bei der Mitgliederversammlung wurde das Amt auf jemand anderen übertragen, der diese Software verwenden möchte. Wie auch immer – mit der von den Mitgliedern beschlossenen Entlastung des Kassenwarts ist der neue/bisherige Kassenwart frei, die Methodik oder das System zu verwenden, mit dem er glaubt am besten zurechtzukommen. Das muss natürlich nicht TreasureDB sein, aber in diesem Fall hat die Lektüre dieses Artikels wenig Sinn.

Dieser Artikel ist Teil einer Serie. Die Serie beginnt mit TreasureDB: ein elektronisches, transparentes Vereinskassenbuch (1). Dort sind auch die anderen Teile verlinkt.

Voraussetzungen

Bevor migriert werden kann, müssen einige Dinge geprüft werden.

Lohnt sich das eigentlich?

Bevor du das Programm installierst, unterziehe das Vorhaben gerne einer Aufwandsschätzung, um späteren Frust auf beiden Seiten zu vermeiden. Lies diese Artikelserie (das ist schon mal nicht wenig), sowie die README-Datei zum Projekt.

Und wie viel Zeit verbringst du – kritisch betrachtet – mit deinem bisherigen System? Wie viel steckst du insbesondere in die Vorbereitung des Kassenberichts? Wie lange dauert eine Kassenprüfung, und ist sie nervenaufreibend? Wie viel Zeit steckt der Vorstand in das Verständnis deiner Aufbereitung, wenn du mal vorübergehend ausfällst oder du für bestimmte Dinge, die die Kasse betreffen (z.B. Einladung der Mitglieder zur jährlichen Versammlung, jeweils mit Aufstellung der Zahlungen und Rückstände pro Mitglied) keine ausreichenden Rechte hast? Wenn die bisher verwendete Software kostenpflichtig ist, ziehe auch diese Kosten in Betracht.

Je stärker das bisherige System einen pragmatischen Ansatz verfolgt, umso aufwendiger ist der Umstieg auf TreasureDB. Wenn die Daten erst migriert sind, dürfte sich dieser Aufwand dafür aber stark reduzieren, da das Programm dem Kassierer die Eingaben zweckorientiert aufbereitet.

Support versus Datenschutz

Auch der Bedarf an Unterstützung ist zu beachten, konkret der Aufwand um Daten für etwaige Supportanfragen vorzubereiten, um sie trotz der Weitergabe zu schützen. Besonders in der Testphase des Programms kann es nötig sein, dass der Kassierer mit mir, dem Entwickler der Software, am Beispiel von Daten kommunizieren muss, damit ich ihm bei Problemen helfen kann.

Schicke tunlichst keine echten Daten. Damit würdest du bzw. würde der Verein Beschwerden oder gar Klagen Dritter im Sinne des Datenschutzgesetzes riskieren.

Ich (oder die Community, die es vielleicht einmal gibt) kann auch am Beispiel fingierter oder pseudonymisierter Daten helfen. In der Distribution ist ein kleines Skript namens pseudonymizr enthalten. Mittels einer Mappingdatei, die als Argument angegeben wird und les- und schreibbar sein sollte, werden speziell markierte Stellen im Eingabestrom pseudonymisiert bzw. bei Angabe des Flags -r depseudonymisiert und ausgegeben. Diese spezielle Markierung ist „X{…}“, wobei X ein beliebiger Buchstabe sein kann; diese Markierung bleibt in der pseudonymisierten Version bestehen.

Sind die Daten vollständig?

Wann ist der Zeitpunkt X anzusetzen, was ist der Stichtag, von dem ausgehend alle Aus- und Eingänge mit TreasureDB zu erfassen sind? Dies muss spätestens der Tag der letzten Entlastung sein.

Der erste Kontoauszug, der erstmalig Buchungen vom Stichtag und spätere enthält, muss vorliegen, genauso lückenlos alle späteren Auszüge. Am besten wäre es, wenn die Kontoauszüge bereits in elektronischer, maschinell verarbeitbarer Form vorliegen. Wenn nicht schon geschehen, müssen diese Daten in einer Tabellenkalkulation importiert werden. Andernfalls bleibt wohl nichts anderes übrig, als die Kontoauszüge händisch oder per automatischer Zeichenerkennung (OCR) abzutippen.

Kontoauszüge haben meist folgende tabellarische Spalten, und alle diese Daten sollten so originalgetreu wie möglich elektronisch vorliegen.

  1. Buchungsdatum
  2. Datum der Wertstellung (evtl.). Welches Datum in TreasureDB erfasst wird, bleibt dem Kassierer überlassen.
  3. Verwendungszweck, stets im originalen Wortlaut aufzubewahren und zu übertragen.
  4. Sollbetrag, wenn es sich bei einer Buchung um eine Sollbuchung handelt, also eine Auszahlung.
  5. Habenbetrag, wenn es sich um eine Habenbuchung handelt, etwa einen Mitgliedsbeitrag.

Kategorien

Zunächst sind in die Datenbank Kategorien von Guthaben und Belastungen einzutragen. Derzeit muss das via SQL erfolgen:

$ echo 'INSERT INTO Category("label")' \
       ' VALUES ("Mitgliedsbeitrag", "Spende", ...);' | \
       sqlite3 trsr.db

Die Kategorien dienen in der HTTP-Schnittstelle zur Filterung von Ansichten.

Anlegen der Konten

Hierzu kann die HTTP-Schnittstelle verwendet werden, alternativ ist SQL möglich:

$ sqlite3 trsr.db <<'SQL'
INSERT INTO Account(ID, name, type, altId, IBAN) VALUES
    ("main", "Vereinskonto", "", 1, ""),
    ("abs", "Abschreibungen", "", 2, NULL),
    ("flow", "Florian Heß", "Member", 123, NULL),
    ... -- weitere Mitgliedskonten
    ;
SQL

Die ID ist das Kürzel des Kontos, das nicht numerisch sein muss, und auch nicht sein sollte, damit die Gefahr bei Vertippern, die u.U. ohne Fehlermeldung akzeptiert werden, gering ist. Nach dem Typ werden Konten nicht nur in der Gesamtansicht gruppiert (HTTP-Schnittstelle). Man kann anhand des Typs Konten zwecks Sammelbelastungen eingrenzen, z.B. für monatliche Mitgliedsbeiträge. Die altId ist die ID pro Typ. Im Falle von Mitgliedern sollte es die ID lt. Mitgliedertabelle sein, die nicht in der Vereinskasse liegen muss (aber kann, TreasureDB wird sie dann geflissentlich ignorieren).

Anreicherung

Alleine nutzen die Kontoauszugsdaten noch nicht. Allenfalls wird TreasureDB daraus den Kontoauszug rekonstruieren können, aber darüberhinaus wird es keinen Sinn haben.

Im Vorfeld müssen die obigen Spalten in einem Texteditor mit den entsprechenden Funktionen der Tabellenkalkulation umsortiert werden, da die Importroutine eine bestimmte Abfolge erwartet.

  1. Buchungs- oder Wertstellungsdatum
  2. Kürzel des assoziierten Kontos. Es ist sehr wichtig, sich hier nicht zu vertun. Um Vertipper zu vermeiden, kommt dir TreasureDB entgegen, in dem nicht nur einfache numerische IDs hier erlaubt sind, die sich leicht vertippen ließen.
  3. Sollbetrag.
  4. Habenbetrag.
  5. Verwendungszweck. Siehe nächsten Abschnitt.
  6. Am Schluss im Feld „Verwendungszweck“ kann die Kategorie dieser Buchung in Form von „-C[…]“ angegeben werden. Bildlich soll diese Notation übrigens an ein Hängeetikett erinnern. Es braucht nur der Anfang eines Labels eingegeben werden, solange dieser eindeutig ist.

Verwendungszweck von Sollbuchungen (Import)

Im Falle einer Sollbuchung stets eingeleitet mit „Rechnungs-ID:“, jeweils ersetzt mit der tatsächlichen ID. Der Doppelpunkt ist wichtig, und die Rechnungs-ID darf keine Leerzeichen enthalten.

Diese ist auch da erforderlich, wo eigentlich keine mit dem jeweiligen  Geschäftspartner vereinbart ist. Es empfiehlt sich, vorab ein Schema für alle virtuellen Rechnungen zu festzulegen, z.B. „MB1612-Mitgliedskürzel“ für Mitgliedsbeiträge für den Dezember 2016. Auch, wenn die Rechnungsnummer im originalen Verwendungszweck enthalten ist, muss sie den Verwendungszweck dennoch einleiten. Das gilt nur für den Import. In der Datenbank wird der originale Verwendungszweck ohne diese ID stehen.

Handelt es sich bei der Sollbuchung um eine externe Auszahlung an ein Vereinsmitglied und soll sie auch mit dessen Konto assoziiert werden, steht diese Ausgangsbuchung normalerweise einer internen Verrechnung zwischen Haupt- und Mitgliedskonto gegenüber. Zum jetzigen Zeitpunkt der Migrationsvorbereitung wird diese noch nicht existieren, weshalb auch die schlichte Rechnungsnummer zunächst in Ordnung wäre. Aber vorsorglich, empfehle ich, sollte die Rechnungs-ID in diesem Fall um ein Anhängsel erweitert werden, etwa um „[Ausz.]“. Die entsprechende interne Verrechnung kann dann mit der Rechnungsnummer versehen werden, ohne dieses Anhängsel.

Natürlich müssen alle Rechnungs-IDs eindeutig sein. Das kann es erfordern, insbesondere bei einfach oder gar nicht strukturierten, schlicht laufenden Nummern, sie um voran- oder nachgestellte Zusätze zu erweitern – am besten ebenfalls nach einem vorab festgelegten Schema.

Erste Zeile

Die erste Zeile sollte der Anfangssaldo sein. Im CSV-Format könnte sie wie folgt aussehen. Beachte, dass der Betrag hier als Centangabe interpretiert wird, da er keinen Punkt enthält:

2016-06-14,main,,123400,"Anfangssaldo ab Entl. Kassenwart
A. B. Christiansen, MV 2016"

Migration

Jetzt gehts ans Eingemachte. Speichern wir unsere Tabelle zunächst als CSV (komma-separierte Tabellenzeilen) oder TSV (tab-separierte Tabellenzeilen). Abhängig von der verwendeten Tabellenkalkulation kann auch der Weg über Markieren, Kopieren und Einfügen gewählt werden. LibreOffice etwa gibt TSV in die Textkonsole aus, damit würde es auch gehen.

Wichtig: Die Beträge müssen entweder in Cent angegeben, also Ganzzahlen ohne Währungszeichen sein, oder als Trennzeichen muss Leerzeichen, Tabulator oder Senkrechtstrich eingestellt werden. Komma wird nur dann unterstützt, wenn es nicht zugleich auch Cent von Euro trennt. Der Verwendungszweck muss mit Anführungszeichen umfasst sein, wo er Zeilenumbrüche enthält. Ggf. sind entsprechende Einstellungen in der Tabellenkalkulation vorzunehmen.

$ export TRSRDB_SQLITE_FILE=trsr.db
$ ./trsr charge ${Eingabedatei}

Gibst du keine Eingabedatei(en) an, wartet das Programm auf einen Eingabedatenstrom. Schließe dann deine Eingabe mit Strg-D ab.

Hier ein Beispiel. Wir haben eine Datei kontoauszug.csv mit folgendem Inhalt erstellt:

2016-10-01,Club,,1452.00 €,Anfangssaldo -C[Übertrag]
2016-10-14;flow;;"72,00 €";Mitgliedsgebühr flow 2016 -C[Mitgliedsbeitrag]
2016-10-28|rose||72,00 €|"Mitgliedsgebühr, D. Rosenthal, 2016 -C[Mitgl]"
2016-11-05 Club 380,00€ "" AMZ2016/45674-01: Neuer Kühlschrank -C[Kauf]
2016-11-13 john 36,00 € J. Albrecht Juli bis Dezember -C[Mitgl]
2016-12-05 Club €18,00
 BF-2016:
 Kontogebühren -C[Kontofüh]

In jeder Zeile habe ich das Format ein bisschen abgewandelt, um die Flexibilität von trsr zu demonstrieren. Verfüttern wir unsere Eingabedatei an das Programm:

$ ./trsr charge < Vortrag/kontoauszug.csv 
Created 1, transferred 0 of 145200.
Created 2, transferred 0 of 7200.
Created 3, transferred 0 of 7200.
Created AMZ2016/45674-01, transferred 0 of 38000.
Created 4, transferred 0 of 3600.
Created BF-2016, transferred 0 of 1800.
$ ./trsr status
Get balance status ...
ID      availbl earned spent   promise arrears even_until
Club     145200      0      0       0   39800
alex          0      0      0       0       0
flow       7200      0      0       0       0
john       3600      0      0       0       0
rose       7200      0      0       0       0

Zu jeder Eingabezeile wird die ID vom Guthaben (Zahl) bzw. von der Belastung (eingebene Zeichenfolge vor dem Doppelpunkt) ausgegeben. Außerdem wird der Betrag in Cent angegeben, und wie viel davon bereits transferiert wurde.

Hinweise für zukünftige Stapelbuchungen

Hier ein kleiner Einblick, was zukünftig die Arbeit mit trsr charge einfacher macht. Wer es später lesen möchte, kann zum Abschnitt „Bewegung! Belastung!“ scrollen.

Sofort-Transfers

Von der Möglichkeit solcher Soforttransfers haben wir hier noch keinen Gebrauch gemacht, es kann aber  in Zukunft Arbeit sparen, wenn wieder eine Reihe von Buchungen vorab in einem Editor präpariert wurden und eingespeist werden sollen.

„>> *“ am Ende des Verwendungszwecks von Habenbuchungen transferriert die Beträge auf alle Belastungen, aufsteigend sortiert nach deren Buchungsdatum, soweit welche mit dem gleichen Konto assoziiert sind. Statt des Sternchens kann auch eine oder mehrere durch Komma getrennte Rechnungs-IDs offener Belastungen angegeben werden.

Analog dazu können auch Belastungen bereits bei Buchung aus angegebenen Guthaben getilgt werden. Dann müssen aber „<<“ verwendet werden. Alternativ zu den doppelten spitzen Klammern kann auch ein Tabulator eingegeben werden.

Interne Verrechnungen

Bei Belastungen, die keine Auszahlungen sind, kann die Referenz eines Zielguthabens in der Habenspalte angegeben werden. Die Referenz ist entweder die numerische ID des Guthabens, oder ein für diese vergebener Name, der mit einem Buchstaben beginnen muss und nur Buchstaben und Ziffern enthalten darf. Der Name ist für die Sitzung gültig, das heißt, mit dem Beenden von trsr verfällt er. Angegeben wird ein Name in der Sollspalte einer Habenbuchung. Er kann dann übrigens auch bei Soforttransfers bei späterhin gebuchten Belastungen angegeben werden.

Oft verwendete Namen können auch schon beim Aufruf von trsr charge definiert werden. Auf der Kommandozeile …

$ ./trsr charge --target-credit Mb=1 # oder kürzer: --tcr Mb=1
...
2017-04-11;Club;79900;;EBAY4354332/17: Beamer ... << Mb
...
$

Durch Benennung von Guthaben vorab kann der Benutzer das Risiko von falschen Referenzierungen erheblich mindern.

Belastung! Bewegung!

Nun, da wir Mitgliedskonten mit Guthaben versehen haben, und wahrscheinlich das Hauptkonto mit allerlei Belastungen wie Raummieten etc., wird TreasureDB immer noch nicht seinen Sinn und Zweck ausspielen können.

Es fehlen die internen Verrechnungen. Die setzen sich zusammen aus Belastungen und Bewegungen. Letztere sind die Verknüpfung zwischen einer Belastung und einem Guthaben, die beide dem gleichen Konto zugeordnet sein müssen, und einen Zeitstempel tragen.

Mit einer Belastung, die keine Auszahlung nach außen ist, muss ein Zielguthaben verknüpft werden. Für alle Mitgliedsbeiträge empfiehlt sich ein und dasselbe Zielguthaben zu verwenden. Du definierst es mit folgendem Kommando:

$ ./trsr charge -a main -v 0 -p "Mitgliedsbeiträge"

Das -v 0 ist entscheidend. Damit legst du fest, dass das Guthaben initial keinen Betrag besitzt.

Vom Stichtag X (Anfangssaldo) ausgehend muss anhand der Unterlagen des entlasteten Kassenwarts ermittelt werden, welches Mitglied aktuell welche Rückstände bis zum aktuellen Datum hat. Dabei gilt die satzungsgemäße Abrechnungseinheit, z.B. Monat. Für jeden Zeitraum (Monat/Jahr) musst du also ermitteln, welche Mitglieder noch keine Mitgliedsgebühr für den jeweiligen Monat entrichtet haben. trsr teilst du dies mit dem folgenden Kommando mit:

$ ./trsr ct --bill-id="MB1601-@{flow,john,dada,...}" -v 600 \
  -p "Mitgliedsbeitrag normal für Januar 2016"

Im Anschluss an dieses Kommando wird interaktiv abgefragt, welche offenen Belastungen von welchen verfügbaren Guthaben beglichen werden soll.

TreasureDB: ein elektronisches, transparentes Vereinskassenbuch (1)

(Dieser öffentliche ENTWURF wurde zuletzt am 4. März geändert, um den aktuellen Stand widerzuspiegeln. Sobald dieser Hinweis verschwindet, gilt die vorliegende Version als final und zitierbar.)

Eines meiner Software-Projekte ist TreasureDB, ein elektronisches Vereinskassenbuch auf Basis des Datenbanksystems SQLite. Es ist Open-Source, steht unter der GPL. Zwar ist die Software auf kleine, eingetragene Vereine in Deutschland zugeschnitten. Dennoch ist die verwendete Sprache des Projektes Englisch, damit das Programm prinzipiell international verwendet werden kann. Wer mag, kann mir mit der Übersetzung helfen, auch auf technischer Ebene.

TreasureDB kann auf drei Arten bedient werden:

  1. Direkt über die sqlite3-Konsole oder einer beliebigen anderen Benutzerschnittstelle für SQLite-Datenbanken. Die Datenbank, die in einer einzigen Datei vorliegt, gewährleistet dabei mit Schlüsselbeziehungen und Triggern (Routinen, die unter definierten Bedingungen automatisch angestoßen werden) selbst die Konsistenz. So kann, wer SQL beherrscht, die Datenbank direkt verwalten, ohne auf andere Tools angewiesen zu sein, und braucht nicht befürchten, dass er/sie die Daten beschädigen könnte.
  2. Auf der Datenbank baut das rein textbasierte Programm trsr auf.  Es wird mit dem Namen einer Funktion gestartet, außerdem kann das zu bearbeitende Konto angegeben werden. Es dient als Schnittstelle zum Nutzer, der kein SQL beherrscht oder verwenden möchte, aber generell die Text-Konsole etwas Graphischem vorzieht.
  3. Eine solche Funktion ist server. Damit wird ein HTTP-Server gestartet. So kann die Datenbank über einen beliebigen Webbrowser gepflegt werden.

Eine Ausbildung zum Buchhalter sollte der Kassenwart nicht benötigen,  um TreasureDB anzuwenden. Die habe ich schließlich auch nicht, von allgemeinem Wirtschaftsunterricht damals in der Oberstufe einmal abgesehen. Damit ist die Zielgruppe der Software, wie auch dieses Artikels, umrissen: Kleine Vereine mit relativer Computeraffinität, z.B. User groups wie die UUG Rhein-Neckar.

Diese kleine Serie besteht aus mehreren Teilen:

  1. 4 Grundbegriffe in ihrem Zusammenhang, Aufbau des Systems – Konten, Guthaben, Belastungen und interne Bewegungen.
  2. Ausgangslage – LibreOffice-Calc Datei vom vorigen Kassenwart (Beispiel), zusätzlich Kontoauszüge, die vollständig ab einem definierten Zeitpunkt elektronisch in tabellarischem Format vorliegen (z.B. CSV, TSV).
  3. Migration – Daten einspielen und Beziehungen zwischen Aus- und Eingängen explizieren, d.h. Kontobewegungen spezifizieren.
  4. Nichttriviale Szenarien – zum Beispiel, wenn ein Mitglied die Mitgliedsgebühr für zwei bezahlt.

Aufbau von TreasureDB

Grundbegriffe

Da ein Bild bekanntlich mehr als tausend Worte sagt:

a) Konto

Ein Konto ist in diesem System lediglich eine Art Gruppenzuordnung von einzelnen Guthaben und von Belastungen. Letztere referenzieren Konten sowohl als belastete als auch als – indirekt über Zielguthaben (s.u.) – begünstigte Konten. Ein Konto kann einen bestimmten Typ haben, zum Beispiel den Typ „Mitgliedskonto“. Es ist möglich, mehrere Konten des gleichen Typs aufeinmal zu belasten. Vereinskonten haben meist keinen Typ, diese werden ausschließlich über ihre ID, ihre Bezeichnung unterschieden, die übrigens bewusst keine Zahl ist, sondern ein frei wählbarer Name. Die Mitgliedsnummer wird bei Mitgliedskonten in einem einfachen Feld eingetragen, das für Nummern in externen Fremdtabellen gedacht ist.

Außerdem kann zu einem Konto eine IBAN gespeichert werden. Ist diese angegeben, muss sie im Verwendungszweck von Auszahlungen stets enthalten sein. Ist keine IBAN enthalten, sind Auszahlungen von diesem Konto nicht möglich. Das Feld kann auch explizit leer definiert werden, muss es sogar für das Hauptkonto des Vereins. Dann werden Auszahlungen nicht geprüft.

b) Guthaben

Mit Guthaben sind zum einen Einzahlungen gemeint, z.B. Mitgliedsbeiträge. Zum anderen gibt es Guthaben, die anfangs gar keine sind, denn sie haben bei Buchung den Betrag 0. Diese Guthaben werden mit Belastungen verknüpft und bilden das Ziel von internen Bewegungen.

Jedes Guthaben hat neben dem Betrag und dem Buchungsdatum auch einen angegebenen Zweck. Bei Einzahlungen ist dies der Verwendungszweck, wie er aus dem Kontoauszug hervorgeht. Bei Zielguthaben ist der Kassierer frei in der Bestimmung des Zwecks. Ich empfehle, eine Gruppenbezeichnung für Belastungen anzugeben, etwa „Mitgliedsbeiträge 2016“.

c) Belastung

Eine Belastung ist eine Forderung, die beglichen oder noch offen sein kann. Es handelt sich um einen Datensatz, der folgende Daten enthält:

  • Identifizierungsangabe des Belegs, z.B. Rechnungsnummer, bei Mitgliedsbeiträgen eine Zeichenfolge nach dem Schema Jahr, Monat und Mitgliedsname;
  • Buchungsdatum;
  • Name des belasteten Kontos;
  • Nummer des Zielguthabens im begünstigten Konto;
  • Grund der Belastung;
  • Betrag.

d) Bewegung

Bewegungen sind das A&O im elektronischen Kassenbuch. Es genügt nicht, einfach Ein- und Ausgänge aufzulisten. Auch ein Zusammenhang zwischen ihnen ist herzustellen und explizit zu speichern, will der Kassierer diesen Zusammenhang nicht bei Bedarf immer neu rekapitulieren müssen.

Eine Bewegung verknüpft also ein Guthaben mit einer Belastung. Sie speichert auch den Zeitpunkt und den Betrag, der im Vergleich zwischen Guthaben und Belastung der jeweils geringere ist. Dieser Betrag wird tatsächlich transferiert, um den Betrag wird die Belastung getilgt, bzw. das Quellguthaben verringert und das Zielguthaben wiederum erhöht.

Die Tilgung von Belastungen und die Verwendung von Guthaben geschieht transparent. Das heißt, die originalen Beträge bleiben zu Referenzzwecken erhalten. Der Abzug davon wird getrennt gespeichert. Wo nötig, wird einfach die Differenz gebildet.

Architektur der Datenbank

Beachte: Selbstgewählte Namen werden im Folgenden in Fettdruck geschrieben.

Tabellen

Obige Grundbegriffe korrelieren mit den Tabellen in der Datenbank, die auf Englisch bezeichnet sind.

  • Konten werden in der Tabelle Account gespeichert.
  • Guthaben in Credit
  • Belastungen in Debit
  • Bewegungen in Transfer

Views

Die Datenbank enthält neben den Tabellen zudem eine Reihe von Views. Diese sind eine Art virtuelle Tabellen. Sie enthalten selbst keine Daten, sondern fragen on demand ihrerseits die Tabellen ab und bereiten die zurückgelieferten Daten zweckorientiert auf. Sie entlasten so den Kassierer beim Überblicken und Nachvollziehen vergangener Ein- und Ausgänge sowie ihrer Zusammenhänge. Das Einfügen, Ändern und Löschen von Datensätzen ist Views per Design nicht möglich.

  • ReconstructedBankStatement (dt. rekonstruierter Kontoauszug): Dieser View ist dazu gedacht, vom Kassenprüfer mit den Papierauszügen der Bank verglichen zu werden. Er enthält alle Einzahlungen, also alle Guthaben, die nicht als Ziel von Belastungen spezifiziert wurden, sowie alle Auszahlungen, also Belastungen ohne Zielguthaben.
  • CurrentArrears (dt. aktuelle Rückstände): Belastungen, die noch nicht vollständig beglichen wurden, also noch nicht oder nicht von genug Bewegungen referenziert werden.
  • AvailableCredits (dt. verfügbare Guthaben): Guthaben, die noch nicht (vollständig) verwendet, also von (genug) Bewegungen referenziert werden.
  • Balance (dt. Abgleich): Zu jedem Account werden bestimmte Daten aggregiert. Da dies der wichtigste View ist, gehe ich detailliert auf die einzelnen Felder ein.
    • Am wichtigsten ist die Summe der offene Belastungen (Spalte arrears).
    • Nicht viel weniger wichtig sind die Guthaben, die noch nicht verwendet wurden (available). Sind beide Spalten auf derselben Zeile >0, bedeutet das schlicht, dass noch Bewegungen zu erstellen sind. Kann der Kassierer Guthaben und Belastungen nicht zusammenbringen, etwa aufgrund missverständlicher Verwendungszwecke, muss er diese Fälle mit dem Mitglied klären.
    • Außerdem enthält die Balance das Datum des Kontoausgleichs, also der letzten Belastung (even_until), die getilgt wurde und zwar in Folge, d.h. zeitlich vor dieser Belastung gibt es ebenfalls keine ungetilgten oder unvollständig getilgten Belastungen;
    • die Summe der Beträge, die aufgrund von Dienstleistungen/Waren auf dem Konto gelandet sind und ausgezahlt oder weiter verwendet wurden, bzw. noch auszuzahlen oder weiter zu verwenden sind (earned);
    • und nicht zuletzt die Summe der Beträge von offenen Belastungen, deren Zielguthaben dem Konto zugeordnet ist (promised).
  • History (dt. Verlauf): enthält alle internen Bewegungen doppelt, also jeweils in Hin- und in Gegenrichtung. So kann dieser View sowohl nach dem belasteten als auch nach dem begünstigten Konto gefiltert werden. Mit der View ReconstructedBankStatement zusammengenommen haben wir also ein komplettes Bild aller erfolgten Kassenvorgänge.
  • Report (dt. Bericht): Dieser View hilft dem Kassierer, Unklarheiten gemeinsam mit Mitgliedern zu beseitigen. Gefiltert nach einem Konto listet es alle Guthaben auf, die nach dem Datum des Kontoausgleichs eingezahlt wurden, alle Abzüge durch Bewegungen von diesen Guthaben, sowie alle offenen Forderungen. Summiert ergibt sich der Saldo, der sich in der Balance wiederfindet.

Trigger

Die folgenden Funktionen, Trigger genannt, löst die Datenbank automatisch zu bestimmten Ereignissen aus. Sie verhindern Fehleingaben und nehmen nötige Verarbeitungen vor.

  • balanceTransfer (dt. Bewegung abgleichen): Bei jedem Eintrag einer Bewegung vergleicht dieser Trigger Guthaben und Belastung, speichert den geringeren Betrag in der Bewegung und zieht ihn jeweils von Guthaben und Belastung ab.
    Er gibt folgende Fehlermeldungen aus:
    It is not the debtor who is set to pay = Es wird versucht, die Bewegung mit einem Guthaben eines anderen Kontos als das belastete zu verknüpfen.
    Target of a debit cannot be an incoming payment = Zielguthaben kann keine Einzahlung sein.
    Credit spent = Guthaben ist vollständig verbraucht und kann daher keine weiteren Belastungen begleichen.
    Debt settled = Belastung ist bereits vollständig beglichen, weshalb es keinen Sinn hat, mehr Guthaben darauf zu verwenden.
    Oops, lost _temp record before increasing spent = Bug (d.h. sollte nie passieren. Hörst du Murphy lachen? Das bildest du dir bestimmt nur ein, ts …)
    Oops, lost _temp record before increasing value = Bug (dito)
  • revokeTransfer (dt. Bewegung widerrufen): Wird eine Bewegung wieder gelöscht, sind die Verarbeitungen entsprechend wieder rückgängig zu machen. Es werden keine Fehlermeldungen ausgegeben.
  • enforceImmutableTransfer (dt. mache Bewegungen unveränderlich): Verhindert willkürliche Änderungen von Bewegungen. Fehlermeldung: Transfer cannot be updated, but needs to be replaced to make triggers run = Bewegungen können nicht geändert werden. Sie müssen ersetzt werden, damit die nötigen Trigger angestoßen werden.
  • enforceZeroPaidAtStart (dt. initialisiere Belastungen mit paid=0): Bei neu eingetragenen Belastungen muss das Feld paid 0 oder gar nicht definiert sein. Fehlermeldung: Debt must be initially unpaid = Eine eingetragene Belastung muss unbeglichen sein.
  • enforceDebtImmutableOutsideTrigger (dt. schütze Belastungen vor Veränderungen außerhalb Triggern): Mögliche Fehlermeldung: paid is set and adjusted automatically according to added Transfer records  = das Feld paid muss von Triggern gesetzt werden. Eingaben wird nicht getraut.
  • rebalanceIncreasedCredit (dt. gleiche erhöhte Guthaben neu aus): Wird eine Bewegung eingetragen, so wird ja das mit der Belastung verknüpfte Zielguthaben erhöht. Sind mit dem Zielguthaben wiederum Bewegungen verknüpft, die Belastungen nicht vollständig begleichen, so werden diese Bewegungen erneut durchgeführt.
  • rebalanceReducedCredit (dt. gleiche verringerte Guthaben neu aus): Wird eine Bewegung gelöscht, muss das Zielguthaben der Belastung entsprechend reduziert werden. Der Trigger stellt außerdem fest, ob die Bewegungen, die dieses Guthaben als Quelle verknüpft haben, auch mit dem geringeren Betrag hätten stattfinden können. Wo dies nicht der Fall ist, werden die überstehenden Bewegungen ebenfalls widerrufen,
  • enforceFixedDebit (dt. schütze Belastungen vor Veränderungen des Betrags): Mögliche Fehlermeldung: Debt is involved in transfers to revoke at first  = Belastung ist schon vollständig oder teilweise beglichen. Der Betrag, das belastete Konto oder das Zielguthaben können daher nicht korrigiert werden.
  • enforceSpentImmutableOutsideTrigger (dt. schütze Guthaben vor Veränderung außerhalb Triggern): Mögliche Fehlermeldung:  spent is set and adjusted automatically according to added Transfer records = Das Feld spent darf nicht händisch verändert werden.
  • enforceZeroSpentAtStart (dt. initialisiere Guthaben mit spent=0): Bei neu eingetragenden Guthaben muss das Feld spent 0 oder undefiniert sein. Mögliche Fehlermeldung: credit must be initially unused  = Einzahlungen dürfen anfangs nicht verwendet worden sein.
  • enforceFixedCredit (dt. schütze Guthabenbetrag gegen Veränderung): Mögliche Fehlermeldung: Credit involved in transactions to revoke at first = Eingezahltes Guthaben ist schon vollständig oder teilweise beglichen. Der Betrag oder das begünstigte Konto können daher nicht geändert werden.
  • checkIBANatTransfer (dt. prüfe IBAN bei Auszahlungen): Ist zum Konto eine IBAN gespeichert – oder stattdessen eine leere Zeichenfolge, wodurch alle IBANs gültig sind –, kann von diesem Konto an die gespeicherte IBAN ausgezahlt werden. Mögliche Fehlermeldung: IBAN used does not match IBAN currently stored in account record = Hat sich der Kassierer bei einer Empfänger-IBAN vertippt? Sollte nicht passieren. Die Bank wird Überweisungen nicht rückgängig machen. Aber der Kassierer kann ja immer noch privat für den Schaden aufkommen, indem er die Bewegung rückgängig macht und die fehlerhafte Auszahlung auf sein Konto umbucht.

Probleme

TreasureDB hat noch einige Probleme, die teilweise nicht so einfach zu lösen sind.

Dazu gehört etwa, das A eine Belastung zugunsten B zwar anzahlen kann, aber unter Umständen B wiederum nicht eine Belastung von A, wobei A das erhaltene Guthaben umgekehrt für die gleiche Belastung verwendet, und so weiter. Auch wenn diese reziproken, oder zirkulären Anzahlungen realiter sehr selten vorkommen mögen, wenn der Kassierer überhaupt einen Sinn darin sieht, derartiges aus Absicht zu definieren: Nicht ausgeschlossen ist, dass er, da er mehr oder weniger versehentlich eben doch eine definiert hat, mit einer kryptischen Fehlermeldung konfrontiert wird: Schlüsseldublette in  __INTERNAL_TRIGGER_STACK.ID oder so. Wenn auch technisch richtig, müsste die Fehlermeldung eigentlich lauten: »Zirkuläre  Anzahlungen werden nicht unterstützt.« Bevor ich Trigger allerdings mit redundanten Checks überfrachte, nur weil mir die Standardfehlermeldungen nicht gefallen, gehe ich lieber erst mal in mich.

Idee: Digest zurückgehaltener E-Mails

Mein Mailprovider wendet mehrere Anti-Spam-Maßnahmen an, die nach seiner Aussage höchst effizient sind. Er bietet jedoch bewusst keine Möglichkeit an, dass der User eigene Regeln festlegen kann, um Mails als Spam auszufiltern. Grund ist vor allem, dass ein gutes Regelwerk sehr gute Kenntnisse des Mediums E-Mail auf technischer Ebene erfordert, die die wenigsten haben – auch ich nicht, weshalb ich auch gerne auf fremde Kompetenz vertraue. Soweit gebe ich ihm Recht und akzeptiere diese Entscheidung.

Leider ist es so, dass ich trotzdem alle naselang Spam kriege. Und zwar, seitdem ich die Domain humanetasking.net registriert habe, denn da habe ich die Mailadresse bei diesem Provider eintragen lassen.

Das ist nicht weiter schlimm, löschen und gut ist, wie es jeder macht. Schwerer wiegt allerdings, dass der E-Mail-Client auf meinem Smartphone jedes Mal einen Ton von sich gibt und unnötig für Ablenkung sorgt. Das muss nicht sein! Klar, ich könnte die akustische Benachrichtigung überhaupt ausschalten oder eben nach einer Alternative suchen, die sich so einstellen lässt, dass nur bei Mails von bekannten Absendern eine akustische Benachrichtigung erfolgt.

Allerdings bin ich der Meinung, dass man weiter grundsätzlich über effiziente Spambekämpfung nachdenken sollte. Vollautomatische regelbasierte Ansätze bringen es nicht allein.

In diesem Artikel stelle ich eine weitere Idee vor.

Akreditierung von Absendern

Wie wäre es, wenn E-Mail beim Zielprovider grundsätzlich in zwei Klassen eingeteilt werden: In E-Mails, bei dem der Absender lt. Adressbuch des Nutzers bekannt ist und E-Mails, bei denen dies nicht der Fall ist? Das ist erst einmal nicht neu. Viele Mailprovider dürften so verfahren, zumindest wenn das Adressbuch serverseitig gepflegt wird. Dies wirft aber u.U. datenschutzrechtliche Probleme auf.

Wenn serverseitig zumindest die Adressen in einer eigenen Adressbuchabteilung wie „Akreditierte Absender“ gespeichert werden, jeweils ohne irgendwelche assoziierten Daten wie Name, Telefonnummer etc., sehe ich kein Problem darin. Im Folgenden fasse ich auch diese Adressen als „bekannt“ auf.

Auch gelten alle Empfänger, an die man selbst E-Mails richtet, als akreditiert, denn man möchte in der Regel eine Antwort, würde sie zumindest nicht als Belästigung empfinden.

Zurückhaltung von E-Mails

Je nachdem, ob der Absender einer bestimmten E-Mail bekannt ist oder nicht, wird diese Mail unterschiedlich behandelt.

  • wenn ja, kommt sie direkt in den Posteingang und gilt als ungelesen.
  • wenn nicht, wird sie zunächst in einen Ordner „Zurückgehalten“ und bekommt das Flag „gelesen“. Dieser Ordner kann als Zwischending von Posteingang und Papierkorb begriffen werden. Mails darin werden nach einer festgelegten Zeitspanne gelöscht.

Digest

Auch diese Teilidee ist nicht neu: Die Software von Mailinglisten bieten seit jeher die Möglichkeit an, statt der Sendung der Beiträge in einzelnen E-Mails alle Beiträge in einer Sammel-E-Mail zu schicken.

Übertragen auf meine Idee könnte in regelmäßigen Intervallen eine E-Mail geschickt werden, die zu jedem Neuzugang im Ordner „Zurückgehalten“ entscheidende Metadaten auflistet.

Diese E-Mail geht natürlich wie üblich in den Posteingang und ist ungelesen. Insgesamt könnte sie etwa lauten:

Guten Tag Herr Heß,

Sie haben in den serverseitigen Maileinstellungen festgelegt, dass E-Mails unbekannter Absender zurückgehalten werden sollen. Seit dem {Datum} wurden {Anzahl} neue E-Mails im Ordner „Zurückgehalten“ gespeichert. Es folgt eine Liste mit den jeweiligen wichtigsten Metadaten.

Nach der Liste folgen weitere Informationen.

=================================================

From: Name <mail@dres.se>
Subject: Re: Mobile Apps Development !!
Date: Tue, 18 Oct 2016 10:39:22 +0000
First words: Hi, I hope business is keeping you busy! I Just
Message-ID: KL1PR0201MB165697BB8C42069263ADBEE291D30@KL1…

From: …

{weitere Einträge}

=================================================

Gern verschieben wir Ihnen diese Mails in den Posteingang und setzen sie auf den Status „ungelesen“. Bitte antworten dazu Sie einfach auf diese Mail. Lassen Sie dabei das Antwortzitat intakt. Unbedingt müssen die Message-Ids enthalten bleiben, denn darauf basierend funktioniert das automatische Verfahren.

Um bestimmte Mails nicht zu verschieben, sondern unwiderruflich zu löschen, entfernen Sie aus dem Antwort-Zitat einfach den entsprechenden Eintrag, mindestens die jeweilige Message-Id. Wenn Sie wünschen, können wir umgekehrt auch alle Mails verschieben, die nicht enthalten sind. Schreiben Sie dazu einfach das Wort „EXCEPT“ an den Anfang der Antwort. Nur dieses Wort und etwaige enthaltene Message-Ids werden maschinell berücksichtigt, alles andere wird ignoriert.

Die Absender-Adressen der verschobenen E-Mails werden im Bereich „Akreditierte Absender“ in Ihrem serverseitigen Adressbuch gespeichert, außer denjenigen, die bereits im Adressbuch auf dem Server enthalten sind. So gelangen Folge-E-Mails dieser Absender automatisch in den Posteingang.

Bitte beachten Sie, dass nach einer Antwort auf diesen Digest der Ordner „Zurückgehalten“ geleert wird. Es ergibt daher keinen Sinn, auf mehrere Digests in Folge zu antworten. Stattdessen kopieren Sie bitte die Message-Ids der gewünschten bzw. zu Mails aus früheren Digests mit in dieselbe Antwort.

Alternativ können Sie natürlich gewünschte Mails mit Ihrem E-Mail-Programm aus dem Ordner „Zurückgehalten“ in beliebige Mailordner verschieben.

Ist das effektive Spambekämpfung?

Es ist indirekte Spambekämpfung. Nicht der Spam selbst wird dadurch verhindert, sondern dem Spam wird erschwert, Aufmerksamkeit zu binden. Denn auch den Spamcharakter einer E-Mail zu erkennen setzt Aufmerksamkeit voraus.

Eben dadurch, dass Mails unbekannter Absender auf das für diese Entscheidung nötige zusammengeschmolzen, dass alle diese Mails auf diese Weise in einer einzigen zusammengefasst werden, wird der Bedarf an Aufmerksamkeit, die das manuelle Aussortieren von Spam benötigt, stark reduziert.

Und letztendlich verliert so Spam auf Anbieterseite stark an Attraktivität. Spammer müssen also zunehmend auf andere Tätigkeitsfelder ausweichen. Der Sumpf wird ausgetrocknet, das Geschäftsmodell ist schließlich keines mehr.

Nichtkommerzielle Werbung und Showcase-Users

Ich stehe aktuell vor dem Problem, dass die Test-Userbase, konkreter die Userbase auf meiner Online-Testinstanz von FlowgencyTM, noch etwas klein ist. Zugegeben – sie ist eigentlich noch gar nicht existent.

Es geht also um Werbung. Ich kann Werbung nicht leiden, besonders wenn sie aufdringlich ist. Um mein Projekt bekannt zu machen und Beta-Tester zu finden, muss ich also etwas tun, was ich eigentlich nicht möchte, worin ich auch nicht leidenschaftlich anstrebe gut zu sein, aber woran nun einmal kein Weg vorbei führt. Das endet bei einem Kompromiss zwischen Selbstverrat und dem Erreichen eines Ziels, statt nur davon zu träumen. Der Clou liegt bei der Wahl der Mittel.

Fanpage auf Facebook? Sorry, ist nicht drin, da war ich mal, und das auch nur kurz. Nein, ich möchte nicht zu diesem Netzwerk gehören, zumindest nicht bewusst. Schon dass die dank meiner Freunde Daten über mich haben, reicht mir zur Genüge.

Professionelle Marketingagenturen? Geht nicht, weil mein Projekt 0% kommerziell ist, ich ein solches Engagement folglich gar nicht refinanzieren könnte. Und andere etwas machen zu lassen, was mich selbst üblicherweise nur nervt, wäre ohnehin schlicht verlogen, zumal es auch auf Seiten der Profis kein produktives Arbeiten mit mir wäre. Schon mein Versuch, eine Marke zu registrieren, war nicht von Erfolg gekrönt. Heute kann ich sagen, zum Glück. Und es gibt ja immer noch den Titelschutz.

Soziale Medien betreffend, ist für mich maximal ein Twitterkanal drin. Grundsätzlich lege ich im Rahmen meiner Humane Tasking Initiative großen Wert darauf, dass alle Werbung informativ ist, aber nicht aufdringlich. Ich bin nun mal Nerd, kein Marktschreier.

Nicht nur das erschwert meine Suche nach Betatestern. Eine noch höhere Barriere sind auch die Terms of Service der Online-Demo-Instanz. Die sind völlig anders als üblich. Ich habe die Überschrift explizit um »Special Agreement« erweitert, damit niemand auf die Idee komme, ich müsste mich an das AGB-Gesetz oder so halten. Das Ding ist wie gesagt nicht kommerziell, kann es auch gar nicht sein, da sie auf Vereinshardware läuft. Unter anderem ist es mir daher nicht möglich, Echtdaten von anderen Leuten zu speichern. Zumindest keine inhaltlichen; ohne echte Anmeldedaten könnte ich das mit der Online-Demo-Instanz sowieso vergessen.

Aber auch technisch sind die ToS eine wirksame Barriere. Ganz bewusst belasse ich es nicht dabei, einen neuen User eine Checkbox I accept the terms anklicken und mir egal sein zu lassen, ob er sie tatsächlich liest, überfliegt oder sich selbst dafür keine Zeit nimmt. Nicht nur muss man sie tatsächlich lesen, um den oder die Fake-Paragrafen herauszufinden und nebenstehendes You’re kidding? anzuklicken. Auch die anderen, verbindlichen sind nicht ohne Bemerkungen gegen die Software, sogar gegen technisch implementiertes mental enhancement überhaupt. Immerhin erstrecken sie sich nur über eine bis anderthalb Seiten, verglichen mit anderen Diensten ein Klacks.

Kurz: Objektiv muss ich mir die Frage gefallen lassen, ob ich Betatester nun will oder nicht. Meine Antwort darauf ist ein klares »Ja, aber …« – siehe oben.

Ich habe mir daher etwas einfallen lassen, und bin zur Zeit noch bei der Entwicklung dieses Features. Es geht darum, wenigstens diejenigen Interessenten zu erreichen, die die auf der Homepage verlinkten Videos gesehen haben, die das Konzept schon mal durchaus anspricht, aber die noch zögern, das Tool lokal zu installieren bzw. besagte ToS-Barriere zu nehmen.

Idee: »Showcase Users«

Es gibt ja schon eine Einstellung im Profil eines Users, das Feld heißt extprivacy, deren Default 0 ist. Alle User mit dem Wert 0 sind gemäß der von ihnen akzeptierten ToS damit einverstanden, dass ihre Daten außer Benutzername, Passwort und E-Mail, in öffentlich gestellten Datenbank-Dumps landen können. Diese Dumps können gemäß der Creative Commons Zero-Lizenz von jedermann frei weiter verwendet werden.

Neue User, die bei Paragraf 2 „I want extended privacy for real data“ angeklickt haben, werden entweder gelöscht, nachdem ich sie per Mail aufgeklärt habe, dass diese Einstellung – wie in Absatz 2d erklärt – leider nur denen vorbehalten ist, die projektunabhängig in meinem Adressbuch stehen und die ich in einem Telefongespräch darüber aufgeklärt habe, wer faktisch Zugriff auf die Daten hat und ob sie die Software nicht eher lokal bei sich installieren möchten (ggf. mit meiner Hilfe) … Oder sie sind dann halt so eine Art »Premium«-User, bei denen extprivacy = 1 ist und deren Daten daher nicht in diesen Dumps landen.

Das Flag könnte nicht nur 0 oder 1 sein, sondern auch NULL. Vom Sinn her wäre das dann sowas wie -1, also »noch weniger Datenschutz«. Auf 0 oder NULL wird üblicherweise mit derselben Anweisung getestet, weil das eine wie das andere boolean false ergibt, man sie bei Bedarf aber mit einem Definiertheitstest auseinanderhalten kann.

User mit undefinierter, also geNULLter extprivacy sind sogenannte Showcase Users. Also so etwas wie, glaube ich, Millionen bei Facebook, die sich nicht die Mühe machen, regelmäßig ihre Privatsphäre-Einstellungen zu überprüfen. 😉 Showcase Users unterscheiden sich von normalen Usern dadurch, dass jedermann sich über ihren Account FlowgencyTM angucken kann, wie es in der aktuellen Version ist. Das ist ein Nachteil der Videos, die veralten recht schnell. Ich meine tatsächlich angucken, nicht ausprobieren – nur Requests mit der HTTP-Methode GET sind dabei möglich, also alles, was über eine URL im Browser aufgerufen werden kann. POST– und andere Requests werden mit einer Fehlermeldung quittiert. Manipulation von Daten durch Fremde müssen also auch Showcaser nicht befürchten, wo doch, habe ich Mist gebaut und muss diese Lücken schließen.

Showcase-Zugriff auf FlowgencyTM ist möglich, in dem man im Browser in der URL vor der Domain user_id@ einfügt. Fragt der Browser nach einem Passwort, wird alles akzeptiert, spielt keine Rolle, und wenn es nur ein Leerzeichen ist. Wenn der Browser ganz leer nicht akzeptiert, überhaupt danach fragt.

Auch reguläre User können auf Anfrage an mich Showcaser werden. Zumindest wenn ich ihnen vertraue, schließlich können sie so alles mögliche veröffentlichen. Showcasers, die regulär werden möchten, brauchen ihren Status nur im Profil revidieren.

© 2017 Flow in Computing

Theme von Anders NorénHoch ↑