Eine Buchhalterin ruft an und fragt, warum sie keine einzige Rechnung findet, die auf das Konto für Dachwartung gebucht wurde. Sie hatte recht. Die Konten standen sauber in der Kontierung jeder einzelnen Rechnung, nur eben in einem Tabellenfeld.
Der Fall selbst ist speziell. Das Problem dahinter ist es nicht: Ein Dokument trägt mehrere gleichartige Positionen, die Positionen sind fachlich wichtig, und trotzdem verschwinden sie aus Suche und Ergebnisliste, sobald man sie richtig modelliert. Dieser Beitrag beschreibt das Muster, mit dem wir das lösen, und zwei Stellen, an denen wir es eingesetzt haben.
Er richtet sich an DocuWare-Partner und Administratoren.
Wann das Problem auftritt
Immer dann, wenn die Beziehung zwischen Dokument und Merkmal nicht eins zu eins ist. Ein paar Fälle, die uns begegnet sind oder naheliegen:
- Eine Rechnung wird auf mehrere Sachkonten gebucht.
- Ein Vertrag hat mehrere Vertragspartner, etwa Eheleute oder eine Erbengemeinschaft.
- Ein Lieferschein listet mehrere Artikel oder Seriennummern.
- Ein Beleg betrifft mehrere Kostenstellen.
- Ein Personaldokument gilt für mehrere Standorte.
In allen Fällen ist die Modellierung klar: Das gehört in ein Tabellenfeld, weil jede Position eigene Attribute trägt (Betrag, Anteil, Gültigkeit, Steuerschlüssel). Ein Kopffeld kann das nicht abbilden.
Und genau ab diesem Punkt beginnt der Ärger.
Was ein Tabellenfeld im Alltag nicht leistet
Zwei Dinge fehlen, und beide fallen erst auf, wenn das System schon läuft.
In der Ergebnisliste taucht der Inhalt nicht auf. Wer vierzig geprüfte Belege vor sich hat, sieht Kreditor, Betrag und Objekt, aber nicht, worauf gebucht wurde. Jede Zeile muss einzeln geöffnet werden. Vierzig Klicks für eine Information, die eine Spalte hätte liefern können.
Und in der Suche ist ein Tabellenfeld unbequem. Auswahllisten, Filter und Ergebnisspalten verhalten sich nicht so, wie die Anwender es von normalen Indexfeldern kennen. Der praktische Effekt ist derselbe wie bei einer Funktion, die es nicht gibt: Sie wird nicht benutzt.
Drei Umwege, die alle kippen
Der erste Reflex ist ein zusätzliches Kopffeld. Ein Workflow schreibt beim Erfassen den Wert aus der Tabelle hinein, fertig. Das funktioniert genau so lange, bis eine Tabelle zwei Zeilen hat. Dann kopiert der Workflow die erste, und niemand merkt es, weil das Feld ja gefüllt aussieht.
Wir hatten diesen Fall zweimal parallel. Bei den Sachkonten stand nur das Konto der ersten Zeile am Beleg. Bei §35a wurde der Betrag der ersten Zeile übernommen und netto auf brutto mit fest verdrahteten 19 Prozent hochgerechnet. Bei zwei §35a-Positionen war der Betrag zu niedrig, bei einer Position mit sieben Prozent schlicht falsch. Beides lief jahrelang unauffällig.
Der zweite Reflex ist der Volltext. Er findet die Kontonummer, wenn sie zufällig auf dem gescannten Papier steht. Nur sucht niemand nach dem, was der Handwerker geschrieben hat. Gesucht wird nach dem, was erfasst wurde.
Der dritte Weg ist ein eigener Schrank für die Positionen. Sauber modelliert, und in manchen Szenarien richtig. Der Preis: Die Anwender müssen zwei Suchen bedienen und den Zusammenhang selbst herstellen. Für einen Bericht ist das in Ordnung, für die tägliche Arbeit nicht.
Das Muster
Ein Feld hält die Wahrheit, ein Feld bedient die Suche, ein Feld bedient das Auge. Die beiden hinteren werden abgeleitet und nie von Hand gepflegt.
| Rolle | Feldtyp | Aufgabe |
|---|---|---|
| Quelle | Tabelle | Wird erfasst, geprüft, exportiert. Einzige Wahrheit. |
| Suche | Stichwort (mehrwertig) | Filter, Auswahlliste, Suchdialog |
| Anzeige | Text | Spalte in Ergebnis- und Aufgabenlisten |
Vier Regeln, ohne die es nicht trägt
Die Ableitung wird immer vollständig neu berechnet, nie fortgeschrieben. Wer inkrementell ergänzt, sammelt Werte an, die längst gelöscht sind. Eine Tabelle ohne Zeilen muss zu leeren abgeleiteten Feldern führen. Das ist die Sorte Fehler, die niemand meldet, weil sie nur beim Suchen auffällt und dort wie ein Bedienfehler aussieht.
Das Suchfeld muss exakt die Werte enthalten, die die Auswahlliste anbietet. Das haben wir erst nach dem ersten Anlauf verstanden. Wenn der Suchdialog eine Auswahlliste aus einem Stammdaten-Schrank bekommt, liefert die dort hinterlegte Anzeigeform, bei uns die volle interne Referenz. Steht im Stichwortfeld nur die Nummer, trifft die Auswahl nie, und die Suche wirkt kaputt.
Trotzdem gehört die Kurzform mit ins Feld. Ein Stichwortfeld ist mehrwertig, also kostet es nichts, beide Schreibweisen abzulegen. Wer den Kontenrahmen auswendig kennt, tippt die Nummer und will nicht durch eine Liste klicken.
Es wird an definierten Punkten geschrieben, nicht bei jeder Änderung. Bei uns sind das die Momente, in denen ein Bearbeitungsschritt abgeschlossen ist. Alles dazwischen ist Zwischenstand.
Beispiel 1: Sachkonten in der Kontierung
Eine Handwerkerrechnung über 4.200 Euro geht auf drei Konten: ein Teil auf Instandhaltung, ein Teil auf Kleinreparaturen, ein Teil wird an einen Mieter weiterbelastet. Jede Zeile hat einen eigenen Nettobetrag, einen eigenen Umsatzsteuerschlüssel, einen Buchungstext und im Weiterbelastungsfall einen Vertragsbezug.
Die Quelle ist ein Tabellenfeld KONTIERUNG mit unter anderem diesen Spalten:
| Spalte | Zweck |
|---|---|
KONTI_SACHKONTO | Interne Referenz des Kontos, Format Nummer - Bezeichnung |
KONTI_NETTOBETRAG, KONTI_BRUTTOBETRAG | Betrag der Zeile |
KONTI_UMSATZSTEUERSCHLUSSEL | Steuerschlüssel, je Zeile unterschiedlich möglich |
KONTI_ZUSATZ_BUCHUNGSTEXT | Was später im Buchungsstapel steht |
KONTI_VERTRAG__INTERNE_REFERE | Vertragsbezug bei Weiterbelastung |
KONTI_SS35A__ART_ | §35a-Kennzeichnung der Position |
Daraus werden zwei Felder abgeleitet.
SACHKONTO__ZUSAMMENFASSUNG_ ist ein Textfeld und enthält die internen Referenzen aller gebuchten Konten, dedupliziert, in der Reihenfolge des ersten Auftretens:
0004510 - Wartung Flachdach, 0004200 - Kleinreparaturen
Das steht als Spalte in der Aufgabenliste der Buchhaltung. Seitdem werden dort spürbar weniger Belege geöffnet, nur um nachzusehen.
SACHKONTO__SUCHE_ ist ein Stichwortfeld und trägt je Konto zwei Einträge:
0004510 - Wartung Flachdach
0004510
0004200 - Kleinreparaturen
0004200
Die volle Referenz für die Auswahlliste, die nackte Nummer für alle, die tippen. Führende Nullen bleiben erhalten, weil der Kontenrahmen sie führt. Eine Rechnung mit vier Zeilen auf zwei Konten erzeugt vier Stichwörter, nicht acht.
Denselben Aufbau nutzen wir für §35a: SS35A__ZUSAMMENFASSUNG_ als Textfeld, SS35A__SUCHE_ als Stichwortfeld, Basis sind die §35a-Spalten derselben Tabelle. Weil jetzt jede Zeile beiträgt statt nur der ersten, stimmt der Gesamtbetrag am Beleg zum ersten Mal. Der Umsatzsteuerfaktor wird pro Zeile aus deren eigenem Netto und Brutto abgeleitet, die festen 19 Prozent sind ersatzlos weg.
Beispiel 2: mehrere Vertragspartner an einem Vertrag
Der zweite Fall liegt in einem anderen Archiv bei einem anderen Kunden und sieht auf den ersten Blick völlig anders aus. Es ist dasselbe Muster.
Ein Vertrag hat nicht zwingend einen Partner. Eheleute unterschreiben zu zweit, eine Erbengemeinschaft tritt zu dritt auf, bei einer GbR stehen mehrere Gesellschafter im Vertrag. Die Sachbearbeitung sucht aber immer nach einer einzelnen Person, weil genau die am Telefon ist.
Der erste Aufbau hatte ein Kopffeld für den Vertragspartner. Es enthielt den Erstgenannten. Wer als Zweiter im Vertrag stand, war über die Suche unauffindbar, obwohl er im Dokument klar benannt war. Das ist derselbe Fehler wie beim Sachkonto, nur mit unangenehmeren Folgen, weil hier jemand am Telefon wartet.
Der Umbau folgt Rolle für Rolle dem Muster:
- Quelle: ein Tabellenfeld mit einer Zeile je Vertragspartner. Je Zeile die Referenz auf den Kontakt, die Rolle im Vertrag und der Zeitraum, in dem die Person Partei ist. Der Zeitraum ist hier wichtiger als beim Sachkonto, weil Partner wechseln, ohne dass der Vertrag endet.
- Suche: ein Stichwortfeld mit je Partner der vollen internen Referenz und zusätzlich der reinen Partnernummer. Damit findet die Suche nach einem Namen jeden Vertrag, in dem die Person in irgendeiner Zeile steht.
- Anzeige: ein Textfeld mit allen Partnern, kommagetrennt, in der Reihenfolge des Vertrags. Steht als Spalte in der Trefferliste, sodass man Vertragsparteien vergleichen kann, ohne Dokumente zu öffnen.
Der interessante Unterschied zum ersten Beispiel: Hier muss man entscheiden, ob ausgeschiedene Partner im Suchfeld bleiben. Wir lassen sie drin, weil Verträge rückwirkend gesucht werden, und trennen den Zeitbezug über die Tabellenspalte. Wer stattdessen nur aktive Partner sucht, filtert zusätzlich über das Vertragsende. Das ist eine fachliche Entscheidung, keine technische.
Wann sich das Muster lohnt
Nicht immer. Eine kurze Entscheidungshilfe:
Ja, wenn nach den Positionen gesucht wird, wenn sie in einer Liste sichtbar sein sollen, und wenn die Anzahl je Dokument klein bleibt (Größenordnung: einstellig bis niedrig zweistellig).
Nein, wenn die Positionen nur für den Export existieren und nie jemand danach sucht. Dann bleibt die Tabelle allein richtig, und zwei zusätzliche Felder sind Ballast.
Eher ein eigener Schrank, wenn die Positionen selbst Dokumente oder eigenständige Vorgänge sind, wenn sie eine eigene Lebensdauer haben, oder wenn ihre Anzahl in die Hunderte geht.
Ableiten im Workflow oder im Code
Bei einem einzigen Mandanten reicht ein AutoIndex. Die Tabelle ändert sich, eine Regel zieht sie zusammen, Feierabend.
Sobald mehrere Mandanten dazukommen, wird aus einer Regel eine Regelfamilie. Jeder neue Kunde bedeutet, dieselbe Regel mit anderen Schrank-IDs und Feldlisten von Hand nachzubauen. Eine Schema-Änderung bedeutet, sie überall gleichzeitig nachzuziehen. Und wenn sie irgendwo nicht mehr greift, lautet das Symptom „Felder driften auseinander”, und die Fehlersuche findet in fremden Cloud-Instanzen statt, eine Regel nach der anderen.
Wir haben die Ableitung deshalb in einen eigenen Dienst gezogen. Der Vorteil ist nicht die Eleganz, sondern dass ein Fehler jetzt einen Stacktrace hat.
Das ist kein Argument gegen AutoIndexe. Es ist ein Argument dafür, den Punkt zu kennen, an dem sie teurer werden als Code.
Das Muster in vier Sätzen
- ✓ Die Tabelle bleibt die einzige Quelle. Abgeleitete Felder werden nie von Hand gepflegt.
- ✓ Ein Stichwortfeld für die Suche, ein Textfeld für die Ergebnisliste. Nicht beides in einem Feld.
- ✓ Das Stichwortfeld muss die Werte enthalten, die die Auswahlliste anbietet, sonst trifft die Auswahl nie.
- ✓ Immer vollständig neu berechnen, damit geleerte Tabellen auch geleerte Felder ergeben.
Wo es an Grenzen stößt
Was ich noch nicht gelöst habe
Eine Sache stört mich bis heute. Das alte Kopffeld aus dem ersten Anlauf steht immer noch im Schrank, gefüllt mit dem Wert der ersten Zeile, und niemand traut sich, es zu entfernen, weil unklar ist, welche Ergebnisliste oder welcher alte Suchdialog es noch anzeigt. Es ist genau die Art von Feld, die in fünf Jahren jemand für eine Wahrheit hält.
Wer das eleganter gelöst hat, insbesondere die Frage, wie man ein totes Indexfeld in einer gewachsenen Konfiguration sicher stilllegt: Ich höre gerne zu. Wir sind unter kontakt@liegenwerk.digital erreichbar, und ich antworte auf fachliche Mails deutlich schneller als auf alle anderen.