Eerste ervaringen met data-integratie op Odoo via Invantive UniversalSQL

Go to English version

Samenvatting

Alle Invantive UniversalSQL-producten ondersteunen Odoo in lees- en schrijfmodus. Alle tabellen kunnen eenvoudig geladen en gelezen worden. Voor de overgang naar Odoo laat Invantive haar relatiebestand uit Invantive Estate automatisch synchroniseren naar Odoo. Een dergelijke synchronisatie draait al o.a. richting Exact Online; het huidige boekhoudpakket.

De huidige (zelf opgelegde) API-limiet van 60 calls per minuut op Odoo werkt niet echt vlot. Loslaten van deze limiet leidde tot een laadsnelheid van circa 43.000 relaties per uur.

Het laden en synchroniseren van data met Odoo via de externe API blijkt (toch beetje onverwacht) erg stabiel en voorspelbaar. Dit was een opluchting; het Odoo supportkanaal is matig. Het binnen enkele minuten kunnen maken van een representatieve testomgeving zonder bijkomende kosten is absoluut een voordeel; hierdoor kan de programmatuur beter getest worden.

De doorlooptijd van het project was circa 60x korter dan op Exact Online, ondanks dat Invantive al ruim 10 jaar ervaring heeft met tienduizenden Exact Online administraties en vrijwel geen ervaring met Odoo. De benodigde hoeveelheid mankracht was circa 15 maal kleiner voor hetzelfde eindresultaat.

Odoo migratie

Recente 22.1-releases van alle Invantive-producten ondersteunen efficient lezen en schrijven in Odoo.

De implementatie van Odoo binnen Invantive is inmiddels weer opgepakt, nadat het probleem met het laden van de abonnementscode was opgelost.

Een aantal business apps die Invantive gebruikt(e) zijn inmiddels de revue gepasseerd en ingedeeld:

