Zwei Tickets kamen in derselben Woche herein.
Das erste: “Last Quarter zeigt mir 15 Monate Dokumente statt 3.”
Das zweite, zwei Tage später: “Der Filter für ‘heute’ lässt eine Rechnung weg, die ich heute Morgen erstellt habe. Der Filter für ‘gestern’ zeigt sie.”
Beide Bugs steckten in Code, den ich gerade ausgeliefert hatte. Keiner war meine Schuld.
Das Plugin
Das Plugin ist ein Document-Overview für einen deutschen Shopware-Händler. Es listet jede Rechnung, jeden Lieferschein und jede Gutschrift quer über Tausende von Bestellungen, mit Leserechten pro Rolle. Das Buchhaltungsteam öffnet es täglich, um das letzte Quartal, diesen Monat, diese Woche abzugleichen. Datumsfilter sind der Weg, mit dem sie überhaupt etwas finden.
Bug 1: das Q1-Gespenst
Ticket eins konnte ich nicht reproduzieren.
Die Buchhaltungsleiterin klickte auf “Last Quarter” und sah 15 Monate Zeilen. Ich klickte auf “Last Quarter” und sah 3. Wir liefen auf demselben Code.
Was sich unterschied, war das Datum. Bei uns beiden war April, aber der Bug feuerte nur, wenn “heute” in den Januar, Februar oder März fiel. Ich stellte meine Dev-Uhr auf den 15. Februar zurück. Beim ersten Versuch tauchte der Bug auf.
Die Ursache war ein einzelner Identifier in Shopwares eigenem Code. Die Funktion, die Anfang und Ende des vorherigen Quartals berechnet, las das Jahr aus zwei verschiedenen Quellen. Der Anfang kam aus dem Quartal. Das Ende kam aus dem heutigen Datum.
// Vorher: endDate verwendet das heutige Jahr und springt in Q1 nach vorn
const endDate = new Date(date.getFullYear(), startDate.getMonth() + 3, 0, 23, 59, 59);
// Nachher: endDate bleibt am Startjahr des Quartals verankert
const endDate = new Date(startDate.getFullYear(), startDate.getMonth() + 3, 0, 23, 59, 59);
Von April bis Dezember lieferten beide Quellen dasselbe Jahr. Die Mathematik stimmte mit sich selbst überein. In Q1 lieferten sie unterschiedliche Jahre. Das vorherige Quartal startete im Oktober des Vorjahres, aber das Enddatum sprang auf Dezember des aktuellen Jahres. 15 Monate über zwei Kalender hinweg.
Ein Ein-Zeichen-Fix verankerte das Ende am Startjahr. Ich schickte ihn upstream.
Bug 2: die Zeitzonen-Falle
Ticket zwei konnte ich auch nicht reproduzieren.
Die Buchhaltungsleiterin hatte ihre Profilzeitzone auf Europe/Berlin. Meine war UTC. Staging war UTC. Die gesamte Entwicklungsumgebung war UTC. Das Buchhaltungsteam in Deutschland arbeitete in Berliner Zeit.
Wenn sie auf “heute” klickte, sollte Shopware die Datenbank nach heute in Berlin fragen. Es fragte nach heute in UTC.
Berlin läuft UTC voraus. Die zwei Tage überlappen sich, decken sich aber nicht. Zeilen vom gestrigen Berliner Tag rutschten ins Ergebnis. Zeilen vom heutigen Morgen Berliner Zeit landeten in Morgen und fielen weg.
Ein Teammitglied in Los Angeles, 8 Stunden zurück, hätte bei jedem Klick auf “heute” den grössten Teil eines Arbeitstages an Rechnungen verloren.
Der Fix fragt die Datenbank nach dem richtigen Kalendertag in der Zeitzone des Benutzers. Derselbe Fix musste auf die Timeframe-Presets ausgeweitet werden. “Last month” für einen Berliner Benutzer heisst der Kalendermonat in Berlin, nicht in UTC. Dasselbe gilt für “last quarter” und “last year.” Tage, die eine Sommerzeit-Umstellung kreuzen, brauchten eine eigene Behandlung, damit ein 23-Stunden-Tag und ein 25-Stunden-Tag nicht als 24 behandelt werden.
Im selben Code steckte ein zweites Problem: jede Änderung an einem Filterfeld feuerte die Datenbank-Query doppelt. Der Fix hat die Doppelung entfernt.
Warum das grösser ist als zwei Tickets
Diese Bugs liegen in Shopware selbst. Sie laufen in jedem Shopware-6-Shop auf der Welt mit.
Jeder Shop mit Benutzern in Nicht-UTC-Zeitzonen, also fast jeder Shop, hat still und leise verzerrte Zeilen zurückbekommen, wenn diese Benutzer nach Datum gefiltert haben. Den meisten ist es nie aufgefallen. Das Fenster fehlender oder überzähliger Zeilen ist klein genug, um wie “die habe ich nur noch nicht gesehen” auszusehen statt wie ein Fehler.
Jeder Shop, der zwischen Januar und März auf “Last Quarter” geklickt hat, hat seit Jahren 15 Monate Zeilen zurückbekommen. Das Buchhaltungsteam, mit dem ich arbeite, hat es bemerkt, weil sie in diesen Filtern leben.
Beide Bugs waren stumm, bis jemand zufällig den Filter unter genau den richtigen Bedingungen an genau dem richtigen Tag benutzt hat. So überleben Core-Bugs in weit verbreiteter Software. Sie brauchen eine bestimmte Person, die etwas Bestimmtes zu einem bestimmten Zeitpunkt tut, und die Bedingungen passen nur in einigen Shops, in einigen Quartalen, in einigen Zeitzonen zusammen.
Was ich dagegen unternommen habe
Zwei Spuren parallel.
Spur eins: das Plugin des Kunden patchen, sodass das Buchhaltungsteam am nächsten Tag einen funktionierenden Filter hatte. Das Plugin erweitert Shopwares Datumsfilter ohnehin schon für das Berechtigungs-Rendering. Einen lokalen Override der fehlerhaften Methoden obendrauf zu setzen, war eine Frage von ein paar Stunden. Der Fix ging über Nacht raus. Das Buchhaltungsteam läuft seither auf einem korrekten Filter.
Spur zwei: dieselben Fixes zurück an Shopware geben, damit jeder andere Shop davon profitiert. Drei Pull Requests gingen in der folgenden Woche raus. Zwei wurden am 27. April gemerged. Der dritte ist noch offen und wird gerade reviewt.
Alle drei waren für das 6.7.10.0-Release am 6. Mai getaggt. Dann ist 6.7.10.0 ohne einen davon ausgeliefert worden.
Ein Milestone-Tag in einem Open-Source-Projekt ist ein Plan. Maintainer taggen, was sie aufnehmen wollen, und schneiden das Release dann auf das, was tatsächlich reviewt und stabil ist. Gemergter Code kann ein bis zwei Patch-Releases in trunk liegen, bevor er wirklich ausgeliefert wird.
Die praktische Lesart für Shop-Betreiber: wenn ein Dienstleister oder Entwickler sagt “ein Fix geht upstream”, erreicht dieser Fix Ihren Shop unter Umständen erst in Wochen oder Monaten. Wenn Sie nicht warten können, ist die Antwort ein lokaler Override über dem fehlerhaften Parent. Genau das haben wir gemacht. Der Override wird an dem Tag entfernt, an dem die Upstream-Version endlich landet.
Wo das Sie hinführt
Wenn Ihr Team von einem Dienstleister immer wieder “bei uns funktioniert es” hört und der Bug Ihre Buchhaltungsleitung oder Ihren Support trotzdem trifft, kann die Ursache in Code liegen, den niemand in Ihrem Projekt geschrieben hat. Die meisten Shops leben mit solchen Bugs, weil niemand Zeit hat, ihnen nach Hause zu folgen. Irgendwer muss das tun.
Wenn Sie einen Shopware-6-Shop betreiben und das die Art von Arbeit ist, die Sie ordentlich erledigt haben möchten, nehmen Sie Kontakt auf. Mehr Projekte, aus denen diese Arbeit kommt, finden Sie in den Case Studies.
PRs auf GitHub: #16380, #16439. Contributor-Profil unter github.com/zaifastafa.
Häufig gestellte Fragen
Woher weiss ich, ob mein Shopware-Shop von diesen Bugs betroffen war? Zwei schnelle Checks. Erstens: Hat einer Ihrer Admin-Benutzer im Profil eine andere Zeitzone als UTC eingestellt? Wenn ja, haben deren Datumsfilter still und leise den falschen Tag zurückgegeben, je nach Zone um eine Stunde bis zu fast einem ganzen Arbeitstag. Zweitens: Hat jemand zwischen Januar und März auf “Last Quarter” geklickt? Wenn ja, kamen 15 Monate Zeilen statt 3. Den meisten Shops ist es nie aufgefallen, weil der Filter trotzdem etwas angezeigt hat.
Soll ich auf das nächste Shopware-Release warten oder einen lokalen Fix bezahlen? Es kommt darauf an, wie sehr Ihr Team auf Datumsfilter angewiesen ist. Ist Datumsfilterung Teil eines täglichen Workflows wie der Buchhaltungs-Reconciliation, lohnt sich ein lokaler Override. Der Override patcht die fehlerhaften Methoden in Ihrem Plugin, kostet ein bis zwei Tage Entwicklungszeit und wird entfernt, sobald der Upstream-Fix landet. Werden Filter nur gelegentlich genutzt, ist es in Ordnung, ein bis zwei Patch-Releases zu warten. Verlassen Sie sich nur nicht darauf, dass ein Milestone-Tag das nächste Release bedeutet.
Waren diese PRs im Shopware-6.7.10.0-Release vom 6. Mai 2026? Nein. Zwei wurden am 27. April in trunk gemerged und für 6.7.10.0 getaggt. Keiner ist ausgeliefert worden. Der dritte ist noch offen. Ein Milestone-Tag ist das, was die Maintainer ausliefern wollen; das Release enthält am Ende, was rechtzeitig fertig war. Gemergter Code kann ein bis zwei Patch-Releases in trunk liegen, bevor er ausgeliefert wird.
Bricht mein lokaler Override etwas, wenn der Upstream-Fix landet? Nicht, wenn der Override die fehlerhafte Methode beim Namen anspricht. Ein sauberer Override liegt über Shopwares Version und gewinnt, wenn beide laufen. Sobald das Patch-Release ankommt, wird der Override überflüssig und mit einem einzigen Löschvorgang entfernt. Es gibt keine Migration, keine Schemaänderung, kein Datenrisiko.
Wir haben seltsames Filter-Verhalten gesehen. Ist das immer ein Core-Bug? Nein, und genau das ist die Falle. Die meisten Filter-Auffälligkeiten sind Plugin-Code, individueller Code oder eine fehlkonfigurierte Berechtigung. Ein Core-Bug sieht anders aus: Er reproduziert sich nur unter bestimmten Umgebungsbedingungen wie einem Datum oder einer Zeitzone, und dieselben Bedingungen reproduzieren ihn auf einer leeren Shopware-Installation ohne Plugins. Das ist der Test, den man laufen lassen sollte, bevor man jemanden dafür bezahlt, dem Bug nachzugehen.