Sincronizzare l'invendibile: casi limite di integrazione tra Odoo e piattaforme proprietarie

Perché le integrazioni Odoo falliscono in silenzio: 8 pattern architetturali per middleware robusti

Perché le integrazioni Odoo falliscono in silenzio: 8 pattern architetturali per middleware robusti

Il problema delle integrazioni Odoo non è tecnologico nella sua essenza. È ontologico: sistemi diversi hanno modelli del mondo incompatibili, e la sincronizzazione forza una traduzione tra ontologie che non si mappano mai perfettamente.

Quando si progetta un'integrazione tra Odoo e un sistema terzo — che sia un ERP legacy, una piattaforma e-commerce proprietaria, un WMS di magazzino o un PIM verticale — la sfida più pericolosa non è quella che emerge come errore. È quella che non emerge affatto. I bug rumorosi si trovano e si correggono. I guasti silenti — quei casi in cui il dato viene scritto correttamente su entrambi i sistemi, ma i due valori divergono semanticamente — si accumulano per settimane prima che qualcuno in contabilità, in magazzino o al customer care noti che qualcosa non torna. Questo articolo raccoglie otto dei casi limite più ricorrenti che incontriamo nei progetti di integrazione Odoo, con una proposta architetturale per ciascuno. L'obiettivo non è la lista completa dei possibili problemi, ma un pattern di ragionamento: rendere esplicite le assunzioni implicite prima che diventino bug.


1. UoM divergenti: il caso dei colli parziali

Odoo gestisce le unità di misura attraverso un sistema di conversione preciso: ogni UoM appartiene a una categoria e ha un fattore rispetto all'unità base. Il problema emerge quando il sistema esterno — tipicamente un WMS industriale — non ha questo modello. Vende in "colli da 6", ma il collo può essere aperto. Odoo non ha nativamente il concetto di collo parziale nel flusso di vendita standard.

Il caso limite tipico: un marketplace invia un ordine da 7 pezzi. Il prodotto su Odoo ha UoM di vendita = collo da 6. L'arrotondamento avviene verso il collo superiore. Il connettore trasforma 7 pezzi → 2 colli = 12 pezzi, ma fattura 7. Il magazzino preleva 12. L'inventario diverge silenziosamente.

Soluzione architetturale

Il middleware deve registrare sempre la UoM sorgente e quella target come tupla, non come semplice valore convertito. Il dato da sincronizzare non è qty = 7, ma:

{ qty_source: 7, uom_source: 'pz', qty_odoo: 2, uom_odoo: 'collo', remainder_unmanaged: 1 }

Il remainder deve generare un evento, non sparire.


2. Finite State Machine degli ordini incompatibili

Questo è probabilmente il caso limite più subdolo. Odoo ha una FSM degli ordini ben definita e gli stati del picking seguono una logica altrettanto rigida. Le piattaforme esterne — specialmente quelle retail o marketplace — hanno FSM molto più ricche, con stati intermedi che Odoo non conosce.

Il problema non è la mancanza di stati su Odoo, ma la direzione delle transizioni. Odoo non permette di tornare da sale a draft senza annullare l'ordine — anche se, a partire da Odoo 16/17, il blocco degli ordini confermati è configurabile e il comportamento può variare leggermente. Ma alcuni sistemi esterni hanno un unconfirm che porta l'ordine in uno stato editabile. Quando il connettore riceve questo evento e tenta di replicarlo, o lo ignora (stato orfano), o annulla e ricrea l'ordine Odoo — perdendo la traccia storica e potenzialmente rompendo la contabilità.

Soluzione architetturale

Mappare esplicitamente le transizioni, non solo gli stati. La tabella di mapping deve essere del tipo:

('confirmed', 'unconfirmed') → noop + flag manuale ('shipped', 'return_in_transit') → crea_return_picking

Gli stati non mappabili non vanno ignorati: vanno accodati in una tabella di eccezioni che un operatore può risolvere manualmente.


3. Stock multi-location: quant vs snapshot

Odoo modella lo stock attraverso i stock.quant, che rappresentano la giacenza fisica per combinazione di prodotto, lotto, ubicazione e package. È un modello transazionale: ogni movimento crea o consuma quant. I WMS industriali, spesso, lavorano diversamente: espongono uno snapshot periodico della giacenza, non un feed transazionale.

