CRM migration

Migrate from Function 365 to Odoo CRM

Field-level mapping, validation, and rollback between Function 365 and Odoo CRM. We move data and schema; workflows are rebuilt natively in Odoo CRM.

Function 365 logo

Function 365

Source

Odoo CRM

Destination

Odoo CRM logo

Compatibility

92%

11 of 12

objects map 1:1 between Function 365 and Odoo CRM.

Complexity

BStandard

Timeline

48–72 hours

Rollback included Accuracy guarantee Field-level validation

Overview

What this migration involves

Function 365 (Microsoft Dynamics 365 CRM) stores data across the Dynamics 365 data model: Contact, Account, Opportunity, and Activity entities with system fields like ownerid and statecode. Odoo CRM consolidates contacts and companies into res.partner (using partner_type to distinguish individuals from organizations), uses crm.lead for both raw leads and qualified opportunities (distinguished by type field), and stores activities as mail.message and mail.activity records linked by res_id. We map Contact rows to res.partner with partner_type='contact', Account rows to res.partner with partner_type='company', and Opportunity rows to crm.lead with type='opportunity'. Function 365's activityParty records (calls, emails, meetings) become mail.activity entries with activity_type_id matching Odoo's standard types. Owner resolution matches by email against res.users. Workflows, business rules, and plugin steps in Dynamics 365 do not migrate — we export workflow definitions as a SharePoint list your Odoo administrator can reference during Flow rebuild. The migration runs via Odoo's xmlrpc/JSON-RPC API against your target Odoo instance, with batch processing for large record sets.

Field-level fidelity

Every standard and custom field arrives verified.

Schema-aware mapping

AI proposes the map; you confirm before any record moves.

Relationships preserved

Parent–child, lookups, and ownership stay linked.

Full activity history

Calls, emails, meetings — with original timestamps.

Attachments & notes

Documents, uploads, and inline notes move with the record.

Why teams make this switch

Two sides of the same decision

Leaving

Function 365 logo

Function 365

What's pushing teams away

  • Functional Medicine + private-healthcare niche means general medical practices, NHS-primary settings, or non-UK clinics often have a tighter fit with Cliniko, Pabau, or country-specific PMS.
  • Implementation requires a paid specialist session (£55/session) plus optional onsite training (£350) — small clinics that expected pure self-serve may find the onboarding gate frustrating.
  • Smaller installed base than Cliniko, Pabau, or Halaxy means fewer integrations, fewer third-party services, and less peer benchmarking for procurement.
  • No public API documentation surfaced in research; integration with lab vendors, payment processors, or downstream EHRs may require vendor coordination.
  • Solo Practitioner tier (£132/month) is steeper than freemium-style PMS competitors; smallest practices may find the entry price hard to justify against single-clinician alternatives.

Choosing

Odoo CRM logo

Odoo CRM

What's pulling them in

  • Teams choose Odoo CRM for its modular architecture — one base install with one-click app additions means they can adopt CRM alone and add accounting, inventory, or sales later as the business grows.
  • Small businesses pick Odoo because the Community edition is free and open-source, with no per-user or contact limits, allowing full evaluation before committing to a paid Enterprise tier.
  • The drag-and-drop Kanban pipeline and AI lead scoring are highlighted across G2 reviews as concrete features that make lead management faster and more visual than spreadsheet-based workflows.
  • Odoo's native integration with email, live chat, SMS, VoIP, and WhatsApp means inbound leads from multiple channels feed into a single pipeline without third-party middleware.
  • Companies in retail, supply chain, and construction value that Odoo's CRM module shares the same PostgreSQL database and UI as its ERP modules, eliminating data silos between sales and operations.

Object mapping

How Function 365 objects map to Odoo CRM

Each row shows how a Function 365 object lands in Odoo CRM, including any object-level transformations, lookup resolution, or schema-design dependencies.

Typical mapping — final map is confirmed during the sample migration step.

Function 365

Contact

maps to

Odoo CRM

res.partner

1:1
Fully supported

Function 365 Contact rows become res.partner records with partner_type set to 'contact'. The contact's primary account lookup (parentcustomerid in Dynamics 365) maps to the parent_id field on the res.partner record, establishing the relationship between individual contacts and their employing organizations. Email addresses, phone numbers, street addresses, city, state, and postal code fields map directly to their corresponding res.partner field names, preserving all standard contact information during the transformation process.

Function 365

Account

maps to

Odoo CRM

res.partner

1:1
Fully supported

Function 365 Account rows become res.partner records with partner_type='company'. The company name maps to name, website maps to website, and industry code maps to industry. Child Contact records under an Account receive parent_id pointing to the newly created company partner, preserving the account-to-contact hierarchy in Odoo's partner structure.

Function 365

Opportunity

maps to

Odoo CRM

crm.lead

