Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5da7451
base_import_match: Import deduplicator.
Mar 18, 2016
f535a34
base_import_match: Add conditionals to fields when importing.
May 12, 2016
b41e3e3
base_import_match: Small rst fixes in readme file
moylop260 May 26, 2016
dd1dc5f
Avoid UnicodeEncodeError. (#544)
yajo Sep 20, 2016
5800a79
base_import_match module
cubells Dec 15, 2016
3a950b2
[10.0][MIG][base_import_match] Migration and update
yajo Jun 22, 2017
b0f9c25
[MIG] base_import_match: Migration to 12.0
ernestotejeda Feb 8, 2019
15c0388
Translated using Weblate (Portuguese (Brazil))
Sep 3, 2019
f117068
[IMP] base_import_match: black, isort, prettier
Rad0van Jul 15, 2020
a78c583
[MIG] base_import_match: Migration to 13.0
Rad0van Jul 16, 2020
26f1949
[IMP] base_import_match: black, isort, prettier
jesusramoneda Feb 25, 2021
627ba4c
[MIG] base_import_match: Migration to 14.0
jesusramoneda Feb 25, 2021
13fe022
Translated using Weblate (Vietnamese)
minhdc Sep 25, 2021
b5076b1
[IMP] update dotfiles [ci skip]
OCA-git-bot Mar 31, 2022
b861382
[MIG] base_import_match: Migration to 15.0
augusto-weiss May 5, 2022
58f1890
[FIX] base_import_match: "Unable to use a closed cursor"
zaoral Sep 14, 2021
5d3a8dc
[MIG] base_import_match: Migration to 16.0
bvanjeelharia Mar 22, 2023
b0a7582
Translated using Weblate (Italian)
mymage Jun 21, 2023
5027a04
Translated using Weblate (Italian)
francesco-ooops Jul 25, 2023
f00ca27
Translated using Weblate (Spanish)
Ivorra78 Aug 25, 2023
9f51e56
[UPD] README.rst
OCA-git-bot Sep 3, 2023
967e284
Translated using Weblate (Italian)
francesco-ooops Sep 7, 2023
80bdcfa
Translated using Weblate (Italian)
mymage Jan 4, 2024
013a817
[IMP] base_import_match: pre-commit auto fixes
bizzappdev Apr 19, 2024
9ea94bf
[MIG] base_import_match: Migration to 17.0
bizzappdev Apr 19, 2024
2ef4ff3
[18.0][MIG] base_import_match: Migration to 18.0
ced-adhoc Jul 3, 2025
c2ab8a9
[UPD] Update base_import_match.pot
Sep 4, 2025
d766ff0
[BOT] post-merge updates
OCA-git-bot Sep 4, 2025
5e04b45
Translated using Weblate (Slovenian)
sysadminmatmoz Jan 22, 2026
d1a1771
[IMP] base_import_match: pre-commit auto fixes
AungKoKoLin1997 Jun 25, 2026
6c7846d
[MIG] base_import_match: Migration to 19.0
AungKoKoLin1997 Jun 25, 2026
c7f643a
[REF] base_import_match: make tests self-contained
AungKoKoLin1997 Jun 30, 2026
ca0b45e
[IMP] base_import_match: add Match column to import preview
AungKoKoLin1997 Jun 25, 2026
9f48040
[FIX] base_import_match: fix name reporting and cache invalidation
AungKoKoLin1997 Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions base_import_match/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
.. image:: https://odoo-community.org/readme-banner-image
:target: https://odoo-community.org/get-involved?utm_source=readme
:alt: Odoo Community Association

=================
Base Import Match
=================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:94003cb380d42369205cf91596be4d31ed1a09d2e9b62adcc3aceea3ec0fa750
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--backend-lightgray.png?logo=github
:target: https://github.com/OCA/server-backend/tree/19.0/base_import_match
:alt: OCA/server-backend
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/server-backend-19-0/server-backend-19-0-base_import_match
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/server-backend&target_branch=19.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

By default, when importing data (like CSV import) with the
``base_import`` module, Odoo follows this rule:

- If you import the XMLID of a record, make an **update**.
- If you do not, **create** a new record.

This module allows you to set additional rules to match if a given
import is an update or a new record.

This is useful when you need to sync heterogeneous databases, and the
field you use to match records in those databases with Odoo's is not the
XMLID but the name, VAT, email, etc.

After installing this module, the import preview UI adds a **Match**
column. Checking the box on a field means:

- Use it to **find existing records** (match key).
- **Do not write** its value during the actual import.

This lets you update records by matching on fields like name, email or
VAT without needing an XMLID in your file.

Pre-configured **Import Match** rules act as templates: their fields are
pre-checked in the import preview. You can toggle any field on or off
before importing, regardless of whether a rule exists.

When match-only fields are selected in the UI, matching uses all
selected fields together to search for a single existing record. If any
row fails to match exactly one record (zero or multiple matches), the
entire import is blocked and errors are shown for the affected rows.
This prevents accidental creation of duplicate records.

Note that conditional rules (fields with a required imported value) are
only applied during programmatic imports; the UI-driven matching uses
the selected fields unconditionally.

For programmatic imports (without UI), the configured rules are used
instead. The import logic in that case is:

- If you import the XMLID of a record, make an **update**.
- If you do not:

- If there are import match rules for the model you are importing:

- Discard the rules that require fields you are not importing.
- Traverse the remaining rules one by one in order to find a
match in the database.

- Skip the rule if it requires a special condition that is not
satisfied.
- If one match is found:

- Stop traversing the rest of valid rules.
- **Update** that record.

- If zero or multiple matches are found:

- Continue with the next rule.

- If all rules are exhausted and no single match is found:

- **Create** a new record.

- If there are no match rules for your model:

- **Create** a new record.

By default 2 rules are installed for production instances:

- One rule that will allow you to update companies based on their VAT,
when ``is_company`` is ``True``.
- One rule that will allow you to update users based on their login.

In demo instances there are more examples.

**Table of contents**

.. contents::
:local:

Configuration
=============

Import Match rules are optional templates that pre-check the **Match**
column in the import preview. To configure them:

1. Go to *Settings > Technical > Database Structure > Import Match*.
2. *Create*.
3. Choose a *Model*.
4. Choose the *Fields* that conform a unique key in that model.
5. If the rule must be used only for certain imported values, check
*Conditional* and enter the **exact string** that is going to be
imported in *Imported value*.

1. Keep in mind that the match here is evaluated as a case sensitive
**text string** always. If you enter e.g. ``True``, it will match
that string, but will not match ``1`` or ``true``.

6. *Save*.

In that list view, you can sort rules by drag and drop.

Usage
=====

To use this module, you need to:

1. Go to any list or kanban view.
2. Go to *Action > Import records* and upload your file.
3. In the import preview, check **Match** on the fields you want to use
as matching keys (e.g. name, email, VAT). These fields will be used
to find existing records but will not be written.
4. Proceed with the import as usual. If any row cannot be matched to
exactly one existing record, the import will be blocked and error
messages will indicate which rows had zero or multiple matches.

If Import Match rules are configured for the model, their fields will be
pre-checked automatically.

Known issues / Roadmap
======================

- Add a setting to throw an error when multiple matches are found
during programmatic imports, instead of falling back to creation of
new record. (UI-driven imports already block on zero or multiple
matches.)
- Support matching on child record fields (one2many subfields) in the
import preview. Currently, matching only works for direct fields of
the imported model.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/server-backend/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/server-backend/issues/new?body=module:%20base_import_match%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Tecnativa

Contributors
------------

- `Tecnativa <https://www.tecnativa.com>`__:

- Jairo Llopis
- Vicent Cubells
- Ernesto Tejeda

- `Quartile <https://www.quartile.co>`__:

- Yoshi Tashiro

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/server-backend <https://github.com/OCA/server-backend/tree/19.0/base_import_match>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
6 changes: 6 additions & 0 deletions base_import_match/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# Copyright 2016 Tecnativa - Vicent Cubells
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import models
from . import wizard
27 changes: 27 additions & 0 deletions base_import_match/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
# Copyright 2016 Tecnativa - Vicent Cubells
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Base Import Match",
"summary": "Try to avoid duplicates before importing",
"version": "19.0.2.0.0",
"category": "Tools",
"website": "https://github.com/OCA/server-backend",
"author": "Tecnativa, Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": ["base_import"],
"data": [
"security/ir.model.access.csv",
"data/base_import_match.xml",
"views/base_import_match_view.xml",
],
"demo": ["demo/base_import_match.xml"],
"assets": {
"web.assets_backend": [
"base_import_match/static/src/**/*.js",
"base_import_match/static/src/**/*.xml",
],
},
}
30 changes: 30 additions & 0 deletions base_import_match/data/base_import_match.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
Copyright 2016 Tecnativa - Vicent Cubells
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo noupdate="1">
<!-- Match partners by VAT when is_company is True -->
<record model="base_import.match" id="res_partner_vat">
<field name="model_id" eval="ref('base.model_res_partner')" />
<field name="sequence" type="int">10</field>
</record>
<record model="base_import.match.field" id="res_partner_vat_vat">
<field name="match_id" ref="base_import_match.res_partner_vat" />
<field name="field_id" ref="base.field_res_partner__vat" />
</record>
<record model="base_import.match.field" id="res_partner_vat_is_company">
<field name="match_id" ref="base_import_match.res_partner_vat" />
<field name="field_id" ref="base.field_res_partner__is_company" />
<field name="conditional">True</field>
<field name="imported_value">True</field>
</record>
<!-- Match users by login -->
<record model="base_import.match" id="res_users_login">
<field name="model_id" eval="ref('base.model_res_users')" />
<field name="sequence" type="int">50</field>
</record>
<record model="base_import.match.field" id="res_users_login_login">
<field name="match_id" ref="base_import_match.res_users_login" />
<field name="field_id" ref="base.field_res_users__login" />
</record>
</odoo>
50 changes: 50 additions & 0 deletions base_import_match/demo/base_import_match.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2016 Grupo ESOC Ingeniería de Servicios, S.L.U. - Jairo Llopis
Copyright 2016 Tecnativa - Vicent Cubells
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo noupdate="1">
<!-- Match partners by name, parent_id and is_company -->
<record model="base_import.match" id="res_partner_parent_name_is_company">
<field name="model_id" eval="ref('base.model_res_partner')" />
<field name="sequence" type="int">20</field>
</record>
<record
model="base_import.match.field"
id="res_partner_parent_name_is_company_name"
>
<field name="match_id" ref="res_partner_parent_name_is_company" />
<field name="field_id" ref="base.field_res_partner__name" />
</record>
<record
model="base_import.match.field"
id="res_partner_parent_name_is_company_parent"
>
<field name="match_id" ref="res_partner_parent_name_is_company" />
<field name="field_id" ref="base.field_res_partner__parent_id" />
</record>
<record
model="base_import.match.field"
id="res_partner_parent_name_is_company_is_company"
>
<field name="match_id" ref="res_partner_parent_name_is_company" />
<field name="field_id" ref="base.field_res_partner__is_company" />
</record>
<!-- Match partner by email -->
<record model="base_import.match" id="res_partner_email">
<field name="model_id" eval="ref('base.model_res_partner')" />
<field name="sequence" type="int">30</field>
</record>
<record model="base_import.match.field" id="res_partner_email_email">
<field name="match_id" ref="base_import_match.res_partner_email" />
<field name="field_id" ref="base.field_res_partner__email" />
</record>
<!-- Match partner by name -->
<record model="base_import.match" id="res_partner_name">
<field name="model_id" eval="ref('base.model_res_partner')" />
<field name="sequence" type="int">40</field>
</record>
<record model="base_import.match.field" id="res_partner_name_name">
<field name="match_id" ref="base_import_match.res_partner_name" />
<field name="field_id" ref="base.field_res_partner__name" />
</record>
</odoo>
Loading
Loading