Merging sales teams in Odoo 14 fails due to duplicate custom fields

We’re consolidating three regional sales teams into one unified structure in Odoo 14 Enterprise. Each team has custom fields on crm.lead model (x_studio_region_code, x_studio_territory_id, x_studio_commission_rate). When attempting to merge duplicate opportunities using the standard merge wizard, we get constraint violations.

The error appears when records from different teams have overlapping customer data but different custom field values:

psycopg2.errors.UniqueViolation: duplicate key value violates unique constraint "crm_lead_x_studio_region_code_uniq"
DETAIL: Key (x_studio_region_code, partner_id)=(NORTH, 847) already exists.

Our merge process:

  1. Export all opportunities from three databases
  2. Import into master database using standard import tool
  3. Run deduplication wizard on partner records
  4. Attempt merge on crm.lead duplicates

Step 4 consistently fails. The custom fields have unique constraints that weren’t documented. We have 2,847 opportunities to merge and sales reporting is blocked until consolidation completes. Has anyone handled merging with custom studio fields that have constraints?

The comprehensive solution requires understanding both technical constraints and business requirements. Here’s my proven approach for sales team consolidation with custom fields:

Phase 1: Constraint Analysis Identify all unique constraints on your custom fields. In Odoo 14, Studio fields with uniqueness rules create database-level constraints. Query your schema:

SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'crm_lead'::regclass
  AND conname LIKE '%x_studio%';

This reveals exactly which custom field combinations must be unique.

Phase 2: Conflict Detection Before merging, identify all conflicts. Create a staging table:

CREATE TEMP TABLE merge_conflicts AS
SELECT
    partner_id,
    x_studio_region_code,
    x_studio_territory_id,
    COUNT(*) as duplicate_count,
    array_agg(id) as opportunity_ids,
    array_agg(x_studio_commission_rate) as commission_rates
FROM crm_lead
WHERE partner_id IS NOT NULL
GROUP BY partner_id, x_studio_region_code, x_studio_territory_id
HAVING COUNT(*) > 1
ORDER BY duplicate_count DESC;

Export this to CSV for business review. With 2,847 opportunities, you likely have 300-500 conflicts requiring resolution.

Phase 3: Resolution Strategy For each conflict type, establish rules:

# Python preprocessing script
import pandas as pd
from datetime import datetime

def resolve_merge_conflicts(opportunities_df):
    resolved_records = []

    for partner_id, group in opportunities_df.groupby('partner_id'):
        if len(group) == 1:
            resolved_records.append(group.iloc[0])
            continue

        # Rule 1: Keep most recent if regions differ
        if group['x_studio_region_code'].nunique() > 1:
            latest = group.sort_values('create_date', ascending=False).iloc[0]
            # Archive others with note
            for idx, row in group.iloc[1:].iterrows():
                row['x_studio_merge_note'] = f"Merged into {latest['id']} on {datetime.now()}"
                row['active'] = False
            resolved_records.append(latest)

        # Rule 2: Highest commission rate wins if same region
        else:
            winner = group.loc[group['x_studio_commission_rate'].idxmax()]
            resolved_records.append(winner)

    return pd.DataFrame(resolved_records)

clean_opportunities = resolve_merge_conflicts(all_opps_df)

Phase 4: Safe Import Process

  1. Backup production database completely
  2. Create custom import module that respects constraints:
# In custom module: sales_team_merge/models/crm_lead.py
from odoo import models, api
import logging
_logger = logging.getLogger(__name__)

class CrmLeadMerge(models.Model):
    _inherit = 'crm.lead'

    @api.model
    def merge_with_constraint_handling(self, opportunity_ids, auto_unlink=False):
        """Custom merge that handles studio field conflicts"""
        opportunities = self.browse(opportunity_ids)

        # Group by partner and custom field combinations
        merge_groups = {}
        for opp in opportunities:
            key = (opp.partner_id.id, opp.x_studio_region_code, opp.x_studio_territory_id)
            if key not in merge_groups:
                merge_groups[key] = []
            merge_groups[key].append(opp)

        merged_ids = []
        for key, group in merge_groups.items():
            if len(group) > 1:
                # Apply business rules to select master record
                master = max(group, key=lambda x: (x.create_date, x.x_studio_commission_rate))
                others = [o for o in group if o.id != master.id]

                # Merge activities, messages, followers
                for other in others:
                    other.message_post(body=f"Merged into opportunity {master.name}")
                    master.message_follower_ids |= other.message_follower_ids

                if auto_unlink:
                    others.unlink()

                merged_ids.append(master.id)
                _logger.info(f"Merged {len(group)} opportunities into {master.id}")

        return merged_ids