1:1
Fully supported

Function 365 Opportunity rows map to crm.lead records where type='opportunity'. The accountid lookup resolves to the target res.partner (company) record. Estimatedvalue maps to planned_revenue; closeprobability maps to priority as a percentage. The original opportunityid is preserved in x_source_opportunity_id for reconciliation.

Function 365

Lead

maps to

Odoo CRM

crm.lead

1:1
Fully supported

Function 365 Lead rows map to crm.lead records where type='lead'. LeadSource maps to source_id (linking to crm.tracking.source). The lead's companyname field is used to either match an existing res.partner company or create a new one before the lead converts to opportunity.

Function 365

PhoneCall / Email / Meeting

maps to

Odoo CRM

mail.activity

1:1
Fully supported

Function 365 activityparty records (phonecall, email, appointment) become mail.activity entries. The activity_type_id is resolved by matching the Dynamics 365 activity type code: 1=Call, 2=Email, 3=Meeting. The res_id links to the target crm.lead or res.partner; the user_id links to the activity's owner.

Function 365

Annotation (Notes/Attachments)

maps to

Odoo CRM

ir.attachment

1:1
Fully supported

Function 365 Annotation records with notetype set to 'annotation' migrate as ir.attachment entries linked to the target model (crm.lead or res.partner) through the res_model and res_id fields. The original document body content and filename are preserved intact during the migration, and Odoo's standard attachment storage path is utilized on the destination instance for all migrated annotation files.

Function 365

SystemUser

maps to

Odoo CRM

res.users

1:1
Fully supported

Function 365 systemuser records are matched to Odoo res.users by email address (internalid). Unmatched users are flagged before migration; your team either creates the Odoo user first or assigns records to a fallback user. The user's full name populates the res.users name field.

Function 365

BusinessUnit

maps to

Odoo CRM

crm.team

1:1
Fully supported

Function 365 BusinessUnit rows map to crm.team records in Odoo, with each business unit becoming a separate CRM team. The business unit's name and member list (derived from systemuser positions within the business unit) populate the team name and team member structure in Odoo. All Opportunity records owned by a business unit are reassigned to the corresponding crm.team based on the opportunity's owning business unit lookup.

Function 365

Stage (ProcessStage)

maps to

Odoo CRM

crm.stage

1:1
Fully supported

Dynamics 365 pipeline stages defined in the ProcessStage entity are mapped value-by-value to Odoo crm.stage records within each corresponding crm.team. The stage's statecode (Active, Won, or Lost) maps to the folded and stage_type flags on the crm.stage record. The original sort order from Dynamics 365's ProcessStage stepid is preserved to maintain the kanban column sequence in Odoo's pipeline view.

Function 365

Custom Entity (x_ tables)

maps to

Odoo CRM

Custom x_ model in Odoo

1:1
Fully supported

Function 365 custom entities (tables created in the Power Platform or solution layers) require Odoo custom model creation via the ORM. We create the model with the appropriate _name, add x_ prefix fields matching the source schema, and set up the many2one relations to standard Odoo models before data loads.

Function 365

LeadToOpportunityProcess (workflow)

maps to

Odoo CRM

crm.lead convert wizard

1:1
Fully supported

Dynamics 365's LeadToOpportunityProcess plugin does not have a direct Odoo equivalent. The conversion logic (creating an Opportunity from a Lead, linking the Contact, setting initial stage) must be replicated using Odoo's crm.lead convert wizard after migration. We document the source conversion rules as a rebuild reference.

Function 365

Connection (record relationships)

maps to

Odoo CRM

mail.followers / res.partner relations

many:1
Fully supported

Function 365 Connections representing N:N record relationships used for customer partnerships and stakeholder tracking are merged into Odoo's mail.followers model for record following functionality, and partner_id relations on crm.lead for stakeholder association. Only connections that represent 'stakeholder' roles migrate as partner_ids on the opportunity record, ensuring that relevant business relationships are preserved in the target Odoo system.

Gotchas + challenges

What specifically takes care here

Platform-specific issues from each side, plus the pair-specific challenges that don't show up on either platform's page on its own.

Function 365 logo

Function 365 gotchas

High

AI-assisted notes are proprietary — verify clinical-record export coverage

High

NHS Number format must be preserved exactly

Medium

Implementation specialist time is paid extra at £55/session

Medium

GDPR consent timestamps are regulatory artefacts

Odoo CRM logo

Odoo CRM gotchas

High

Odoo.sh version gating blocks assisted migrations from trial

High

Enterprise modules fail to install on Community after database restore

Medium

Custom module view inheritance breaks between Odoo major versions

Medium

Custom fields risk losing their application context on Community

Low

API access for Community is gated behind the Custom Plan