Il caso limite: il WMS esegue un inventario fisico alle 02:00 e pubblica il risultato. Il connettore Odoo riceve questo snapshot e deve riconciliarlo. Ma nel frattempo Odoo ha elaborato spedizioni tra le 02:00 e l'ora di ricezione. Se il connettore semplicemente imposta la giacenza Odoo allo snapshot, cancella quei movimenti logicamente.

Soluzione architetturale

Il middleware deve calcolare il delta tra snapshot e realtà Odoo, non sovrascrivere:

delta = snapshot_wms - (giacenza_odoo_al_momento_snapshot + movimenti_odoo_post_snapshot)

Solo il delta va applicato tramite inventory.adjustment. Mai sovrascrivere la giacenza Odoo direttamente.


4. Logiche fiscali: tax position vs codici esterni

Odoo usa le fiscal.position per mappare tasse e conti contabili in funzione del partner. Una fiscal position può trasformare IVA 22% → IVA 0% per clienti UE con partita IVA valida. Il sistema esterno, tipicamente, invia semplicemente un tax_code come stringa: "IVA22", "EXEMPT", "INTRACOMUNITARIO".

Il caso limite: la piattaforma esterna gestisce la logica fiscale in autonomia e invia già i valori calcolati (subtotal, tax, total). Se il connettore non imposta la fiscal position corretta sul partner, Odoo applica la tassa standard e genera una fattura diversa da quella del sistema sorgente. La divergenza emerge solo in contabilità, settimane dopo.

Soluzione architetturale

Mai fidarsi dei valori fiscali calcolati dal sistema esterno. Il middleware deve tradurre il tax_code esterno nella fiscal.position Odoo corretta e lasciare che sia Odoo a ricalcolare. Serve una lookup table esplicita, validata dal commercialista, non generata automaticamente.


5. Varianti prodotto: il modello piatto vs il modello Odoo

Odoo gestisce i prodotti con un modello a due livelli: product.template (il prodotto "concettuale") e product.product (la variante specifica, determinata dalla combinazione di attributi). Un template con 3 taglie × 4 colori genera 12 varianti.

Le piattaforme e-commerce proprietarie e i PIM spesso usano un modello piatto: ogni SKU è un record indipendente, con eventuali campi parent_sku e variant_attributes come metadati opzionali ma non strutturali.

Il caso limite classico: la piattaforma esterna crea un nuovo colore per un prodotto esistente. Il connettore deve decidere se creare una nuova variante su un template esistente (corretto) o un nuovo template monoprodotto (più semplice ma sbagliato). Se sceglie la seconda strada, il prodotto si sdoppia: listini disallineati, regole di riordino non aggiornate, anagrafica duplicata.

Soluzione architetturale

Implementare una logica di product matching in due fasi. Prima si cerca un template con lo stesso base_sku o attributo identificativo. Solo se non lo si trova, si crea un nuovo template. La creazione di varianti deve avvenire tramite l'ORM Odoo — mai via SQL diretto — per garantire che tutti i campi calcolati vengano generati correttamente.


6. Listini prezzi: la complessità nascosta delle pricelist Odoo

Le product.pricelist di Odoo supportano regole per categoria, per quantità, con date di validità, con dipendenza da altre pricelist. La piattaforma esterna, quasi sempre, espone i prezzi come un semplice campo numerico per SKU.

Il caso limite: Odoo ha una pricelist "Clienti Premium" con sconto del 15% su tutto il catalogo, eccetto la categoria "Prodotti in promozione" che ha un prezzo fisso. Quando il connettore legge il prezzo da esporre all'esterno, usa product.list_price invece di passare per l'API pubblica delle pricelist. Espone prezzi sbagliati in modo silenzioso.

Soluzione architetturale

La sincronizzazione dei prezzi verso l'esterno deve sempre passare per pricelist.get_product_price(product, qty, partner) con i parametri corretti. Questo è il metodo pubblico corretto da usare in contesti di integrazione — evitare di leggere list_price direttamente o di accedere a metodi interni (prefissati con underscore) che non fanno parte dell'API pubblica di Odoo. La sincronizzazione deve avvenire per ogni combinazione (pricelist, prodotto, scaglione di quantità) rilevante.


7. Identità cliente: il problema del golden record

Odoo usa res.partner con un modello gerarchico: un'azienda (is_company=True) può avere contatti figlio (parent_id). Le piattaforme esterne spesso non distinguono tra azienda e contatto: ogni account è un'entità piatta.