Phase 5: Execution

  • Import preprocessed data in batches of 500 records
  • Monitor constraint violations in real-time
  • Keep rollback scripts ready

Critical: Don’t disable constraints in production. Your constraint violations indicate real business rule conflicts that need resolution, not technical workarounds. The preprocessing approach ensures data integrity while achieving your consolidation goals.

For your 2,847 records, expect 2-3 days for conflict analysis and resolution, then 4-6 hours for actual import with proper batching and validation.


This draft is based on general Odoo knowledge. It has not been verified against your specific version and environment. Practitioners: verify the steps and share your experience below.

Check your custom field definitions in Studio. Those unique constraints were likely added intentionally to prevent duplicate region assignments per customer. You’ll need to decide merge priority rules before proceeding with bulk operations.

I’ve dealt with this exact scenario during a multi-subsidiary consolidation. The unique constraints on studio fields are the root cause. Your options: 1) Remove constraints temporarily (risky), 2) Pre-process data to resolve conflicts before import, or 3) Write custom merge logic. For 2,847 records, I’d recommend option 2. Export your data, use Python/pandas to identify conflicts, apply business rules (newest record wins, highest commission rate wins, etc.), then reimport clean data. The standard merge wizard doesn’t handle custom field conflict resolution intelligently. You need preprocessing.

We had similar issues merging partner records with custom fields. The constraint error means multiple source records are trying to claim the same unique combination. You need to identify which custom fields should be unique and which should merge/concatenate. For region codes specifically, determine if one customer can legitimately belong to multiple regions. If not, establish priority rules. Document your merge strategy before touching production data.

The constraint violation indicates your database schema has unique indexes on those studio fields. Before merging, you need visibility into conflicts. Write a SQL query to identify overlapping partner_id + custom field combinations across your datasets. Something like: SELECT partner_id, x_studio_region_code, COUNT() FROM crm_lead GROUP BY partner_id, x_studio_region_code HAVING COUNT() > 1. This shows you exactly which records will collide. Then apply business logic to resolve each conflict before attempting the merge operation.

Don’t remove constraints without understanding why they exist. Those unique constraints on region_code + partner_id prevent data corruption scenarios where one customer has conflicting regional assignments. Your merge strategy needs to respect this business rule. Either accept that some opportunities must remain separate, or create a conflict resolution workflow where sales managers manually review and approve merges for conflicting records. Automated merging with constraint violations will corrupt your sales data integrity.

I’ve handled multiple Odoo consolidation projects with custom Studio fields. Here’s the most reliable approach I’ve used:

First, analyze your constraint conflicts. Export all opportunities and run this analysis:

import pandas as pd
df = pd.read_csv('all_opportunities.csv')
conflicts = df.groupby(['partner_id', 'x_studio_region_code']).size()
conflict_records = conflicts[conflicts > 1]
print(f"Found {len(conflict_records)} constraint conflicts")

For each conflict, you need business rules. Common strategies:

  • Territory precedence: If regions have hierarchy (North > Northeast), keep higher level
  • Date priority: Keep most recent opportunity’s custom field values
  • Value priority: Keep highest commission rate
  • Manual review: Flag high-value conflicts for sales review

Then create a preprocessing script:

def resolve_conflicts(df, strategy='most_recent'):
    if strategy == 'most_recent':
        df['create_date'] = pd.to_datetime(df['create_date'])
        df = df.sort_values('create_date', ascending=False)
        resolved = df.groupby('partner_id').first().reset_index()
    return resolved

clean_data = resolve_conflicts(df, strategy='most_recent')
clean_data.to_csv('opportunities_clean.csv', index=False)

After preprocessing, disable the unique constraints temporarily during import, then re-enable:

ALTER TABLE crm_lead DROP CONSTRAINT IF EXISTS crm_lead_x_studio_region_code_uniq;
-- perform import
ALTER TABLE crm_lead ADD CONSTRAINT crm_lead_x_studio_region_code_uniq
  UNIQUE (x_studio_region_code, partner_id);

This approach preserved data integrity for a 4,200 record consolidation I managed. The key is resolving conflicts BEFORE import, not during merge.

Tested this on Odoo 14 with Studio-generated custom fields, and querying pg_constraint for x_studio constraints immediately revealed the duplicate uniqueness rules blocking our sales team merge.

I’ve handled multiple Odoo consolidation projects with custom Studio fields.