From 6e47bcfc55c9fe0ef9c5e1163dd25a39eddc0c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 17 Oct 2025 14:51:15 +0200 Subject: [PATCH] Accept email without content as incoming document It allow the creation of incoming document without data as long as it has children. This supports the case of email without text nor HTML content but only an attachment. Closes #14109 --- modules/document_incoming/document.py | 15 ++++++++-- .../document_incoming/tests/test_module.py | 29 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/modules/document_incoming/document.py b/modules/document_incoming/document.py index 9085c16802..a0e4e7e667 100644 --- a/modules/document_incoming/document.py +++ b/modules/document_incoming/document.py @@ -41,8 +41,11 @@ class Incoming(DeactivableMixin, Workflow, ModelSQL, ModelView): company = fields.Many2One('company.company', "Company", states=_states) data = fields.Binary( "Data", filename='name', - file_id=file_id, store_prefix=store_prefix, required=True, - states=_states) + file_id=file_id, store_prefix=store_prefix, + states={ + 'required': ~Eval('children'), + 'readonly': _states['readonly'], + }) parsed_data = fields.Dict(None, "Parsed Data", readonly=True) file_id = fields.Char("File ID", readonly=True) mime_type = fields.Function( @@ -125,6 +128,14 @@ class Incoming(DeactivableMixin, Workflow, ModelSQL, ModelView): }, ) + @classmethod + def __register__(cls, module): + super().__register__(module) + table_h = cls.__table_handler__(module) + + # Migration from 7.6: remove not null on data + table_h.not_null_action('data', action='remove') + @fields.depends('name') def on_change_with_mime_type(self, name=None): if self.name: diff --git a/modules/document_incoming/tests/test_module.py b/modules/document_incoming/tests/test_module.py index 62aef6ce44..ac699ff3ad 100644 --- a/modules/document_incoming/tests/test_module.py +++ b/modules/document_incoming/tests/test_module.py @@ -84,6 +84,35 @@ class DocumentIncomingTestCase(CompanyTestMixin, ModuleTestCase): self.assertEqual(child.data, b'data') self.assertEqual(child.source, 'inbound_email') + @with_transaction() + def test_document_from_inbound_email_without_content(self): + "Test document from inbound email without content" + pool = Pool() + Document = pool.get('document.incoming') + Email = pool.get('inbound.email') + Rule = pool.get('inbound.email.rule') + + with patch.object(Email, 'as_dict') as as_dict: + as_dict.return_value = { + 'subject': "Subject", + 'attachments': [{ + 'filename': "document", + 'data': b'data', + }], + } + + email = Email() + rule = Rule( + document_incoming_type='document_incoming', + document_incoming_company=None, + ) + + document = Document.from_inbound_email(email, rule) + + self.assertFalse(document.data) + child, = document.children + self.assertEqual(child.data, b'data') + class DocumentIncomingRouteTestCase(RouteTestCase): "Test Document Incoming route"