Sync Contacts App

Sync Odoo Contacts across independent databases on a daily schedule.

This module is designed for a closed, federated swarm, utilizing a batch + broadcast architecture where peers implicitly trust contact directory updates from the swarm.

What Gets Synced

Profiles and Localization: Core contact details (name, email, phone, website) and localization data (street, city, zip, country) are fully synchronized for records sharing a sync_uuid. Profile classifications (is_company, vat, job position) and state management (active status for archived contacts) sync seamlessly.

Merging and Rerouting: When a remote contact is merged into a local contact, appending its UUID to the surviving contact’s Sync UUID Aliases (sync_alias_ids) safely handles deduplication. The resolve_alias trait intercepts incoming payloads and transparently reroutes updates meant for the swallowed UUID to the surviving contact.

Avatars: Author avatars (image_1920) are transferred as CID (Content Identifier) blobs via the is_cid schema trait.

Hierarchy and Scoping: Parent company linkages (parent_id) are maintained. Contacts without a corresponding parent locally will safely ignore the linkage (on_miss: ignore). Updates are strictly scoped to the authoritative node (sync_author_node_id), meaning only the owning node is allowed to push changes to a contact. Sub-contacts dynamically inherit the author node of their parent.

Setup

1. Install sync_contacts

Install sync_contacts on both Odoo instances. It depends on sync_base and the standard contacts module.

Navigate to the /odoo/contacts/v1 topic and set the swarm_ids field to include the swarms you want to sync with. This will restrict contact federation to nodes in those swarms.

2. Shared UUIDs

When contacts are synced, they are mapped via sync_uuid. This UUID ensures that a contact created on Node A maps perfectly to a contact on Node B, avoiding duplicate records across databases.

Topic Reference

The /odoo/contacts/v1 topic orchestrates the synchronization behavior for Contacts using the following configurations:

  • Routing (broadcast): Events are pushed to the entire swarm so that the directory is uniformly updated across the network.

  • Export (batch): A daily cron job triggers the batch export logic. The export_domain restricts exports to:

    [
        ('sync_pending', '=', True),
        ('user_ids', '=', False),
        '|',
            ('active', '=', True),
            ('active', '=', False),
    ]
    
    • sync_pending: This field becomes True immediately upon creation (when sync_date is empty) and upon any modification (when Odoo updates the write_date past the sync_date).

    • user_ids: Excludes contacts tied to system users (internal Odoo users) by default. You can remove this constraint if you wish to sync user profiles.

    • active: The custom '|', active=True, active=False tuple bypasses Odoo’s default active test, ensuring archived contacts are synchronized across the network seamlessly.

  • Delivery (pull): Nodes receive the broadcasted event via standard message propagation.

  • Import (batch): The remote peer queues the batch payload and processes it on a scheduled cron, creating or updating contacts seamlessly.

  • Retention (auto_delete): A scheduled task garbage collects fully processed inbound and outbound events.

Schema reference

The $ prefix denotes JSON payload fields. The sync_contacts JSON schema uses a batched array format.

__root__ → sync_uuid (record identity)

Upsert key: find res.partner where sync_uuid = $sync_uuid. On miss: create a new record. On hit: update in-place. Scoping (for): strictly limit updates to records where sync_author_node_id matches the event sender node (__from__).

name, email, phone, website, is_company, vat, function, active, street, city, zip

Written directly from their respective $ fields.

sync_author_node_id

Written automatically on creation via the set_from_node trait, reading the network event’s sender. This drives the scoping constraint.

country_id

Lookup: res.country where code = $country_code. On miss: ignore.

parent_id

Lookup: res.partner where sync_uuid = $parent_uuid. On miss: ignore. The resolve_alias pre-hook trait intercepts payloads to remap swallowed UUIDs to survivor UUIDs before this lookup occurs.

image_1920

Written from $avatar_cid via the is_cid: store trait, which fetches the binary payload and base64-encodes it into the image field asynchronously.

License

LGPL-3

See also

sync_contacts — App — Technical Reference.