@@ -33,16 +33,15 @@ Key Capabilities
3333
3434- Define matching rules per model using field combinations to identify
3535 existing records
36- - Match on sub-fields within related records (e.g., household ID within
37- individual)
38- - Apply conditional matching rules only when specific imported values
39- are present
36+ - Match on sub-fields within related records (e.g., ``parent_id/name ``)
37+ - Apply conditional matching rules only when a specific imported value
38+ is present
4039- Skip duplicate creation or update existing records when matches are
4140 found
42- - Process imports with more than 100 records asynchronously using
43- ``job_worker ``
44- - Clear one2many/many2many associations before update to prevent
45- duplicate entries
41+ - Split imports exceeding 100 rows into chunks and process
42+ asynchronously via ``job_worker ``
43+ - Strip falsy one2many/many2many values on write to prevent duplicate
44+ relational entries
4645
4746Key Models
4847~~~~~~~~~~
@@ -53,8 +52,8 @@ Key Models
5352| ``spp.import.match `` | Matching rule configuration for a |
5453| | specific model |
5554+-----------------------------+----------------------------------------+
56- | ``spp.import.match.fields `` | Individual fields used in a rule, |
57- | | supports sub-fields |
55+ | ``spp.import.match.fields `` | Individual field in a rule, with |
56+ | | optional sub-field |
5857+-----------------------------+----------------------------------------+
5958
6059Configuration
@@ -63,23 +62,21 @@ Configuration
6362After installing:
6463
65641. Navigate to **Registry > Configuration > Import Match **
66- 2. Create a new matching rule and select the target model (e.g.,
65+ 2. Create a matching rule and select the target model (e.g.,
6766 ``res.partner ``)
68673. Add one or more fields to match on (e.g., national ID, or first name
6968 + date of birth)
70694. Enable **Overwrite Match ** to update existing records when matches
7170 are found
72715. For conditional matching, enable **Is Conditional ** on a field and
73- specify the expected imported value
72+ set the expected imported value
7473
7574UI Location
7675~~~~~~~~~~~
7776
7877- **Menu **: Registry > Configuration > Import Match
79- - **Import Dialog **: Matching applies automatically during CSV import
80- via the standard Odoo import interface
81- - **Queue Jobs **: Registry > Queue Jobs > Jobs (to monitor asynchronous
82- imports)
78+ - **Import Dialog **: Select a matching rule and overwrite option from
79+ the import sidebar
8380
8481Security
8582~~~~~~~~
@@ -94,11 +91,11 @@ Extension Points
9491~~~~~~~~~~~~~~~~
9592
9693- Override ``spp.import.match._match_find() `` to customize matching
97- logic for specific use cases
94+ logic
9895- Override ``spp.import.match._usable_rules() `` to filter which rules
9996 apply based on context
100- - Inherits ``base.load() `` to inject matching logic into all model
101- imports
97+ - Overrides ``base.load() `` to inject matching into all model imports
98+ - Overrides `` base.write() `` to strip falsy one2many/many2many values
10299
103100Dependencies
104101~~~~~~~~~~~~
@@ -111,6 +108,288 @@ Dependencies
111108.. contents ::
112109 :local:
113110
111+ Usage
112+ =====
113+
114+ Prerequisites
115+ -------------
116+
117+ - The module **OpenSPP Import Match ** is installed
118+ - You are logged in as a user with the **SPP Admin **
119+ (``spp_security.group_spp_admin ``) role
120+
121+ Test 1: Create a Matching Rule
122+ ------------------------------
123+
124+ **Steps: **
125+
126+ 1. Navigate to **Registry > Configuration > Import Match **
127+ 2. Click **New **
128+ 3. Enter a name (e.g., "Match Partner by Name")
129+ 4. In the **Match Details ** tab, set **Model ** to ``Contact ``
130+ (res.partner)
131+ 5. Verify that **Model Name ** and **Model Description ** auto-populate
132+ as ``res.partner `` and ``Contact ``
133+ 6. In the **Fields ** list, click **Add a line **
134+ 7. Select a non-relational field (e.g., ``Name ``)
135+ 8. Verify that the **Sub-Field ** column is read-only (greyed out) for
136+ non-relational fields
137+ 9. Leave **Is Conditional ** unchecked and **Imported Value ** empty
138+ 10. Click **Save **
139+
140+ **Expected: **
141+
142+ - The rule appears in the list view with the name you entered
143+ - The list view shows a drag handle for reordering by sequence
144+
145+ Test 2: Verify Duplicate Field Validation
146+ -----------------------------------------
147+
148+ **Steps: **
149+
150+ 1. Open the matching rule created in Test 1
151+ 2. In the **Fields ** list, click **Add a line **
152+ 3. Select the same non-relational field (e.g., ``Name ``) that already
153+ exists in the list
154+
155+ **Expected: **
156+
157+ - A validation error appears: "Field 'Name', already exists!"
158+ - The duplicate field is not added
159+
160+ Test 3: Sub-Field on Relational Fields
161+ --------------------------------------
162+
163+ **Steps: **
164+
165+ 1. Create a new matching rule for model ``Contact `` (res.partner)
166+ 2. In the **Fields ** list, add a relational field (e.g., ``Parent ``
167+ which is a Many2one)
168+ 3. Verify that the **Sub-Field ** column becomes editable and required
169+ 4. Select a sub-field (e.g., ``Name ``)
170+ 5. Save the rule
171+
172+ **Expected: **
173+
174+ - The field's computed name displays as ``parent_id/name ``
175+ (field/sub-field format)
176+ - The rule saves without error
177+
178+ Test 4: Model Change Clears Fields
179+ ----------------------------------
180+
181+ **Steps: **
182+
183+ 1. Open the matching rule created in Test 3
184+ 2. Change the **Model ** to a different model (e.g., ``Country ``)
185+ 3. Observe the **Fields ** list
186+
187+ **Expected: **
188+
189+ - All previously configured fields are cleared from the list
190+ - The field domain updates to show only fields from the newly selected
191+ model
192+
193+ Test 5: Import with Matching — Skip Duplicates
194+ ----------------------------------------------
195+
196+ **Steps: **
197+
198+ 1. Create a matching rule for ``Contact `` with the ``Name `` field and
199+ **Overwrite Match ** unchecked
200+ 2. Create a contact manually with Name = "Test Import Match"
201+ 3. Navigate to **Contacts ** and click **Import records ** (or use the
202+ gear/action menu)
203+ 4. Upload a CSV file containing:
204+ ::
205+
206+ name,email
207+ Test Import Match,updated@example.com
208+ Brand New Contact,new@example.com
209+
210+ 5. In the import sidebar, locate the **Import Matching ** section
211+ 6. Select the matching rule you created from the dropdown
212+ 7. Verify the **Overwrite Match ** checkbox is unchecked
213+ 8. Verify the helper text reads: "Matched records will be skipped."
214+ 9. Click **Test ** (dry run)
215+
216+ **Expected: **
217+
218+ - An info message appears: "1 to skip, 1 to create"
219+ - No records are modified yet
220+
221+ 10. Click **Import **
222+
223+ **Expected: **
224+
225+ - A success notification appears: "1 skipped, 1 created"
226+ - The existing "Test Import Match" contact retains its original email
227+ (not updated)
228+ - A new "Brand New Contact" record is created with email
229+ ``new@example.com ``
230+
231+ Test 6: Import with Matching — Overwrite
232+ ----------------------------------------
233+
234+ **Steps: **
235+
236+ 1. Using the same matching rule from Test 5, re-import the same CSV
237+ 2. In the import sidebar, select the matching rule
238+ 3. Check the **Overwrite Match ** checkbox
239+ 4. Verify the helper text changes to: "Matched records will be
240+ overwritten with imported data."
241+ 5. Click **Test ** (dry run)
242+
243+ **Expected: **
244+
245+ - An info message appears showing counts for records to overwrite and to
246+ create
247+
248+ 6. Click **Import **
249+
250+ **Expected: **
251+
252+ - A success notification with overwrite/create counts
253+ - The "Test Import Match" contact now has email ``updated@example.com ``
254+ - The "Brand New Contact" record either has a second copy created or is
255+ matched (depending on whether a Name rule matched it from the previous
256+ import)
257+
258+ Test 7: Conditional Matching
259+ ----------------------------
260+
261+ **Steps: **
262+
263+ 1. Create a new matching rule for ``Contact `` with **Overwrite Match **
264+ checked
265+ 2. Add the ``Name `` field to the match fields
266+ 3. Check **Is Conditional ** on the Name field
267+ 4. Set **Imported Value ** to "Test Import Match"
268+ 5. Save the rule
269+ 6. Import a CSV:
270+ ::
271+
272+ name,email
273+ Test Import Match,conditional@example.com
274+ Some Other Name,other@example.com
275+
276+ 7. Select this matching rule in the import sidebar
277+
278+ **Expected: **
279+
280+ - The row with name "Test Import Match" matches the condition and the
281+ existing record is overwritten
282+ - The row with name "Some Other Name" does not match the condition
283+ (imported value differs from "Test Import Match"), so the entire rule
284+ is skipped for that row and a new record is created
285+
286+ Test 8: Import Matching Dropdown Visibility
287+ -------------------------------------------
288+
289+ **Steps: **
290+
291+ 1. Navigate to a model that has NO matching rules configured (e.g.,
292+ ``Countries ``)
293+ 2. Open the import dialog
294+
295+ **Expected: **
296+
297+ - The **Import Matching ** section does not appear in the sidebar (the
298+ dropdown is hidden when no rules exist for the model)
299+
300+ 3. Navigate to a model that HAS matching rules (e.g., ``Contacts ``)
301+ 4. Open the import dialog
302+
303+ **Expected: **
304+
305+ - The **Import Matching ** section appears with a dropdown listing all
306+ rules for that model
307+ - The default selection is "-- No Matching --"
308+ - The **Overwrite Match ** checkbox is hidden until a rule is selected
309+
310+ Test 9: Overwrite Match Default from Rule
311+ -----------------------------------------
312+
313+ **Steps: **
314+
315+ 1. Create a matching rule with **Overwrite Match ** checked on the rule
316+ form
317+ 2. Open the import dialog for the rule's model
318+ 3. Select this rule from the **Import Matching ** dropdown
319+
320+ **Expected: **
321+
322+ - The **Overwrite Match ** checkbox is automatically checked (inherits
323+ the rule's default)
324+ - The user can uncheck it to override the default for this import
325+
326+ 4. Select "-- No Matching --" from the dropdown
327+
328+ **Expected: **
329+
330+ - The **Overwrite Match ** checkbox disappears
331+
332+ Test 10: Asynchronous Import (Large File)
333+ -----------------------------------------
334+
335+ **Steps: **
336+
337+ 1. Create a matching rule for ``Contact `` with the ``Name `` field
338+ 2. Prepare a CSV with more than 100 rows of contact data (101+ rows of
339+ data, not counting the header)
340+ 3. Open the import dialog and upload the CSV
341+ 4. Select the matching rule
342+ 5. Click **Import ** (not Test)
343+
344+ **Expected: **
345+
346+ - A notification appears: "Successfully added on Queue"
347+ - The browser navigates back to the previous page
348+ - The import is processed in the background via job worker
349+ - Check job status by navigating to **Settings > Technical > Queue Jobs
350+ > Jobs ** (requires technical user access)
351+
352+ Test 11: Multiple Matches Error
353+ -------------------------------
354+
355+ **Steps: **
356+
357+ 1. Create two contacts with the same email: ``duplicate@example.com ``
358+ 2. Create a matching rule for ``Contact `` using the ``Email `` field
359+ 3. Import a CSV:
360+ ::
361+
362+ email,name
363+ duplicate@example.com,Updated Name
364+
365+ 4. Select the matching rule and click **Import **
366+
367+ **Expected: **
368+
369+ - An error is raised: "Multiple matches found for '...'" (where '...' is
370+ the name of the first matched record)
371+ - No records are modified
372+
373+ Test 12: Security — Non-Admin Access
374+ ------------------------------------
375+
376+ **Steps: **
377+
378+ 1. Log in as a user without the **SPP Admin ** role
379+ 2. Try to navigate to **Registry > Configuration > Import Match **
380+
381+ **Expected: **
382+
383+ - The **Import Match ** menu item is not visible
384+ - The user cannot access the matching rule configuration
385+
386+ 3. Open the import dialog for any model
387+
388+ **Expected: **
389+
390+ - The **Import Matching ** section does not appear (the ``searchRead `` on
391+ ``spp.import.match `` returns no results due to access rules)
392+
114393Bug Tracker
115394===========
116395
0 commit comments