Pair-specific challenges

  • Lead-to-Opportunity conversion logic is not portable

    Dynamics 365's LeadToOpportunityProcess plugin fires on lead qualification, creating an Opportunity record, linking the Contact, and initializing the sales process with configurable field mappings. Odoo handles this with a crm.lead convert wizard that your sales team runs manually or via an automated action. We preserve the source plugin's field-mapping configuration as a structured JSON document your Odoo administrator can reference when configuring the convert wizard — but the conversion logic itself must be rebuilt. This is not a data-migration problem; it is a workflow-rebuild requirement that must be planned alongside the data migration.

  • Dynamics 365 custom fields use logical names that Odoo must create explicitly

    Function 365 custom fields follow the Power Platform naming convention (e.g., new_customfield, new_registrationdate) and are stored as columns in the Dataverse table. Odoo requires explicit model creation via Python class definition and a data file registering each x_ field in ir.model.fields before data can load. If your Dynamics 365 instance has more than 20 custom fields on Contact, Account, or Opportunity, the Odoo model preparation step adds a planning session to your migration timeline. We provide a field inventory report from the source system before any Odoo model work begins.

  • Activity participation lists flatten in Odoo's single-owner model

    Dynamics 365 ActivityParty records support N-party activities — a meeting can list five contacts as optional attendees and two as required. Odoo's mail.activity model assigns a single user_id as responsible, with no native equivalent for the participationType (sender, toRecipient, optionalAttendee). We migrate the primary contact as the activity target (res_id/res_model) and store additional participants as a comma-separated list in the activity note field. If your team relies on tracking which internal users were required vs. optional attendees, that metadata requires a custom activity extension in Odoo.

  • Business unit hierarchy does not map to Odoo teams without manual configuration

    Dynamics 365 business units form a hierarchical security model — child business units inherit permissions from parents, and records owned by a child BU are visible to parent BU users based on cascading sharing rules. Odoo's crm.team model is flat — each team is independent and record rules are additive rather than hierarchical. Teams with a parent-child relationship in Dynamics 365 must be mapped as separate crm.team records in Odoo, and the visibility rules must be rebuilt as explicit record rules per team. We deliver a team-mapping plan before data loads so your Odoo admin can pre-create the team structure.

  • Notes with rich-text content require content-type handling

    Dynamics 365 Annotation records with isdocument=false store HTML content in the notetext field. Odoo's note.note model stores plain text by default. We convert HTML notes to Odoo's message_partner_ids-based communication log (mail.message) with subtype note on the parent record. Inline images in Dynamics 365 notes are downloaded and re-attached as ir.attachment entries linked to the target record. Formatting inconsistencies between the source HTML and Odoo's sanitized text output are documented in the field-level diff report.

Migration approach

Six steps for a successful Function 365 to Odoo CRM data migration

  1. Extract Function 365 data via Dataverse API

    We authenticate against your Dynamics 365 tenant using an Azure AD application registration (oauth2 with client credentials flow) and pull data via the Dataverse Web API. For each entity (Contact, Account, Opportunity, Lead, ActivityPointer, Annotation), we paginate usingodata.nextLink to handle large result sets. The extraction runs against a read-only application user to avoid disrupting active sessions. We log record counts per entity and flag any API throttling responses for retry with exponential backoff. The export produces a structured JSONL file per entity, timestamped and checksummed for integrity verification.

  2. Audit Odoo target schema and create custom models

    Before any data loads, we inspect the target Odoo instance using xmlrpc to read ir.model and ir.model.fields. We identify which standard fields already exist and which x_ custom fields need creation. For each Dynamics 365 custom entity, we generate a Python model definition and a CSV data file that creates the model via Odoo's module loading mechanism. We also query existing crm.stage and crm.team records to map the target pipeline structure. The schema plan is delivered as a human-readable document that your Odoo admin reviews and approves before FlitStack creates any fields.

  3. Run a sample migration with field-level diff

    A representative sample — typically 100–300 records per major entity — migrates first using Odoo's xmlrpc/jsonrpc endpoint. We write each record, then query it back and generate a diff showing source field value vs. destination field value. The diff report highlights: value-mapping mismatches (e.g., industry codes that didn't resolve), owner resolution failures (unmatched emails), and truncation issues on text fields with Odoo's character limits. Your team reviews the diff and approves field mapping adjustments before the full migration runs.

  4. Execute full migration with delta-pickup window

    The full dataset loads in batches of 500 records per entity using Odoo's create() method via the ORM API. Batch processing avoids transaction timeout on large inserts. After the initial load completes, we open a 24–48 hour delta-pickup window during which any records modified in Dynamics 365 are re-extracted and updated in Odoo using write() with the original source GUID preserved in x_ fields. A final reconciliation query counts source records vs. destination records per entity and surfaces any remaining delta records. The audit log records every create and write operation with timestamp and operator.

  5. Deliver reconciliation report and rollback playbook

    The final deliverable is a reconciliation report showing: record counts per entity (source vs. destination), owner resolution rate (percentage of records with a matched Odoo user), delta records remaining, and field-level error summary. If reconciliation fails any SLA threshold, FlitStack runs a one-click rollback that deletes the migrated records and reverts the Odoo database to its pre-migration snapshot. The workflow export (Power Automate definitions) and lead-conversion mapping reference are delivered as SharePoint document attachments in the final report package.

