Der Kunde kam mit einer vermeintlich einfachen Anfrage zu uns: Er hatte einen bestehenden WooCommerce-Shop, der relativ gut lief, ein paar kleinere Anpassungen brauchte und anschliessend betreut werden sollte. Bestellungen kamen herein, grössere Probleme gab es nicht, und niemand wollte ein neues System. Wir prüften den Code, fanden Verbesserungen bei der Performance und einige Sicherheitsprobleme, behoben das Nötige und erwarteten eines unserer einfachsten Supportprojekte.
Wer schon mit WooCommerce gearbeitet hat, weiss: Sicherheitsprobleme sind sein zweiter Vorname, wenn man es nicht richtig zubereitet, und die blosse Installation von Wordfence wird sie nicht beseitigen. Dieser Teil überraschte uns nicht. Überraschend war, wie gründlich wir den eigentlichen Auftrag hinter der Anfrage missverstanden hatten.
Das ist die Geschichte unseres Fuck-ups: Wir glaubten zu verstehen, was der Kunde brauchte, reparierten viel zu lange etwas, das hätte ersetzt werden müssen, und trafen schliesslich die unangenehme Entscheidung, einen grossen Teil der bisherigen Logik wegzuwerfen und einen anderen Ablauf zu bauen. Wir kamen zu einer besseren Lösung, hätten aber beiden Seiten Zeit und Geld sparen können, wenn wir den Bedarf am Anfang richtig verstanden hätten.
«Das System funktioniert gut. Bitte nicht ändern.»
Der Kunde hatte seine Grenzen deutlich beschrieben. Es gab keine grossen neuen Anforderungen, der Shop funktionierte, und die Produkte wurden aus dem Kassensystem, dem POS, importiert. Er verwaltete sie anschliessend selbst in WooCommerce, war mit diesem Ablauf zufrieden und wollte keinen anderen vorgeschlagen bekommen.
Das klang vernünftig, und wir respektierten es. Was wir nicht untersuchten, war die Bedeutung von «funktioniert gut» im Alltag: Wie viele Produkte brauchten manuelle Aufmerksamkeit, welche Informationen kamen aus der Kasse, was musste im Shop korrigiert werden, und was passierte, wenn die beiden Systeme unterschiedlicher Meinung waren? Wir übernahmen die Beschreibung des Kunden, bevor wir ausreichend gesehen hatten, wie der Prozess tatsächlich ablief.
Der erste ernste Hinweis kam per Telefon: Einige Produkte waren aus dem Katalog verschwunden. Wir gingen Logs und Datenbankeinträge durch, fanden überschriebene Produktdaten und einen beschädigten Katalog und stellten fest, dass mehr als zehn Prozent der Artikel betroffen waren. Der naheliegende Verdächtige war das Importskript, das Preise, Bestände und neue Produkte aus der Kasse nach WooCommerce brachte.
Das Skript war lange vor uns da gewesen. Wir hatten seinen Code bei der Projektübernahme geprüft, eine relativ überschaubare Logik vorgefunden und danach kontrolliert, dass die Importe ohne Fehler liefen. Dahinter standen Jahre eines scheinbar erfolgreichen Betriebs, weshalb «Was funktioniert, fasst man nicht an» wie eine vernünftige Regel wirkte. Wir sollten noch herausfinden, wie viel Arbeit das Wort «scheinbar» in diesem Satz für uns übernahm.
Der Import war erfolgreich, der Katalog nicht
Zunächst kümmerten wir uns um den unmittelbaren Schaden: das letzte Backup entpacken, Produktdaten extrahieren und mit der bestehenden Datenbank zusammenführen. Wir stellten die Produkte wieder her und stoppten das Skript für die Untersuchung. Der Kunde informierte uns über Preisänderungen und grössere Bestandsänderungen, die wir manuell einpflegten, damit die Abweichungen beherrschbar blieben.
Dann sahen wir genauer hin, als es die Frage «Läuft das Skript?» verlangt hätte. Was bekam es eigentlich geliefert? Die stündliche CSV-Datei hatte keine Kopfzeile; einige repräsentative Zeilen sahen in einer Tabellenansicht so aus:

