1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_mx_edi/tests/test_cfdi_invoice.py
2024-12-10 09:04:09 +07:00

2004 lines
88 KiB
Python

# -*- coding: utf-8 -*-
from .common import TestMxEdiCommon, EXTERNAL_MODE
from odoo import fields, Command
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.tools import misc
from odoo.tools.misc import file_open
from datetime import datetime
from dateutil.relativedelta import relativedelta
from lxml import etree
from pytz import timezone
from odoo.addons.l10n_mx_edi.models.l10n_mx_edi_document import CFDI_DATE_FORMAT
@tagged('post_install_l10n', 'post_install', '-at_install', *(['-standard', 'external'] if EXTERNAL_MODE else []))
class TestCFDIInvoice(TestMxEdiCommon):
def test_invoice_misc_business_values(self):
for move_type, output_file in (
('out_invoice', 'test_misc_business_values_invoice'),
('out_refund', 'test_misc_business_values_credit_note')
):
with self.mx_external_setup(self.frozen_today), self.subTest(move_type=move_type):
invoice = self._create_invoice(
invoice_incoterm_id=self.env.ref('account.incoterm_FCA').id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 2000.0,
'quantity': 5,
'discount': 20.0,
}),
# Ignored lines by the CFDI:
Command.create({
'product_id': self.product.id,
'price_unit': 2000.0,
'quantity': 0.0,
}),
Command.create({
'product_id': self.product.id,
'price_unit': 0.0,
'quantity': 10.0,
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, output_file)
def test_customer_in_mx(self):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice()
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_customer_in_mx_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, 'test_customer_in_mx_pay')
def test_customer_in_us(self):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(partner_id=self.partner_us.id)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_customer_in_us_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, 'test_customer_in_us_pay')
def test_customer_no_country(self):
with self.mx_external_setup(self.frozen_today):
self.partner_us.country_id = None
invoice = self._create_invoice(partner_id=self.partner_us.id)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_customer_no_country_inv')
def test_customer_in_mx_to_public(self):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_customer_in_mx_to_public_inv')
def test_invoice_taxes(self):
def create_invoice(taxes_list, l10n_mx_edi_cfdi_to_public=False):
invoice_line_ids = []
for i, taxes in enumerate(taxes_list, start=1):
invoice_line_ids.append(Command.create({
'product_id': self.product.id,
'price_unit': 1000.0 * i,
'quantity': 5,
'discount': 20.0,
'tax_ids': [Command.set(taxes.ids)],
}))
# Full discounted line:
invoice_line_ids.append(Command.create({
'product_id': self.product.id,
'price_unit': 1000.0 * i,
'discount': 100.0,
'tax_ids': [Command.set(taxes.ids)],
}))
return self._create_invoice(
invoice_line_ids=invoice_line_ids,
l10n_mx_edi_cfdi_to_public=l10n_mx_edi_cfdi_to_public,
)
with self.mx_external_setup(self.frozen_today):
for index, taxes_list in enumerate(self.existing_taxes_combinations_to_test, start=1):
with self.subTest(index=index):
# Test the invoice CFDI.
self.partner_mx.l10n_mx_edi_no_tax_breakdown = False
invoice = create_invoice(taxes_list)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_invoice_taxes_{index}_invoice')
# Test the payment CFDI.
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_invoice_taxes_{index}_payment')
# Test the global invoice CFDI.
invoice = create_invoice(taxes_list, l10n_mx_edi_cfdi_to_public=True)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
self._assert_global_invoice_cfdi_from_invoices(invoice, f'test_invoice_taxes_{index}_ginvoice')
# Test the invoice with no tax breakdown.
self.partner_mx.l10n_mx_edi_no_tax_breakdown = True
invoice = create_invoice(taxes_list)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_invoice_taxes_{index}_invoice_no_tax_breakdown')
def test_invoice_addenda(self):
with self.mx_external_setup(self.frozen_today):
self.partner_mx.l10n_mx_edi_addenda = self.env['ir.ui.view'].create({
'name': 'test_invoice_cfdi_addenda',
'type': 'qweb',
'arch': """
<t t-name="l10n_mx_edi.test_invoice_cfdi_addenda">
<test info="this is an addenda"/>
</t>
"""
})
invoice = self._create_invoice()
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_invoice_addenda')
def test_invoice_negative_lines_dispatch_same_product(self):
""" Ensure the distribution of negative lines is done on the same product first. """
product1 = self.product
product2 = self._create_product()
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': product1.id,
'quantity': 5.0,
'tax_ids': [],
}),
Command.create({
'product_id': product2.id,
'quantity': -5.0,
'tax_ids': [],
}),
Command.create({
'product_id': product2.id,
'quantity': 12.0,
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_invoice_negative_lines_dispatch_same_product')
def test_invoice_negative_lines_dispatch_same_amount(self):
""" Ensure the distribution of negative lines is done on the same amount. """
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 12.0,
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': 3.0,
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': 6.0,
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': -3.0,
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_invoice_negative_lines_dispatch_same_amount')
def test_invoice_negative_lines_dispatch_same_taxes(self):
""" Ensure the distribution of negative lines is done exclusively on lines having the same taxes. """
product1 = self.product
product2 = self._create_product()
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': product1.id,
'quantity': 12.0,
'tax_ids': [],
}),
Command.create({
'product_id': product1.id,
'quantity': 3.0,
'tax_ids': [],
}),
Command.create({
'product_id': product2.id,
'quantity': 6.0,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
Command.create({
'product_id': product1.id,
'quantity': -3.0,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_invoice_negative_lines_dispatch_same_taxes')
def test_invoice_negative_lines_dispatch_biggest_amount(self):
""" Ensure the distribution of negative lines is done on the biggest amount. """
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 3.0,
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': 12.0,
'discount': 10.0, # price_subtotal: 10800
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': 8.0,
'discount': 20.0, # price_subtotal: 6400
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': -22.0,
'discount': 22.0, # price_subtotal: 17160
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_invoice_negative_lines_dispatch_biggest_amount')
def test_invoice_negative_lines_zero_total(self):
""" Test an invoice completely refunded by the negative lines. """
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 12.0,
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': -12.0,
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'move_id': invoice.id,
'state': 'invoice_sent',
'attachment_id': False,
'cancel_button_needed': False,
}])
def test_invoice_negative_lines_orphan_negative_line(self):
""" Test an invoice in which a negative line failed to be distributed. """
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 12.0,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
Command.create({
'product_id': self.product.id,
'quantity': -2.0,
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'move_id': invoice.id,
'state': 'invoice_sent_failed',
}])
def test_global_invoice_negative_lines_zero_total(self):
""" Test an invoice completely refunded by the negative lines. """
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 12.0,
'tax_ids': [],
}),
Command.create({
'product_id': self.product.id,
'quantity': -12.0,
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
self.env['l10n_mx_edi.global_invoice.create'] \
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context']) \
.create({})\
.action_create_global_invoice()
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'invoice_ids': invoice.ids,
'state': 'ginvoice_sent',
'attachment_id': False,
'cancel_button_needed': False,
}])
def test_global_invoice_negative_lines_orphan_negative_line(self):
""" Test a global invoice containing an invoice having a negative line that failed to be distributed. """
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 12.0,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
Command.create({
'product_id': self.product.id,
'quantity': -2.0,
'tax_ids': [],
}),
],
)
with self.with_mocked_pac_sign_success():
self.env['l10n_mx_edi.global_invoice.create'] \
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context']) \
.create({})\
.action_create_global_invoice()
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'invoice_ids': invoice.ids,
'state': 'ginvoice_sent_failed',
}])
def test_global_invoice_including_partial_refund(self):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 10.0,
}),
Command.create({
'product_id': self.product.id,
'quantity': -2.0,
}),
],
)
refund = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
move_type='out_refund',
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 3.0,
}),
Command.create({
'product_id': self.product.id,
'quantity': -1.0,
}),
],
reversed_entry_id=invoice.id,
)
invoices = invoice + refund
with self.with_mocked_pac_sign_success():
# Calling the global invoice on the invoice will include the refund automatically.
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})\
.action_create_global_invoice()
self._assert_global_invoice_cfdi_from_invoices(invoices, 'test_global_invoice_including_partial_refund')
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'invoice_ids': invoices.ids,
'state': 'ginvoice_sent',
}])
def test_global_invoice_including_full_refund(self):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 10.0,
}),
],
)
refund = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
move_type='out_refund',
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 10.0,
}),
],
reversed_entry_id=invoice.id,
)
invoices = invoice + refund
with self.with_mocked_pac_sign_success():
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(invoices.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})\
.action_create_global_invoice()
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'invoice_ids': invoices.ids,
'state': 'ginvoice_sent',
'attachment_id': False,
}])
def test_global_invoice_not_allowed_refund(self):
with self.mx_external_setup(self.frozen_today):
refund = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
move_type='out_refund',
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 3.0,
}),
],
)
with self.assertRaises(UserError):
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(refund.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})\
.action_create_global_invoice()
def test_global_invoice_refund_after(self):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 10.0,
}),
],
)
with self.with_mocked_pac_sign_success():
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})\
.action_create_global_invoice()
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [{
'invoice_ids': invoice.ids,
'state': 'ginvoice_sent',
}])
refund = self._create_invoice(
l10n_mx_edi_cfdi_to_public=True,
move_type='out_refund',
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': 3.0,
}),
],
reversed_entry_id=invoice.id,
)
with self.assertRaises(UserError):
self.env['l10n_mx_edi.global_invoice.create'] \
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context']) \
.create({})
with self.with_mocked_pac_sign_success():
self.env['account.move.send']\
.with_context(active_model=refund._name, active_ids=refund.ids)\
.create({})\
.action_send_and_print()
self._assert_invoice_cfdi(refund, 'test_global_invoice_refund_after')
self.assertRecordValues(refund.l10n_mx_edi_invoice_document_ids, [{
'move_id': refund.id,
'invoice_ids': refund.ids,
'state': 'invoice_sent',
}])
def test_invoice_company_branch(self):
self.env.company.write({
'child_ids': [Command.create({
'name': 'Branch A',
'zip': '85120',
'l10n_mx_edi_certificate_ids': [Command.set(self.certificate.ids)],
})],
})
self.cr.precommit.run() # load the CoA
branch = self.env.company.child_ids
self.product.company_id = branch
with self.mx_external_setup(self.frozen_today - relativedelta(hours=1)):
invoice = self._create_invoice(company_id=branch.id)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, 'test_invoice_company_branch')
def test_invoice_then_refund(self):
# Create an invoice then sign it.
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
with self.with_mocked_pac_sign_success():
self.env['account.move.send']\
.with_context(active_model=invoice._name, active_ids=invoice.ids)\
.create({})\
.action_send_and_print()
self._assert_invoice_cfdi(invoice, 'test_invoice_then_refund_1')
# You are no longer able to create a global invoice.
with self.assertRaises(UserError):
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})
# Create a refund.
results = self.env['account.move.reversal']\
.with_context(active_model='account.move', active_ids=invoice.ids)\
.create({
'reason': "turlututu",
'journal_id': invoice.journal_id.id,
})\
.refund_moves()
refund = self.env['account.move'].browse(results['res_id'])
refund.auto_post = 'no'
refund.action_post()
# You can't make a global invoice for it.
with self.assertRaises(UserError):
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(refund.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})
# Create the CFDI and sign it.
with self.with_mocked_pac_sign_success():
self.env['account.move.send']\
.with_context(active_model=refund._name, active_ids=refund.ids)\
.create({})\
.action_send_and_print()
self._assert_invoice_cfdi(refund, 'test_invoice_then_refund_2')
self.assertRecordValues(refund, [{
'l10n_mx_edi_cfdi_origin': f'01|{invoice.l10n_mx_edi_cfdi_uuid}',
}])
def test_global_invoice_then_refund(self):
# Create a global invoice and sign it.
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
with self.with_mocked_pac_sign_success():
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(invoice.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})\
.action_create_global_invoice()
self._assert_global_invoice_cfdi_from_invoices(invoice, 'test_global_invoice_then_refund_1')
# You are not able to create an invoice for it.
wizard = self.env['account.move.send']\
.with_context(active_model=invoice._name, active_ids=invoice.ids)\
.create({})
self.assertRecordValues(wizard, [{'l10n_mx_edi_enable_cfdi': False}])
# Refund the invoice.
results = self.env['account.move.reversal']\
.with_context(active_model='account.move', active_ids=invoice.ids)\
.create({
'reason': "turlututu",
'journal_id': invoice.journal_id.id,
})\
.refund_moves()
refund = self.env['account.move'].browse(results['res_id'])
refund.auto_post = 'no'
refund.action_post()
# You can't do a global invoice for a refund
with self.assertRaises(UserError):
self.env['l10n_mx_edi.global_invoice.create']\
.with_context(refund.l10n_mx_edi_action_create_global_invoice()['context'])\
.create({})
# Sign the refund.
with self.with_mocked_pac_sign_success():
self.env['account.move.send']\
.with_context(active_model=refund._name, active_ids=refund.ids)\
.create({})\
.action_send_and_print()
self._assert_invoice_cfdi(refund, 'test_global_invoice_then_refund_2')
def test_invoice_send_and_print_fallback_pdf(self):
# Trigger an error when generating the CFDI
self.product.unspsc_code_id = False
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 1000.0,
'tax_ids': [Command.set(self.tax_0.ids)],
}),
],
)
template = self.env.ref(invoice._get_mail_template())
invoice.with_context(skip_invoice_sync=True)._generate_pdf_and_send_invoice(template, force_synchronous=True, allow_fallback_pdf=True)
self.assertFalse(invoice.invoice_pdf_report_id, "invoice_pdf_report_id shouldn't be set with the proforma PDF.")
def test_import_invoice(self):
file_name = "test_import_bill"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
self.partner_mx.company_id = self.env.company
# Read the problematic xml file that kept causing crash on bill uploads
with file_open(full_file_path, "rb") as file:
new_invoice = self._upload_document_on_journal(
journal=self.company_data['default_journal_sale'],
content=file.read(),
filename=file_name,
)
self.assertRecordValues(new_invoice, [{
'currency_id': self.comp_curr.id,
'partner_id': self.partner_mx.id,
'amount_tax': 80.0,
'amount_untaxed': 500.0,
'amount_total': 580.0,
'invoice_date': fields.Date.from_string('2024-04-08'),
'l10n_mx_edi_payment_method_id': self.env.ref('l10n_mx_edi.payment_method_efectivo').id,
'l10n_mx_edi_usage': 'G03',
'l10n_mx_edi_cfdi_uuid': '8CA06290-4800-4F93-8B1B-25B208BB1AFF',
}])
self.assertRecordValues(new_invoice.invoice_line_ids, [{
'quantity': 1.0,
'price_unit': 500.0,
'discount': 0.0,
'product_id': self.product.id,
'product_uom_id': self.product.uom_po_id.id,
'tax_ids': self.tax_16.ids,
}])
# The state of the document should be "Sent".
self.assertEqual(new_invoice.l10n_mx_edi_invoice_document_ids.state, 'invoice_sent')
# The "Update SAT" button should appear continuously (after posting).
new_invoice.action_post()
self.assertRecordValues(new_invoice, [{
'need_cancel_request': True,
'l10n_mx_edi_update_sat_needed': True,
}])
def test_import_bill(self):
# Invoice with payment policy = PUE, otherwise 'FormaPago' (payment method) is set to '99' ('Por Definir')
# and the initial payment method cannot be backtracked at import
file_name = "test_import_bill"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
# company's partner is not linked by default to its company.
self.env.company.partner_id.company_id = self.env.company
# Read the problematic xml file that kept causing crash on bill uploads
with file_open(full_file_path, "rb") as file:
file_content = file.read()
new_bill = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file_content,
filename=file_name,
)
self.assertRecordValues(new_bill, [{
'currency_id': self.comp_curr.id,
'partner_id': self.env.company.partner_id.id,
'amount_tax': 80.0,
'amount_untaxed': 500.0,
'amount_total': 580.0,
'invoice_date': fields.Date.from_string('2024-04-08'),
'l10n_mx_edi_payment_method_id': self.env.ref('l10n_mx_edi.payment_method_efectivo').id,
'l10n_mx_edi_usage': 'G03',
'l10n_mx_edi_cfdi_uuid': '8CA06290-4800-4F93-8B1B-25B208BB1AFF',
}])
self.assertRecordValues(new_bill.invoice_line_ids, [{
'quantity': 1.0,
'price_unit': 500.0,
'discount': 0.0,
'product_id': self.product.id,
'product_uom_id': self.product.uom_po_id.id,
'tax_ids': self.tax_16_purchase.ids,
}])
# The state of the document should be "Sent".
self.assertEqual(new_bill.l10n_mx_edi_invoice_document_ids.state, 'invoice_received')
# The "Update SAT" button should appear continuously (after posting).
new_bill.action_post()
self.assertTrue(new_bill.l10n_mx_edi_update_sat_needed)
with self.with_mocked_sat_call(lambda _x: 'valid'):
new_bill.l10n_mx_edi_cfdi_try_sat()
self.assertTrue(new_bill.l10n_mx_edi_update_sat_needed)
# Check the error about duplicated fiscal folio.
new_bill_same_folio = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file_content,
filename=file_name,
)
new_bill_same_folio.action_post()
self.assertRecordValues(new_bill_same_folio, [{'duplicated_ref_ids': new_bill.ids}])
def test_add_cfdi_on_existing_bill_without_cfdi(self):
file_name = 'test_add_cfdi_on_existing_bill'
file_path = f'{self.test_module}/tests/test_files/{file_name}.xml'
with file_open(file_path, 'rb') as file:
cfdi_invoice = file.read()
attachment = self.env['ir.attachment'].create({
'mimetype': 'application/xml',
'name': f'{file_name}.xml',
'raw': cfdi_invoice,
})
bill = self.env['account.move'].create({
'move_type': 'in_invoice',
'partner_id': self.partner_mx.id,
'date': self.frozen_today.date(),
'invoice_date': self.frozen_today.date(),
'invoice_line_ids': [Command.create({'product_id': self.product.id})],
})
prev_invoice_line_ids = bill.invoice_line_ids
# Bill was created without a cfdi invoice
self.assertRecordValues(bill, [{
'l10n_mx_edi_cfdi_attachment_id': None,
'l10n_mx_edi_cfdi_uuid': None,
}])
# post message with the cfdi invoice attached
bill.message_post(attachment_ids=attachment.ids)
# check that the uuid is now set and the cfdi attachment is linked but the invoice lines did not change
self.assertRecordValues(bill, [{
'l10n_mx_edi_cfdi_attachment_id': attachment.id,
'l10n_mx_edi_cfdi_uuid': '42000000-0000-0000-0000-000000000001',
'invoice_line_ids': prev_invoice_line_ids.ids,
}])
def test_add_cfdi_on_existing_bill_with_cfdi(self):
# Check that uploading a CFDI on a bill with an existing CFDI doesn't change the fiscal
# folio or CFDI document
file_name = "test_import_bill"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
self.env.company.partner_id.company_id = self.env.company
with file_open(full_file_path, "rb") as file:
file_content = file.read()
bill = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file_content,
filename=file_name,
)
file_name = 'test_add_cfdi_on_existing_bill'
file_path = f'{self.test_module}/tests/test_files/{file_name}.xml'
with file_open(file_path, 'rb') as file:
cfdi_invoice = file.read()
attachment = self.env['ir.attachment'].create({
'mimetype': 'application/xml',
'name': f'{file_name}.xml',
'raw': cfdi_invoice,
})
initial_uuid = bill.l10n_mx_edi_cfdi_uuid
initial_attachment_id = bill.l10n_mx_edi_document_ids.attachment_id.id
# post message with a different cfdi invoice attached
bill.message_post(attachment_ids=attachment.ids)
# check that the uuid and attachment have not changed to those of the attachment
self.assertRecordValues(bill, [{
'l10n_mx_edi_cfdi_uuid': initial_uuid,
'l10n_mx_edi_cfdi_attachment_id': initial_attachment_id,
}])
def test_import_bill_with_extento(self):
file_name = "test_import_bill_with_extento"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
# Read the problematic xml file that kept causing crash on bill uploads
with file_open(full_file_path, "rb") as file:
new_bill = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file.read(),
filename=file_name,
)
self.assertRecordValues(new_bill.invoice_line_ids, (
{
'quantity': 1,
'price_unit': 54017.48,
'tax_ids': self.tax_16_purchase.ids,
},
{
'quantity': 1,
'price_unit': 17893.00,
'tax_ids': self.tax_0_exento_purchase.ids,
}
))
def test_import_bill_without_tax(self):
file_name = "test_import_bill_without_tax"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
# Read the problematic xml file that kept causing crash on bill uploads
with file_open(full_file_path, "rb") as file:
new_bill = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file.read(),
filename=file_name,
)
self.assertRecordValues(new_bill.invoice_line_ids, (
{
'quantity': 1,
'price_unit': 54017.48,
'tax_ids': self.tax_16_purchase.ids,
},
{
'quantity': 1,
'price_unit': 17893.00,
# This should be empty due to the error causing missing attribute 'TasaOCuota' to result in empty tax_ids
'tax_ids': [],
}
))
def test_import_bill_with_withholding(self):
file_name = "test_import_bill_with_withholding"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
# Read the problematic xml file that kept causing crash on bill uploads
with file_open(full_file_path, "rb") as file:
new_invoice = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file.read(),
filename=file_name,
)
self.assertRecordValues(new_invoice.line_ids.sorted(), (
{
'balance': 147.0,
'tax_ids': (self.tax_16_purchase + self.tax_4_purchase_withholding).ids,
'display_type': 'product',
'tax_line_id': False,
},
{
'balance': -5.88,
'tax_ids': [],
'display_type': 'tax',
'tax_line_id': self.tax_4_purchase_withholding.id,
},
{
'balance': 23.52,
'tax_ids': [],
'display_type': 'tax',
'tax_line_id': self.tax_16_purchase.id,
},
{
'balance': -164.64,
'tax_ids': [],
'display_type': 'payment_term',
'tax_line_id': False,
},
))
def test_import_invoice_cfdi_unknown_partner(self):
'''Test the import of invoices with unknown partners:
* The partner should be created correctly
* On the created move the "CFDI to Public" field (l10n_mx_edi_cfdi_to_public) should be set correctly.
'''
mx = self.env.ref('base.mx')
subtests = [
{
'xml_file': 'test_import_invoice_cfdi_unknown_partner_1',
'expected_invoice_vals': {
'l10n_mx_edi_cfdi_to_public': False,
},
'expected_partner_vals': {
'name': "INMOBILIARIA CVA",
'vat': 'ICV060329BY0',
'country_id': mx.id,
'property_account_position_id': False,
'zip': '26670',
},
},
{
'xml_file': 'test_import_invoice_cfdi_unknown_partner_2',
'expected_invoice_vals': {
'l10n_mx_edi_cfdi_to_public': False,
},
'expected_partner_vals': {
'name': "PARTNER_US",
'vat': False,
'country_id': False,
'property_account_position_id': self.env['account.chart.template'].ref('account_fiscal_position_foreign').id,
'zip': False,
},
},
{
'xml_file': 'test_import_invoice_cfdi_unknown_partner_3',
'expected_invoice_vals': {
'l10n_mx_edi_cfdi_to_public': True,
},
'expected_partner_vals': {
'name': "INMOBILIARIA CVA",
'vat': False,
'country_id': mx.id,
'property_account_position_id': False,
'zip': False,
},
},
]
for subtest in subtests:
xml_file = subtest['xml_file']
with self.subTest(msg=xml_file), self.mocked_retrieve_partner():
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{xml_file}.xml')
with file_open(full_file_path, "rb") as file:
invoice = self._upload_document_on_journal(
journal=self.company_data['default_journal_sale'],
content=file.read(),
filename=f'{xml_file}.xml',
)
self.assertRecordValues(invoice, [subtest['expected_invoice_vals']])
# field 'property_account_position_id' is company dependant
partner = invoice.partner_id.with_company(company=invoice.company_id)
self.assertRecordValues(partner, [subtest['expected_partner_vals']])
def test_upload_xml_to_generate_invoice_with_exento_tax(self):
self.env['account.tax'].search([('name', '=', 'Exento')]).unlink()
self.env['account.tax.group'].search([('name', '=', 'Exento')]).unlink()
file_name = "test_import_bill_with_extento"
full_file_path = misc.file_path(f'{self.test_module}/tests/test_files/{file_name}.xml')
with file_open(full_file_path, "rb") as file:
new_bill = self._upload_document_on_journal(
journal=self.company_data['default_journal_purchase'],
content=file.read(),
filename=file_name,
)
self.assertRecordValues(new_bill.invoice_line_ids, (
{
'quantity': 1,
'price_unit': 54017.48,
},
{
'quantity': 1,
'price_unit': 17893.00,
}
))
def test_cfdi_rounding_1(self):
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 398.28,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
Command.create({
'product_id': self.product.id,
'price_unit': 108.62,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
Command.create({
'product_id': self.product.id,
'price_unit': 362.07,
'tax_ids': [Command.set(self.tax_16.ids)],
})] + [
Command.create({
'product_id': self.product.id,
'price_unit': 31.9,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
] * 12,
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_1_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_1_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_2(self):
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'quantity': quantity,
'price_unit': price_unit,
'discount': discount,
'tax_ids': [Command.set(self.tax_16.ids)],
})
for quantity, price_unit, discount in (
(30, 84.88, 13.00),
(30, 18.00, 13.00),
(3, 564.32, 13.00),
(33, 7.00, 13.00),
(20, 49.88, 13.00),
(100, 3.10, 13.00),
(2, 300.00, 13.00),
(36, 36.43, 13.00),
(36, 15.00, 13.00),
(2, 61.08, 0),
(2, 13.05, 0),
)
])
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_2_{rounding_method}_inv')
payment = self._create_payment(invoice, currency_id=self.comp_curr.id)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_2_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_3(self):
today = self.frozen_today
today_minus_1 = self.frozen_today - relativedelta(days=1)
self.setup_rates(self.usd, (today_minus_1, 1 / 17.187), (today, 1 / 17.0357))
def run(rounding_method):
with self.mx_external_setup(today_minus_1):
invoice = self._create_invoice(
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 7.34,
'quantity': 200,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_3_{rounding_method}_inv')
with self.mx_external_setup(today):
payment = self._create_payment(
invoice,
payment_date=today,
currency_id=self.usd.id,
)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_3_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_4(self):
today = self.frozen_today
today_minus_1 = self.frozen_today - relativedelta(days=1)
self.setup_rates(self.usd, (today_minus_1, 1 / 16.9912), (today, 1 / 17.068))
def run(rounding_method):
with self.mx_external_setup(today_minus_1):
invoice1 = self._create_invoice(
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 68.0,
'quantity': 68.25,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice1._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice1, f'test_cfdi_rounding_4_{rounding_method}_inv_1')
with self.mx_external_setup(today):
invoice2 = self._create_invoice(
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 68.0,
'quantity': 24.0,
'tax_ids': [Command.set(self.tax_16.ids)],
}),
],
)
with self.with_mocked_pac_sign_success():
invoice2._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice2, f'test_cfdi_rounding_4_{rounding_method}_inv_2')
invoices = invoice1 + invoice2
with self.mx_external_setup(today):
payment = self._create_payment(
invoices,
amount=7276.68,
currency_id=self.usd.id,
payment_date=today,
)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_4_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_5(self):
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': price_unit,
'quantity': quantity,
})
for quantity, price_unit in (
(412.0, 43.65),
(412.0, 43.65),
(90.0, 50.04),
(500.0, 11.77),
(500.0, 34.93),
(90.0, 50.04),
)
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_5_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_5_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_6(self):
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': price_unit,
'quantity': quantity,
'discount': 30.0,
})
for quantity, price_unit in (
(7.0, 724.14),
(4.0, 491.38),
(2.0, 318.97),
(7.0, 224.14),
(6.0, 206.90),
(6.0, 129.31),
(6.0, 189.66),
(16.0, 775.86),
(2.0, 7724.14),
(2.0, 1172.41),
)
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_6_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_6_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_7(self):
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': price_unit,
'quantity': quantity,
'tax_ids': [Command.set(taxes.ids)],
})
for quantity, price_unit, taxes in (
(12.0, 457.92, self.tax_26_5_ieps + self.tax_16),
(12.0, 278.04, self.tax_26_5_ieps + self.tax_16),
(12.0, 539.76, self.tax_26_5_ieps + self.tax_16),
(36.0, 900.0, self.tax_16),
)
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_7_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_7_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_8(self):
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': price_unit,
'quantity': quantity,
'tax_ids': [Command.set(taxes.ids)],
})
for quantity, price_unit, taxes in (
(1.0, 244.0, self.tax_0_ieps + self.tax_0),
(8.0, 244.0, self.tax_0_ieps + self.tax_0),
(1.0, 2531.0, self.tax_0),
(1.0, 2820.75, self.tax_6_ieps + self.tax_0),
(1.0, 2531.0, self.tax_0),
(8.0, 468.0, self.tax_0_ieps + self.tax_0),
(1.0, 2820.75, self.tax_6_ieps + self.tax_0),
(1.0, 210.28, self.tax_7_ieps),
(1.0, 2820.75, self.tax_6_ieps + self.tax_0),
)
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_8_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_8_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_9(self):
usd_exchange_rates = (
1 / 17.1325,
1 / 17.1932,
1 / 17.0398,
1 / 17.1023,
1 / 17.1105,
1 / 16.7457,
)
def quick_create_invoice(rate, quantity_and_price_unit):
# Only one rate is allowed per day and to make the test working in external_mode, we need to create 6 rates in less than
# 3 days. So let's create/unlink the rate.
rate = self.setup_rates(self.usd, (self.frozen_today, rate))
invoice = self._create_invoice(
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': price_unit,
'quantity': quantity,
'tax_ids': [Command.set(self.tax_16.ids)],
})
for quantity, price_unit in quantity_and_price_unit
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
rate.unlink()
return invoice
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice1 = quick_create_invoice(
usd_exchange_rates[0],
[(80.0, 21.9)],
)
invoice2 = quick_create_invoice(
usd_exchange_rates[1],
[(200.0, 13.36)],
)
invoice3 = quick_create_invoice(
usd_exchange_rates[1],
[(1200.0, 0.36), (1000.0, 0.44), (800.0, 0.44), (800.0, 0.23)],
)
invoice4 = quick_create_invoice(
usd_exchange_rates[2],
[(200.0, 21.9)],
)
invoice5 = quick_create_invoice(
usd_exchange_rates[3],
[(1000.0, 0.36), (500.0, 0.44), (500.0, 0.23), (400.0, 0.87), (200.0, 0.44)],
)
invoice6 = quick_create_invoice(
usd_exchange_rates[4],
[(200.0, 14.4)],
)
self.setup_rates(self.usd, (self.frozen_today, usd_exchange_rates[5]))
payment = self._create_payment(
invoice1 + invoice2 + invoice3 + invoice4 + invoice5 + invoice6,
currency_id=self.env.company.currency_id.id,
)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_9_{rounding_method}_pay')
self._test_cfdi_rounding(run)
def test_cfdi_rounding_10(self):
def create_invoice(**kwargs):
return self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': price_unit,
'tax_ids': [Command.set(taxes.ids)],
})
for price_unit, taxes in (
(550.0, self.tax_0),
(505.0, self.tax_16),
(495.0, self.tax_16),
(560.0, self.tax_0),
(475.0, self.tax_16),
)
],
**kwargs,
)
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = create_invoice()
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_10_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_10_{rounding_method}_pay')
invoice = create_invoice(l10n_mx_edi_cfdi_to_public=True)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
self._assert_global_invoice_cfdi_from_invoices(invoice, f'test_cfdi_rounding_10_{rounding_method}_ginvoice')
self.tax_16.price_include = True
self._test_cfdi_rounding(run)
def test_cfdi_rounding_11(self):
def create_invoice(limit=1, **kwargs):
return self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 3.47,
'tax_ids': [Command.set(self.tax_16.ids)],
})
for iteration in range(limit)
],
**kwargs,
)
def run(rounding_method):
with self.mx_external_setup(self.frozen_today):
invoice = create_invoice(2)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
self._assert_invoice_cfdi(invoice, f'test_cfdi_rounding_11_{rounding_method}_inv')
payment = self._create_payment(invoice)
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment.move_id, f'test_cfdi_rounding_11_{rounding_method}_pay')
invoices = create_invoice(l10n_mx_edi_cfdi_to_public=True) + create_invoice(l10n_mx_edi_cfdi_to_public=True)
with self.with_mocked_pac_sign_success():
invoices._l10n_mx_edi_cfdi_global_invoice_try_send()
self._assert_global_invoice_cfdi_from_invoices(invoices, f'test_cfdi_rounding_11_{rounding_method}_ginvoice')
self._test_cfdi_rounding(run)
def test_partial_payment_1(self):
date1 = self.frozen_today - relativedelta(days=2)
date2 = self.frozen_today - relativedelta(days=1)
date3 = self.frozen_today
self.setup_rates(self.chf, (date1, 16.0), (date2, 17.0), (date3, 18.0))
with self.mx_external_setup(date1):
invoice = self._create_invoice() # 1160 MXN
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
# Pay 10% in MXN.
payment1 = self._create_payment(
invoice,
amount=116.0,
currency_id=self.comp_curr.id,
payment_date=date1,
)
with self.with_mocked_pac_sign_success():
payment1.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment1.move_id, 'test_partial_payment_1_pay1')
# Pay 10% in CHF (rate 1:16)
payment2 = self._create_payment(
invoice,
amount=1856.0,
currency_id=self.chf.id,
payment_date=date1,
)
with self.with_mocked_pac_sign_success():
payment2.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment2.move_id, 'test_partial_payment_1_pay2')
with self.mx_external_setup(date2):
# Pay 10% in CHF (rate 1:17).
payment3 = self._create_payment(
invoice,
amount=1972.0,
currency_id=self.chf.id,
payment_date=date2,
)
with self.with_mocked_pac_sign_success():
payment3.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment3.move_id, 'test_partial_payment_1_pay3')
with self.mx_external_setup(date3):
# Pay 10% in CHF (rate 1:18).
payment4 = self._create_payment(
invoice,
amount=2088.0,
currency_id=self.chf.id,
payment_date=date3,
)
with self.with_mocked_pac_sign_success():
payment4.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment4.move_id, 'test_partial_payment_1_pay4')
def test_partial_payment_2(self):
date1 = self.frozen_today - relativedelta(days=2)
date2 = self.frozen_today - relativedelta(days=1)
date3 = self.frozen_today
self.setup_rates(self.chf, (date1, 16.0), (date2, 17.0), (date3, 18.0))
self.setup_rates(self.usd, (date1, 17.0), (date2, 16.5), (date3, 16.0))
with self.mx_external_setup(date1):
invoice = self._create_invoice(
currency_id=self.chf.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 16000.0,
}),
],
) # 18560 CHF = 1160 MXN
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
with self.mx_external_setup(date2):
# Pay 10% in MXN (116 MXN = 1972 CHF).
payment1 = self._create_payment(
invoice,
amount=116.0,
currency_id=self.comp_curr.id,
payment_date=date2,
)
with self.with_mocked_pac_sign_success():
payment1.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment1.move_id, 'test_partial_payment_2_pay1')
# Pay 10% in USD (rate 1:16.5)
payment2 = self._create_payment(
invoice,
amount=1914.0,
currency_id=self.usd.id,
payment_date=date2,
)
with self.with_mocked_pac_sign_success():
payment2.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment2.move_id, 'test_partial_payment_2_pay2')
with self.mx_external_setup(date3):
# Pay 10% in MXN (116 MXN = 2088 CHF).
payment3 = self._create_payment(
invoice,
amount=116.0,
currency_id=self.comp_curr.id,
payment_date=date3,
)
with self.with_mocked_pac_sign_success():
payment3.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment3.move_id, 'test_partial_payment_2_pay3')
# Pay 10% in USD (rate 1:16)
payment4 = self._create_payment(
invoice,
amount=1856.0,
currency_id=self.usd.id,
payment_date=date3,
)
with self.with_mocked_pac_sign_success():
payment4.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment4.move_id, 'test_partial_payment_2_pay4')
def test_partial_payment_3(self):
""" Test a reconciliation chain with reconciliation with credit note in between. """
date1 = self.frozen_today - relativedelta(days=2)
date2 = self.frozen_today - relativedelta(days=1)
date3 = self.frozen_today
self.setup_rates(self.usd, (date1, 17.0), (date2, 16.5), (date3, 17.5))
with self.mx_external_setup(date1):
# MXN invoice at rate 19720 USD = 1160 MXN (1:17)
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 1000.0,
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
with self.mx_external_setup(date2):
# Pay 1914 USD = 116 MXN (1:16.5)
payment1 = self._create_payment(
invoice,
amount=1914.0,
currency_id=self.usd.id,
payment_date=date2,
)
with self.with_mocked_pac_sign_success():
payment1.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment1.move_id, 'test_partial_payment_3_pay1')
self.assertRecordValues(invoice, [{'amount_residual': 1044.0}])
# USD Credit note at rate 1914 USD = 116 MXN (1:16.5)
refund = self._create_invoice(
move_type='out_refund',
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 1650.0,
}),
],
)
(refund + invoice).line_ids.filtered(lambda line: line.display_type == 'payment_term').reconcile()
self.assertRecordValues(invoice + refund, [
# The refund reconciled 116 MXN:
# - 112.59 MXN with the invoice.
# - 3.41 MXN as an exchange difference (1972 - 1914) / 17 ~= 3.41)
{'amount_residual': 931.41},
{'amount_residual': 0.0},
])
# Pay 1914 USD = 116 MXN (1:16.5)
# The credit note should be subtracted from the residual chain.
payment2 = self._create_payment(
invoice,
amount=1914.0,
currency_id=self.usd.id,
payment_date=date2,
)
with self.with_mocked_pac_sign_success():
payment2.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment2.move_id, 'test_partial_payment_3_pay2')
self.assertRecordValues(invoice, [{'amount_residual': 815.41}])
with self.mx_external_setup(date3):
# Pay 10% in USD at rate 1:17.5
payment3 = self._create_payment(
invoice,
amount=2030.0,
currency_id=self.usd.id,
payment_date=date3,
)
with self.with_mocked_pac_sign_success():
payment3.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment3.move_id, 'test_partial_payment_3_pay3')
self.assertRecordValues(invoice, [{'amount_residual': 699.41}])
# USD Credit note at rate 2030 USD = 116 MXN (1:17.5)
refund = self._create_invoice(
move_type='out_refund',
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 1750.0,
}),
],
)
(refund + invoice).line_ids.filtered(lambda line: line.display_type == 'payment_term').reconcile()
self.assertRecordValues(invoice + refund, [
# The refund reconciled 116 MXN with the invoice.
# An exchange difference of (2030 - 1972) / 17 ~= 3.41 has been created.
{'amount_residual': 580.0},
{'amount_residual': 0.0},
])
# Pay 10% in USD at rate 1:17.5
# The credit note should be subtracted from the residual chain.
payment4 = self._create_payment(
invoice,
amount=2030.0,
currency_id=self.usd.id,
payment_date=date3,
)
with self.with_mocked_pac_sign_success():
payment4.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(payment4.move_id, 'test_partial_payment_3_pay4')
self.assertRecordValues(invoice, [{'amount_residual': 464.0}])
def test_foreign_curr_statement_and_invoice_modify_exchange_move(self):
""" Test a bank reconciliation multi currency reconcliation with remaining amount in company currency """
date1 = self.frozen_today - relativedelta(days=1)
date2 = self.frozen_today
# Rates for how much USD for 1 MXN
self.setup_rates(self.usd, (date1, 1.5), (date2, 2.0001))
with self.mx_external_setup(date1):
# 2 MXN invoices at rate 100 USD + 16% = 116 USD = 77.3 MXN (1:1.5)
invoice1 = self._create_invoice(
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 100,
}),
],
)
invoice2 = self._create_invoice(
currency_id=self.usd.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 100,
}),
],
)
with self.with_mocked_pac_sign_success():
invoice1._l10n_mx_edi_cfdi_invoice_try_send()
invoice2._l10n_mx_edi_cfdi_invoice_try_send()
bank_journal = self.env['account.journal'].create({
'name': 'Bank 123456',
'code': 'BNK67',
'type': 'bank',
'bank_acc_number': '123456',
'currency_id': self.env.ref('base.USD').id,
'l10n_mx_edi_payment_method_id': self.env.ref('l10n_mx_edi.payment_method_transferencia').id, # To default to this payment method
})
with self.mx_external_setup(date2):
# Bank transaction 232 USD = 115.9942 MXN ≃ 115.99 MXN (1:2.0001)
# At this rate 116 USD = 57.9971 MXN ≃ 58.00 MXN which is not half of 115.99 MXN
# => create a 0.01 MXN auto balance line
statement_line1 = self.env['account.bank.statement.line'].create({
'journal_id': bank_journal.id,
'amount': 232.0,
'date': date2,
'payment_ref': 'test'
})
# Open bank reconciliation widget
wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=statement_line1.id).new({})
invoice_lines = (invoice1 | invoice2).line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable')
wizard._action_add_new_amls(invoice_lines)
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 232.0, 'currency_id': self.usd.id, 'balance': 115.99},
{'flag': 'new_aml', 'amount_currency': -116.0, 'currency_id': self.usd.id, 'balance': -77.34},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.usd.id, 'balance': 19.34},
{'flag': 'new_aml', 'amount_currency': -116.0, 'currency_id': self.usd.id, 'balance': -77.34},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.usd.id, 'balance': 19.34},
{'flag': 'auto_balance', 'amount_currency': 0.00, 'currency_id': self.usd.id, 'balance': 0.01},
])
# Remove 0.01 in the balance of first exchange line
first_exchange_line = wizard.line_ids.filtered(lambda x: x.flag == 'exchange_diff')[:1]
wizard._js_action_mount_line_in_edit(first_exchange_line.index)
first_exchange_line.balance = 19.35
wizard._line_value_changed_balance(first_exchange_line)
# Every line balance so no 'auto_balance' is generated
self.assertRecordValues(wizard.line_ids, [
# pylint: disable=C0326
{'flag': 'liquidity', 'amount_currency': 232.0, 'currency_id': self.usd.id, 'balance': 115.99},
{'flag': 'new_aml', 'amount_currency': -116.0, 'currency_id': self.usd.id, 'balance': -77.34},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.usd.id, 'balance': 19.35},
{'flag': 'new_aml', 'amount_currency': -116.0, 'currency_id': self.usd.id, 'balance': -77.34},
{'flag': 'exchange_diff', 'amount_currency': 0.0, 'currency_id': self.usd.id, 'balance': 19.34},
])
self.assertRecordValues(wizard, [{'state': 'valid'}])
wizard._action_validate()
self.assertRecordValues(statement_line1, [{'is_reconciled': True}])
self.assertRecordValues(invoice1, [{'payment_state': 'paid'}])
self.assertRecordValues(invoice2, [{'payment_state': 'paid'}])
with self.with_mocked_pac_sign_success():
statement_line1.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(statement_line1.move_id, 'test_foreign_curr_statement_and_invoice_modify_exchange_move')
def test_foreign_curr_payment_comp_curr_invoice_forced_balance(self):
date1 = self.frozen_today - relativedelta(days=1)
date2 = self.frozen_today
self.setup_rates(self.chf, (date1, 16.0), (date2, 17.0))
with self.mx_external_setup(date1):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 1000.0, # = 62.5 CHF
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
with self.mx_external_setup(date2):
payment = self.env['account.payment.register'] \
.with_context(active_model='account.move', active_ids=invoice.ids) \
.create({
'payment_date': date2,
'currency_id': self.chf.id,
'amount': 62.0, # instead of 62.5 CHF
'payment_difference_handling': 'reconcile',
'writeoff_account_id': self.env.company.expense_currency_exchange_account_id.id,
}) \
._create_payments()
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(
payment.move_id,
'test_foreign_curr_payment_comp_curr_invoice_forced_balance',
)
def test_comp_curr_payment_foreign_curr_invoice_forced_balance(self):
date1 = self.frozen_today - relativedelta(days=1)
date2 = self.frozen_today
self.setup_rates(self.chf, (date1, 16.0), (date2, 17.0))
with self.mx_external_setup(date1):
invoice = self._create_invoice(
currency_id=self.chf.id,
invoice_line_ids=[
Command.create({
'product_id': self.product.id,
'price_unit': 62.5, # = 1000 MXN
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
with self.mx_external_setup(date2):
payment = self.env['account.payment.register'] \
.with_context(active_model='account.move', active_ids=invoice.ids) \
.create({
'payment_date': date2,
'currency_id': self.comp_curr.id,
'amount': 998.0, # instead of 1000.0 MXN
'payment_difference_handling': 'reconcile',
'writeoff_account_id': self.env.company.expense_currency_exchange_account_id.id,
}) \
._create_payments()
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
self._assert_invoice_payment_cfdi(
payment.move_id,
'test_comp_curr_payment_foreign_curr_invoice_forced_balance',
)
def test_cfdi_date_with_timezone(self):
def assert_cfdi_date(document, tz, expected_datetime=None):
self.assertTrue(document)
cfdi_node = etree.fromstring(document.attachment_id.raw)
cfdi_date_str = cfdi_node.get('Fecha')
expected_datetime = (expected_datetime or self.frozen_today.astimezone(tz).replace(tzinfo=None)).replace(microsecond=0)
current_date = datetime.strptime(cfdi_date_str, CFDI_DATE_FORMAT).replace(microsecond=0)
self.assertEqual(current_date, expected_datetime)
addresses = [
# America/Tijuana UTC-8 (-7 DST)
{
'state_id': self.env.ref('base.state_mx_bc').id,
'zip': '22750',
'timezone': timezone('America/Tijuana'),
},
# America/Bogota UTC-5
{
'state_id': self.env.ref('base.state_mx_q_roo').id,
'zip': '77890',
'timezone': timezone('America/Bogota'),
},
# America/Boise UTC-7 (-6 DST)
{
'state_id': self.env.ref('base.state_mx_chih').id,
'zip': '31820',
'timezone': timezone('America/Boise'),
},
# America/Guatemala (Tiempo del centro areas)
{
'state_id': self.env.ref('base.state_mx_nay').id,
'zip': '63726',
'timezone': timezone('America/Guatemala'),
},
# America/Matamoros UTC-6 (-5 DST)
{
'state_id': self.env.ref('base.state_mx_tamps').id,
'zip': '87300',
'timezone': timezone('America/Matamoros'),
},
# Pacific area
{
'state_id': self.env.ref('base.state_mx_son').id,
'zip': '83530',
'timezone': timezone('America/Hermosillo'),
},
# America/Guatemala UTC-6
{
'state_id': self.env.ref('base.state_mx_ags').id,
'zip': '20914',
'timezone': timezone('America/Guatemala'),
},
]
for address in addresses:
tz = address.pop('timezone')
with self.subTest(zip=address['zip']):
self.env.company.partner_id.write(address)
# Invoice on the future.
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_date=self.frozen_today + relativedelta(days=2),
invoice_line_ids=[Command.create({'product_id': self.product.id})],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
document = invoice.l10n_mx_edi_invoice_document_ids.filtered(lambda x: x.state == 'invoice_sent')[:1]
assert_cfdi_date(document, tz)
# Invoice on the past.
date_in_the_past = self.frozen_today - relativedelta(days=2)
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_date=date_in_the_past,
invoice_line_ids=[Command.create({'product_id': self.product.id})],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
document = invoice.l10n_mx_edi_invoice_document_ids.filtered(lambda x: x.state == 'invoice_sent')[:1]
assert_cfdi_date(document, tz, expected_datetime=date_in_the_past.replace(hour=23, minute=59, second=0))
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(invoice_line_ids=[Command.create({'product_id': self.product.id})])
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
document = invoice.l10n_mx_edi_invoice_document_ids.filtered(lambda x: x.state == 'invoice_sent')[:1]
assert_cfdi_date(document, tz)
# Test an immediate payment.
payment = self.env['account.payment.register'] \
.with_context(active_model='account.move', active_ids=invoice.ids) \
.create({'amount': 100.0}) \
._create_payments()
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
document = payment.l10n_mx_edi_payment_document_ids.filtered(lambda x: x.state == 'payment_sent')[:1]
assert_cfdi_date(document, tz)
# Test a payment made 10 days ago but send today.
with self.mx_external_setup(self.frozen_today - relativedelta(days=10)):
payment = self.env['account.payment.register'] \
.with_context(active_model='account.move', active_ids=invoice.ids) \
.create({'amount': 100.0}) \
._create_payments()
with self.mx_external_setup(self.frozen_today):
with self.with_mocked_pac_sign_success():
payment.move_id._l10n_mx_edi_cfdi_payment_try_send()
document = payment.l10n_mx_edi_payment_document_ids.filtered(lambda x: x.state == 'payment_sent')[:1]
assert_cfdi_date(document, tz)
def test_invoice_negative_lines_cfdi_amounts(self):
""" Ensure the base amounts are correct in CFDI xml after dispatching the negative lines. """
product1 = self._create_product(name='product_1')
product2 = self._create_product(name='product_2')
product3 = self._create_product(name='product_3')
downpayment = self._create_product(name='down_payment')
with self.mx_external_setup(self.frozen_today):
invoice = self._create_invoice(
invoice_line_ids=[
Command.create({
'product_id': product1.id,
'price_unit': 1000.0,
'quantity': 1.0,
}),
Command.create({
'product_id': product2.id,
'price_unit': 1500.0,
'quantity': 1.0,
}),
Command.create({
'product_id': product3.id,
'price_unit': 3000.0,
'quantity': 1.0,
}),
Command.create({
'product_id': downpayment.id,
'price_unit': 4950.0, # It represents a 90% down payment
'quantity': -1.0,
}),
],
)
with self.with_mocked_pac_sign_success():
invoice._l10n_mx_edi_cfdi_invoice_try_send()
# The down payment line should be dispatched to the 3 other lines, removing the 2
# biggest ones and generating a discount of 450 on the lowest one
self._assert_invoice_cfdi(invoice, 'test_invoice_negative_lines_cfdi_amounts')