# -*- coding: utf-8 -*- import base64 import textwrap from datetime import timedelta from freezegun import freeze_time from itertools import product from unittest.mock import patch from odoo import Command from odoo.addons.documents_account.tests.common import DocumentsAccountTestCommon, TEXT, PDF from odoo.exceptions import UserError from odoo.tests.common import tagged, HttpCase from odoo.addons.account.tests.test_account_move_send import TestAccountMoveSendCommon from odoo.addons.documents.models.documents_document import DocumentsDocument from odoo.tools.misc import file_open @tagged('post_install', '-at_install', 'test_document_bridge') class TestCaseDocumentsBridgeAccount(DocumentsAccountTestCommon, HttpCase): def test_action_view_documents_account_move(self): """ Test the behavior of opening default folder when there are more than one documents. """ account_move_test_1, account_move_test_2 = self.env['account.move'].create([{ 'name': 'Journal Entry 1', 'move_type': 'entry', }, { 'name': 'Journal Entry 2', 'move_type': 'entry', }]) self.setup_sync_journal_folder(account_move_test_1.journal_id, self.folder_a) self.assertFalse(account_move_test_1.has_documents, "Should be False because no attachment is attached to this record") self.assertFalse(account_move_test_2.has_documents, "Should be False because no attachment is attached to this record") attachments = self.env['ir.attachment'].create([{ 'name': 'fileText_test.txt', 'res_model': 'account.move', 'res_id': account_move_test_1.id, }, { 'name': 'fileText_test2.txt', 'res_model': 'account.move', 'res_id': account_move_test_1.id, }]) self.assertTrue(account_move_test_1.has_documents, "Should be True because attachment is attached to this record") self.assertFalse(account_move_test_2.has_documents, "Should be False because no attachment is attached to this record") # If both the documents have same folder, open that folder. action = account_move_test_1.action_view_documents_account_move() self.assertEqual(action['context']['searchpanel_default_user_folder_id'], str(self.folder_a.id), "The 'folder A' should be the default.") # If both the documents have different folder, open the 'All' folder. folder_test = self.env['documents.document'].create({'name': 'folder_test', 'type': 'folder'}) document = self.env['documents.document'].search([('attachment_id', '=', attachments[0].id)]) document.folder_id = folder_test.id action = account_move_test_1.action_view_documents_account_move() self.assertFalse(action['context']['searchpanel_default_user_folder_id'], "The 'All' folder should be the default.") def test_bridge_folder_workflow(self): """ tests the create new business model (vendor bill & credit note). """ self.assertFalse(self.document_txt.res_model, "failed at default res model") account_moves_count_pre = self.env['account.move'].sudo().search_count([]) multi_return = (self.document_txt | self.document_gif).account_create_account_move('in_invoice') account_moves_count_post = self.env['account.move'].sudo().search_count([]) self.assertEqual(account_moves_count_post - account_moves_count_pre, 2) self.assertEqual(multi_return.get('type'), 'ir.actions.act_window', 'failed at invoice workflow return value type') self.assertEqual(multi_return.get('res_model'), 'account.move', 'failed at invoice workflow return value res model') self.assertEqual(self.document_txt.res_model, 'account.move', "failed at workflow_bridge_dms_account" " new res_model") vendor_bill_txt = self.env['account.move'].search([('id', '=', self.document_txt.res_id)]) self.assertTrue(vendor_bill_txt.exists(), 'failed at workflow_bridge_dms_account vendor_bill') self.assertEqual(self.document_txt.res_id, vendor_bill_txt.id, "failed at workflow_bridge_dms_account res_id") self.assertEqual(vendor_bill_txt.move_type, 'in_invoice', "failed at workflow_bridge_dms_account vendor_bill type") vendor_bill_gif = self.env['account.move'].search([('id', '=', self.document_gif.res_id)]) self.assertEqual(self.document_gif.res_id, vendor_bill_gif.id, "failed at workflow_bridge_dms_account res_id") account_moves_count_pre = self.env['account.move'].sudo().search_count([]) single_return = self.document_txt.account_create_account_move('in_invoice') account_moves_count_post = self.env['account.move'].sudo().search_count([]) self.assertEqual(account_moves_count_post - account_moves_count_pre, 0) self.assertEqual(single_return.get('res_model'), 'account.move', 'failed at invoice res_model action from workflow create model') invoice = self.env[single_return['res_model']].browse(single_return.get('res_id')) attachments = self.env['ir.attachment'].search([('res_model', '=', 'account.move'), ('res_id', '=', invoice.id)]) self.assertEqual(len(attachments), 1, 'there should only be one ir attachment matching') def test_bridge_account_account_settings_on_write(self): """ Makes sure the settings apply their values when an ir_attachment is set as message_main_attachment_id on invoices. """ folder_test = self.env['documents.document'].create({'name': 'folder_test', 'type': 'folder'}) for invoice_type in ['in_invoice', 'out_invoice', 'in_refund', 'out_refund', 'entry']: invoice_test = self.env['account.move'].with_context(default_move_type=invoice_type).create({ 'name': 'invoice_test', 'move_type': invoice_type, }) setting = self.setup_sync_journal_folder(invoice_test.journal_id, folder_test) attachments = self.env["ir.attachment"] for i in range(3): attachment = self.env["ir.attachment"].create({ "datas": TEXT, "name": f"fileText_test{i}.txt", "mimetype": "text/plain", "res_model": "account.move", "res_id": invoice_test.id, }) attachment.register_as_main_attachment(force=False) attachments |= attachment document = self.env["documents.document"].search( [("attachment_id", "=", attachments[0].id)] ) self.assertEqual( document.folder_id, folder_test, "the text test document have a folder" ) def check_main_attachment_and_document( main_attachment, doc_attachment, previous_attachment_ids ): self.assertRecordValues( invoice_test, [{"message_main_attachment_id": main_attachment.id}], ) self.env["documents.document"].flush_model() if invoice_test.move_type == "entry": expected = { "attachment_id": attachments[0].id, "previous_attachment_ids": [], } else: expected = { "attachment_id": doc_attachment.id, "previous_attachment_ids": previous_attachment_ids, } self.assertRecordValues(document, [expected]) # Ensure the main attachment is the first one and ensure the document is correctly linked check_main_attachment_and_document(attachments[0], attachments[0], []) # Switch the main attachment to the second one and ensure the document is updated correctly invoice_test.write({"message_main_attachment_id": attachments[1].id}) check_main_attachment_and_document( attachments[1], attachments[1], attachments[0].ids ) # Switch the main attachment to the third one and ensure the document is updated correctly attachments[2].register_as_main_attachment(force=True) check_main_attachment_and_document( attachments[2], attachments[2], (attachments[0] + attachments[1]).ids ) # Ensure all attachments are still linked to the invoice attachments = self.env["ir.attachment"].search( [("res_model", "=", "account.move"), ("res_id", "=", invoice_test.id)] ) self.assertEqual( len(attachments), 3, "there should be 3 attachments linked to the invoice", ) # deleting the setting to prevent duplicate settings. setting.unlink() def test_bridge_account_account_settings_on_write_with_versioning(self): """ With accounting-document centralization activated, make sure that the right attachment is set as main attachment on the invoice when versioning is involved and only one document is being created and updated. """ folder_test = self.env["documents.document"].create({"name": "folder_test", "type": "folder"}) invoice_test = ( self.env["account.move"] .with_context(default_move_type="in_invoice") .create({ "name": "invoice_test", "move_type": "in_invoice", }) ) self.setup_sync_journal_folder(invoice_test.journal_id, folder_test) attachments = self.env["ir.attachment"] for i in range(1, 3): attachment = self.env["ir.attachment"].create({ "datas": TEXT, "name": f"attachment-{i}.txt", "mimetype": "text/plain", "res_model": "account.move", "res_id": invoice_test.id, }) attachment.register_as_main_attachment(force=False) attachments |= attachment first_attachment, second_attachment = attachments[0], attachments[1] document = self.env["documents.document"].search( [("res_model", "=", "account.move"), ("res_id", "=", invoice_test.id)] ) self.assertEqual( len(document), 1, "there should be 1 document linked to the invoice" ) self.assertEqual( document.folder_id, folder_test, "the text test document have a folder" ) def check_main_attachment_and_document( main_attachment, doc_attachment, previous_attachment_ids ): self.assertRecordValues( invoice_test, [{"message_main_attachment_id": main_attachment.id}], ) self.assertRecordValues( document, [ { "attachment_id": doc_attachment.id, "previous_attachment_ids": previous_attachment_ids, } ], ) # Ensure the main attachment is attachment-1 check_main_attachment_and_document(first_attachment, first_attachment, []) # Version the main attachment: # attachment-1 become attachment-3 # version attachement become attachment-1 document.write({ "datas": TEXT, "name": "attachment-3.txt", "mimetype": "text/plain", }) third_attachment = document.attachment_id first_attachment = document.previous_attachment_ids[0] check_main_attachment_and_document( third_attachment, third_attachment, first_attachment.ids ) # Switch main attachment to attachment-2 second_attachment.register_as_main_attachment(force=True) check_main_attachment_and_document( second_attachment, second_attachment, (first_attachment + third_attachment).ids, ) # restore versioned attachment (attachment-1) document.write({"attachment_id": document.previous_attachment_ids[0].id}) check_main_attachment_and_document( second_attachment, first_attachment, (third_attachment + second_attachment).ids, ) # Switch main attachment to attachment-3 third_attachment.register_as_main_attachment(force=True) check_main_attachment_and_document( third_attachment, third_attachment, (second_attachment + first_attachment).ids, ) # Ensure there is still only one document linked to the invoice document = self.env["documents.document"].search( [("res_model", "=", "account.move"), ("res_id", "=", invoice_test.id)] ) self.assertEqual( len(document), 1, "there should be 1 document linked to the invoice" ) def test_journal_entry(self): """ Makes sure the settings apply their values when an ir_attachment is set as message_main_attachment_id on invoices. """ folder_test = self.env['documents.document'].create({'name': 'Bills', 'type': 'folder'}) invoice_test = self.env['account.move'].with_context(default_move_type='entry').create({ 'name': 'Journal Entry', 'move_type': 'entry', }) setting = self.setup_sync_journal_folder(invoice_test.journal_id, folder_test) attachments = self.env['ir.attachment'].create([{ 'datas': TEXT, 'name': 'fileText_test.txt', 'mimetype': 'text/plain', 'res_model': 'account.move', 'res_id': invoice_test.id }, { 'datas': TEXT, 'name': 'fileText_test2.txt', 'mimetype': 'text/plain', 'res_model': 'account.move', 'res_id': invoice_test.id }]) documents = self.env['documents.document'].search([('attachment_id', 'in', attachments.ids)]) self.assertEqual(len(documents), 2) setting.unlink() def test_bridge_account_sync_partner(self): """ Tests that the partner is always synced on the document, regardless of settings """ partner_1, partner_2 = self.env['res.partner'].create([{'name': 'partner_1'}, {'name': 'partner_2'}]) self.document_txt.partner_id = partner_1 (self.document_txt | self.document_gif).account_create_account_move('in_invoice') move = self.env['account.move'].browse(self.document_txt.res_id) self.assertEqual(move.partner_id, partner_1) move.partner_id = partner_2 self.assertEqual(self.document_txt.partner_id, partner_2) def test_embedded_pdf(self): document = self.env['documents.document'].create({ 'name': 'test', 'folder_id': self.folder_a.id, 'datas': base64.b64encode(b'\n '), }) self.assertEqual(document.mimetype, 'text/xml') self.assertFalse(document._extract_pdf_from_xml()) self.assertFalse(document.thumbnail_status) self.assertFalse(document.has_embedded_pdf) document = self.env['documents.document'].create({ 'name': 'test', 'folder_id': self.folder_a.id, 'datas': base64.b64encode(b'\n JVBERi0gRmFrZSBQREYgY29udGVudA== '), }) self.assertEqual(document.mimetype, 'text/xml') self.assertEqual(document._extract_pdf_from_xml(), b'%PDF- Fake PDF content') self.assertEqual(document.thumbnail_status, 'client_generated') self.assertTrue(document.has_embedded_pdf) def test_pdf_first_page_route_for_embedded_pdf(self): with file_open("documents_account/tests/assets/vendor_bill_example.xml", "rb") as xml_file: xml_bytes = xml_file.read() document = self.env['documents.document'].create({ 'name': 'test', 'folder_id': self.folder_a.id, 'datas': base64.b64encode(xml_bytes).decode(), }) self.assertEqual(document.mimetype, "text/xml") self.assertTrue(document.has_embedded_pdf) self.assertTrue(document._extract_pdf_from_xml()) self.assertEqual(document.thumbnail_status, 'client_generated') self.authenticate("admin", "admin") pdf_first_page = self.url_open(f'/documents/content/pdf_first_page/{document.access_token}') self.assertEqual(pdf_first_page.status_code, 200) def test_tour_embedded_pdf_thumbnail_generation(self): self.start_tour("/odoo", "test_embedded_pdf_thumbnail_generation", login="admin") def test_move_document_unlink(self): """Test that the document is sent to trash when the `account.move` is unlinked.""" document1, document2 = self.document_txt, self.document_gif (document1 | document2).account_create_account_move('in_invoice') self.assertEqual(document1.res_model, "account.move") self.assertEqual(document2.res_model, "account.move") move1 = self.env["account.move"].browse(document1.res_id).exists() move2 = self.env["account.move"].browse(document2.res_id).exists() self.assertTrue(move1) self.assertTrue(move2) attachment1 = self.env['ir.attachment'].search([ ('res_model', '=', move1._name), ('res_id', '=', move1.id), ]) attachment2 = self.env['ir.attachment'].search([ ('res_model', '=', move2._name), ('res_id', '=', move2.id), ]) # attachment not linked to a document attachment3 = self.env['ir.attachment'].create({ 'name': 'Attachment 3', 'res_model': move2._name, 'res_id': move2.id, }) self.assertEqual(len(attachment1), 1) self.assertEqual(len(attachment2), 1) self.env.flush_all() (move1 | move2).unlink() self.assertTrue(attachment1.exists()) self.assertTrue(document1.exists()) self.assertFalse(document1.active) self.assertTrue(attachment2.exists()) self.assertTrue(document2.exists()) self.assertFalse(document2.active) self.assertFalse(attachment3.exists(), "That attachment is not linked to a record and so it should be removed") # removing the document in the trash clean the attachment document2.unlink() self.assertFalse(attachment2.exists()) def test_workflow_create_misc_entry(self): misc_entry_action = (self.document_txt | self.document_gif).account_create_account_move('entry') move = self.env['account.move'].browse(self.document_txt.res_id) self.assertEqual(misc_entry_action.get('res_model'), 'account.move') self.assertEqual(move.move_type, 'entry') def test_workflow_create_bank_statement_raise_content_parsing(self): for document in self.document_txt, self.document_gif: with self.subTest(document=document.name): with self.assertRaises(UserError) as err: document.account_create_account_bank_statement() self.assertEqual( err.exception.args[0], f"All or part of the following file(s) could not be imported:\n" f"- {document.name}: Could not make sense of the given file.\n" f"Did you install the module to support this type of file?", ) def test_workflow_create_bank_statement_raise_no_journal(self): new_company = self.env['res.company'].create({'name': 'new_company_without_journals'}) doc_in_new_company = self.document_txt.copy({'company_id': new_company.id}) for sudo in False, True: # Running test with sudo similulates server actions that run with sudo rights with self.subTest(sudo=sudo): with self.assertRaises(UserError) as err: doc_in_new_company.with_company(new_company).sudo(sudo).account_create_account_bank_statement() self.assertEqual( err.exception.args[0], "No journal could be found in company new_company_without_journals for any of those types: bank", ) def test_workflow_create_vendor_bill(self): vendor_bill_entry_action = self.document_txt.account_create_account_move('in_invoice') move = self.env['account.move'].browse(self.document_txt.res_id) self.assertEqual(vendor_bill_entry_action.get('res_model'), 'account.move') self.assertEqual(move.move_type, 'in_invoice') def test_workflow_create_vendor_receipt(self): # Activate the group for the vendor receipt vendor_receipt_action = self.document_txt.account_create_account_move('in_receipt') move = self.env['account.move'].browse(self.document_txt.res_id) self.assertEqual(vendor_receipt_action.get('res_model'), 'account.move') self.assertEqual(move.move_type, 'in_receipt') def test_documents_xml_attachment(self): """ Tests that XML and PDF attachments are correctly synced as documents, whether they are linked to the move during create() or write(). """ folder_test = self.env['documents.document'].create({'name': 'Bills', 'type': 'folder'}) invoice = self.init_invoice('out_invoice', amounts=[1000], post=True) self.setup_sync_journal_folder(invoice.journal_id, folder_test) expected_sync_att_ids = [] for fmt, no_doc in product(('application/xml', 'text/xml', 'application/txt'), (False, True)): expected_doc_count = 0 if fmt == 'application/txt' or no_doc else 1 folder_domain = [('folder_id', '=', folder_test.id)] if expected_doc_count else [] fmt_name = fmt.replace('/', '_') # 1. Test sync via attachment.create() create_att = self.env['ir.attachment'].with_context(no_document=no_doc).create({ 'raw': b'', 'name': f'create-{fmt_name}.txt', 'mimetype': fmt, 'res_model': 'account.move', 'res_id': invoice.id, }) self.assertEqual( self.env['documents.document'].search_count([('attachment_id', '=', create_att.id)] + folder_domain), expected_doc_count, f"{fmt} failed correct sync behavior on create() with no_documents={no_doc}" ) # 2. Test sync via attachment.write() write_att = self.env['ir.attachment'].create({ 'raw': b'', 'name': f'write-{fmt_name}.txt', 'mimetype': fmt, }) self.assertFalse(self.env['documents.document'].search_count([('attachment_id', '=', write_att.id)])) write_att.with_context(no_document=no_doc).write({ 'res_model': 'account.move', 'res_id': invoice.id, }) self.assertEqual( self.env['documents.document'].search_count([('attachment_id', '=', write_att.id)] + folder_domain), expected_doc_count, f"{fmt} failed correct sync behavior on write() with no_documents={no_doc}" ) if expected_doc_count == 1: expected_sync_att_ids.extend([create_att.id, write_att.id]) # Test PDF sync pdf_att = self.env['ir.attachment'].create({ 'datas': PDF, 'name': 'file.pdf', 'mimetype': 'application/pdf', 'res_model': 'account.move', 'res_id': invoice.id, }) self.assertFalse(invoice.message_main_attachment_id) self.assertFalse( self.env['documents.document'].search_count([('attachment_id', '=', pdf_att.id)]), "PDF should not be attached if not main attachment", ) pdf_att.register_as_main_attachment(force=True) self.assertTrue( self.env["documents.document"].search_count([ ("attachment_id", "=", pdf_att.id), ("folder_id", "=", folder_test.id), ]) ) expected_sync_att_ids.append(pdf_att.id) # Test final count of syncs all_docs = self.env['documents.document'].search([('attachment_id', 'in', expected_sync_att_ids)]) self.assertEqual(len(all_docs), len(expected_sync_att_ids), "All XMLs and the registered PDF must be synced") self.assertEqual(all_docs.mapped('folder_id'), folder_test, "All documents must be in the synced folder") def test_embeddable_server_action_domain(self): """Test the domain that filters server actions that can be embedded on a folder. Especially that server actions that create a record with a journal of a company different from the current one are filtered out. """ IrActionServer = self.env['ir.actions.server'] def get_server_actions_for_company(company): return IrActionServer.with_company(company).search( self.env['documents.document'].with_company(company)._get_embeddable_server_action_domain()) company_1 = self.env.company company_2 = self.setup_other_company()['company'] companies = company_1 | company_2 pre_existing = {company: get_server_actions_for_company(company) for company in companies} action_base_vals = {'model_id': self.env['ir.model']._get_id('documents.document')} action_create_base_vals = { **action_base_vals, 'state': 'documents_account_record_create', 'documents_account_create_model': 'account.move.in_invoice', } journal_id_per_company = { company: self.env['account.move'].with_company(company)._get_suitable_journal_ids('in_invoice').ids[0] for company in companies } for company in companies: other_company = companies - company action_single_company_vals = { **action_create_base_vals, 'name': f'single {company.name}', 'documents_account_journal_id': journal_id_per_company[company], } IrActionServer.with_company(company).create([ action_single_company_vals, { **action_base_vals, 'name': f'multi {company.name}', 'state': 'multi', 'child_ids': [Command.create(action_single_company_vals)], }, { **action_base_vals, 'name': 'multi invalid', 'state': 'multi', 'child_ids': [ IrActionServer.with_company(company).create(action_single_company_vals).id, IrActionServer.with_company(other_company).create({ **action_single_company_vals, 'documents_account_journal_id': journal_id_per_company[other_company] }).id, ], }]) action_single_vals = { **action_create_base_vals, 'name': 'single', } IrActionServer.create([ action_single_vals, { **action_base_vals, 'name': 'multi', 'state': 'multi', 'child_ids': [Command.create(action_single_vals)], }]) self.assertEqual( set((get_server_actions_for_company(company_1) - pre_existing[company_1]).mapped('name')), {'multi', f'multi {company_1.name}', 'single', f'single {company_1.name}'} ) self.assertEqual( set((get_server_actions_for_company(company_2) - pre_existing[company_2]).mapped('name')), {'multi', f'multi {company_2.name}', 'single', f'single {company_2.name}'} ) def test_gc_clear_bin_for_journal_folder(self): """Check that account setting folders are excluded from garbage collector.""" folder_setting, other_folder = self.env['documents.document'].create([ {'name': 'Folder setting', 'type': 'folder'}, {'name': 'Other folder', 'type': 'folder'}, ]) self.env['documents.account.folder.setting'].search([]).unlink() self.setup_sync_journal_folder(self.company_data['default_journal_sale'], folder_setting) (folder_setting | other_folder).action_archive() document_deletion_date = folder_setting.write_date + timedelta( days=folder_setting.get_deletion_delay(), seconds=30) with freeze_time(document_deletion_date): self.env["documents.document"]._gc_clear_bin() self.assertTrue(folder_setting.exists(), "folder linked to journal setting should not be deleted after gc_clear_bin") self.assertFalse(other_folder.exists(), "folder not linked to journal setting should be deleted after gc_clear_bin") def test_permissions_adding_document_attachment(self): """Check that attachment of send_and_print can be copied in composer.""" invoice = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2016-01-01', 'partner_id': self.partner_a.id, 'invoice_line_ids': [Command.create({ 'quantity': 1, 'price_unit': 500, 'tax_ids': [], })] }) attachment = self.env['ir.attachment'].create([{ 'name': 'att.pdf', 'res_id': invoice.id, 'res_model': 'account.move', 'res_field': 'invoice_pdf_report_file', # simulates send & print 'datas': 'test', 'type': 'binary', }]) attachment.register_as_main_attachment() document = self.env['documents.document'].search([('attachment_id', '=', attachment.id)], limit=1) self.assertTrue(document) document.with_user(self.simple_accountman).add_documents_attachment("mail.compose.message", 0) attachment_copy = self.env['ir.attachment'].search([('original_id', '=', attachment.id)], limit=1) self.assertTrue(attachment_copy) @tagged('post_install_l10n', 'post_install', '-at_install') class TestAccountMoveSendDocument(DocumentsAccountTestCommon, TestAccountMoveSendCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.folder_test = cls.env['documents.document'].create({'name': 'folder_test', 'type': 'folder'}) cls.journal = cls.company_data['default_journal_purchase'] cls.setup_sync_journal_folder(cls.journal, cls.folder_test) def test_log_note_and_incoming_email_document_creation(self): """ Test that posting a file via log note or incoming email syncs to Documents ONLY if its mimetype is explicitly allowed by `_should_attach_to_record` (e.g., PDFs, CSVs). Unlisted formats (e.g., images) are orphaned and must not trigger a document sync. """ test_cases = [ ('invoice.pdf', 'application/pdf', PDF, True), ('receipt.png', 'image/png', base64.b64encode(b'dummy image content'), False), ] origins = ('log_note', 'email') for origin, (filename, mimetype, content, should_sync) in product(origins, test_cases): with self.subTest(origin=origin, filename=filename, mimetype=mimetype): if origin == 'log_note': invoice = self.init_invoice("in_invoice", amounts=[1000], post=False) attachment = self.env['ir.attachment'].create({ 'name': filename, 'datas': content, 'mimetype': mimetype, 'res_model': 'account.move', 'res_id': invoice.id, }) invoice.message_post(message_type='comment', attachment_ids=attachment.ids) elif origin == 'email': content_str = content.decode() if isinstance(content, bytes) else content email_raw = textwrap.dedent(f"""\ MIME-Version: 1.0 Date: Fri, 26 Nov 2021 16:27:45 +0100 Message-ID: Subject: Incoming bill {filename} From: Someone To: {self.journal.alias_id.display_name} Content-Type: multipart/alternative; boundary="000000000000a47519057e029630" --000000000000a47519057e029630 Content-Type: text/plain; charset="UTF-8" Here is your document. --000000000000a47519057e029630 Content-Type: {mimetype} Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="{filename}" {content_str} --000000000000a47519057e029630-- """) invoice_id = self.env['mail.thread'].message_process( 'account.move', email_raw, custom_values={'move_type': 'in_invoice', 'journal_id': self.journal.id} ) invoice = self.env['account.move'].browse(invoice_id) attachment = self.env['ir.attachment'].search([('name', '=', filename)], limit=1) self.assertTrue(attachment) self.assertEqual(invoice.message_main_attachment_id, attachment) doc = self.env['documents.document'].search([('attachment_id', '=', attachment.id)]) if should_sync: self.assertEqual(attachment.res_model, 'account.move') self.assertTrue(doc) else: self.assertFalse(attachment.res_model) self.assertFalse(doc) def test_send_and_print_document_creation(self): """ Makes sure the documents are created when attaching pdf and xml to the move """ folder_test = self.env['documents.document'].create({'name': 'Bills', 'type': 'folder'}) move = self.init_invoice("out_invoice", amounts=[1000], post=True) setting = self.setup_sync_journal_folder(move.journal_id, folder_test) wizard = self.create_send_and_print(move) wizard.action_send_and_print() attachments = move.attachment_ids | move.invoice_pdf_report_id documents = self.env['documents.document'].search([('attachment_id', 'in', attachments.ids)]) self.assertEqual(len(documents), len(attachments), "Each move attachment should create a corresponding document") with patch.object(DocumentsDocument, '_get_is_multipage', return_value=False): setting.unlink()