Die Spalten enthielten Titel, EAN, Preis, Aktionspreis, Bestand, Marke, zwei leere Spalten, eine Lieferantenartikelnummer, ein lieferantenbezogenes Feld und eine weitere leere Spalte. Eine eigene Spalte für Grösse oder Variante gab es nicht; die Grösse stand nur im Titel. Titel wurden nach 40 Zeichen abgeschnitten, sodass «AWAKENING SE» für «AWAKENING Serum» stand. Manche EANs waren Platzhalter wie 0000000000000, und manche Zeilen hatten keinen Titel und einen Preis von CHF 0.05.
Bei bestehenden Produkten glichen wir die EAN ab und aktualisierten Preis und Bestand. Ein neuer Artikel wurde als Entwurf in WooCommerce angelegt, wo der Kunde den Titel korrigierte, Beschreibungen, Bilder und Marke ergänzte und ihn veröffentlichte. Wenn ein Parfum mehrere Grössen hatte, lieferte die Kasse separate Zeilen; das übergeordnete Produkt und die Zuordnung der Varianten legte der Kunde manuell an.
Man sieht, warum wir das zunächst für handhabbar hielten. Der Titel war abgeschnitten, wurde aber ohnehin von Hand korrigiert; die Kasse lieferte die Verkaufsdaten, der Kunde die Präsentation. Ungeklärt blieb, wer die Kassendaten pflegte, wie dort gearbeitet wurde und ob eine EAN über die Zeit beim gleichen Artikel blieb. In unseren Köpfen hatten wir einen Kassenexport stillschweigend zu einer zuverlässigen Produktdatenverwaltung befördert.
Die fehlenden Produkte waren nicht einfach verschwunden
Unsere Integration behandelte die EAN als stabile Identität, während Kasse und Shop unterschiedliche Vorstellungen davon hatten, wann ein Produkt noch dasselbe Produkt war. Ein Artikel konnte etwa bei einer Neuauflage oder einem erneuten Wareneingang mit einer anderen EAN in der Kasse erscheinen, obwohl der Webshop ihn weiterhin als denselben Duft in derselben Grösse behandeln musste.

Das Shopprodukt blieb mit der alten EAN verknüpft, zeigte Bestand null und erhielt keine Updates mehr, während der Import einen vermeintlich neuen Artikel anlegen konnte. Nichts stürzte ab, niemand bekam eine Fehlermeldung, und das alte Shopprodukt war noch vorhanden. Nur die Verbindung war unbemerkt kaputtgegangen.
Das passierte bereits vor unserer Übernahme. Über Monate und Jahre sammelten sich entkoppelte Produkte an, und wir hatten weder nach diesem Fehlerbild gesucht noch sein Ausmass erkannt. Als wir später Bestandsfehler beheben wollten, führte unsere Änderung dazu, dass diese Produkte offline gingen. Das Grundproblem war älter als unsere Beteiligung; dass wir den Schaden auslösten und das Problem bei der Übernahme übersahen, lag aber in unserer Verantwortung.
Danach trafen wir die nächste falsche Entscheidung: Wir wollten die Abweichungen reparieren und den bisherigen Ablauf fortsetzen. Wir suchten EAN-Änderungen in Logs, gruben alte Backups aus, stellten Produkte wieder her und ordneten ihnen die richtigen Verknüpfungen zu. Wo das nicht automatisch ging, bekam der Kunde Listen zur manuellen Bearbeitung. Wir behandelten jede Entdeckung weiterhin wie einen weiteren Defekt in einem grundsätzlich brauchbaren Aufbau.
Hinter jeder Antwort ging eine neue Schublade auf
Der Kunde begann, die verbleibenden Produkte zu korrigieren, und kam mit dem nächsten Problem zurück: Ganze Marken wurden nicht angezeigt. Ein Blick in die Markenspalte machte diesen Teil des Rätsels etwas weniger rätselhaft.

