Finanzmanager/docs/buchungen.md
fds2610-bot d1db40fa55 Fix #126: Taschenrechner an den Betragsfeldern
Jedes Zahlenfeld (inputmode="decimal") zeigt rechts ein Rechnersymbol.
Klick darauf oder Alt+R öffnet einen Taschenrechner mit Grundrechenarten
und Klammern, komplett per Tastatur bedienbar: Ziffern, + - * / ( ),
Backspace, Entf, Pfeiltasten, C löscht alles, = setzt das Ergebnis ein,
Enter überträgt ins Feld, Escape schließt und fokussiert das Feld. Das
Ergebnis steht nach jedem Tastendruck unter der Rechnung.

Das Symbol ist ein Hintergrundbild (kein Wrapper-Element, kein Eingriff in
die Layouts); gerechnet wird mit eigenem Parser statt eval. In modalen
Dialogen wandert der Rechner mit hinein. Stück/Kurs übernehmen 6,
Zinssätze 3 Nachkommastellen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:22:18 +02:00

22 KiB
Raw Blame History

Buchungen

Kontoblatt, Buchungsmaske und die Saldenrechnung.

Umbuchungen: ein Datensatz, zwei Sichten

Eine Umbuchung wird einmal gespeichert – als Buchung auf dem Ausgangskonto mit gegenkonto_id. Das Kontoblatt des Gegenkontos zeigt sie mit umgekehrtem Vorzeichen.

Der Grund ist nicht nur Sparsamkeit: Zwei gespiegelte Datensätze laufen früher oder später auseinander (einer wird geändert, der andere nicht). Und der QIF-Export macht es genauso – dort steht jede Umbuchung genau einmal, die Gegenseite nur als L[Zielkonto].

Buchung::kontoblatt() vereinigt deshalb drei Mengen:

  1. Buchungen mit konto_id = X (eigene Sicht)
  2. Buchungen mit gegenkonto_id = X ohne Splitzeilen, Betrag negiert
  3. Splitzeilen mit gegenkonto_id = X, Betrag negiert

Zeilen aus 2 und 3 tragen gegensicht = true. Sie sind nicht bearbeitbar – das geht auf dem Ursprungskonto –, damit eine Umbuchung nicht aus zwei Richtungen widersprüchlich geändert wird.

Beide Sichten nennen das Gegenüber. In der Kategoriespalte steht das jeweils andere Konto in eckigen Klammern: auf dem Ausgangskonto das Ziel, auf dem Gegenkonto das Ursprungskonto. In der Gegensicht liefert die Abfrage dafür b.konto_id als gegenkonto_id – dieselbe Spalte, aus der anderen Richtung gelesen. Ohne diese Angabe stünde auf dem Gegenkonto eine Zeile, die sich „Umbuchung“ nennt und verschweigt, woher sie kommt.

Dieselbe Dreiteilung gilt für die Salden (Buchung::saldenJeKonto()), die alle Konten mit sechs Abfragen auf einmal berechnet statt je Konto einzeln.

Mehrfachauswahl im Kontoblatt

Vor dem Datum steht je Zeile ein Ankreuzfeld, in der Titelzeile eines für alle gerade angezeigten Buchungen. Auswahl löschen in der Aktionsleiste schickt die Auswahl an POST /konten/{id}/buchungen/loeschen.

Zwei Punkte, die sich nicht ändern dürfen:

  • Der Knopf steht über der Tabelle, die Ankreuzfelder stehen in ihr. Verbunden sind sie über form="buchungsauswahl", nicht über Verschachtelung – ein Formular um die ganze Tabelle würde das Filterformular umschließen.
  • Zeilen mit gegensicht = true bekommen kein Ankreuzfeld, und der Controller prüft zusätzlich konto_id. Die Gegenseite einer fremden Umbuchung wird nur auf ihrem Ursprungskonto gelöscht – dieselbe Regel wie beim Bearbeiten.

