243 lines
10 KiB
Python
243 lines
10 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
import base64
|
||
|
|
||
|
from freezegun import freeze_time
|
||
|
from os.path import join as opj
|
||
|
|
||
|
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||
|
from odoo import fields
|
||
|
from odoo.tools import misc
|
||
|
|
||
|
from lxml import etree
|
||
|
|
||
|
|
||
|
class TestUBLCommon(AccountTestInvoicingCommon):
|
||
|
|
||
|
@classmethod
|
||
|
def setUpClass(cls):
|
||
|
super().setUpClass()
|
||
|
|
||
|
cls.other_currency = cls.setup_other_currency('USD', rounding=0.001)
|
||
|
|
||
|
# Required for `product_uom_id` to be visible in the form views
|
||
|
cls.env.user.groups_id += cls.env.ref('uom.group_uom')
|
||
|
|
||
|
# remove this tax, otherwise, at import, this tax with children taxes can be selected and the total is wrong
|
||
|
cls.tax_armageddon.children_tax_ids.unlink()
|
||
|
cls.tax_armageddon.unlink()
|
||
|
|
||
|
cls.move_template = cls.env['mail.template'].create({
|
||
|
'auto_delete': True,
|
||
|
'body_html': '<p>TemplateBody for <t t-out="object.name"></t><t t-out="object.invoice_user_id.signature or \'\'"></t></p>',
|
||
|
'description': 'Sent to customers with their invoices in attachment',
|
||
|
'email_from': "{{ (object.invoice_user_id.email_formatted or user.email_formatted) }}",
|
||
|
'model_id': cls.env['ir.model']._get_id('account.move'),
|
||
|
'name': "Invoice: Test Sending",
|
||
|
'partner_to': "{{ object.partner_id.id }}",
|
||
|
'subject': "{{ object.company_id.name }} Invoice (Ref {{ object.name or 'n/a' }})",
|
||
|
'report_template_ids': [(4, cls.env.ref('account.account_invoices').id)],
|
||
|
'lang': "{{ object.partner_id.lang }}",
|
||
|
})
|
||
|
|
||
|
# Fixed Taxes
|
||
|
cls.recupel = cls.env['account.tax'].create({
|
||
|
'name': "RECUPEL",
|
||
|
'amount_type': 'fixed',
|
||
|
'amount': 1,
|
||
|
'include_base_amount': True,
|
||
|
'sequence': 1,
|
||
|
})
|
||
|
cls.auvibel = cls.env['account.tax'].create({
|
||
|
'name': "AUVIBEL",
|
||
|
'amount_type': 'fixed',
|
||
|
'amount': 1,
|
||
|
'include_base_amount': True,
|
||
|
'sequence': 2,
|
||
|
})
|
||
|
|
||
|
def assert_same_invoice(self, invoice1, invoice2, **invoice_kwargs):
|
||
|
self.assertEqual(len(invoice1.invoice_line_ids), len(invoice2.invoice_line_ids))
|
||
|
self.assertRecordValues(invoice2, [{
|
||
|
'partner_id': invoice1.partner_id.id,
|
||
|
'invoice_date': fields.Date.from_string(invoice1.date),
|
||
|
'currency_id': invoice1.currency_id.id,
|
||
|
'amount_untaxed': invoice1.amount_untaxed,
|
||
|
'amount_tax': invoice1.amount_tax,
|
||
|
'amount_total': invoice1.amount_total,
|
||
|
**invoice_kwargs,
|
||
|
}])
|
||
|
|
||
|
default_invoice_line_kwargs_list = [{}] * len(invoice1.invoice_line_ids)
|
||
|
invoice_line_kwargs_list = invoice_kwargs.get('invoice_line_ids', default_invoice_line_kwargs_list)
|
||
|
self.assertRecordValues(invoice2.invoice_line_ids, [{
|
||
|
'quantity': line.quantity,
|
||
|
'price_unit': line.price_unit,
|
||
|
'discount': line.discount,
|
||
|
'product_id': line.product_id.id,
|
||
|
'product_uom_id': line.product_uom_id.id,
|
||
|
**invoice_line_kwargs,
|
||
|
} for line, invoice_line_kwargs in zip(invoice1.invoice_line_ids, invoice_line_kwargs_list)])
|
||
|
|
||
|
# -------------------------------------------------------------------------
|
||
|
# IMPORT HELPERS
|
||
|
# -------------------------------------------------------------------------
|
||
|
|
||
|
@freeze_time('2017-01-01')
|
||
|
def _assert_imported_invoice_from_etree(self, invoice, attachment):
|
||
|
"""
|
||
|
Create an account.move directly from an xml file, asserts the invoice obtained is the same as the expected
|
||
|
invoice.
|
||
|
"""
|
||
|
# /!\ use the same journal as the invoice's one to import the attachment !
|
||
|
invoice.journal_id.create_document_from_attachment(attachment.ids)
|
||
|
new_invoice = self.env['account.move'].search([], order='id desc', limit=1)
|
||
|
|
||
|
self.assertTrue(new_invoice)
|
||
|
self.assert_same_invoice(invoice, new_invoice)
|
||
|
|
||
|
def _update_invoice_from_file(self, module_name, subfolder, filename, invoice):
|
||
|
""" Create an attachment from a file and post it on the invoice
|
||
|
"""
|
||
|
file_path = opj(module_name, subfolder, filename)
|
||
|
with misc.file_open(file_path, 'rb', filter_ext=('.xml',)) as file:
|
||
|
attachment = self.env['ir.attachment'].create({
|
||
|
'name': filename,
|
||
|
'datas': base64.encodebytes(file.read()),
|
||
|
'res_id': invoice.id,
|
||
|
'res_model': 'account.move',
|
||
|
})
|
||
|
invoice.message_post(attachment_ids=[attachment.id])
|
||
|
|
||
|
def _assert_imported_invoice_from_file(self, subfolder, filename, invoice_vals, move_type='in_invoice'):
|
||
|
""" Create an empty account.move, update the xml file, and then check the invoice values. """
|
||
|
if move_type in self.env['account.move'].get_purchase_types():
|
||
|
journal = self.company_data['default_journal_purchase']
|
||
|
else:
|
||
|
journal = self.company_data['default_journal_sale']
|
||
|
invoice = self.env['account.move'].create({'move_type': move_type, 'journal_id': journal.id})
|
||
|
self._update_invoice_from_file(
|
||
|
module_name='l10n_account_edi_ubl_cii_tests',
|
||
|
subfolder=subfolder,
|
||
|
filename=filename,
|
||
|
invoice=invoice,
|
||
|
)
|
||
|
invoice_vals = invoice_vals.copy()
|
||
|
invoice_lines = invoice_vals.pop('invoice_lines', False)
|
||
|
self.assertRecordValues(invoice, [invoice_vals])
|
||
|
if invoice_lines:
|
||
|
self.assertRecordValues(invoice.invoice_line_ids, invoice_lines)
|
||
|
|
||
|
# -------------------------------------------------------------------------
|
||
|
# EXPORT HELPERS
|
||
|
# -------------------------------------------------------------------------
|
||
|
|
||
|
@freeze_time('2017-01-01')
|
||
|
def _generate_move(self, seller, buyer, send=True, **invoice_kwargs):
|
||
|
"""
|
||
|
Create and post an account.move.
|
||
|
"""
|
||
|
|
||
|
# Setup the seller.
|
||
|
self.env.company.write({
|
||
|
'partner_id': seller.id,
|
||
|
'name': seller.name,
|
||
|
'street': seller.street,
|
||
|
'zip': seller.zip,
|
||
|
'city': seller.city,
|
||
|
'vat': seller.vat,
|
||
|
'country_id': seller.country_id.id,
|
||
|
})
|
||
|
|
||
|
move_type = invoice_kwargs['move_type']
|
||
|
account_move = self.env['account.move'].create({
|
||
|
'partner_id': buyer.id,
|
||
|
'partner_bank_id': (seller if move_type == 'out_invoice' else buyer).bank_ids[:1].id,
|
||
|
'invoice_payment_term_id': self.pay_terms_b.id,
|
||
|
'invoice_date': '2017-01-01',
|
||
|
'date': '2017-01-01',
|
||
|
'currency_id': self.other_currency.id,
|
||
|
'narration': 'test narration',
|
||
|
'ref': 'ref_move',
|
||
|
**invoice_kwargs,
|
||
|
'invoice_line_ids': [
|
||
|
(0, 0, {
|
||
|
'sequence': i,
|
||
|
**invoice_line_kwargs,
|
||
|
})
|
||
|
for i, invoice_line_kwargs in enumerate(invoice_kwargs.get('invoice_line_ids', []))
|
||
|
],
|
||
|
})
|
||
|
|
||
|
account_move.action_post()
|
||
|
if send:
|
||
|
# will set the right UBL format by default thanks to the partner's compute
|
||
|
account_move._generate_and_send(sending_methods=['manual'])
|
||
|
return account_move
|
||
|
|
||
|
def _assert_invoice_attachment(self, attachment, xpaths, expected_file_path):
|
||
|
"""
|
||
|
Get attachment from a posted account.move, and asserts it's the same as the expected xml file.
|
||
|
"""
|
||
|
self.assertTrue(attachment)
|
||
|
|
||
|
xml_content = base64.b64decode(attachment.with_context(bin_size=False).datas)
|
||
|
xml_etree = self.get_xml_tree_from_string(xml_content)
|
||
|
|
||
|
expected_file_full_path = misc.file_path(f'{self.test_module}/tests/test_files/{expected_file_path}')
|
||
|
expected_etree = etree.parse(expected_file_full_path).getroot()
|
||
|
|
||
|
modified_etree = self.with_applied_xpath(
|
||
|
expected_etree,
|
||
|
xpaths
|
||
|
)
|
||
|
|
||
|
self.assertXmlTreeEqual(
|
||
|
xml_etree,
|
||
|
modified_etree,
|
||
|
)
|
||
|
|
||
|
return attachment
|
||
|
|
||
|
def _test_import_partner(self, attachment, seller, buyer):
|
||
|
"""
|
||
|
Given a buyer and seller in an EDI attachment.
|
||
|
* Uploading the attachment as an invoice should create an invoice with the partner = buyer.
|
||
|
* Uploading the attachment as a vendor bill should create a bill with the partner = seller.
|
||
|
"""
|
||
|
# Import attachment as an invoice
|
||
|
new_invoice = self.company_data['default_journal_sale']._create_document_from_attachment(attachment.ids)
|
||
|
self.assertEqual(buyer, new_invoice.partner_id)
|
||
|
|
||
|
# Import attachment as a vendor bill
|
||
|
new_invoice = self.company_data['default_journal_purchase']._create_document_from_attachment(attachment.ids)
|
||
|
self.assertEqual(seller, new_invoice.partner_id)
|
||
|
|
||
|
def _test_import_in_journal(self, attachment):
|
||
|
"""
|
||
|
If the context contains the info about the current default journal, we should use it
|
||
|
instead of infering the journal from the move type.
|
||
|
"""
|
||
|
journal2 = self.company_data['default_journal_sale'].copy()
|
||
|
journal2.default_account_id = self.company_data['default_account_revenue'].id
|
||
|
journal3 = journal2.copy()
|
||
|
journal3.default_account_id = self.company_data['default_account_revenue'].id # Not copied
|
||
|
|
||
|
# Use the journal if it's set
|
||
|
new_invoice = journal2._create_document_from_attachment(attachment.id)
|
||
|
self.assertEqual(new_invoice.journal_id, journal2)
|
||
|
|
||
|
# If no journal, fallback on the context
|
||
|
new_invoice2 = self.env['account.journal'].with_context(default_journal_id=journal3.id)._create_document_from_attachment(attachment.id)
|
||
|
self.assertEqual(new_invoice2.journal_id, journal3)
|
||
|
|
||
|
# If no journal and no journal in the context, fallback on the move type
|
||
|
new_invoice3 = self.env['account.journal'].with_context(default_move_type='out_invoice')._create_document_from_attachment(attachment.id)
|
||
|
self.assertEqual(new_invoice3.journal_id, self.company_data['default_journal_sale'])
|
||
|
|
||
|
def _test_encoding_in_attachment(self, attachment, filename):
|
||
|
"""
|
||
|
Generate an invoice, assert that the tag '<?xml version='1.0' encoding='UTF-8'?>' is present in the attachment
|
||
|
"""
|
||
|
self.assertTrue(filename in attachment.name)
|
||
|
self.assertIn(b"<?xml version='1.0' encoding='UTF-8'?>", attachment.raw)
|