«ESCENTR» und «NASOMAT» waren abgeschnittene Namen, «M2 Beauty» und «M2 Beauté» bezeichneten dieselbe Marke, ebenso «Sud Pacifique» und «Comptoir Sud Pacifique». Im hier beschriebenen Datenbestand hatten 1'269 von 2'903 Artikeln überhaupt keine Marke; die Zuordnung berücksichtigt inzwischen 167 verschiedene Schreibweisen. Mit jeder Korrektur kam eine weitere Annahme zum Vorschein, die wir über die Daten gemacht hatten.
Im zweiten Monat war der Kunde verärgert, und wir spürten, wie sein Vertrauen schwand. Unsere Entwickler arbeiteten mit vollem Einsatz, konnten aber die entscheidenden Fragen immer noch nicht zuverlässig beantworten: Welche Produkte sollten online sein, welche Marken waren unvollständig, welche Änderungen waren beabsichtigt, und was erwartete der Kunde als Nächstes?
Als wir die tatsächliche Arbeit des Kunden durchgingen, wurde noch etwas offensichtlich. Dieser angeblich zuverlässige Ablauf setzte jeden Tag eine enorme Menge menschlicher Arbeit voraus: Produkte öffnen, Titel korrigieren, Marken zuweisen, Bilder suchen, Grössen anordnen, neue Artikel prüfen und entscheiden, was online gehen konnte. Der Kunde hatte sich so sehr daran gewöhnt, die Schwächen des Systems auszugleichen, dass diese Arbeit wie ein normaler Bestandteil des Shopbetriebs wirkte.
Stellt euch vor, ihr erledigt diese ganze lästige Arbeit und entdeckt dann, dass ein Import sie überschrieben, entkoppelt oder verschwinden lassen hat. Ihr wisst nicht, ob die Entwickler den Stand aus einem Backup zurückholen können oder ob ihr alles noch einmal von Hand machen müsst. Die Frustration betraf verlorene Arbeitsstunden und das Vertrauen in den Katalog, nicht bloss einen merkwürdigen CSV-Parser.
An diesem Punkt änderte unsere Untersuchung endlich die Richtung. Der Kunde hatte uns beauftragt, die bestehende Verbindung zwischen Kasse und WooCommerce zu betreuen. Was er brauchte, war ein Ende seiner Rolle als die Person, die diese Verbindung mit Handarbeit zusammenhielt. Diesen Bedarf hatte er bei der Anfrage selbst nicht erkannt, und wir hatten ihn bei der Annahme des Auftrags ebenfalls übersehen.
Die schwierige Entscheidung war, die alte Verbindung nicht weiter zu flicken
Wir riefen den Kunden an und erklärten, dass eine weitere Runde Patches den bestehenden Aufbau nicht zuverlässig machen würde. Die Produktdaten waren zu stark verstreut, ihre Identität hing von Werten ab, die sich ausserhalb des Shops änderten, und die manuelle Redaktionsarbeit hatte keinen sicheren zentralen Ort. Das war schon lange so gewesen; unser Fehler bestand darin, es bei der Übernahme nicht zu entdecken und dann zwei Monate Symptome zu behandeln, statt den Aufbau zu ändern.
Schwierig war, jetzt ein neues System vorzuschlagen, nachdem wir zugesagt hatten, den bisherigen Ablauf zu erhalten, während der Kunde bereits frustriert war und an unserer Fähigkeit zweifelte, den Schaden zu beheben. Wir mussten eingestehen, dass ein Teil unserer bisherigen Arbeit nicht zu einer dauerhaften Lösung führte, einen erheblichen Teil der Importlogik entfernen und den Kunden bitten, nochmals Aufwand in die Bereinigung des Katalogs zu stecken. Das vertraute System weiter zu flicken fühlte sich kurzfristig einfacher an, aber wir glaubten nicht mehr daran, dass es diese Aufgabe zuverlässig erfüllen konnte.
Unser Vorschlag war eine eigenständige Plattform für Produktinformationen zwischen Kasse und WooCommerce. Titel, Beschreibungen, Bilder, Marken, Kategorien und Grössen sollten einen zentralen Ort bekommen; die Kasse würde Preis und Bestand liefern, und der Shop die geprüften Produktinformationen anzeigen, die er erhielt. Die EAN würde zu einer ausdrücklichen Verknüpfung mit einer Produktgrösse, statt die Identität des Produkts selbst zu sein.
Der Anteil des Kunden blieb echte Arbeit: die Marken durchgehen, über Online-Produkte entscheiden und Daten bereinigen. Der Unterschied bestand darin, dass diese Arbeit nun in einem System gespeichert wurde, das sie verwaltete, ihre Geschichte aufbewahrte und zeigte, wenn ein anderes System abwich. Wir änderten den Aufbau, der seine Handarbeit so verletzlich gemacht hatte.
Wir nannten das Werkzeug Flakon. Dass ein Parfumgeschäft seine Produktdaten in einen Flakon füllt, amüsierte uns mehr, als es ein vernünftiger Softwarename wahrscheinlich sollte. Kurzzeitig hatten wir «PMS» als Arbeitstitel verwendet, erinnerten uns dann aber an die anderen Bedeutungen der Abkürzung; eine Parfumflasche erschien uns als die sicherere Wahl.
Bei der Marke anfangen, weil der Kunde dort anfängt
Eine unserer nützlichsten Erkenntnisse hatte nichts mit dem Importalgorithmus zu tun: Der Kunde verbrachte den grössten Teil seiner Bearbeitungszeit innerhalb einer Marke. Eine endlose Liste aller Produkte war eine korrekte Datenbankansicht und ein schlechter Einstieg in seinen Arbeitstag. Deshalb wurden Marken zum wichtigsten Zugang in Flakon.
Innerhalb einer Marke sieht der Kunde die bereits verfügbaren Produkte, die Offline-Produkte und Entwürfe, fehlende Informationen sowie neue Artikel aus der Kasse. Er muss sich dieses Bild nicht mehr aus verschiedenen Listen zusammensuchen oder im Kopf behalten, was er zuletzt korrigiert hat.