Filter und Seite hängen an der Abfragezeichenfolge des Formularziels. Der Controller baut den Weg zurück aus den bekannten Feldern neu zusammen, statt ein mitgeschicktes Ziel zu übernehmen; so lässt sich kein fremder Weiterleitungsort einschleusen.

Kategorie und Gegenkonto in einem Feld

Eine Buchung ist entweder kategorisiert oder eine Umbuchung – beides zugleich gibt es nicht. Deshalb stehen Kategorien und Konten in derselben Auswahlliste; die Konten unten, in eckigen Klammern, wie das Kategoriefeld im FinanzManager sie schreibt. Das eigene Konto fehlt in der Liste.

Die Liste steht in Views/buchungen/zuordnungsliste.php. Sie wird nicht nur in der Buchungsmaske, deren Splitzeilen, der Schnellzuordnung im Kontoblatt und im Splitdialog eingebunden, sondern überall, wo eine Kategorie gewählt wird (#124): Regeln samt ihren Splitzeilen, Umsatzabgleich, Buchungsvorlagen, Regelmäßige Buchungen, Zahlungsverkehr und Verträge. Die Werte tragen ein Präfix, das BuchungenController::zuordnungAus() wieder auseinandernimmt (zuordnungAusFormular() nimmt ersatzweise die versteckten Einzelfelder kategorie_id/gegenkonto_id einer Tabellenzeile, zuordnungswert() baut den Wert für die Vorauswahl):

Wert Bedeutung
`` (leer) keine Zuordnung
kategorie:12 Kategorie 12
konto:7 Umbuchung mit Gegenkonto 7
split Splitdialog öffnen (nur im Kontoblatt)
depot:7 Wertpapiergeschäft im Depot 7 (Kontoblatt und Buchungsmaske)

Die Auswahl Ausgabe / Einnahme bestimmt seither nur noch die Richtung des Betrags.

Angeboten werden nur aktive Umbuchungskonten, je Kontengruppe in einer eigenen Gruppe („Umbuchungskonten – Liquide Mittel“, ohne Kontengruppen schlicht „Umbuchungskonten“) und in der Reihenfolge der Kontengruppen (Konto::umbuchungsgruppen()). Ein inaktives Konto, das schon gewählt ist, bleibt sichtbar stehen – sonst ginge die Zuordnung beim nächsten Speichern verloren. Alle übrigen inaktiven Konten stehen als verborgene Einträge (<option hidden>) in der Liste, damit kontoblatt.js eine bestehende Splitzeile damit vorbelegen kann; die Kombobox zeigt sie nur an, solange sie gewählt sind.

Die Filter von Buchungsbericht und Export führen die Umbuchungskonten ebenfalls unter den Kategorien (siehe docs/auswertungen.md und docs/import-export.md).

Ein Depot steht zweimal in der Liste: als gewöhnliche Umbuchung (konto:7) und in der Gruppe Wertpapiergeschäft im Depot (depot:7). Beides gibt es wirklich – auf ein Depot lässt sich Geld überweisen, ohne dass etwas gekauft wird. depot:7 speichert keine Zuordnung, sondern öffnet die Wertpapiermaske; daraus wird am Ende meist eine Splitbuchung (siehe docs/wertpapiere.md, Geschäft aus einer Kontobuchung). In den Splitzeilen fehlt die Gruppe: Dort steht der Betrag der Zeile schon fest, und die Umbuchung ins Depot ist genau das, was gebraucht wird.

Schnellzuordnung im Kontoblatt

Ein Klick auf die Kategoriespalte öffnet die Liste in der Zelle; die Auswahl geht an POST /buchungen/{id}/zuordnung, der Eintrag Splitbuchung öffnet stattdessen den Splitdialog (POST /buchungen/{id}/splitbuchung). Beide Wege gehen über Buchung::zuordnen() und Buchung::splitsErsetzen(), die den Rest der Buchung unberührt lassen und den Monatsabschluss achten.

Drei Punkte, die sich nicht ändern dürfen:

  • Die Auswahlliste gibt es einmal; kontoblatt.js hängt sie in die angeklickte Zelle und danach zurück. Eine Liste je Zeile wären bei hundert Zeilen ebenso viele vollständige Kategoriebäume im HTML.
  • Ohne JavaScript führt derselbe Klick in die Buchungsmaske – die Zelle ist ein Link, kein Knopf.
  • Zeilen mit gegensicht = true führen zu ihrer Buchung, statt die Auswahl zu öffnen: Sie werden auf ihrem Ursprungskonto geändert, wie beim Bearbeiten und Löschen. kontoblatt.js erkennt sie an der Klasse gegenseite.
  • Feld und Auswahlliste sind gleich breit (13rem), und die Zelle zeigt nichts an, was nicht auch in der Liste stünde – kein „zuordnen …“. Sonst rückt beim Anklicken die halbe Zeile: Die Spalte wüchse auf die Breite der längsten Kategorie, und der Empfänger daneben bräche um. Senkrecht rückt nichts, weil die Zeilenhöhe ohnehin feststeht (siehe unten).

Die Auswahl eines Depots (depot:7) öffnet statt beider den Wertpapierdialog; ohne JavaScript führt sie auf /buchungen/{id}/wertpapier?depot=7, wo dieselben Felder als gewöhnliches Formular stehen. Auch die Buchungsmaske leitet dorthin, nachdem sie die Buchung gespeichert hat.

Eine schon gesplittete Buchung öffnet auf Klick direkt den Splitdialog, nicht die Auswahlliste: Deren einziger sinnvoller Eintrag wäre für sie ohnehin Splitbuchung gewesen. Wer eine Aufteilung wieder in eine einzelne Zuordnung wandeln will, löscht sie im Dialog und ordnet die dann unzugeordnete Buchung anschließend zu – oder geht über die Buchungsmaske.

Typeahead (Feature #41)

Sowohl der Kategorie-Filter oben im Kontoblatt als auch die Auswahlliste der Schnellzuordnung tragen data-kombobox. static/kombobox.js baut ein so markiertes <select> beim Laden der Seite in ein Eingabefeld mit darunter aufklappender, gefilterter Liste um – Optgroups bleiben als Überschriften erhalten. Das ursprüngliche <select> bleibt unsichtbar im Formular stehen, Wert, Name und change-Ereignis ändern sich nicht; kontoblatt.js liest und setzt weiterhin nur select.value.

Beim Öffnen der Schnellzuordnung ruft kontoblatt.js select._kombobox.oeffnen() statt showPicker() auf – ein verstecktes <select> ließe sich weder fokussieren noch mit showPicker() öffnen. Die Klassen-Auswahl bleibt bewusst ein gewöhnliches <select> ohne data-kombobox: Ihre Liste ist kurz, ein Typeahead brächte dort nichts.

Klasse im Kontoblatt

Die Klassenspalte ist nach derselben Bauart anklickbar: eine zweite Liste (#klassenauswahl), die kontoblatt.js in die angeklickte Zelle hängt. Ihr erster Eintrag — keine — löscht die vorhandene Klasse, jeder andere setzt sie; gespeichert wird sofort beim Anklicken, über POST /buchungen/{id}/klasse und Buchung::klasseSetzen().

Die Klasse steht neben der Zuordnung, nicht in ihr: Sie ist die zweite Auswertungsdimension. Anders als Buchung::zuordnen() rührt klasseSetzen() deshalb weder Kategorie noch Gegenkonto noch Splitzeilen an – auch eine gesplittete Buchung bleibt beim Wechsel der Klasse gesplittet. Den Monatsabschluss achtet die Methode wie alle anderen.

Bei einer Splitbuchung zählen die Klassen ihrer Zeilen (Buchung::splitKlasse()): Tragen alle Zeilen dieselbe Klasse, steht sie in der Spalte; unterscheiden sie sich – eine Zeile ohne Klasse zählt dabei als eigene Angabe –, steht dort die Marke Splitbuchung, und der Tooltip nennt je Zeile Kategorie (oder Gegenkonto) und Klasse. Hat keine Zeile eine Klasse, bleibt es bei der Klasse der Buchung selbst. Ein Klick auf die Klassenzelle einer Splitbuchung öffnet den Splitdialog statt der Klassenliste (data-split): Dort stehen ihre Klassen.

Zeilenhöhe und Tooltips im Kontoblatt

Jede Buchungszeile ist zwei Textzeilen hoch, gleich wie viel in ihr steht, und ihr Inhalt steht senkrecht in der Mitte. Eine Zeile, die mit ihrem Text wächst, ließe die Liste beim Blättern hüpfen; eine, die auf eine Zeile schrumpft, machte lange Empfänger unlesbar. Die Höhe hängt an table.kontoblatt td, das Abschneiden an .zellentext (zwei Zeilen, danach Auslassungspunkte). Empfänger und Buchungstext stehen deshalb in einem <div> und nicht unmittelbar in der Zelle: Abschneiden kann nur ein Element, das kein Tabellenfeld ist.

Die Marke der Umbuchungs-Gegenseite steht neben dem abgeschnittenen Kasten (.zellenreihe), nicht darin – sonst zählte sie als eine der beiden Zeilen und die Zeile würde höher als die übrigen.

Den vollständigen Text zeigt der Tooltip:

  • Empfänger und Buchungstext: kontoblatt.js setzt den Tooltip nach dem Laden und nach jeder Größenänderung des Fensters – aber nur dort, wo wirklich etwas abgeschnitten ist. Ob das der Fall ist, weiß erst der Browser nach dem Umbruch, und das hängt an der Fensterbreite. Ein Tooltip, der bloß wiederholt, was ohnehin dasteht, wäre lästig.
  • Kategorie und Klasse tragen ihren Tooltip vom Server. Bei einer Splitbuchung nennt er die Zeilen der Aufteilung: je Zeile Kategorie (oder Gegenkonto) und Betrag, sonst nichts. Notiz und Klasse stünden nur im Weg; die Klassen nennt bei verschiedenen Klassen der Tooltip der Klassenspalte (siehe oben).

Splitbuchungen

Eine Buchung ist entweder einfach zugeordnet oder gesplittet. Sobald Splitzeilen vorliegen, räumt das Model kategorie_id und gegenkonto_id der Buchung; die Zuordnung steckt dann ausschließlich in den Zeilen.

Die Summe der Zeilen muss dem Buchungsbetrag entsprechen – geprüft im Model (Toleranz ein halber Cent, für Rundungen aus der Betragsumrechnung) und in beiden Masken als Restbetragsanzeige. Beide rechnen mit public/static/betrag.js; die Regel für das deutsche Zahlenformat steht damit einmal im Browser und einmal in PHP, nicht dreimal. Eine Splitzeile darf ein Gegenkonto tragen; dann erscheint auf jenem Konto nur der Betrag dieser Zeile. Auf das eigene Konto darf sie nicht zeigen – die Buchung zählte dort doppelt.

In der Buchungsmaske sperren sich beide Wege gegenseitig (public/static/buchungsmaske.js): Sobald in einer Splitzeile irgendetwas steht, sind Kategorie / Gegenkonto und Klasse gesperrt; steht dort umgekehrt eine Kategorie, ein Gegenkonto oder eine Klasse, sind alle Splitzeilen gesperrt. Gesperrte Felder schickt der Browser nicht mit. Stehen beim Öffnen beide Seiten voll, gilt wie im Model die Splitbuchung. Der Knopf Splitbuchungen löschen rechts über der Splittabelle erscheint, sobald eine Splitzeile belegt ist; er leert alle Zeilen und gibt die direkten Felder wieder frei. Gespeichert wird erst mit dem Formular.

Der Knopf Splitbuchung löschen im Dialog schickt POST /buchungen/{id}/splitbuchung mit aktion=loeschen; der Controller ruft dann Buchung::splitsErsetzen($id, []), ganz gleich was in den Feldern steht. Gelöscht werden die Zeilen samt Zuordnung, nicht die Buchung – sie steht danach mit ihrem Betrag und ohne Kategorie im Kontoblatt, und die Gegenseite einer Zeile mit Gegenkonto verschwindet von jenem Konto.

Bewusst ohne Kategorie

Manche Buchungen sollen absichtlich keine Kategorie bekommen (#134): Abschreibungen etwa – als Ausgabe kategorisiert belasteten sie die Auswertung ein zweites Mal neben dem gesunkenen Wert des Kontos – oder Solarerträge, die nur rechnerisch existieren. Der Schalter Bewusst ohne Kategorie in der Buchungsmaske setzt buchungen.bewusst_ohne_kategorie (Migration 028_bewusst_ohne_kategorie.sql).

Solche Buchungen gelten nicht als offene Arbeit: Sie fehlen unter den offenen Vorgängen im Cockpit, im Prüflauf des Monatsabschlusses, im Fettdruck der Kontenübersicht (Buchung::unkategorisierteJeKonto()), bei Regeln auf Buchungen ohne Kategorie anwenden (Buchung::ohneKategorie()) und als Kandidat für das automatische Zusammenführen zu einer Umbuchung. Das Kontoblatt zeigt in der Zuordnungsspalte „bewusst ohne Kategorie“. Saldo und Auswertungen bleiben unverändert – im Buchungsbericht stehen sie wie bisher unter „Nicht kategorisiert“.

Die Markierung gilt nur, solange die Buchung nichts zugeordnet hat. Das Model (Buchung::normalisiere()) lässt sie fallen, sobald eine Kategorie, ein Gegenkonto oder Splitzeilen dazukommen – eine Zuordnung geht nie verloren. Dasselbe gilt für die Schnellzuordnung im Kontoblatt (Buchung::zuordnen()); wird dort die Zuordnung nur geleert, bleibt die Markierung stehen. In der Maske sperrt der gesetzte Schalter Kategorie / Gegenkonto und die Splitzeilen, umgekehrt lässt er sich nur setzen, solange dort nichts steht. Die Klasse bleibt frei.

Salden

Spalte Bedeutung
Online-Saldo Stand des letzten Bankabrufs (konten.saldo_online, ab Phase 5)
Aktueller Saldo Anfangssaldo plus alle Buchungen bis heute
Erwarteter Saldo zusätzlich die künftig datierten Buchungen

In die Summe der Kontostände zählen nur Konten mit in_summe = 1 – das entspricht der Σ-Spalte im FinanzManager. Durchlaufende Auslagen und Rentenversicherungen bleiben so außen vor, erscheinen aber weiter in der Liste. Der QIF-Export kennt das Kennzeichen nicht; es wird im Kontoformular gepflegt.

Beträge in der Oberfläche

Eingegeben wird immer ein positiver Betrag; die Richtung bestimmt die Auswahl Ausgabe / Einnahme. KontenController::zahl() liest das deutsche Format und entscheidet bei einem Punkt ohne Komma nach der Stellenzahl: drei Ziffern dahinter heißt Tausendertrenner (1.500), sonst Dezimaltrenner (1234.56).

Taschenrechner (Feature #126)

Jedes Zahlenfeld (<input type="text" inputmode="decimal">) trägt rechts ein Rechnersymbol. Ein Klick darauf – oder Alt+R im Feld – öffnet unter dem Feld (fehlt dort der Platz, darüber) einen Taschenrechner mit den Grundrechenarten. In der Anzeigezeile steht oben die Rechnung, darunter nach jedem Tastendruck ihr Ergebnis; der bisherige Feldwert ist die erste Zahl der Rechnung.

Taste Wirkung
0–9, , bzw. . Ziffer bzw. Dezimalkomma
+ - * / ( ) Grundrechenart, Klammern (Punkt vor Strich)
Backspace / Entf Zeichen links / rechts der Schreibmarke löschen
Pfeil links / rechts Schreibmarke bewegen
C gesamte Eingabe löschen
= Rechnung durch ihr Ergebnis ersetzen
Enter Ergebnis ins Feld übertragen, Rechner schließen
Escape schließen ohne Übernahme, Fokus zurück ins Feld

Übertragen wird mit zwei Nachkommastellen; Felder mit data-nachkommastellen (Stück und Kurs: 6, Zinssatz: 3) übernehmen genauer. Das Feld bekommt input- und change-Ereignisse, als wäre der Wert getippt – Restbeträge der Splitbuchung, die Depotberechnung und die Speichern-Knöpfe der Listen ziehen mit. data-ohne-rechner nimmt ein Feld aus.

Umgesetzt in public/static/rechner.js (im Seitenrahmen eingebunden), ohne Element um das Feld: Das Symbol ist ein Hintergrundbild aus app.css, den Klick darauf erkennt das Skript an der Position. Gerechnet wird mit einem eigenen kleinen Parser statt eval (die CSP verböte es ohnehin). Steht das Feld in einem modalen <dialog>, wandert der Rechner mit hinein; Escape schließt dann nur den Rechner, nicht den Dialog.

Bestätigungen und die Content-Security-Policy

Die CSP erlaubt keine Inline-Skripte (script-src 'self'). Rückfragen vor dem Löschen dürfen deshalb nicht als onsubmit="return confirm(…)" im HTML stehen – der Browser verwirft sie stillschweigend, und gelöscht wird ohne Nachfrage. Stattdessen:

<form method="post" action="…" data-bestaetigen="Buchung wirklich löschen?">

public/static/app.js hängt den Dialog daran. Ein E2E-Test besteht auf dem Dialog, damit das nicht unbemerkt zurückfällt.

Abgleich mit dem Bargeld-Tool

Bargeldausgaben werden in einem zweiten Programm (FSCash) erfasst; im Finanzmanager kommen sie als Kartenumsatz oder Abhebung an. Wer einer solchen Buchung eine Kategorie zuweist, soll sehen, ob sie dort schon steht.

Eingerichtet wird das unter Verwaltung → Bargeld-Tool: Adresse, Benutzerkennung, Passwort (AES-verschlüsselt wie die FinTS-PIN), Suchzeitraum in Tagen und die Auswahl der Kategorien, die überhaupt abgeglichen werden. Die Auswahl steht als bargeld_abgleich an der Kategorie selbst und nicht als Liste von Nummern in den Einstellungen – so verschwindet sie mit der Kategorie, statt als tote Nummer liegenzubleiben. Die übrigen Angaben liegen im Einstellungsbereich bargeld, der bewusst keinen Reiter im Einstellungsdialog hat; er steht dafür in Einstellung::EIGENE_SEITEN (Test 02 prüft, dass jeder Bereich erreichbar bleibt).

Geprüft wird beim Zuweisen einer Kategorie – aus der Schnellzuordnung des Kontoblatts ebenso wie aus der Buchungsmaske. Services\BargeldAbgleich::pruefe() verlangt vier Dinge: eine Kategorie an der Buchung (keine Umbuchung, keine Splitbuchung), diese Kategorie vorgemerkt, die Buchung nicht als „Ohne Bargeld-Abgleich“ markiert, eine Adresse konfiguriert. Fällt eines weg, wird der Status gelöscht – auch beim Umkategorisieren, sonst bliebe ein alter Haken an einer Buchung stehen, die niemand mehr abgleicht. Gesucht wird über Services\BargeldTool mit dem Betrag der Buchung, rückwärts ab ihrem Datum: Bargeld wird ausgegeben, bevor der Umsatz auf dem Konto ankommt, nie danach.

Das Ergebnis steht als bargeld_status, bargeld_hinweis und bargeld_geprueft_am an der Buchung. Das Kontoblatt liest es von dort und geht nicht bei jedem Blättern erneut übers Netz. In der schmalen Spalte zwischen Klasse und Betrag steht:

Symbol Status Bedeutung
● grau NULL Die Buchung wird nicht abgeglichen
○ grau NULL Die Buchung ist bewusst ohne Bargeld-Abgleich gebucht (#136)
✓ grün gefunden Im Bargeld-Tool steht eine Buchung mit demselben Betrag
✗ rot fehlt Dort steht keine
! fehler Der Abruf ist gescheitert

Das Ausrufezeichen behält ausdrücklich die Textfarbe des Farbschemas: Der Fehler liegt beim Abruf, nicht bei der Buchung – rot oder grün wäre eine Aussage über die Buchung, die niemand treffen kann. Der Tooltip nennt den Treffer bzw. die Meldung der Gegenstelle. Ist keine Adresse hinterlegt, entfällt die Spalte ganz; sie zeigte sonst in jeder Zeile denselben grauen Kreis.

Buchungen mit dem Status fehler zählen im Cockpit als offener Vorgang „Bargeld-Tool-Prüfung fehlgeschlagen“ (siehe docs/cockpit.md) – ein Klick führt in den Buchungsbericht über den gesamten Zeitraum, gefiltert auf „Bargeld-Tool: Abruf gescheitert“. Buchungen mit dem Status fehlt zählen als eigener offener Vorgang „im Bargeld-Tool nicht gefunden“ und führen in denselben Bericht mit dem Filter „nicht gefunden“. Der Bericht zeigt Konto, Kategorie bzw. Gegenkonto und Klasse; die Meldung der Gegenstelle steht im Tooltip der Spalte „Bar“. Eine eigene Liste dafür gibt es nicht mehr (#131).

Ohne Bargeld-Abgleich (#136). Sammelbuchungen und Differenzausgleiche tragen eine vorgemerkte Kategorie, stehen aber absichtlich nicht im Bargeld-Tool. Der Schalter Ohne Bargeld-Abgleich in der Buchungsmaske setzt buchungen.ohne_bargeld_abgleich; die Buchung wird dann nicht geprüft, ein früherer Status (fehlt, fehler) wird beim Speichern gelöscht, und sie erscheint nicht mehr unter den offenen Vorgängen im Cockpit. Die Schnellzuordnung lässt den Schalter stehen. Er erscheint nur, wenn eine Adresse hinterlegt ist; ohne Bargeld-Tool trägt die Maske einen gesetzten Wert unsichtbar weiter.

Ein Fehlschlag ist ein Ergebnis, kein Abbruch. Weder ein abgeschaltetes Bargeld-Tool noch ein Netzfehler darf verhindern, dass die Kategorie gespeichert wird; der Abruf bricht nach acht Sekunden ab. Null Treffer sind ausdrücklich kein Fehler – ein 404 dagegen schon, denn er kann ebenso gut heißen, dass es den Endpunkt nicht gibt.

Die Schnittstelle

Zwei lesende Aufrufe, beschrieben im Ticket „automatischer Abgleich mit Bargeld-Tool“:

GET {adresse}/api/v1/buchungen?von=2026-08-09&bis=2026-08-19&betrag=-12.50&waehrung=EUR&limit=20
GET {adresse}/api/v1/status

Beide mit HTTP Basic Auth. Die Suche antwortet mit {"anzahl": n, "buchungen": [...]}; je Buchung sind id, datum und betrag nötig, zweck, empfaenger, waehrung, kategorie und konto gehen in den Tooltip. Der Statusaufruf ist freiwillig – kennt die Gegenstelle ihn nicht (404), prüft der Verbindungstest über eine Suchabfrage.

Der Betrag geht vorzeichenbehaftet über die Leitung, so wie er im Finanzmanager steht: Ausgaben negativ. Führt das Bargeld-Tool seine Beträge positiv, muss es umrechnen.

{adresse} ist die Basisadresse ohne Schnittstellenpfad. Steht am Ende der eingetragenen Adresse trotzdem /api oder /api/v1, kürzt der Finanzmanager sie beim Speichern – die Adresse aus der Browserzeile zu kopieren ist der naheliegende Weg und führte sonst zu /api/v1/api/v1/status und einem 404 im Verbindungstest.