diff --git a/edi_core_oca/__manifest__.py b/edi_core_oca/__manifest__.py index 8d788619f..32ee3f2bf 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": "18.0.1.10.0", + "version": "18.0.1.10.1", "website": "https://github.com/OCA/edi-framework", "development_status": "Beta", "license": "LGPL-3", diff --git a/edi_core_oca/migrations/18.0.1.10.1/post-migration.py b/edi_core_oca/migrations/18.0.1.10.1/post-migration.py new file mode 100644 index 000000000..a1e335ebf --- /dev/null +++ b/edi_core_oca/migrations/18.0.1.10.1/post-migration.py @@ -0,0 +1,22 @@ +# Copyright 2026 Camptocamp SA (http://www.camptocamp.com) +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + # Backfill the stored flag for records already holding a file. + env.cr.execute( + """ + UPDATE edi_exchange_record AS record + SET has_exchange_file = TRUE + WHERE EXISTS ( + SELECT 1 + FROM ir_attachment AS attachment + WHERE attachment.res_model = 'edi.exchange.record' + AND attachment.res_field = 'exchange_file' + AND attachment.res_id = record.id + ) + """ + ) diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py index 251952d91..ae61d755f 100644 --- a/edi_core_oca/models/edi_backend.py +++ b/edi_core_oca/models/edi_backend.py @@ -320,7 +320,9 @@ def _output_check_send(self, exchange_record): raise exceptions.UserError( self.env._("Record ID=%d is not meant to be sent!") % exchange_record.id ) - if not exchange_record.exchange_file: + # Only check that a file exists: avoid loading its content, which may + # live in a filesystem storage. + if not exchange_record.with_context(bin_size=True).exchange_file: raise exceptions.UserError( self.env._("Record ID=%d has no file to send!") % exchange_record.id ) @@ -408,7 +410,7 @@ def _output_new_records_domain(self, record_ids=None): ("type_id.exchange_file_auto_generate", "=", True), ("type_id.direction", "=", "output"), ("edi_exchange_state", "=", "new"), - ("exchange_file", "=", False), + ("has_exchange_file", "=", False), ] if record_ids: domain.append(("id", "in", record_ids)) @@ -651,7 +653,7 @@ def _input_pending_records_domain(self, record_ids=None): ("backend_id", "=", self.id), ("type_id.direction", "=", "input"), ("edi_exchange_state", "=", "input_pending"), - ("exchange_file", "=", False), + ("has_exchange_file", "=", False), ] if record_ids: domain.append(("id", "in", record_ids)) diff --git a/edi_core_oca/models/edi_exchange_record.py b/edi_core_oca/models/edi_exchange_record.py index 685a98bb0..d4373649a 100644 --- a/edi_core_oca/models/edi_exchange_record.py +++ b/edi_core_oca/models/edi_exchange_record.py @@ -60,7 +60,14 @@ class EDIExchangeRecord(models.Model): ) related_record_exists = fields.Boolean(compute="_compute_related_record_exists") related_name = fields.Char(compute="_compute_related_name", compute_sudo=True) - exchange_file = fields.Binary(attachment=True, copy=False) + exchange_file = fields.Binary( + attachment=True, + copy=False, + inverse="_inverse_exchange_file", + ) + # Stored flag mirroring the presence of exchange_file: searching on it + # avoids the costly subqueries on ir_attachment. + has_exchange_file = fields.Boolean(default=False, copy=False) exchange_filename = fields.Char( compute="_compute_exchange_filename", readonly=False, store=True ) @@ -169,6 +176,10 @@ def _compute_exchange_filename(self): if not rec.exchange_filename: rec.exchange_filename = rec.type_id._make_exchange_filename(rec) + def _inverse_exchange_file(self): + for rec in self: + rec.has_exchange_file = bool(rec.exchange_file) + @api.depends("exchange_file") def _compute_exchange_filechecksum(self): for rec in self: @@ -187,7 +198,7 @@ def _compute_exchanged_on(self): def _compute_exchange_file_frozen(self): for rec in self: rec.exchange_file_frozen = bool( - rec.exchange_file + rec.with_context(bin_size=True).exchange_file and rec.edi_exchange_state in ("input_processed", "output_sent", "output_sent_and_processed") ) and not self.env.user.has_group( diff --git a/edi_core_oca/tests/test_record.py b/edi_core_oca/tests/test_record.py index 11ab90ac6..c3f5f70d3 100644 --- a/edi_core_oca/tests/test_record.py +++ b/edi_core_oca/tests/test_record.py @@ -244,3 +244,18 @@ def test_related_records(self): self.assertEqual(len(record.related_record_ids), 2) self.assertEqual(record.related_record_ids[0].record, self.partner) self.assertEqual(record.related_record_ids[1].record, spain) + + def test_has_exchange_file(self): + """has_exchange_file mirrors the presence of exchange_file.""" + vals = { + "model": self.partner._name, + "res_id": self.partner.id, + } + record = self.backend.create_record("test_csv_output", vals) + self.assertFalse(record.has_exchange_file) + + record.exchange_file = base64.b64encode(b"ABC") + self.assertTrue(record.has_exchange_file) + + record.exchange_file = False + self.assertFalse(record.has_exchange_file) diff --git a/edi_exchange_record_file_manager/models/edi_exchange_record.py b/edi_exchange_record_file_manager/models/edi_exchange_record.py index 0e1480446..6d0bf77c6 100644 --- a/edi_exchange_record_file_manager/models/edi_exchange_record.py +++ b/edi_exchange_record_file_manager/models/edi_exchange_record.py @@ -90,7 +90,9 @@ def _is_file_updatable_via_file_manager(self) -> bool: - the record contains a file - the record file can be updated (not "frozen") """ - return bool(self.exchange_file) and not self.exchange_file_frozen + return bool(self.with_context(bin_size=True).exchange_file) and not ( + self.exchange_file_frozen + ) @api.depends("exchange_file_frozen", "edi_exchange_state") @api.depends_context("uid") # Visibility depends on user access rights