From dec2e44c0935f04c679469002e9f914e7b660741 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 26 Mar 2025 11:59:58 -0400 Subject: [PATCH 1/4] odoo_partner_scrapper: get rid of broken static components, to be replaced by an action later --- .../static/src/js/odoo_scrapper.js | 68 ------------------- .../src/xml/odoo_scrapper_templates.xml | 20 ------ .../views/res_partner_views.xml | 25 +++---- 3 files changed, 11 insertions(+), 102 deletions(-) delete mode 100644 bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js delete mode 100644 bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml diff --git a/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js b/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js deleted file mode 100644 index cd7b9ef..0000000 --- a/bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js +++ /dev/null @@ -1,68 +0,0 @@ -/** @odoo-module **/ - -import ListController from 'web.ListController'; -import ListView from 'web.ListView'; - -import KanbanController from 'web.KanbanController'; -import KanbanView from 'web.KanbanView'; - -const viewRegistry = require('web.view_registry'); - -const OdooScrapperListController = ListController.extend({ - // buttons_template must match the t-name on the template for the button (static xml) - buttons_template: 'odoo_scrapper.list_view_buttons', - events: _.extend({}, ListController.prototype.events, { - 'click .o_button_get_partner': '_onGetPartnerClick', - }), - // This may need to be async function() if there needs to be an await this._rpc({ ... }); call to not reload early - _onGetPartnerClick: function () { - this._rpc({ - model: 'res.partner', - method: 'get_odoo_partner', - args: [], - }).then(() => { - this.reload(); - }); - // Couldn't test this for real, but it runs the action. May need a this.reload() - }, -}); - -export const OdooScrapperListView = ListView.extend({ - config: _.extend({}, ListView.prototype.config, { - Controller: OdooScrapperListController, - }), -}); - -// key must match with the js_class attribute of the tree view you want to modify -viewRegistry.add('res_partner_odoo_scrapper_tree', OdooScrapperListView); - -const OdooScrapperKanbanController = KanbanController.extend({ - // buttons_template must match the t-name on the template for the button (static xml) - buttons_template: 'odoo_scrapper.kanban_view_buttons', - events: _.extend({}, KanbanController.prototype.events, { - 'click .o_button_get_partner': '_onGetPartnerClick', - }), - // This may need to be async function() if there needs to be an await this._rpc({ ... }); call to not reload early - _onGetPartnerClick: function () { - this._rpc({ - model: 'res.partner', - method: 'get_odoo_partner', - args: [], - }).then(() => { - this.reload(); - }); - // Couldn't test this for real, but it runs the action. May need a this.reload() - }, -}); - -export const OdooScrapperKanbanView = KanbanView.extend({ - config: _.extend({}, KanbanView.prototype.config, { - Controller: OdooScrapperKanbanController, - }), -}); - -// key must match with the js_class attribute of the tree view you want to modify -viewRegistry.add('res_partner_odoo_scrapper_kanban', OdooScrapperKanbanView); - - - diff --git a/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml b/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml deleted file mode 100644 index af0a1dd..0000000 --- a/bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/bemade_odoo_partner_scrapper/views/res_partner_views.xml b/bemade_odoo_partner_scrapper/views/res_partner_views.xml index 9dc982c..9e16ea9 100644 --- a/bemade_odoo_partner_scrapper/views/res_partner_views.xml +++ b/bemade_odoo_partner_scrapper/views/res_partner_views.xml @@ -8,19 +8,22 @@
- Odoo User - Odoo Partner + +Odoo User + +Odoo Partner
-

Partner

+

+ + Partner

- + @@ -38,9 +41,6 @@ res.partner - - res_partner_odoo_scrapper_tree - @@ -54,20 +54,17 @@ res.partner - - res_partner_odoo_scrapper_kanban - - + oe_kanban_global_click o_kanban_record_has_image_fill o_res_partner_kanban oe_kanban_color_#{record.color} - + - + From cc9a5e172da0f8b8030a442a2dd53be677271cde Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 26 Mar 2025 21:24:35 -0400 Subject: [PATCH 2/4] new module account_email_to_pdf Since Odoo 18, emails coming in to an alias creating account moves (vendor bills) get rejected if they do not contain an attachment that can be read by the system. This means that sending a plain email receipt with no attachment bounces, when it would be nice to have a vendor bill with the message in the chatter as a minimum. This module checks for attachments and injects one, in the form of a simple pdf containing the email header and contents, if there was no attachment to begin with. This should enable sending of a simple email and not having it bounce due to there being no attachment. --- account_email_to_pdf/__init__.py | 1 + account_email_to_pdf/__manifest__.py | 23 ++++++ account_email_to_pdf/models/__init__.py | 1 + account_email_to_pdf/models/account_move.py | 87 +++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 account_email_to_pdf/__init__.py create mode 100644 account_email_to_pdf/__manifest__.py create mode 100644 account_email_to_pdf/models/__init__.py create mode 100644 account_email_to_pdf/models/account_move.py diff --git a/account_email_to_pdf/__init__.py b/account_email_to_pdf/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/account_email_to_pdf/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_email_to_pdf/__manifest__.py b/account_email_to_pdf/__manifest__.py new file mode 100644 index 0000000..f5c4d75 --- /dev/null +++ b/account_email_to_pdf/__manifest__.py @@ -0,0 +1,23 @@ +{ + 'name': 'Account Email to PDF', + 'version': '18.0.1.0.0', + 'category': 'Accounting', + 'summary': 'Convert email messages to PDF attachments for vendor bills', + 'description': """ +Account Email to PDF +=================== +This module converts email messages without attachments into PDF attachments +when processing incoming emails for vendor bills. + +Instead of rejecting emails without attachments, the system will create a PDF +from the email content and attach it to the message, allowing the vendor bill +creation process to continue. + """, + 'author': 'Bemade', + 'website': 'https://bemade.org', + 'depends': ['account'], + 'data': [], + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/account_email_to_pdf/models/__init__.py b/account_email_to_pdf/models/__init__.py new file mode 100644 index 0000000..9c0a421 --- /dev/null +++ b/account_email_to_pdf/models/__init__.py @@ -0,0 +1 @@ +from . import account_move diff --git a/account_email_to_pdf/models/account_move.py b/account_email_to_pdf/models/account_move.py new file mode 100644 index 0000000..e9eb647 --- /dev/null +++ b/account_email_to_pdf/models/account_move.py @@ -0,0 +1,87 @@ +import base64 +import logging +from datetime import datetime +from email.utils import formatdate +from html import escape + +from odoo import models, fields +from odoo.tools.pdf import html_to_pdf + +_logger = logging.getLogger(__name__) + + +class AccountMove(models.Model): + _inherit = 'account.move' + + def _check_and_decode_attachment(self, attachments): + """Override to convert email message to PDF if no attachments are present.""" + if not attachments or self.env.context.get('no_new_invoice'): + # Original code would return False here, causing email rejection + # Instead, we'll create a PDF from the email message + message_dict = self.env.context.get('message_dict', {}) + if message_dict: + try: + # Create a PDF from the email content + pdf_attachment = self._create_pdf_from_email(message_dict) + if pdf_attachment: + # Add the PDF to the attachments list + return super()._check_and_decode_attachment([pdf_attachment]) + except Exception as e: + _logger.exception("Error creating PDF from email: %s", e) + + # If we couldn't create a PDF, fall back to original behavior + return False + + # If there are attachments, proceed with the original method + return super()._check_and_decode_attachment(attachments) + + def _create_pdf_from_email(self, message_dict): + """Create a PDF attachment from an email message.""" + # Extract email details + email_from = message_dict.get('email_from', 'Unknown Sender') + email_date = message_dict.get('date', datetime.now()) + subject = message_dict.get('subject', 'No Subject') + body = message_dict.get('body', '') + + # Format the date if it's a datetime object + if isinstance(email_date, datetime): + email_date = email_date.strftime('%Y-%m-%d %H:%M:%S') + + # Create HTML content for the PDF + html_content = f""" + + + + + + + + + + """ + + # Convert HTML to PDF + pdf_content = html_to_pdf(html_content) + + # Create attachment tuple (filename, content, mime_type) + filename = f"Email_{subject.replace(' ', '_')[:30]}.pdf" + attachment = ( + filename, + base64.b64encode(pdf_content).decode('utf-8'), + 'application/pdf' + ) + + _logger.info("Created PDF attachment from email: %s", filename) + return attachment From f3259fd6a10bd11f31863dac7df012b91ad43c23 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 27 Mar 2025 10:49:45 -0400 Subject: [PATCH 3/4] updates to account_email_to_pdf - total rewrite --- account_email_to_pdf/__manifest__.py | 26 ++-- account_email_to_pdf/models/account_move.py | 122 ++++++++++++++---- account_email_to_pdf/tests/__init__.py | 1 + .../tests/test_email_to_pdf.py | 103 +++++++++++++++ 4 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 account_email_to_pdf/tests/__init__.py create mode 100644 account_email_to_pdf/tests/test_email_to_pdf.py diff --git a/account_email_to_pdf/__manifest__.py b/account_email_to_pdf/__manifest__.py index f5c4d75..8b4e4f0 100644 --- a/account_email_to_pdf/__manifest__.py +++ b/account_email_to_pdf/__manifest__.py @@ -1,11 +1,11 @@ { - 'name': 'Account Email to PDF', - 'version': '18.0.1.0.0', - 'category': 'Accounting', - 'summary': 'Convert email messages to PDF attachments for vendor bills', - 'description': """ + "name": "Account Email to PDF", + "version": "18.0.1.0.0", + "category": "Accounting", + "summary": "Convert email messages to PDF attachments for vendor bills", + "description": """ Account Email to PDF -=================== +==================== This module converts email messages without attachments into PDF attachments when processing incoming emails for vendor bills. @@ -13,11 +13,11 @@ Instead of rejecting emails without attachments, the system will create a PDF from the email content and attach it to the message, allowing the vendor bill creation process to continue. """, - 'author': 'Bemade', - 'website': 'https://bemade.org', - 'depends': ['account'], - 'data': [], - 'installable': True, - 'auto_install': False, - 'license': 'LGPL-3', + "author": "Bemade", + "website": "https://bemade.org", + "depends": ["account"], + "data": [], + "installable": True, + "auto_install": False, + "license": "LGPL-3", } diff --git a/account_email_to_pdf/models/account_move.py b/account_email_to_pdf/models/account_move.py index e9eb647..14bed27 100644 --- a/account_email_to_pdf/models/account_move.py +++ b/account_email_to_pdf/models/account_move.py @@ -1,24 +1,28 @@ import base64 import logging +import os +import subprocess +import tempfile +from contextlib import closing from datetime import datetime from email.utils import formatdate from html import escape from odoo import models, fields -from odoo.tools.pdf import html_to_pdf +from odoo.tools.misc import find_in_path _logger = logging.getLogger(__name__) class AccountMove(models.Model): - _inherit = 'account.move' + _inherit = "account.move" def _check_and_decode_attachment(self, attachments): """Override to convert email message to PDF if no attachments are present.""" - if not attachments or self.env.context.get('no_new_invoice'): + if not attachments or self.env.context.get("no_new_invoice"): # Original code would return False here, causing email rejection # Instead, we'll create a PDF from the email message - message_dict = self.env.context.get('message_dict', {}) + message_dict = self.env.context.get("message_dict", {}) if message_dict: try: # Create a PDF from the email content @@ -28,25 +32,95 @@ class AccountMove(models.Model): return super()._check_and_decode_attachment([pdf_attachment]) except Exception as e: _logger.exception("Error creating PDF from email: %s", e) - - # If we couldn't create a PDF, fall back to original behavior - return False - - # If there are attachments, proceed with the original method + + # Proceed with the original method + # If we were unable to generate a PDF, the original method will bounce the email return super()._check_and_decode_attachment(attachments) - + + @classmethod + def _html_to_pdf(cls, html_content): + """Convert HTML content to PDF using wkhtmltopdf. + + Args: + html_content (str): HTML content to convert to PDF + + Returns: + bytes: PDF content as bytes or False if conversion failed + """ + wkhtmltopdf_bin = find_in_path("wkhtmltopdf") + if not wkhtmltopdf_bin: + _logger.error("Cannot find wkhtmltopdf executable in system path") + return False + + # Create temporary files for the HTML input and PDF output + html_file_fd, html_file_path = tempfile.mkstemp( + suffix=".html", prefix="email_to_pdf." + ) + pdf_file_fd, pdf_file_path = tempfile.mkstemp( + suffix=".pdf", prefix="email_to_pdf." + ) + + try: + # Write the HTML content to the temporary file + with closing(os.fdopen(html_file_fd, "wb")) as html_file: + html_file.write(html_content.encode("utf-8")) + + # Close the PDF file descriptor as wkhtmltopdf will write to it + os.close(pdf_file_fd) + + # Basic wkhtmltopdf command arguments + command = [wkhtmltopdf_bin] + command.extend(["--encoding", "utf-8"]) + command.extend(["--page-size", "A4"]) + command.extend(["--margin-top", "10mm"]) + command.extend(["--margin-bottom", "10mm"]) + command.extend(["--margin-left", "10mm"]) + command.extend(["--margin-right", "10mm"]) + command.append(html_file_path) + command.append(pdf_file_path) + + # Execute wkhtmltopdf + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + out, err = process.communicate() + + if process.returncode not in [0, 1]: + _logger.error( + "wkhtmltopdf failed with error code %s: %s", process.returncode, err + ) + return False + + # Read the generated PDF + with open(pdf_file_path, "rb") as pdf_file: + pdf_content = pdf_file.read() + + return pdf_content + + except Exception as e: + _logger.exception("Error during PDF generation: %s", e) + return False + + finally: + # Clean up temporary files + try: + os.unlink(html_file_path) + os.unlink(pdf_file_path) + except (OSError, IOError): + _logger.error("Failed to remove temporary files") + def _create_pdf_from_email(self, message_dict): """Create a PDF attachment from an email message.""" # Extract email details - email_from = message_dict.get('email_from', 'Unknown Sender') - email_date = message_dict.get('date', datetime.now()) - subject = message_dict.get('subject', 'No Subject') - body = message_dict.get('body', '') - + email_from = message_dict.get("email_from", "Unknown Sender") + email_date = message_dict.get("date", datetime.now()) + subject = message_dict.get("subject", "No Subject") + body = message_dict.get("body", "") + # Format the date if it's a datetime object if isinstance(email_date, datetime): - email_date = email_date.strftime('%Y-%m-%d %H:%M:%S') - + email_date = email_date.strftime("%Y-%m-%d %H:%M:%S") + # Create HTML content for the PDF html_content = f""" @@ -71,17 +145,19 @@ class AccountMove(models.Model): """ - + # Convert HTML to PDF - pdf_content = html_to_pdf(html_content) - + pdf_content = self._html_to_pdf(html_content) + if not pdf_content: + return False + # Create attachment tuple (filename, content, mime_type) filename = f"Email_{subject.replace(' ', '_')[:30]}.pdf" attachment = ( filename, - base64.b64encode(pdf_content).decode('utf-8'), - 'application/pdf' + base64.b64encode(pdf_content).decode("utf-8"), + "application/pdf", ) - + _logger.info("Created PDF attachment from email: %s", filename) return attachment diff --git a/account_email_to_pdf/tests/__init__.py b/account_email_to_pdf/tests/__init__.py new file mode 100644 index 0000000..00d0605 --- /dev/null +++ b/account_email_to_pdf/tests/__init__.py @@ -0,0 +1 @@ +from . import test_email_to_pdf diff --git a/account_email_to_pdf/tests/test_email_to_pdf.py b/account_email_to_pdf/tests/test_email_to_pdf.py new file mode 100644 index 0000000..954cf45 --- /dev/null +++ b/account_email_to_pdf/tests/test_email_to_pdf.py @@ -0,0 +1,103 @@ +import base64 +from odoo.tests.common import TransactionCase +from odoo.tools.misc import find_in_path + + +class TestHtmlToPdf(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Check if wkhtmltopdf is available + cls.wkhtmltopdf_available = bool(find_in_path("wkhtmltopdf")) + + # Get the model class to access the classmethod + cls.account_move = cls.env["account.move"] + + # Simple test HTML content + cls.test_html = """ + + + + + +

Test HTML Document

+

This is a test paragraph with bold text and italic text.

+
    +
  • List item 1
  • +
  • List item 2
  • +
  • List item 3
  • +
+ + + """ + + def test_html_to_pdf_conversion(self): + """Test the direct HTML to PDF conversion.""" + if not self.wkhtmltopdf_available: + self.skipTest("wkhtmltopdf not available") + + # Call the method to convert HTML to PDF + pdf_content = self.account_move._html_to_pdf(self.test_html) + + # Verify the PDF was created + self.assertTrue(pdf_content, "PDF content should be generated") + + # Verify it's a valid PDF + self.assertTrue( + pdf_content.startswith(b"%PDF-"), "Content should be a valid PDF" + ) + self.assertTrue(len(pdf_content) > 100, "PDF should have reasonable size") + + def test_html_to_pdf_with_complex_content(self): + """Test HTML to PDF conversion with more complex content.""" + if not self.wkhtmltopdf_available: + self.skipTest("wkhtmltopdf not available") + + # More complex HTML with tables and images + complex_html = """ + + + + + +

Complex HTML Test

+ + + + + + + + + + + + + + + + +
Header 1Header 2Header 3
Row 1, Cell 1Row 1, Cell 2Row 1, Cell 3
Row 2, Cell 1Row 2, Cell 2Row 2, Cell 3
+ + + """ + + # Convert complex HTML to PDF + pdf_content = self.account_move._html_to_pdf(complex_html) + + # Verify the PDF was created + self.assertTrue( + pdf_content, "PDF content should be generated from complex HTML" + ) + self.assertTrue( + pdf_content.startswith(b"%PDF-"), "Content should be a valid PDF" + ) + self.assertTrue(len(pdf_content) > 100, "PDF should have reasonable size") From ad2bc057a285b24370905d0c7989bc5a5f5f7c89 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Thu, 27 Mar 2025 11:23:58 -0400 Subject: [PATCH 4/4] further fixes and tests for email_to_pdf --- account_email_to_pdf/models/account_move.py | 39 ++++++-- account_email_to_pdf/tests/__init__.py | 1 + .../tests/test_email_integration.py | 88 +++++++++++++++++++ .../tests/test_email_to_pdf.py | 1 - 4 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 account_email_to_pdf/tests/test_email_integration.py diff --git a/account_email_to_pdf/models/account_move.py b/account_email_to_pdf/models/account_move.py index 14bed27..df99587 100644 --- a/account_email_to_pdf/models/account_move.py +++ b/account_email_to_pdf/models/account_move.py @@ -28,8 +28,19 @@ class AccountMove(models.Model): # Create a PDF from the email content pdf_attachment = self._create_pdf_from_email(message_dict) if pdf_attachment: - # Add the PDF to the attachments list - return super()._check_and_decode_attachment([pdf_attachment]) + _logger.info( + "Successfully created PDF attachment, proceeding with invoice creation" + ) + # We need to return the result of _extend_with_attachments directly + # as that's what the original method would return + # Convert the list to a recordset before passing to _extend_with_attachments + attachment_recordset = self.env["ir.attachment"].browse( + [pdf_attachment.id] + ) + return self._extend_with_attachments( + attachment_recordset, + new=bool(self._context.get("from_alias")), + ) except Exception as e: _logger.exception("Error creating PDF from email: %s", e) @@ -110,7 +121,14 @@ class AccountMove(models.Model): _logger.error("Failed to remove temporary files") def _create_pdf_from_email(self, message_dict): - """Create a PDF attachment from an email message.""" + """Create a PDF attachment from an email message. + + Args: + message_dict (dict): Email message dictionary + + Returns: + ir.attachment: The created attachment record or False if failed + """ # Extract email details email_from = message_dict.get("email_from", "Unknown Sender") email_date = message_dict.get("date", datetime.now()) @@ -151,13 +169,16 @@ class AccountMove(models.Model): if not pdf_content: return False - # Create attachment tuple (filename, content, mime_type) + # Create a proper ir.attachment record filename = f"Email_{subject.replace(' ', '_')[:30]}.pdf" - attachment = ( - filename, - base64.b64encode(pdf_content).decode("utf-8"), - "application/pdf", - ) + attachment_vals = { + "name": filename, + "datas": base64.b64encode(pdf_content), + "mimetype": "application/pdf", + "res_model": "mail.message", + "res_id": message_dict.get("id", 0), + } + attachment = self.env["ir.attachment"].create(attachment_vals) _logger.info("Created PDF attachment from email: %s", filename) return attachment diff --git a/account_email_to_pdf/tests/__init__.py b/account_email_to_pdf/tests/__init__.py index 00d0605..8e19221 100644 --- a/account_email_to_pdf/tests/__init__.py +++ b/account_email_to_pdf/tests/__init__.py @@ -1 +1,2 @@ from . import test_email_to_pdf +from . import test_email_integration diff --git a/account_email_to_pdf/tests/test_email_integration.py b/account_email_to_pdf/tests/test_email_integration.py new file mode 100644 index 0000000..5dbe556 --- /dev/null +++ b/account_email_to_pdf/tests/test_email_integration.py @@ -0,0 +1,88 @@ +import base64 +from odoo.tests.common import TransactionCase +from odoo.tools.misc import find_in_path +from unittest.mock import patch + + +class TestEmailProcessing(TransactionCase): + """Integration tests for email processing with PDF generation.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + # Check if wkhtmltopdf is available + cls.wkhtmltopdf_available = bool(find_in_path("wkhtmltopdf")) + + # Get the model class to access the methods + cls.account_move = cls.env["account.move"] + + def test_create_pdf_from_email(self): + """Test that an email without attachments can be converted to PDF. + + This test directly verifies that the _create_pdf_from_email method + correctly generates a PDF attachment from an email message without + attachments, allowing the invoice creation process to continue. + """ + if not self.wkhtmltopdf_available: + self.skipTest("wkhtmltopdf not available") + + # Create a sample email message dictionary (similar to what would be parsed from an email) + message_dict = { + "subject": "Test Invoice", + "from": "test@example.com", + "to": "invoices@example.com", + "body": "

Invoice Test

This is a test invoice.

", + "attachments": [], # No attachments + "message_id": "", + } + + # Call the method directly to create a PDF from the email + attachment = self.account_move._create_pdf_from_email(message_dict) + + # Verify that an attachment was created + self.assertTrue(attachment, "An attachment should have been created") + # The actual name format is 'Email_' + subject + '.pdf' with spaces replaced by underscores + self.assertEqual( + attachment.name, + "Email_Test_Invoice.pdf", + "Attachment name should match expected format", + ) + self.assertEqual( + attachment.mimetype, "application/pdf", "Attachment should be a PDF" + ) + + # Verify the content of the PDF attachment + pdf_data = base64.b64decode(attachment.datas) + self.assertTrue(pdf_data.startswith(b"%PDF-"), "Content should be a valid PDF") + self.assertTrue(len(pdf_data) > 100, "PDF should have a reasonable size") + + def test_check_and_decode_attachment_with_empty_attachments(self): + """Test that _check_and_decode_attachment doesn't reject emails with no attachments.""" + if not self.wkhtmltopdf_available: + self.skipTest("wkhtmltopdf not available") + + # Set up a context with a message_dict to simulate email processing + message_dict = { + "subject": "Test Invoice", + "from": "test@example.com", + "to": "invoices@example.com", + "body": "

Invoice Test

This is a test invoice.

", + "attachments": [], # No attachments + "message_id": "", + } + + # Call the method with an empty attachments list + # We need to pass the message_dict in the context so _create_pdf_from_email can access it + result = self.account_move.with_context( + message_dict=message_dict + )._check_and_decode_attachment([]) + + # Verify that the result is not False (which would mean email rejection) + self.assertNotEqual( + result, + False, + "Should not reject the email when no attachments are provided", + ) + + # Verify that the result contains attachment data + self.assertTrue(result, "Should return attachment data") diff --git a/account_email_to_pdf/tests/test_email_to_pdf.py b/account_email_to_pdf/tests/test_email_to_pdf.py index 954cf45..1acddea 100644 --- a/account_email_to_pdf/tests/test_email_to_pdf.py +++ b/account_email_to_pdf/tests/test_email_to_pdf.py @@ -1,4 +1,3 @@ -import base64 from odoo.tests.common import TransactionCase from odoo.tools.misc import find_in_path