App Status Toelichting
ActiveCampaign Gepauzeerd Uit kostenoverwegingen vervangen door Sendinblue (tegenwoordige Brevo). Later evalueren of Odoo dit ook betrouwbaar zou kunnen, maar gezien [Odoo e-mailproblematiek](Configureer een externe e-mailserver op Odoo.com met Amazon Simple Email Service en Route53 voorlopig gezien als te groot risico.
Basecone Kandidaat Voorbereiding; zie Exact Online.
Calendly Kandidaat Voorbereiding; eerst data-integratie met Invantive Estate draaiend.
Confluence Live Vervangen door kennismodule.
Declaree Kandidaat Na boekhouding.
Discourse Out-of-scope Geen realistisch alternatief op Odoo.
DizzyData Kandidaat Voorbereiding; zie Exact Online.
Dlvr.it Gepauzeerd De Social Marketing app lijkt te buggy als betrouwbaar alternatief. In loop van jaar kijken of kwaliteit verbeterd is.
Exact Online Kandidaat Voorbereiding; eerst data-integratie met Invantive Estate en Exact Online draaiend voor geleidelijke invoering.
Invantive Estate Out-of-scope Geen realistisch alternatief op Odoo.
JIRA Service Desk Live Vervangen door projectenmodule.
JIRA Software Live Vervangen door projectenmodule.
Sendinblue Chat Out-of-scope Standaard Odoo Chat is functioneel en technisch te beperkt voor zinvolle chatsupport.
Teams Out-of-scope Voorlopig out-of-scope gezien hoge kwaliteits- en real-time eisen, en beperkte support.

Uitdaging: per abuis data geladen

Tijdens de implementatie zijn ook de agenda’s van alle medewerkers gekoppeld om op termijn te pogen ook Calendly te vervangen door Odoo. Conform projectdoelstelling zijn de boekjes en video’s van Odoo niet uitgebreid bestudeerd vooraf en is de integratie aangezet. Die bleek wonderbaarlijk eenvoudig te maken en ook goed te werken.

Een zij-effect was dat ook contacten geladen worden indien ze betrokken zijn bij een agenda-afspraak. Op zich begrijpelijk; het geeft een beter beeld van de organisatie. Maar eigenlijk was het de bedoeling om via een data-integratie met Invantive Estate het relatiebestand automatisch te synchroniseren met Odoo.

Wat zijn Invantive Estate en Invantive Studio?

Invantive Estate is een back-office systeem, dat Invantive’s administratieve proces centraal aanstuurt en vastlegt, in combinatie met een geïntegreerd real-time datawarehouse. Invantive Estate is technologisch gebaseerd op Invantive Studio; Invantive Studio is vergelijkbaar met de core laag van Odoo waar op basis van metadata bedrijfslogica, data-elementen en processen gedefinieerd worden die automatisch leiden tot een werkende bedrijfsapplicatie met eenzelfde lagenstructuur als Odoo.

Er zijn echter verschillen tussen Invantive Estate en Odoo, en de onderliggende lagen.

Details

Het voornaamste verschil is het databaseplatform: Invantive Estate werkt op basis van Oracle RDBMS, terwijl Odoo zich baseert op PostgreSQL. Ook ligt het accent van Invantive Studio meer op compliance en regulatory reporting gezien de typische toepassingsgebieden met real-time datawarehousing en tijdreisfuncties, terwijl Odoo Studio veel beter is in het modelleren van user interfaces.

Compliance is belangrijk, maar vooral voor persoonsgegevens en financiële transacties. Odoo biedt geen tijdreisfuncties, maar biedt zowel qua diepte als breedte een aanmerkelijk betere audittrail dan Exact Online, waardoor Invantive bij de overstap ook verbeteringen kan realiseren in het In-Control zijn (waarbij er nul ambitie is om dikke ISO-boeken vol te schrijven).

Invantive Studio is voornamelijk actief gebleven als Microsoft Windows-product in combinatie met Oracle, terwijl Odoo zich verder ontwikkeld heeft richting webtechnologie. Dat is ook te zien aan de user interfaces.

Invantive Studio is traditioneel:

Terwijl Odoo Studio en apps vooral browser-gebaseerd werken:

Een van de feestjes van herkenning was ook dat zelfs concepten dezelfde namen hadden, zoals “Aanvullende Bedrijfsregels”.

“Aanvullende Bedrijfsregels” in Invantive Estate zien er als volgt uit:

En in Invantive Studio als:

En in Odoo is de inhoud vergelijkbaar:

Contactpersonen in lijn brengen

De contactpersonen die in Odoo geladen zijn via de agenda’s van de medewerkers willen we graag in lijn brengen met de contactpersonen zoals ze geregistreerd staan in het centrale back-office Invantive Estate. Dit betekent dat er een relatie tussen de contactpersonen in de twee pakketten dient te komen, plus een stukje aanvulling en/of opruiming.

Een contact in Odoo kent twee vormen:

  • een bedrijf
  • een persoon

Via een keuzevlaggetje wordt dat aangegeven op de contactenkaart. Deze gegevens zijn gesplitst in Invantive Estate tussen “Organisaties” en “Personen”.

De data-integratie van het leidende Invantive Estate richting Odoo vereist dus het samenvoegen van twee soorten gegevens in Odoo contacten. De relatie dient uiteindelijk te zorgen dat elk lid van de groep van personen en organisaties in Invantive Estate maximaal (en bij voorkeur “precies”) één keer voorkomt in de Odoo. Andersom dient elke contact in Odoo te relateren naar precies één persoon of precies één organisatie in Invantive Estate.

Kortstondig kunnen contacten in Odoo ontstaan die nog niet bestaan in Invantive Estate. Door het ontbreken van de relatie kunnen die geïdentificeerd worden en vervolgens samengevoegd met een bestaande relatie, verwijderd in Odoo of opgevoerd in Invantive Estate.

Het leggen van de relatie gebeurt voorlopig alleen aan de zijde van Odoo door een veld toe te voegen op contacten. Dit veld bevat een unieke referentie naar het bronsysteem Invantive Estate.

Voor organisaties uit Invantive Estate is dat de tekst “LVR-”, plus de waarde van de technische sleutel van de organisatie. Voor personen uit Invantive Estate is dat de tekst “GBR-”, plus de waarde van de technische sleutel van de persoon. Door te kiezen voor de technische sleutel blijft identificatie mogelijk, ook als de naam van een persoon of organisatie wijzigt.

De kuising van reeds geladen contacten

In Odoo stonden plotsklaps circa 1.000 contacten, zonder relatie naar Invantive Estate. Om de levensvatbaarheid van data-integratie met Invantive UniversalSQL op Odoo te testen is een kopie gemaakt van de productiedatabase. Dit duurde enkele minuten.

Vervolgstap was om via Odoo Studio op contacten een veld toe te voegen op het model res.partner. Het veld heet x_studio_orig_system_reference en is maximaal 240 tekens lang:

image

De veldwaarde wordt niet gekopieerd bij kopiëren en is bewust wijzigbaar gehouden voor alle gebruikers totdat de keten strak draait. Het veld is geïndexeerd omdat er vaak gezocht zal worden met een puntquery of een bepaalde waarde al bestaat.

Een programmatische controle op uniciteit is toegevoegd als geautomatiseerde actie zodat er geen dubbelen kunnen ontstaan zonder een foutmelding op te wekken:

met de volgende code:

newosr = str(record.x_studio_orig_system_reference)
if newosr is not None:
    existing_records = env['res.partner'].search([('x_studio_orig_system_reference', '=', newosr)])
    if len(existing_records) > 1:
        raise UserError('OSR value ' + newosr + ' must be unique.')

Op Invantive is vervolgens een database gedefinieerd die zowel de Odoo-backup bevat (met alias ‘odoob’) als Invantive Estate (met alias ‘ora’):

image

In deze database is ook het eigen veld terug te vinden op de tabel res.partner die Invantive UniversalSQL beschikbaar stelt voor het gelijknamige Odoo-model:

Vervolgens is met het volgende blok Invantive PSQL voor alle in Odoo-geregistreerde contactpersonen het veld x_studio_orig_system_reference gevuld met de waarde van een persoon uit Invantive Estate met hetzelfde e-mailadres:

begin
  for r in
  ( select pnr.id
    ,      'GBR-' || to_char(gbr.gbr_id) orig_system_reference
    from   res.partner@odoob pnr
    join   bubs_gebruikers_v@ora gbr
    on     gbr.gbr_email_adres = pnr.email
    where  pnr.company_type = 'person'
    and    pnr.x_studio_orig_system_reference is null
    order
    by     pnr.id
  )
  loop
    update res.partner@odoob
    set    x_studio_orig_system_reference = r.orig_system_reference
    where  id = r.id
    ;
  end loop;
end;

Het bleek dat circa 85% van alle contactpersonen zo herkend konden worden. Het restant bleek uit verschillende groepen te bestaan:

  • bekende contactpersonen die meerdere e-mailadressen hanteren; deze zijn in Odoo samengevoegd onder het leidende e-mailadres met de “Samenvoegen” actie.
  • contactpersonen die niet in het back-office systeem stonden, maar wel in de agenda’s. Deze zijn toegevoegd aan Invantive Estate.

Labels voor Odoo-contacten

Binnen Invantive wordt intensief gebruik gemaakt van “labels”. Labels worden gebruikt om flexibel objecten te kenmerken, zodat bijvoorbeeld een e-mail over een driver specifiek en gericht gestuurd kan worden aan personen die er ook waarschijnlijk ook echt baat bij kunnen hebben.

De labels worden in Invantive Estate vastgelegd als “Klassificaties”. Klassificaties in Invantive Estate hebben een boomstructuur, waarbij de decimale punt een dieper niveau aangeeft. Een voorbeeld: de klassificatie “Organisatie.School.Basis” heeft drie niveau’s die steeds verder inzoomen:

  • het betreft een organisatie,
  • die ook bovendien een school is, en
  • nog sterker ook een basisschool is.

Odoo werkt op verschillende delen van de apps ook met labels. Technisch heten ze soms anders (“categorieën”, “tags”,etc.) en soms kennen ze een boomstructuur en soms is het gewoon een platte lijst.

Voor contacten in Odoo zijn er labels met een boomstructuur.

De volgende SQL-code laadt een aantal hoofdniveau’s en vervolgens de bijbehorende waardes uit Invantive Estate in Odoo:

create or replace table PartnerCategories1@InMemoryStorage
as
select 'KLE-' || to_char(kle.kle_id) x_studio_orig_system_reference
,      true Active
,      kle.kle_code_sub name
from   bubs_klassificaties_v@ora kle
where  1=1
and    kle.kle_diepte = 1
and    ... criteria op welke kle.kle_code ...

insert into res.partner_category@odoop
( x_studio_orig_system_reference
, Active
, name
)
select x_studio_orig_system_reference
,      Active
,      name 
from   PartnerCategories1@InMemoryStorage

create or replace table PartnerCategories2@InMemoryStorage
as
select 'KLE-' || to_char(kle.kle_id) x_studio_orig_system_reference
,      true Active
,      kle.kle_code_sub name
,      pnt.id parent_id
--
, kle_code
, pnt.name
, pnt.parent_id_label
from   bubs_klassificaties_v@ora kle
join   res.partner_category@odoop pnt
on     pnt.name = ... filter op kle.kle_code ...
where  1=1
and    kle.kle_diepte = 2
and    ... filter op kle.kle_code ...

insert into res.partner_category@odoop
( x_studio_orig_system_reference
, Active
, name
, parent_id
)
select x_studio_orig_system_reference
,      Active
,      name 
,      parent_id
from   PartnerCategories2@InMemoryStorage

Hierbij wordt het laden eenmalig uitgevoerd via het insert-statement.

Het is ook mogelijk om hiervoor het synchronize-statement te gebruiken:

synchronize PartnerCategories1@InMemoryStorage
to          res.partner_category@odoop
with        insert or update
identified by name
continue on first 500 errors

Hierbij worden ook wijzigingen bijgewerkt en het aantal API-calls beperkt door alleen wijzigingen uitgevoerd worden als dat nodig is.

De resulterende lijst van contactlabels in Odoo ziet er dan bijvoorbeeld zo uit:

Testomgeving

Met Odoo is het mogelijk om binnen enkele minuten zonder bijkomende kosten een representatieve testomgeving te maken. Dat is absoluut een voordeel bij het testen van data-integratie; hierdoor kan de programmatuur beter getest worden en is er minder kans op datafouten bij de in de productiename.

Contacten

Al met al verliepen deze stappen voorspoedig. Het laden van de Organisaties en Personen vanuit Invantive Estate in Odoo is vervolgens uitgevoerd. Voorbereidende stappen gebeurden door onderstaande code.

Merk op dat Odoo alleen de selectie van talen bij contacten toestaat die ook geïnstalleerd zijn, terwijl Invantive Estate elke taal toestaat.

Merk verder op dat Odoo het toestaat om detaildata mee te laden met een contact, zoals de labels. In twee in-memory tabellen gke en lke worden daarom vooraf alvast een lijst van de labels per persoon en organisatie klaargezet.

--
-- Odoo chooses contact language from installed languages, whereas
-- Invantive Estate allows any language.
--
create or replace table lang@InMemoryStorage
as
select code
,      substr(code, 1, 2) lge_code
from   lang@odoop

--
-- Labels on persons per person.
--
create or replace table gke@inmemorystorage
as
select gke.gbr_id
,      listagg(distinct pcy.id, ', ') category_id
from   bubs_gbr_kle_v@ora gke
join   bubs_klassificaties_v@ora kle
on     kle.kle_id = gke.kle_id
join   res.partner_category@odoop pcy
on     pcy.display_name = replace(kle.kle_code, 'Invantive.', '', '.', ' / ')
group
by     gke.gbr_id

--
-- Labels on organisations per organisation.
--
create or replace table lke@inmemorystorage
as
select lke.lvr_id
,      listagg(distinct pcy.id, ', ') category_id
from   bubs_lvr_kle_v@ora lke
join   bubs_klassificaties_v@ora kle
on     kle.kle_id = lke.kle_id
join   res.partner_category@odoop pcy
on     pcy.display_name = replace(kle.kle_code, 'Invantive.', '', '.', ' / ')
group
by     lke.lvr_id

create or replace table gbr@InMemoryStorage
as
select gbr.*
,      'LVR-' || to_char(gbr.lvr_id) lvr_osr
from   bubs_gebruikers_v@ora gbr

Ook al zijn bedrijven en contactpersonen verenigd in 1 tabel, dan nog is het nodig om eerst de bedrijven te laden in Odoo. Contactpersonen kunnen anders niet meteen gekoppeld worden aan hun bedrijf.

De volgende tabel zet de organisatiedata uit Invantive Estate klaar voor het laden als bedrijven in Odoo:

create or replace table PartnerSoll1@InMemoryStorage
as
select 'company' company_type
,      'LVR-' || to_char(lvr.lvr_id) x_studio_orig_system_reference
,      lvr.lvr_naam
       name
,      lvr.lvr_email_adres email
,      lvr.lvr_website_url website
,      l.code lang
,      lvr.lvr_werk_tel phone
,      lvr.lvr_mobiel_tel mobile
,      case
       when lvr.lvr_btw_nummer = 'UNKNOWN'
       then null
       when lvr.lvr_btw_nummer = 'N/A'
       then null
       else lvr.lvr_btw_nummer
       end
       vat
,      case
       when lvr.lvr_kvk_nummer = 'UNKNOWN'
       then null
       else lvr.lvr_kvk_nummer
       end
       l10n_nl_kvk
,      null function
,      lvr.lvr_adres_regel_1 street
,      lvr.lvr_adres_regel_2 street2
,      lvr.lvr_plaats city
,      cse.id state_id
,      lvr.lvr_postcode zip
,      cty.id country_id
,      lke.category_id
,      null parent_id
from   bubs_leveranciers_v@ora lvr
left
outer
join   lke@inmemorystorage lke
on     lke.lvr_id = lvr.lvr_id
left
outer
join   lang@InMemoryStorage l
on     l.lge_code = lvr.lge_code
left
outer
join   country@odoop cty
on     cty.code = lvr.lvr_land
left
outer
join   ( select * from country_state@odoop cse ) cse
on     cse.country_id = cty.id 
and    cse.code = lvr.lvr_provincie
where  1=1

In de bovenstaande code wordt country_state in een inner-view uitgerekend om te voorkomen dat een bug in de huidige Odoo-driver van Invantive tot een error leidt bij het toepassen van een join set.

Deze tabel kan met een synchronize naar res.partner geladen worden in Odoo.

De personen in Invantive Estate kunnen als contactpersonen geladen worden via de volgende tabel:

create or replace table PartnerSoll2@InMemoryStorage
as
select 'person' company_type
,      'GBR-' || to_char(gbr.gbr_id) x_studio_orig_system_reference
,      replace(trim(gbr.gbr_voornaam || ' ' || gbr.gbr_tussenvoegsel || ' ' || gbr.gbr_achternaam), '  ', ' ') name
,      gbr.gbr_email_adres email
,      gbr.gbr_website_url website
,      l.code lang
,      gbr.gbr_werk_tel phone
,      gbr.gbr_mobiel_tel mobile
,      null vat
,      null l10n_nl_kvk
,      gbr.gbr_functie function
,      gbr.gbr_adres_regel_1 street
,      gbr.gbr_adres_regel_2 street2
,      gbr.gbr_plaats city
,      cse.id state_id
,      gbr.gbr_postcode zip
,      cty.id country_id
,      gke.category_id
,      pnt.id parent_id
from   gbr@InMemoryStorage gbr
left
outer
join   res.partner@odoop pnt
on     pnt.x_studio_orig_system_reference = gbr.lvr_osr
left
outer
join   gke@inmemorystorage gke
on     gke.gbr_id = gbr.gbr_id
left
outer
join   lang@InMemoryStorage l
on     l.lge_code = gbr.lge_code
left
outer
join   country@odoop cty
on     cty.code = gbr.gbr_land
left
outer
join   ( select * from country_state@odoop cse ) cse
on     cse.country_id = cty.id 
and    cse.code = gbr.gbr_provincie
where  1=1

Prestaties

Bij het laden is voor de eerste groep van 30.000 contacten de eigen API-limiet van 60 calls per minuut aangehouden. Dit duurde echter lang, ook al werden de labels meteen geladen.

Odoo biedt geen externe API waarmee meerdere toevoegingen en/of bijwerkingen gebundeld als één API-call opgestuurd kan worden. Daarom is na 30.000 contacten de keuze gemaakt om de eigen API-limiet uit te schakelen. Er zijn nogmaals pakweg 100.000 contacten zonder limiet. De laadsnelheid werd hierbij circa 12x hoger (12 contacten per seconde in serieel uitgevoerde code). Ook bij dit grotere volume aan mutaties werd het gebruik van de externe API niet beperkt of geblokkeerd door Odoo.

Voor de toekomst overwegen we nog of en hoe het aantal API-calls per minuut beperkt moet worden. Odoo heeft blijkbaar een meer dan gemiddeld efficiënte implementatie van de API, die hoge aantallen transacties aan kan.

Odoo en Exact Online vergeleken

Het laden van de labels, personen en organisaties vanuit Invantive Estate in Odoo met een Invantive data-integratie query is bijzonder voorspoedig verlopen.

Op Exact Online kostte een vergelijkbare laadactie circa 60x zo veel doorlooptijd door API-limieten, bugs en functionele beperkingen in Exact Online. Het verschil in benodigde hoeveelheid manuren op Exact Online t.o.v. Odoo voor de automatische integratie van organisaties en personen was kleiner; er was ongeveer het 15-voudige aantal manuren nodig om het relatiebestand in lijn te krijgen op Exact Online ten opzichte van Odoo.

Vooral het ontbreken van bugs in het algemeen en onverklaarbare wijzigingen in het bijzonder viel op bij het laden van Odoo. Dit was wel een zorgpunt vooraf; Odoo is altijd vrij technisch georiënteerd geweest en de zorg was dat we met het matige Odoo-supportkanaal maanden in gesprek moesten gaan om bugs opgelost te krijgen. Het bleek echter dat Odoo qua externe API bijzonder stabiel en voorspelbaar werkt, zeker vergeleken met Exact Online.