Files
enterprise-addons/documents_account/tests/test_documents.py
T

747 lines
35 KiB
Python

# -*- 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'<?xml version="1.0" ?>\n<test> </test>'),
})
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'<?xml version="1.0" ?>\n<test> <Attachment>JVBERi0gRmFrZSBQREYgY29udGVudA==</Attachment> </test>'),
})
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'<text/>',
'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'<text/>',
'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: <test-{filename}-{origin}@example.com>
Subject: Incoming bill {filename}
From: Someone <someone@some.company.com>
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()