Ein Angebot für Odoo Entwicklung ist meistens eine einzige Zahl: Stunden. Über das, was innerhalb dieser Stunden passiert, sagt sie nichts. Und genau dort entscheidet sich, ob das Odoo Modul auch nach dem nächsten Versionssprung noch funktioniert.
Deshalb hier die Reihenfolge, in der ich arbeite. Das meiste davon ist Narbengewebe aus den Versionen 15 bis 19.
Bevor es ein Odoo Modul gibt, gibt es zwei Dokumente
Das erste ist der Plan: was das System am Tag nach Abschluss der Arbeit anders machen soll, formuliert in der Sprache des Geschäfts. „Ein Lagermitarbeiter scannt einen Code, und die Zeile schließt sich" ist ein Plan. „Ein Feld an stock.move.line anhängen" ist keiner — das ist bereits eine Antwort, und meistens die falsche. In neun von zehn Fällen braucht der Scan eine Barcode-Nomenklaturregel und einen Eingriff in die vorhandene Kommissionierung, kein neues Feld irgendwo. Der Plan muss lange genug in der Sprache des Lagermitarbeiters bleiben, damit das überhaupt sichtbar bleibt.
Das zweite ist die Architektur, und hier hört Odoo Entwicklung auf, wie Softwareentwicklung im Allgemeinen auszusehen. Ein Odoo Modul lebt nicht für sich. Es sitzt im Code von jemand anderem — ein Major Release pro Jahr, fast wöchentlich ein Patch-Build, von einem Unternehmen, das nichts von Ihnen weiß. Die Architektur beantwortet deshalb einen engeren Satz von Fragen:
Welche bestehenden Modelle betroffen sind und ob Vererbung reicht. Was dazukommt: ein Feld, eine Methode, eine Ansicht, eine Route. Und was unangetastet bleibt. Welches Standardverhalten überschrieben wird — und ob der Eingriff überhaupt die richtige Form hat: _inherit erweitert ein Modell an Ort und Stelle, _inherits legt still und leise einen Fremdschlüssel und einen delegierten Datensatz an. So landet man bei einer zusätzlichen Zeile pro Produkt, an deren Grund sich niemand mehr erinnert.
Which edition the system runs on, because a plan built around an Enterprise model is not a plan on Community. How many companies, websites and languages one instance has to serve: forty websites in a single Odoo instance is a different architecture from one.
Und die Frage, die am zuverlässigsten übersprungen wird: woher die Daten kommen und was die Odoo Integration um drei Uhr morgens tut, wenn die Gegenseite nicht mehr antwortet. „Zwei Wiederholungen, dann wird der Auftrag geparkt und ich bekomme eine Mail" ist eine Architektur. Stillschweigen ist auch eine — nur hat sie niemand gewählt.
Die Architektur wird gelesen, bevor sie gebaut wird
Sobald diese Seite existiert, sieht man sie. Die Entscheidungen, nicht den Code: diese drei Modelle werden angefasst, dieses nicht, die Odoo Integration läuft über eine Job-Queue, damit ein fehlgeschlagener Lieferantenaufruf zu einem wiederholbaren Datensatz wird, den sich jemand ansehen kann, und alles Zeitgesteuerte läuft als ir.cron mit Lock, denn ein Cron, der davon ausgeht, sich nie selbst zu überholen, liegt irgendwann falsch.
Der Review dauert etwa eine Stunde, und es ist der letzte billige Moment, um es sich anders zu überlegen. Ändern Sie es nach der Umsetzung, zahlen Sie eine Woche. Ändern Sie es nach dem Go-live, zahlen Sie zusätzlich die zwei Wochen, in denen das Lager alles doppelt erfasst hat.
Hier sage ich auch den Satz, der mich Geld kostet: Manchmal lautet die ehrliche Antwort Studio, oder ein Modul aus dem App Store, das jemand anderes pflegt, oder gar nichts. Odoo kann das bereits — schlecht, aber ausreichend — und ein Custom-Modul brächte ein Jahr Wartung, um zehn Minuten im Monat zu sparen. Ein eigenes Odoo Modul ist die teuerste dieser Optionen, und es sollte sich den Zuschlag sachlich verdienen.
Umsetzung: das Odoo Modell erben, nicht forken
Die Regel ist langweilig: erweitern, nie kopieren. Eine geforkte Core-Datei funktioniert gut, bis das Upgrade kommt — danach gehört sie für immer Ihnen.
Es gibt eine ehrliche Ausnahme. Manche Core-Methoden haben keine Naht: nichts, worum man super() legen könnte, kein Hook. Wenn eine davon komplett ersetzt werden muss, trägt die Kopie einen Kommentar mit Quelldatei, Branch und Version, aus der sie stammt. Dann hat das nächste Upgrade ein Diff zum Prüfen statt eines Rätsels.
In der Praxis: Vererbung für Modelle, xpath für Ansichten, QWeb für Berichte, Overrides, die das Original weiterhin aufrufen. Felder an Core-Modellen tragen das Präfix des Moduls, damit ein gleichnamiges Core-Feld zwei Versionen später nicht auf meinem landet — und damit der Feldname verrät, welches Modul es angelegt hat. Das Präfix x_ fasse ich nicht an, das gehört Studio.
Zwei Fallen, die eine Erwähnung wert sind, weil beide ein Deployment kosten, bis man sie findet. Ansichtsvererbung akzeptiert kein beliebiges Attribut als Selektor: ein xpath auf aria-label wird rundheraus abgelehnt. Und hasclass(...) findet nichts, wenn das übergeordnete Template diese Klasse mit t-attf-class zusammensetzt, denn im Quelltext gibt es gar kein class-Attribut. Beide Fehler zeigen sich erst bei der Installation des Moduls — also nachdem die statischen Prüfungen grün waren. Ich lese das rohe XML des Templates, das ich erweitere, nie das gerenderte HTML, in dem die Klassen schon zusammengeklebt sind und ein kaputter Selektor richtig aussieht.
Noch etwas, bei dem Kunden verständlicherweise das Gegenteil annehmen: Die Deinstallation eines Moduls löscht die Spalten, die es angelegt hat, samt Daten darin. Deshalb wird die Deinstallation auf einer Kopie getestet, und wenn ein Feld etwas enthält, das das Unternehmen danach noch braucht, wird das entschieden, bevor das Feld geschrieben wird — nicht an dem Tag, an dem jemand das Häkchen setzt.
Das meiste davon, was ein Odoo Modul eine Migration auf die nächste Version überleben lässt, wird hier entschieden: in Entscheidungen, die von außen niemand sieht und die zwei Jahre später alle spüren.
Tests, und was KI hier wirklich leistet
Jede nicht triviale Änderung bekommt Tests: ein TransactionCase für die ORM-Logik, eine HttpCase-Tour, wenn sich ein Bildschirm ändert, beide mit post_install getaggt, damit sie gegen die Module laufen, die später tatsächlich neben meinem installiert sind.
Ich schreibe sie mit KI, und dieser Satz ist inzwischen so gedehnt worden, dass er eine Definition verdient. Schnell ist sie in der mechanischen Hälfte: Fixtures zusammenbauen, den dritten und vierten Zweig einer Bedingung abdecken, aus „prüfen, dass ein stornierter Auftrag die Reservierung freigibt" lauffähigen Code machen. Tests waren früher das Erste, was gestrichen wurde, wenn die Schätzung eng wurde. Das sind sie nicht mehr, und darin liegt der ganze praktische Unterschied — nicht, dass die Tests besser geworden wären, sondern dass sie jetzt überhaupt geschrieben werden.
Was sie nicht kann: entscheiden, was getestet werden muss. Sie weiß nicht, dass ausgerechnet diese Integration scheitert, wenn ein Lieferant ein Komma als Dezimaltrennzeichen schickt, denn diese Tatsache steht nicht im Code — sie steht in den letzten zwei Jahren der Exporte dieses Lieferanten. Auszuwählen, welche Fälle einen Test verdienen, bleibt meine Aufgabe, und ich habe noch keinen Weg gefunden, sie abzugeben.
Zwei Regeln drumherum. Ein generierter Test muss erst scheitern, bevor er besteht: Wenn ich den Code nicht kaputt machen und ihn rot werden sehen kann, testet er nichts — und generierte Tests sind sehr gut darin, überzeugend nichts zu testen. Und Fixtures sind erfundene Datensätze, kein Export Ihrer Kunden.
Staging auf echten Daten, dann Produktion
A change goes to staging first, on a copy of the real database. Demo data is polite. It has no customers with two spaces in the name, no products imported years ago under rules nobody wrote down, no invoices in a currency that was discontinued. It also has no volume: a search that answers instantly on demo data took eight seconds on a hundred and ten thousand products.
Eine Kopie Ihrer Datenbank ist immer noch Ihre Datenbank, mit Ihren Kunden darin. Also wird sie neutralisiert, bevor jemand damit arbeitet: ausgehende Mails aus, geplante Jobs aus, Zahlungsanbieter auf Testzugangsdaten. Sonst schickt der erste Durchlauf eines Workflows auf Staging dreihundert echten Menschen eine Mail von einem Server, den es gar nicht geben sollte.
Auf Staging prüft die Person die Arbeit, die sie beauftragt hat — an ihren eigenen Datensätzen. Dann Produktion, in schriftlicher Reihenfolge: welches Modul aktualisiert wird, welches Migrationsskript vor und welches nach dem Update läuft, und was mit Daten passiert, die bereits in der Datenbank liegen. Veröffentlichte Inhalte sind die übliche Überraschung — ein mit noupdate geladener Datensatz wird nicht erneut importiert, egal was Sie tun. Text, der bereits live ist, wird also durch Schreiben auf den Datensatz in einer Migration geändert, oder gar nicht. Und ein Rollback, das ich mindestens einmal wirklich ausgeführt habe. Ein angenommenes Rollback ist kein Rollback.
Die Fehler, die nur in der Produktion auftauchen
Here is the part of Odoo development that estimates never mention. Some defects need a real cache and a real visitor before they exist at all, and a test database has neither. I have written separately about what holds and what breaks in a live Odoo system; these three are from one week on this very site.
Das Consent-Banner schrieb die Antwort des Besuchers im falschen Format. „Nur notwendige" speicherte sauberes JSON, „Akzeptieren" speicherte die Zeichenkette true — weil eine vollständige Zustimmung die Interaktionen der Seite neu startet und der Close-Handler des Dialogs auf einer frischen Instanz lief, die noch ihren Standardwert hielt. Auf dem Server erwartet die Prüfung, die dieses Cookie liest, ein Dictionary und löscht alles andere: ein Kompatibilitätszweig, der genau das tut, wofür er geschrieben wurde. Wer also auf „Akzeptieren" geklickt hatte, wurde auf der nächsten Seite erneut gefragt, und die Analytics dahinter blieb bei null, während sie perfekt installiert aussah.
Der zweite versteckt sich in einer Einstellungsmaske. Ob ein Tracker laden darf, entscheidet eine Methode, die mit „wenn das Cookie-Banner abgeschaltet ist, gilt die Zustimmung als erteilt" beginnt — die Überlegung dahinter: Wer das Banner abschaltet, hat die Zustimmung anders eingeholt. Nehmen Sie das Häkchen heraus, und jeder Besucher gilt stillschweigend als zustimmend, jeweils eine Stunde lang, weil die gerenderte Seite im Cache liegt. Nichts in dieser Einstellungsmaske sieht gefährlich aus.
Der dritte: Ein Conversion-Event hing an einem Moment im Seitenlebenszyklus, der längst vorbei war, als das Skript geladen wurde — denn das Skript kommt in einem verzögerten Bundle, das erst startet, wenn die Seite fertig geladen ist. Das Formular funktionierte, der Lead kam an, die Zahl blieb bei null.
Alle drei wurden auf dieselbe Weise gefunden: die Live-Seite öffnen, beide Buttons klicken, Cookie und Netzwerk-Tab lesen statt des Codes. Das sind fünfzehn Minuten, und es ist der einzige Ort, an dem diese Fehler existieren. Ich rechne das als Teil der Arbeit ab.
Was die nächste Version kostet
Der Artikel wäre unehrlich, wenn er Ihnen rät, nach Upgrades zu fragen, und der Frage dann selbst ausweicht. Die Datenbank zu aktualisieren ist Odoos Aufgabe, und darin sind sie gut. Ihren Custom-Code zu aktualisieren ist meine, und der Preis hängt an einer Sache: wie viel vom Modul auf Standardverhalten aufsitzt, das sich bewegt hat.
Ein Modul, das ein eigenes Modell, ein paar Felder und einige Ansichten mitbringt, übersteht eine Hauptversion meistens mit einem Nachmittag Korrekturen. Ein Modul, das create überschreibt, einen Bericht patcht und von vier Core-Templates erbt, ist eine Woche. Gut zu wissen, bevor es Ihnen gehört: create ist @api.model_create_multi und bekommt eine Liste von Datensätzen, nicht einen. Ein Override, der für einen einzelnen Wertesatz geschrieben wurde, scheitert deshalb nicht laut — er verarbeitet still die erste Zeile eines Massenimports und ignoriert den Rest.
Und Upgrades laufen Version für Version. Odoo überspringt nichts, Custom-Code wird also über jeden einzelnen Schritt getragen statt einmal am Ende.
Rechte gehören zur Funktion
Jedes Modell, das ich anlege, kommt mit seinen Zugriffsregeln, und mit einer Datensatzregel, wenn die Daten nicht für alle sind — geschrieben zur selben Zeit wie das Modell. Rechte, die einem fertigen Modul nachträglich verpasst werden, sind der Weg, auf dem ein Vertriebler die Margen einer anderen Region liest.
Wonach ich in fremdem Code am gründlichsten suche, auch in meinem eigenen: sudo(). Es schaltet sämtliche Regeln auf einen Schlag ab, es ist der schnellste Weg, einen Berechtigungsfehler verschwinden zu lassen, und in einem öffentlichen Controller ist es ein Datenleck mit freundlicher URL.
Feinschliff nach dem Start
Fertig ist das Modul etwa zwei Wochen nach dem Go-live, wenn die Liste der kleinen Reibungen zurückkommt: Dieses Feld sollte einen Standardwert haben, dieser Bildschirm einen Klick weniger, dieser Bericht wird jeden Morgen um acht gelesen, also gehört er als Menüeintrag hin und nicht als Filter, den jemand von Hand neu setzt und montags falsch macht.
Dieser Durchgang ist kurz. Er ist zugleich der Unterschied zwischen einem Modul, das seine Tests besteht, und einem, um das die Leute nicht mehr herumarbeiten. Lassen Sie ihn weg, bekommen Sie eine technisch korrekte Funktion — und daneben eine Tabellenkalkulation, die die eigentliche Arbeit macht.
Die eine Frage, die sich lohnt
Nichts davon werden Sie parat haben, wenn Sie einem Entwickler gegenübersitzen. Deshalb hier die Frage, die ich an Ihrer Stelle stellen würde:
Was passiert mit diesem Modul, wenn Odoo die nächste Version veröffentlicht?
Nennt die Antwort ein konkretes Risiko — dieser Override hängt an einer Methode, die Odoo oft anfasst, also wird sie bei jedem Upgrade neu geprüft — dann hat die Person schon einmal eine Odoo Migration gemacht. „Das wird schon passen" kann ebenfalls die wahre Antwort sein, bei einem Modul mit zwei Feldern und einer Listenansicht. Worauf Sie hören: ob Ihr Gegenüber Ihnen sagen kann, welcher von beiden Fällen Ihrer ist.
That question works on anyone you are considering, and the answer tells you more than a badge does — which is most of the difference between an Odoo partner and an independent developer.
If you have a system that runs and a change you are not sure how to make, write to me. The first conversation is about what should be different when the work is done, not about hours.