Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions spp_import_match/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,26 @@ Test 12: Security — Non-Admin Access
Changelog
=========

19.0.2.0.2
~~~~~~~~~~

- chore(views): hide the conditional-gate columns (``Is Conditional``,
``Condition Field``, ``Condition Value``) from the match-rule fields
list — the schema and matching-engine wiring stay in place, but no
current import flow uses the gate, so the columns are kept out of the
UI until a real use case lands.

19.0.2.0.1
~~~~~~~~~~

- fix(matching): add a ``condition_field_id`` Many2one column to
``spp.import.match.fields`` and rewrite the matching loop so
conditional rows act as pure gates — never added to the DB search
domain. Renames the IMPORTED VALUE column heading to **Condition
Value**. Fixes the case where a CSV-only metadata column (e.g.
``data_source``) was being injected into the search domain and causing
zero matches.

19.0.2.0.0
~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion spp_import_match/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"name": "OpenSPP Import Match",
"summary": "OpenSPP Import Match enhances data import processes by intelligently matching incoming records against existing data, preventing duplication and ensuring registry integrity. It provides configurable matching logic and supports seamless updates to existing records during bulk data onboarding.",
"category": "OpenSPP/Integration",
"version": "19.0.2.0.0",
"version": "19.0.2.0.2",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
Expand Down
38 changes: 36 additions & 2 deletions spp_import_match/models/import_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,21 @@ def _match_find(self, model, converted_row, imported_row):
domain = list()
for field in combination.field_ids:
if field.is_conditional:
if imported_row[field.name] != field.imported_value:
# Conditional rows are pure *gates* — they decide whether
# the rule applies to this CSV row. The CSV column that
# carries the gate value is `condition_field_id` when
# set; otherwise we fall back to `field_id` for
# backwards compatibility with rules created before
# OP#991. Crucially, a conditional row is **not** added
# to the DB search domain — the gate column may be a
# CSV-only metadata field (e.g. `data_source`) that
# doesn't exist on the registrant model.
gate_field_name = field.condition_field_id.name if field.condition_field_id else field.field_id.name
if imported_row.get(gate_field_name) != field.imported_value:
combination_valid = False
break
continue

if field.field_id.name in converted_row:
row_value = converted_row[field.field_id.name]
# Skip matching on empty values to avoid false matches
Expand Down Expand Up @@ -141,7 +153,29 @@ class SPPImportMatchFields(models.Model):
match_id = fields.Many2one("spp.import.match", string="Match", ondelete="cascade")
model_id = fields.Many2one(related="match_id.model_id")
is_conditional = fields.Boolean()
imported_value = fields.Char(help="This will be used as a condition to disregard this field if matched")
condition_field_id = fields.Many2one(
"ir.model.fields",
string="Condition Field",
ondelete="cascade",
domain="[('model_id', '=', model_id)]",
help=(
"When `Is Conditional` is set, the rule only fires for CSV rows "
"whose value in this field equals `Condition Value`. The "
"condition field is used purely as a gate — it is **not** added "
"to the database search domain, so it can safely be a CSV-only "
"metadata column (e.g. `data_source`) that doesn't have data on "
"the registrant. Leave empty to fall back to the legacy "
"behaviour where `Field` is used as both the gate and the "
"search predicate."
),
)
Comment on lines +156 to +171
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The implementation of condition_field_id as a Many2one to ir.model.fields with a domain restricted to the current model (model_id) contradicts the stated requirement in the PR description.

The description mentions that the gate column may be a "CSV-only metadata field (e.g. data_source) that doesn't exist on the registrant model". If the field does not exist on the model, it will not be present in ir.model.fields for that model, making it impossible to select in the UI. To support arbitrary CSV columns as gates, this field should likely be a Char field or allow for non-model field selection.

imported_value = fields.Char(
string="Condition Value",
help=(
"Expected value of the condition field. The rule only applies "
"when the imported row's `Condition Field` matches this value."
),
)

def _compute_name(self):
for rec in self:
Expand Down
8 changes: 8 additions & 0 deletions spp_import_match/readme/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### 19.0.2.0.2

- chore(views): hide the conditional-gate columns (`Is Conditional`, `Condition Field`, `Condition Value`) from the match-rule fields list — the schema and matching-engine wiring stay in place, but no current import flow uses the gate, so the columns are kept out of the UI until a real use case lands.

