Skip to content
Open
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
2 changes: 1 addition & 1 deletion edi_core_oca/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 22 additions & 0 deletions edi_core_oca/migrations/18.0.1.10.1/post-migration.py
Original file line number Diff line number Diff line change
@@ -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
)
"""
)
8 changes: 5 additions & 3 deletions edi_core_oca/models/edi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down
15 changes: 13 additions & 2 deletions edi_core_oca/models/edi_exchange_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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:
Expand All @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if we have the boolen why we use this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I assume it's a performance improvement. Using bin_size=True will prevent Odoo from loading the whole file from the filestore or the DB, encoding it via base64 and storing the whole file content in the cache (just to check whether it exists); instead it will simply read the stored field file_size and return None if there's no file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know, but the boolean is stored and it should be faster to search on the database than checking the size of a file, isn't it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, that's right. I thought you were talking about the bool() used in the snippet, not the Boolean field itself, my bad 😅

and rec.edi_exchange_state
in ("input_processed", "output_sent", "output_sent_and_processed")
) and not self.env.user.has_group(
Expand Down
15 changes: 15 additions & 0 deletions edi_core_oca/tests/test_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading