Sari la conținut

Dezvoltare Odoo: de la estimare la producție

18 septembrie 2026 de
Dezvoltare Odoo: de la estimare la producție

O ofertă pentru dezvoltare Odoo e de obicei un singur număr: ore. Nu spune nimic despre ce se întâmplă în interiorul acelor ore, iar acolo se decide dacă modulul Odoo mai funcționează după următoarea schimbare de versiune.

Deci iată ordinea în care lucrez pe proiectele mele. Mare parte din ea am învățat-o pe pielea mea, de la versiunea 15 la 19.

Înainte să existe vreun modul Odoo, există două documente

Primul e planul: ce trebuie să facă sistemul altfel în ziua în care se termină lucrarea, scris în limbajul afacerii. „Un om din depozit scanează un cod și linia se închide” e un plan. „Adaugă un câmp în stock.move.line” nu e - asta e deja un răspuns, și de regulă unul greșit. De nouă ori din zece, scanarea are nevoie de o regulă de nomenclatură de coduri de bare și de o suprascriere pe transferul existent, nu de un câmp nou undeva. Planul trebuie să rămână în limbajul omului din depozit suficient de mult timp cât să se mai vadă asta.

Al doilea e arhitectura, iar aici dezvoltarea Odoo încetează să semene cu dezvoltarea în general. Un modul Odoo nu trăiește singur. Stă în interiorul codului altcuiva - o versiune majoră pe an, un build de corecții în aproape fiecare săptămână, de la o companie care nu știe că existați. Așa că arhitectura răspunde la un set mai îngust de întrebări:

Care modele existente sunt implicate și dacă e suficient să fie moștenite. Ce se adaugă: un câmp, o metodă, o vizualizare, o rută. Și ce rămâne neatins. Ce comportament din nucleu se suprascrie și dacă suprascrierea are măcar forma potrivită - _inherit extinde un model pe loc, în timp ce _inherits adaugă pe tăcute o cheie străină și o înregistrare delegată, și așa ajunge un sistem cu un rând în plus per produs și nimeni nu-și mai amintește de ce.

Pe ce ediție rulează sistemul, pentru că un plan construit în jurul unui model din Enterprise nu e un plan pe Community. Câte companii, site-uri și limbi trebuie să deservească o singură instanță: patruzeci de site-uri într-o singură instanță Odoo e cu totul altă arhitectură decât unul singur.

Și întrebarea care se sare cel mai constant: de unde vin datele și ce face integrarea Odoo la trei dimineața, când partea cealaltă nu mai răspunde. „Reîncearcă de două ori, apoi parchează comanda și îmi trimite un e-mail” e o arhitectură. Tăcerea e și ea o arhitectură - doar că n-a ales-o nimeni.

Arhitectura se citește înainte să fie construită

Odată ce pagina aceea există, o vedeți. Deciziile, nu codul: aceste trei modele se ating, acesta nu, integrarea Odoo trece printr-o coadă de joburi, ca un apel eșuat către furnizor să devină o înregistrare reîncercabilă la care se poate uita cineva, iar tot ce e programat rulează ca ir.cron care predă munca lungă cozii, pentru că un worker de cron e oprit când atinge limita de timp.

Revizuirea durează cam o oră și e ultimul moment ieftin în care vă mai puteți răzgândi. Vă răzgândiți după implementare și plătiți o săptămână. Vă răzgândiți după lansare și plătiți în plus și cele două săptămâni în care depozitul a introdus totul de două ori.

Tot aici spun și lucrul care mă costă bani: uneori răspunsul onest e Studio, sau un modul din app store întreținut de altcineva, sau nimic. Odoo face deja asta - prost, dar acceptabil - iar un modul custom ar adăuga un an de mentenanță ca să salveze zece minute pe lună. Un modul Odoo custom e cea mai scumpă dintre aceste opțiuni și ar trebui să câștige pe merit.

Implementare: moșteniți modelul Odoo, nu-l copiați

Regula e plictisitoare: extindeți, nu copiați niciodată. Un fișier din nucleu copiat funcționează impecabil până la upgrade, iar de atunci e al dumneavoastră pe veci.

Există o singură excepție onestă. Unele metode din nucleu n-au nicio cusătură - nu există ce să înconjurați cu super(), nici de ce să vă agățați. Când una trebuie înlocuită în întregime, copia poartă un comentariu cu fișierul-sursă, ramura și versiunea din care vine, ca următorul upgrade să aibă un diff de verificat în loc de un mister.

În practică: moștenire pentru modele, xpath pentru vizualizări, QWeb pentru rapoarte, suprascrieri care tot apelează originalul. Câmpurile adăugate pe un model din nucleu poartă prefixul modulului, ca un câmp din nucleu cu același nume, apărut două versiuni mai târziu, să nu aterizeze peste al meu, și ca numele câmpului să spună ce modul l-a pus acolo. Prefixul x_ nu-l ating - acela îi aparține lui Studio.

Două capcane care merită numite, pentru că amândouă costă un deploy până să le găsiți. Moștenirea de vizualizări nu acceptă un atribut arbitrar drept selector: un xpath pe aria-label e refuzat din start. Iar hasclass(...) nu găsește nimic atunci când șablonul părinte construiește clasa respectivă cu t-attf-class, pentru că în sursă nu există niciun atribut class. Ambele erori apar abia la instalarea modulului - adică după ce verificările statice erau verzi. Eu citesc XML-ul brut al șablonului pe care îl extind, niciodată HTML-ul randat, unde clasele sunt deja lipite între ele și un selector stricat arată corect.

Încă ceva ce clienții presupun, rezonabil, că funcționează invers: dezinstalarea unui modul șterge coloanele pe care le-a adăugat, și datele din ele. Așa că dezinstalarea se testează pe o copie, iar dacă un câmp ține ceva ce afacerea va mai vrea după aceea, asta se decide înainte să fie scris câmpul, nu în ziua în care cineva bifează căsuța.

Cea mai mare parte din ce face un modul Odoo să supraviețuiască unei migrări Odoo la versiunea următoare se decide aici, în alegeri pe care nimeni nu le vede din afară și pe care toată lumea le simte doi ani mai târziu.

Testele, și ce face de fapt AI-ul aici

Orice modificare netrivială primește teste: un TransactionCase pentru logica din ORM, un tur HttpCase atunci când se schimbă un ecran, ambele marcate post_install, ca să ruleze lângă modulele care chiar vor fi instalate alături de al meu.

Le scriu cu AI, și expresia asta a fost întinsă destul de mult încât merită definită. E rapid la jumătatea mecanică: asamblat fixture-uri, acoperit a treia și a patra ramură a unei condiții, transformat „verifică dacă o comandă anulată eliberează rezervarea” în cod care rulează. Testele erau primul lucru tăiat când estimarea se strâmta. Nu mai sunt, și asta e toată diferența practică - nu că testele au devenit mai bune, ci că acum chiar se scriu.

Ce nu poate face e să decidă ce anume să testeze. Nu știe că exact integrarea asta cade când un furnizor trimite virgulă ca separator zecimal, pentru că faptul ăsta nu trăiește în cod - trăiește în ultimii doi ani de exporturi ale furnizorului respectiv. Alegerea cazurilor care merită un test rămâne treaba mea și n-am găsit nicio cale s-o predau.

Două reguli în jurul acestui lucru. Un test generat trebuie să pice înainte să treacă: dacă nu pot strica codul și să-l văd roșu, testul nu testează nimic, iar testele generate sunt foarte bune la a nu testa nimic în mod convingător. Și fixture-urile sunt înregistrări inventate, nu un export cu clienții dumneavoastră.

Staging pe date reale, apoi producție

O modificare merge întâi pe staging, pe o copie a bazei de date reale. Datele demo sunt politicoase. N-au clienți cu două spații în nume, n-au produse importate acum câțiva ani după reguli pe care nu le-a scris nimeni, n-au facturi într-o monedă scoasă din uz. Și n-au nici volum: o căutare care răspunde instantaneu pe date demo a durat opt secunde pe o sută zece mii de produse.

O copie a bazei de date e tot baza dumneavoastră, cu clienții dumneavoastră în ea, așa că se neutralizează înainte să lucreze cineva pe ea: e-mailurile de ieșire oprite, joburile programate oprite, procesatorii de plăți pe credențiale de test. Altfel, prima rerulare a unui flux pe staging trimite e-mailuri către trei sute de oameni reali, de pe un server care nici n-ar trebui să existe.

Pe staging, lucrarea e verificată de omul care a cerut-o, pe propriile lui înregistrări. Apoi producția, într-o ordine scrisă: ce modul se actualizează, ce script de migrare rulează înainte de actualizare și care după, și ce se întâmplă cu datele deja aflate în baza de date. Conținutul publicat e surpriza obișnuită - o înregistrare încărcată cu noupdate nu se reimportă orice ați face, așa că un text deja publicat se schimbă scriind în înregistrare dintr-o migrare, sau nu se schimbă deloc. Și un rollback pe care chiar l-am rulat cel puțin o dată. Un rollback presupus nu e un rollback.

Defectele care apar doar în producție

Iată partea din dezvoltarea Odoo pe care estimările n-o pomenesc niciodată. Unele defecte au nevoie de un cache real și de un vizitator real ca să existe, iar o bază de test n-are niciuna dintre ele. Am scris separat despre ce ține și ce se rupe într-un sistem Odoo viu; cele trei de mai jos sunt dintr-o singură săptămână, chiar pe acest site.

Bannerul de consimțământ scria răspunsul vizitatorului într-un format greșit. „Doar esențiale” salva JSON corect; „Accept” salva textul true, pentru că un consimțământ complet repornește interacțiunile paginii, iar handlerul de închidere al ferestrei rula pe o instanță proaspătă care încă ținea valoarea implicită. Pe server, verificarea care citește acel cookie așteaptă un dicționar și șterge orice altceva - o ramură de compatibilitate care face exact ce a fost scrisă să facă. Așa că toți cei care apăsau „accept” erau întrebați din nou pe pagina următoare, iar analytics-ul din spate rămânea la zero, arătând perfect instalat.