Mehrfachaktionen ergaben sich aus derselben Beobachtung. «Diese ganze Marke offline nehmen, bis auf diese vier Produkte» ist eine redaktionelle Entscheidung, aus der die alte Oberfläche Dutzende Einzelaktionen machte. Wir ergänzten Filter und Auswahlmöglichkeiten, damit der Kunde die Online-Produkte auswählen, die Ausnahmen abwählen, den Rest offline nehmen und gemeinsam an den Shop senden konnte.

Eine Vorschau musste zeigen, wie der Shop aussehen wird
Eine weitere Erkenntnis war, dass ein technischer Feldvergleich nicht ausreichte, um über die Fertigstellung eines Produkts zu entscheiden. Der Kunde musste das Ergebnis im tatsächlichen Shoplayout sehen: Titel, Bilder, Grössen und die Zusammenhänge zwischen Produkten. Wir machten die Übertragung an den Testshop zu einem Bestandteil der Prüfung, damit er Änderungen dort ansehen konnte, bevor sie live gingen.
Auch «Speichern» und «Senden» blieben getrennte Schritte. Wer seine Arbeit in Flakon speichert, soll damit nicht überraschend die Kundensicht verändern, und die Oberfläche muss zeigen, welche Version jeder Shop gerade enthält. Bei einer Übertragung protokolliert Flakon die beabsichtigten Änderungen, prüft, ob sich der Shop seit dieser Entscheidung verändert hat, schreibt die betreffenden Felder und liest sie anschliessend zur Kontrolle wieder aus.
Deshalb verboten wir auch die Bearbeitung in WordPress nicht einfach. Der Kunde brauchte weiterhin Handlungsspielraum. So zu tun, als würde jede Änderung immer aus unserem neuen Editor kommen, hätte denselben blinden Fleck wiederhergestellt. Flakon erkennt Änderungen direkt im Shop und lässt den Bearbeiter entscheiden, ob er sie übernehmen oder ersetzen möchte, statt einen Streit stillschweigend zu gewinnen, von dem der Kunde gar nichts wusste.
«Was hat sich geändert?» wurde Teil des Produkts
Neue Kassendaten sollten den Kunden nicht mit der Frage zurücklassen, was über Nacht passiert war. Neue Artikel kommen in einen Eingang, Preis- und Bestandsänderungen haben einen sichtbaren Verlauf, und Änderungen an Titeln oder Marken werden zur Prüfung angezeigt, statt den Katalog unbemerkt umzuschreiben. Wenn eine EAN wechselt, kann der Bearbeiter die bestehende Grösse mit dem neuen Kassenartikel verbinden, statt das Produkt neu aufzubauen.