### 19.0.2.0.1

- fix(matching): add a `condition_field_id` Many2one column to `spp.import.match.fields` and rewrite the matching loop so conditional rows act as pure gates — never added to the DB search domain. Renames the IMPORTED VALUE column heading to **Condition Value**. Fixes the case where a CSV-only metadata column (e.g. `data_source`) was being injected into the search domain and causing zero matches.

### 19.0.2.0.0

- Initial migration to OpenSPP2
22 changes: 22 additions & 0 deletions spp_import_match/static/description/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,28 @@ <h2><a class="toc-backref" href="#toc-entry-15">Changelog</a></h2>
</div>
</div>
<div class="section" id="section-1">
<h1>19.0.2.0.2</h1>
<ul class="simple">
<li>chore(views): hide the conditional-gate columns (<tt class="docutils literal">Is Conditional</tt>,
<tt class="docutils literal">Condition Field</tt>, <tt class="docutils literal">Condition Value</tt>) from the match-rule fields
list — the schema and matching-engine wiring stay in place, but no
current import flow uses the gate, so the columns are kept out of the
UI until a real use case lands.</li>
</ul>
</div>
<div class="section" id="section-2">
<h1>19.0.2.0.1</h1>
<ul class="simple">
<li>fix(matching): add a <tt class="docutils literal">condition_field_id</tt> Many2one column to
<tt class="docutils literal">spp.import.match.fields</tt> and rewrite the matching loop so
conditional rows act as pure gates — never added to the DB search
domain. Renames the IMPORTED VALUE column heading to <strong>Condition
Value</strong>. Fixes the case where a CSV-only metadata column (e.g.
<tt class="docutils literal">data_source</tt>) was being injected into the search domain and causing
zero matches.</li>
</ul>
</div>
<div class="section" id="section-3">
<h1>19.0.2.0.0</h1>
<ul class="simple">
<li>Initial migration to OpenSPP2</li>
Expand Down
20 changes: 15 additions & 5 deletions spp_import_match/tests/test_import_match_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,31 @@ def test_match_find_conditional_skip(self):
self.assertFalse(result.id)

def test_match_find_conditional_match(self):
"""Test _match_find uses rule when conditional value matches."""
partner = self.env["res.partner"].create({"name": "ConditionalMatchTest"})
"""Test _match_find applies the rule when the conditional gate passes.

Under OP#991 semantics, an `is_conditional=True` row is a pure gate —
it decides whether the rule applies to this CSV row but is never
added to the DB search domain (the gate column may be a CSV-only
metadata field that doesn't exist on the registrant model). The
combination must include at least one non-conditional row to
provide the actual search predicate. Here `name` is the gate and
`email` is the search predicate.
"""
partner = self.env["res.partner"].create({"name": "ConditionalMatchTest", "email": "conditional@example.com"})
match = self._create_match_rule(
[
{
"field_id": self.name_field.id,
"is_conditional": True,
"imported_value": "ConditionalMatchTest",
}
},
{"field_id": self.email_field.id},
]
)
result = match._match_find(
self.env["res.partner"],
{"name": "ConditionalMatchTest"},
{"name": "ConditionalMatchTest", "id": None},
{"email": "conditional@example.com"},
{"name": "ConditionalMatchTest", "email": "conditional@example.com", "id": None},
)
self.assertEqual(result, partner)

Expand Down
21 changes: 18 additions & 3 deletions spp_import_match/views/import_match_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,26 @@
/>
<field name="match_id" column_invisible="1" />
<field name="model_id" column_invisible="1" />
<field name="is_conditional" />
<!-- Odoo 19: Converted attrs to individual attributes -->
<!-- Conditional gate columns hidden for now —
the schema (is_conditional / condition_field_id /
imported_value) and the matching-engine wiring
stay in place, but no current import flow needs
the gate, so the columns are kept out of the UI
until a use case lands. See OP#991. -->
<field
name="is_conditional"
column_invisible="1"
/>
<field
name="condition_field_id"
string="Condition Field"
column_invisible="1"
options="{'no_create': True}"
/>
<field
name="imported_value"
readonly="is_conditional == False"
string="Condition Value"
column_invisible="1"
/>
</list>
</field>
Expand Down
Loading