Il caso limite: un cliente registrato sull'e-commerce come privato acquista anche come dipendente di un'azienda usando un indirizzo email aziendale. Se il connettore crea sempre un nuovo res.partner senza cercare record esistenti, nel tempo Odoo si riempie di duplicati che spezzano la cronologia ordini, la contabilità e le analisi CRM.

Soluzione architetturale

Implementare una logica di entity resolution prima di ogni create su res.partner. I candidati al match vanno cercati per email, VAT, e nome fuzzy. La regola di decisione deve essere esplicita:

match con score > soglia → usa quel record match multipli → metti in coda per revisione manuale nessun match → crea un nuovo record

Mai creare in silenzio senza aver cercato.


8. Timezone e date naive: il bug che emerge a mezzanotte

Odoo internamente lavora in UTC. Le date di consegna, le scadenze di listino vengono salvate in UTC e convertite nel timezone dell'utente in visualizzazione. I sistemi esterni spesso inviano date in timezone locale senza indicazione esplicita: 2024-01-15 23:30:00 senza +01:00.

Il caso limite: la piattaforma esterna invia un ordine con delivery_date = "2024-01-16" intendendo il 16 gennaio in CET (UTC+1). Odoo interpreta quella data come UTC. L'ordine viene schedulato con un giorno di anticipo. In estate, con UTC+2, lo stesso bug fa avanzare la data di due giorni. Il magazzino spedisce in anticipo e la pianificazione giornaliera si sovrappone.

Soluzione architetturale

Definire nel contratto dell'integrazione quale timezone usano i timestamp. Normalizzare sempre in UTC prima di scrivere su Odoo. Usare sempre date con timezone esplicito (datetime con tzinfo, non date naive). Registrare nel middleware la timezone sorgente per ogni transazione.


Il principio architetturale trasversale

Tutti questi casi limite condividono una struttura comune: nascono da assunzioni implicite nei protocolli di integrazione. La soluzione non è scrivere più codice di gestione degli errori — è rendere esplicite le assunzioni.

Un'integrazione robusta non è quella che gestisce gli errori meglio. È quella che trasforma le ambiguità in decisioni esplicite prima che arrivino al dato.

Un middleware di integrazione Odoo maturo deve garantire quattro proprietà fondamentali:

Contratto dati versionato. Specifica non solo i tipi dei campi, ma le semantiche: cosa significa qty? Pezzi fisici? Unità di imballaggio? Al lordo dei resi? Questo contratto deve essere versionato nel repository come qualsiasi altro artefatto di progetto.

Layer di validazione semantica. Rifiuta i messaggi ambigui prima che raggiungano Odoo, non dopo. Meglio un errore esplicito in ingresso che un dato silenziosamente sbagliato in uscita.

Tabella di eccezioni persistente e dashboard di riconciliazione. Le eccezioni non risolte sono informazione: se ogni giorno entrano 20 ordini con tax_code = "UNKNOWN", quella è una mappatura mancante da aggiungere. Ma l'osservabilità deve andare oltre: serve una Dashboard di Riconciliazione che confronti i totali tra i sistemi (es. Totale Incassato su Gateway vs Totale Fatturato su Odoo). Se i log sono verdi ma i totali non quadrano, la dashboard espone il "guasto silente" prima che diventi un problema contabile.

Idempotenza garantita. Lo stesso messaggio, ricevuto due volte, deve produrre lo stesso risultato. È banale da enunciare e difficilissimo da implementare correttamente con le varianti prodotto e la logistica Odoo, ma è il requisito che trasforma un'integrazione fragile in una robusta.

La tendenza naturale, sotto pressione di progetto, è di far funzionare l'integrazione per il caso normale e rimandare i casi limite a "fase 2" che spesso non arriva mai. Questi otto pattern mostrano che i casi limite non sono edge case rari: sono situazioni che emergono invariabilmente non appena i volumi crescono o le configurazioni dei clienti si diversificano.

La differenza tra un connettore che regge in produzione e uno che genera ticket ogni settimana non sta nel numero di casi gestiti. Sta nel fatto che i casi non gestiti siano espliciti, tracciati, e visibili — non silenziosamente sbagliati.

Autoreadmin
Potrebbero interessarti...
back to top icon