Die Gestaltung der Oberfläche war dabei genauso wichtig wie das Protokoll. Wir wollten, dass der Kunde ohne ständig geöffnetes Handbuch arbeiten kann. Deshalb beschreiben Schaltflächen die Aktion, Erklärungen erscheinen dort, wo Entscheidungen getroffen werden, und der nächste Schritt ist am Produkt sichtbar. «Mit anderem Artikel verknüpfen» sagt einem Bearbeiter mehr als eine technische Beschreibung des Identitätsabgleichs; «Online», «Offline» und «Entwurf» sind leichter zu verstehen als mehrere überlappende Sichtbarkeitsbegriffe. Hilfe ist vorhanden, aber der alltägliche Ablauf soll sich selbst erklären.

Wir lernten ausserdem, dass die Aufzeichnung verdächtiger Vorgänge nicht ausreicht, wenn niemand sie bemerkt. Im Testbetrieb entdeckten wir, dass der Kunde ungefähr 500 Bilder gelöscht hatte, um Speicherplatz zu sparen, obwohl kein Platzproblem bestand, und fertige Produkte als Entwurf abgelegt hatte, um sie aus dem Verkauf zu nehmen. Aus seiner Sicht lösten diese Handlungen ein unmittelbares Problem; im alten Aufbau hatten wir davon wenig gesehen. Flakon bekam einen gut sichtbaren Bereich für ungewöhnliche Vorgänge, etwa gehäufte Bildentfernungen, gelöschte Produkte und unerwartet grosse Änderungen. Es bewahrt zudem eigene Kopien importierter Bilder auf, damit auf den Nachweis einer Löschung auch eine Wiederherstellung folgen kann.
Die Technologien folgten der Aufgabe
Flakon ist eine separate Laravel-Anwendung auf PHP 8.4 mit einer eigenen MariaDB-Datenbank und einer Vue-3-Oberfläche, die über Inertia angebunden ist und TypeScript sowie Tailwind CSS verwendet. PHP passte zum bestehenden Managed Hosting, während Laravel uns die Anwendungsstruktur, Validierung, Zeitsteuerung und Warteschlangen für Importe, Verlauf und Synchronisation gab. Mit Vue und Inertia konnten wir den interaktiven Editor innerhalb derselben Anwendung bauen, ohne zusätzlich eine separate Frontend-API betreiben zu müssen.
Wichtiger als die Framework-Namen war die Grenze zu WordPress. Flakon liest Shopdaten über einen Datenbankzugang mit ausschliesslichen Leserechten, um den Katalog effizient vergleichen zu können, schreibt aber über die WooCommerce REST API. Direkte SQL-Schreibzugriffe mögen schneller aussehen, umgehen jedoch Verhalten, auf das der Shop angewiesen ist: Cache-Aktualisierung, Lookup-Tabellen und Integrationen, die auf Produktänderungen reagieren. Wir wollten, dass der Shop Änderungen über seinen eigenen vorgesehenen Weg verarbeitet, und kontrollierten anschliessend unabhängig, was angekommen war.
Produkte und Grössen haben in Flakon eigene Identitäten; der Kassenartikel wird ausdrücklich verknüpft. Produktinhalte und Verkaufswerte werden getrennt verfolgt, damit ein Preis- oder Bestandsupdate nicht zur Erlaubnis wird, einen sorgfältig bearbeiteten Titel zu überschreiben oder Varianten umzusortieren. Der Kassenbestand wird übertragen, wenn sich der Wert in der Kasse ändert, statt ihn bei jedem Import blind zurückzusetzen und damit womöglich Bestandsabgänge durch Shopbestellungen rückgängig zu machen.
Auch der Export wird vor der Annahme auf Plausibilität geprüft. Eine leere Datei, ein starker Rückgang der Artikelzahl, doppelte EANs oder ein ungewöhnlicher Bestandssprung sollen eine Untersuchung auslösen, statt automatisch Kataloglöschungen zu verursachen. Zusammen mit Änderungsverlauf, wiederherstellbaren Versionen und geprüften Übertragungen reagieren diese Grenzen auf die konkreten Arten, wie dieser Ablauf bereits gescheitert war.
Was sich geändert hat und was wir früher hätten tun müssen
Wir bauten die erste Version mit Unterstützung durch KI und unserer Erfahrung aus ähnlichen Systemen schnell auf und liessen im Testbetrieb Importe aus Kasse und WooCommerce laufen, während wir die Logs beobachteten. Die Präsentation verlief gut. In den folgenden Wochen bereinigte der Kunde fast alle Produktdaten, während wir Fehler behoben und die Oberfläche an die Arbeit anpassten, die wir nun tatsächlich sehen konnten.
Manche Anpassungen entstanden innerhalb weniger Stunden nach einem konkreten Problem. Eine neue EAN führte zur Aktion für eine neue Verknüpfung, eine Grösse unter dem falschen Duft zu «Grösse entfernen» mit einem Weg zurück in den Eingang, und wiederkehrende Änderungen ganzer Marken zur Mehrfachauswahl. Nützlich war diese Rückkopplung: Wir konnten sehen, was der Kunde vorhatte, verstehen, warum die bestehende Aktion umständlich war, und den Ablauf verbessern, statt ihn erneut um Ausgleichsarbeit zu bitten.
Die Verbindung lautet heute POS → Flakon → WooCommerce, mit einem zentralen Ort für Produktinformationen. Der Austausch des Kassensystems ist bereits geplant. Auch ein späterer Shopwechsel würde nicht mehr bedeuten, mit einem Katalog von vorn zu beginnen, dessen einzige verlässliche Fassung im alten Shop lebt. Wir machen daraus keinen erfundenen Prozentsatz eingesparter Arbeitsstunden; die konkrete Verbesserung ist, dass Identität, Redaktionsarbeit, Prüfung und Verlauf nun einen definierten Ort haben.
Die Lektion für uns war unangenehm, weil wir sie eigentlich schon kannten: Vertraut «Das System funktioniert gut» erst, wenn ihr verstanden habt, welche Arbeit nötig ist, damit es so bleibt. Das wirft dem Kunden keine Unehrlichkeit vor. Menschen beschreiben, womit sie zu leben gelernt haben, und von ihnen kann man nicht erwarten, dass sie ein System entwerfen, das sie noch nie hatten. Es war unsere Aufgabe, den Workflow zu untersuchen und den Bedarf hinter der Anfrage zu erkennen.
Hätten wir das am Anfang getan, hätten wir Flakon früher vorschlagen und dem Kunden wie uns selbst viel Zeit, Geld und Frustration sparen können. Debuggt die User Stories, nicht nur den Code – besonders dann, wenn alle sagen, dass der Code schon seit Jahren funktioniert.