← Back to work
2023 · Salesforce → Odoo migration

DISH Digital Solutions

A multi-million-euro Salesforce stack rebuilt on Odoo 16 for METRO's restaurant-tech arm — the commercial backend that powers DISH for tens of thousands of independent restaurants across Europe. We led the feature recreation: FastAPI gateway, RabbitMQ event bus, Vonage telephony, sales flow and product catalogue.

Role
Feature recreation · API · integrations · sales flow
Duration
2023 – 2025, ~22 months
Team
1 Hazenfield engineer, embedded
PROJECT HERO · PLACEHOLDER
FIG. 01
Context

DISH is METRO's restaurant-tech arm — the SaaS platform tens of thousands of independent restaurants across Europe use to take reservations, build websites, run their menus and accept orders. BSS — Business Support Software — is the commercial backend behind it: subscriptions, billing, sales pipeline, customer support, telephony, the partner platform.

The brief was a Salesforce-to-Odoo migration: lift the entire commercial stack off a multi-million-euro-a-year subscription and rebuild it on top of Odoo 16. The savings argument was simple. The execution was not — over ninety custom Odoo modules, a parallel data-migration team, and a client engineering team spread across several vendors.

We led feature recreation from inside that team. Data migration was handled by a separate group; we did not own accounting. Everywhere else — the FastAPI gateway that other DISH tools call into, the RabbitMQ bus that keeps Odoo synchronised with its sibling systems, the Vonage telephony integration ported from Salesforce, the sales flow and signature consents, the product catalogue and subscription lifecycle — we wrote, maintained, or were the senior voice in the room.

We also pushed for engineering practices we'd brought from elsewhere: a unit-testing protocol that new modules had to clear before merge, sharper code-review workflow, and architecture decisions written into the repo where future teammates would actually find them.

Scope

What we built.

dish_api01

FastAPI REST gateway — the Odoo-outside contract every sibling system talks to. Maintained.

dish_rabbit_mq02

RabbitMQ publish / subscribe; keeps Odoo and its sibling services in sync. Maintained.

dish_vonage03

Vonage telephony, ported from a Salesforce integration: dialler, recording, presence.

dish_vonage_user_helpdesk04

Queue presence, routing and channel-closure webhooks for the helpdesk team.

dish_sale + siblings05

Sales order flow end-to-end on Odoo: quoting, signature consents, project handover, referral attribution.

dish_sales_settings06

Cross-module sales configuration surface.

dish_product + dish_product_catalog07

Product master and the catalogue exposed to sales and the API.

dish_product_subscription08

Subscription lifecycle: billing cycle, renewal, cancellation.

dish_product_upgrade_downgrade09

Plan changes that respect billing periods, with prorated charges where they belong.

dish_crm10

CRM extensions shaped for the marketplace. Maintained.

dish_partner_handshakes11

Cross-system partner identity reconciliation. Maintained.

dish_send_message + dish_whatsapp_widget12

Outbound messaging surfaces. Maintained.

Testing protocol13

Unit-testing gate every new module had to clear before merge, with coverage thresholds enforced in CI.

Approach

What the work looked like, in four pieces.

01

Feature parity, not Odoo defaults

Recreated the live Salesforce experience inside Odoo, module by module — sales, products, CRM, helpdesk, telephony — while a parallel team moved the data. The goal wasn't 'Odoo's idea of a sales flow' but DISH's, with the keystrokes and shortcuts the agents already knew.

02

API & event-driven plumbing

dish_api is the FastAPI REST gateway: JWT auth, dependency injection, services and controllers cleanly split, an OpenAPI contract the calling teams wire against without touching Odoo. dish_rabbit_mq is the event side — Odoo publishes domain events, sibling services consume them, with a mass-publishing path so backfills do not tail-load the broker.

03

Vonage, ported off Salesforce

Salesforce had handed Vonage a tidy integration; we put it back together on Odoo. Two modules — dish_vonage for the core (dialler, recording, presence) and dish_vonage_user_helpdesk for the routing, queue presence and channel-closure webhooks the helpdesk team relies on. The webhooks are under test so a Vonage-side rename doesn't break the dialler silently.

04

Engineering practices, raised

With a dozen vendors in the same codebase, the work survives only if the rules are written down. We brought a unit-testing protocol new modules had to clear before merge, a code-review template with the questions reviewers should actually ask, and architecture decisions kept in the repo rather than buried in a Confluence search.

Engineering highlights

A handful of the solves we are proudest of.

01

FastAPI gateway as the Odoo-outside contract

dish_api is the FastAPI surface every other DISH service calls when it needs Odoo state. Built on the OCA fastapi module with our own dispatcher, services and controllers cleanly split; auth, caching, hooks and i18n in their own places; JWT auth gated by a fastapi_group. The OpenAPI spec is the contract — sibling teams wire against it without ever touching Odoo's ORM.

02

RabbitMQ pub / sub for cross-system sync

Odoo is the system of record for partners, contacts and subscriptions, but sibling services need to know when those change. dish_rabbit_mq publishes the events and subscribes to inbound ones. Edge cases learnt by iteration: archival actions that fired on every record (tightened by automated-action domains), establishment-and-contact deletes that selected the wrong records (fixed), and a mass-publishing path so a backfill doesn't tail-load the broker one event at a time.

03

Vonage telephony, ported off Salesforce

Salesforce had handed Vonage a tidy integration; we had to put it back together on Odoo. Two modules — dish_vonage for the core (dialler, recording, presence) and dish_vonage_user_helpdesk for routing, queue presence and channel-closure webhooks. Webhooks come under test so a Vonage-side rename doesn't break the dialler silently.

04

Sales flow recreation

DISH's sales agents had a particular shape to their day in Salesforce — quoting, signature consents, downstream project handover, referral attribution. dish_sale and its siblings reproduce that flow inside Odoo's sale.order with the same shortcuts and the same downstream consequences. The handover into dish_sale_project closes the loop.

05

Subscription lifecycle with plan changes

A B2B SaaS lives by its subscriptions. dish_product_subscription owns the lifecycle; dish_product_upgrade_downgrade owns plan changes that respect billing periods — no surprise mid-cycle charges, prorated where it makes sense — with a test suite that pins the billing maths.

06

Engineering practices, written into the repo

Unit-testing protocol imported from prior work: new modules need a real test suite before merge; coverage thresholds enforced in CI. Code-review template with the questions we wished reviewers were asking. Architecture decisions kept in the repo so they could be evolved rather than lost.

Outcomes

A few shapes, in their raw form.

Multi-€M
Annual licence retired
95
Custom Odoo modules in BSS
7
Modules under our maintainership
~22 mo
Embedded with the team

Stack
Odoo 16PythonFastAPIPostgreSQLRabbitMQVonageDockerOpenAPI

Have a project that deserves this kind of care?

Start a conversation