A doua se ascunde într-un ecran de setări. Dacă un tracker are voie să se încarce se decide într-o metodă care începe cu „dacă bannerul de cookie-uri e oprit, consimțământul e dat” - raționamentul fiind că un proprietar care a oprit bannerul și-a construit consimțământul altfel. Debifați căsuța respectivă și fiecare vizitator e numărat tacit drept consimțit, câte o oră odată, pentru că pagina randată e în cache. Nimic din acel ecran de setări nu arată periculos.

A treia: un eveniment de conversie era legat de un moment din ciclul de viață al paginii care trecuse deja când se încărca scriptul, pentru că scriptul vine într-un bundle amânat care pornește după ce pagina s-a terminat de încărcat. Formularul funcționa, lead-ul ajungea, cifra rămânea zero.

Toate trei au fost găsite la fel - deschideți site-ul live, apăsați ambele butoane, citiți cookie-ul și tabul de rețea în loc de cod. Sunt cincisprezece minute și e singurul loc în care aceste bug-uri există. Le facturez ca parte din lucrare.

Cât costă versiunea următoare

Articolul ar fi necinstit dacă v-ar spune să întrebați despre upgrade-uri și apoi ar ocoli întrebarea. Upgrade-ul bazei de date e treaba celor de la Odoo și se pricep. Upgrade-ul codului dumneavoastră custom e treaba mea, iar prețul depinde de un singur lucru: cât din modul stă peste comportament din nucleu care s-a mutat.

Un modul care își adaugă propriul model, câteva câmpuri și câteva vizualizări supraviețuiește de obicei unei versiuni majore cu o după-amiază de corecții. Un modul care suprascrie create, modifică un raport și moștenește patru șabloane din nucleu înseamnă o săptămână. Merită știut înainte să-l aveți pe cap: create e @api.model_create_multi și primește o listă de înregistrări, nu una singură, așa că o suprascriere scrisă pentru un singur set de valori nu eșuează zgomotos - procesează tăcut primul rând dintr-un import în lot și le ignoră pe restul.

Și upgrade-urile se fac versiune cu versiune. Odoo nu sare peste nimic, așa că orice migrare Odoo cară codul custom prin fiecare pas, nu o singură dată la final.

Drepturile fac parte din funcționalitate

Fiecare model pe care îl adaug vine cu regulile lui de acces și cu o regulă de înregistrare atunci când datele nu sunt pentru toată lumea - scrise în același timp cu modelul. Drepturile lipite ulterior pe un modul terminat sunt modul în care un om de vânzări ajunge să citească marjele altei regiuni.

Lucrul pe care îl caut cel mai insistent în codul oricui, inclusiv în al meu, e sudo(). Oprește toate regulile dintr-o dată, e cea mai rapidă cale de a face să dispară un bug de permisiuni, iar într-un controller public e o scurgere de date cu URL prietenos.

Șlefuirea de după lansare

Modulul e gata cam la două săptămâni după lansare, când se întoarce lista de mici frecări: acest câmp ar trebui să aibă o valoare implicită, acest ecran are nevoie de un clic mai puțin, acest raport e citit în fiecare dimineață la opt, deci ar trebui să fie un element de meniu și nu un filtru pe care cineva îl reaplică manual și îl greșește lunea.

Această trecere e scurtă. E și diferența dintre un modul care își trece testele și unul pe care oamenii nu-l mai ocolesc. Săriți peste ea și veți avea o funcționalitate corectă tehnic, cu un tabel Excel lângă ea care face treaba adevărată.

Singura întrebare care merită pusă

Nu vă veți aminti nimic din toate acestea când veți sta față în față cu un dezvoltator. Așa că iată întrebarea pe care aș pune-o în locul dumneavoastră:

Ce se întâmplă cu acest modul când Odoo scoate versiunea următoare?

Dacă răspunsul numește un risc concret - această suprascriere stă pe o metodă pe care Odoo o atinge des, deci se reverifică la fiecare upgrade - omul a mai făcut o migrare Odoo. „O să fie bine” poate fi și el răspunsul adevărat, pentru un modul care adaugă două câmpuri și o vizualizare de tip listă. Ce ascultați e dacă vă poate spune în care dintre cele două cazuri intră al dumneavoastră.

Această întrebare funcționează cu oricine ați lua în calcul, iar răspunsul vă spune mai mult decât un badge - ceea ce e, în mare, diferența dintre un partener Odoo și un dezvoltator independent.

Dacă aveți un sistem care merge și o schimbare pe care nu știți cum s-o faceți, scrieți-mi. Prima discuție e despre ce trebuie să fie altfel când lucrarea e gata, nu despre ore.

Partener Odoo sau dezvoltator independent: care e diferența cu adevărat
Din ce se calculează nivelurile Ready, Silver și Gold și cine e plătit când cumpărați mai multe locuri.