Platform deep dives

Context on both ends of the pair

Function 365 logo

Function 365

Source

Strengths

  • Integrated PMS (booking, notes, prescriptions, billing, lab orders, telehealth) in one product.
  • GDPR and HIPAA support built into the data model.
  • Transparent per-licence published pricing on the vendor shop.
  • AI-assisted clinical note generation reduces practitioner admin time.
  • Tiered licence pricing rewards larger practices with lower per-seat cost.

Weaknesses

  • Niche fit (UK private healthcare + Functional Medicine) — not suited for NHS-primary or non-UK general practice.
  • Implementation specialist time billed separately (£55/session) plus £350 onsite training.
  • Smaller installed base than Cliniko/Pabau means thinner integration ecosystem.
  • No public API documentation visible in research.
  • Solo Practitioner price (£132/month) higher than some freemium-style PMS competitors.
Odoo CRM logo

Odoo CRM

Destination

Strengths

  • Modular open-source architecture lets teams start with CRM and add ERP apps as needs grow, all sharing one PostgreSQL database.
  • Free Community edition with no contact limits and full source code access means zero licensing cost for evaluation and small deployments.
  • Drag-and-drop Kanban pipeline with AI lead scoring gives a visual, prioritized view of the sales funnel without requiring custom configuration.
  • Native integrations with email, live chat, SMS, VoIP, WhatsApp, and social media feed all inbound leads into a single unified inbox.
  • Active Odoo Community Association (OCA) maintains dozens of community-maintained modules on GitHub for extended functionality.

Weaknesses

  • Gmail and email integration reliability is a recurring complaint — threads drop and conversations scatter across inboxes, disrupting sales team workflows.
  • Enterprise edition pricing stacks quickly: multiple apps at per-user rates ($25–$50/user/month) plus Odoo.sh hosting costs more than many SMBs anticipate.
  • Setup and configuration complexity increases significantly once custom fields, automation rules, and multiple installed modules are in play.
  • Odoo.sh trial databases run on a version (e.g., 18.3) that is not directly migratable to Odoo.sh, blocking the assisted migration path Odoo advertises.
  • Version upgrades between major Odoo releases (e.g., 17→18) frequently break custom module view definitions and XPath expressions, requiring manual remediation.

Complexity grading

How hard is this migration?

Standard CRM migration. All 8 core objects map 1:1 between Function 365 and Odoo CRM.

B

Overall complexity

Standard migration

Derived from compatibility, mapping clarity, API constraints, and data volume across Function 365 and Odoo CRM.

  • Object compatibility

    A

    All 8 core objects map 1:1 between Function 365 and Odoo CRM.

  • Field mapping clarity

    C

    Field mapping is derived from defaults — final spec confirmed during the sample migration.

  • Timeline complexity

    B

    8-object category — typical timelines run 2–7 days end-to-end.

  • API constraints

    B

    Function 365: Not publicly documented.

  • Data volume sensitivity

    B

    Function 365 doesn't expose a bulk API — REST + parallelization used for high-volume runs.

Estimator

Estimate your Function 365 to Odoo CRM migration cost

Rule-based pricing — no per-record fees, no manual quotes. Migrations over 2M records are scoped individually.

Step 1

What are you migrating?

Pick a category, then your source and destination platforms.

Category

FAQ

Frequently asked questions about Function 365 to Odoo CRM data migrations

Answers to the questions buyers ask most during Function 365 to Odoo CRM migration scoping. Not seeing yours? Book a call.

Can't find your answer?

Walk through your Function 365 to Odoo CRM migration with a real engineer — 30 minutes, free, written quote within 24 hours.

Book a free 30 minute consultation

Most Function 365 to Odoo CRM migrations complete in 48–72 hours of clock time for datasets under 50,000 records. Larger migrations with 500,000+ records or multiple custom entities extend to 5–10 days. The longest planning step is Odoo schema preparation — creating custom models and mapping pipeline stages to crm.team — which runs in parallel with data extraction and typically takes 1–3 days depending on custom field count. The delta-pickup window (24–48 hours) is included in the overall timeline.

Adjacent paths

Related migrations to explore

Ready when you are

Move from Function 365.
Land in Odoo CRM, intact.

Tell us record counts and timeline. We'll come back with a written quote inside 1 business day — no commitment, no sales pitch.

Accuracy guarantee Rollback included Quote in 1 business day