From 344fb2bd0f1d7916fff888670a0d72136262b4ef Mon Sep 17 00:00:00 2001 From: SilvioC2C Date: Thu, 26 Mar 2026 16:21:27 +0100 Subject: [PATCH] [FIX] edi_core_oca: fix record rule Exchange Record's ``res_id`` is a ``Many2onReference`` field, which internally converts False-ish values to 0 before storing them to the cache and the DB. The rule's domain old leaf ``('res_id', '=', False)`` was instead converted to a SQL query clause ``WHERE "edi_exchange_record.res_id" IS NULL``. Since all ``edi_exchange_record`` rows contain a non-negative integer in the ``res_id`` column, the rule old domain leaf always failed to fetch any record. Changing the leaf to ``('res_id', '=', 0)`` fixes the issue, making such Exchange Records visible again for internal users. --- edi_core_oca/__manifest__.py | 2 +- .../migrations/19.0.1.0.3/post-mig.py | 24 ++++++++++++++ edi_core_oca/security/ir_model_access.xml | 2 +- edi_core_oca/tests/test_security.py | 32 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 edi_core_oca/migrations/19.0.1.0.3/post-mig.py diff --git a/edi_core_oca/__manifest__.py b/edi_core_oca/__manifest__.py index c798375b5..a797c779c 100644 --- a/edi_core_oca/__manifest__.py +++ b/edi_core_oca/__manifest__.py @@ -9,7 +9,7 @@ Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. """, - "version": "19.0.1.0.2", + "version": "19.0.1.0.3", "website": "https://github.com/OCA/edi-framework", "development_status": "Beta", "license": "LGPL-3", diff --git a/edi_core_oca/migrations/19.0.1.0.3/post-mig.py b/edi_core_oca/migrations/19.0.1.0.3/post-mig.py new file mode 100644 index 000000000..6b52c0399 --- /dev/null +++ b/edi_core_oca/migrations/19.0.1.0.3/post-mig.py @@ -0,0 +1,24 @@ +# Copyright 2026 Camptocamp SA (http://www.camptocamp.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from logging import getLogger + +from openupgradelib import openupgrade + +_logger = getLogger(__name__) + + +@openupgrade.migrate() +def migrate(env, version): + xmlid = "edi_core_oca.rule_edi_exchange_record_user" + if rule := env.ref(xmlid, False): + old_domain = (rule.domain_force or "").strip() + new_domain = ["|", ("model", "!=", False), ("res_id", "=", 0)] + _logger.info( + f"Updating {rule} ({xmlid=}) domain:\n" + f" - old: {old_domain}\n" + f" - new: {new_domain}" + ) + rule.domain_force = new_domain + else: + _logger.warning(f"No rule found with XMLID '{xmlid}', skipping...") diff --git a/edi_core_oca/security/ir_model_access.xml b/edi_core_oca/security/ir_model_access.xml index 8c85f038b..31c9c73ae 100644 --- a/edi_core_oca/security/ir_model_access.xml +++ b/edi_core_oca/security/ir_model_access.xml @@ -122,7 +122,7 @@ ['|', ('model','!=', False), ('res_id', '=', False)] + >['|', ('model', '!=', False), ('res_id', '=', 0)] diff --git a/edi_core_oca/tests/test_security.py b/edi_core_oca/tests/test_security.py index e4749a0b6..4f8f1771c 100644 --- a/edi_core_oca/tests/test_security.py +++ b/edi_core_oca/tests/test_security.py @@ -311,3 +311,35 @@ def test_search_pagination_with_inaccessible_middle_records(self): # The records fetched from the second page must be present in the final result self.assertIn(visible_id_2, records.ids) + + def test_search_no_res_id(self): + """Test Exc Rec visibility for internal users when ``res_id`` is False-ish + + Exchange Record's ``res_id`` is a ``Many2onReference`` field, which internally + converts False-ish values to 0 before storing them to the cache and the DB. + The rule's domain old leaf ``('res_id', '=', False)`` was instead converted to a + SQL query clause ``WHERE "edi_exchange_record.res_id" IS NULL``. + Since all ``edi_exchange_record`` rows contain a non-negative integer in the + ``res_id`` column, the rule old domain leaf always failed to fetch any record. + + Changing the leaf to ``('res_id', '=', 0)`` fixes the issue, making such + Exchange Records visible again for internal users. + """ + # Add the test user to the internal users group + self.user.write({"group_ids": [(4, self.env.ref("base.group_user").id)]}) + + # Create Exchange Records with no model (condition ``('model', '!=', False)`` + # will fail) and False-ish record ID (to test condition ``('res_id', '=', 0)``): + # such False-ish values are all converted to 0 by ``fields.Many2oneReference`` + # methods (and methods of its superclasses) when updating the cache values and + # preparing SQL queries to flush to the DB + exc_recs = self.env["edi.exchange.record"] + type_code = "test_csv_output" + vals = {"model": False} + for res_id in (0, 0.00, False, None, "", self.env["base"]): + exc_recs += self.backend.create_record(type_code, vals | {"res_id": res_id}) + self.assertEqual(exc_recs.mapped("res_id"), [0] * len(exc_recs)) + + # Check that the test user can actually fetch such records + exc_recs_model = self.env["edi.exchange.record"].with_user(self.user) + self.assertEqual(exc_recs_model.search([("id", "in", exc_recs.ids)]), exc_recs)