3346 lines
149 KiB
Python
3346 lines
149 KiB
Python
# -*- coding: utf-8 -*-
|
|
from .common import TestMxEdiCommon, EXTERNAL_MODE
|
|
from odoo import fields, Command
|
|
from odoo.exceptions import UserError, ValidationError
|
|
from odoo.tests import tagged
|
|
from odoo.tools import misc
|
|
from odoo.tools.misc import file_open
|
|
|
|
import base64
|
|
|
|
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,
|
|
}),
|
|
# Product with Predial Account
|
|
Command.create({
|
|
'product_id': self._create_product(l10n_mx_edi_predial_account='123456789').id,
|
|
'quantity': 3.0,
|
|
'tax_ids': [],
|
|
}),
|
|
# 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_customer_mx_incomplete_address(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
for zip_code, country in (['33826', None], [None, self.env.ref('base.mx')], [None, None]):
|
|
self.partner_mx.zip = zip_code
|
|
self.partner_mx.country_id = country
|
|
invoice = self._create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertTrue(invoice.l10n_mx_edi_cfdi_to_public)
|
|
self._assert_invoice_cfdi(invoice, 'test_customer_mx_incomplete_address')
|
|
|
|
def test_invoice_taxes_no_tax(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the invoice CFDI.
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 1000.0,
|
|
'quantity': 5,
|
|
'discount': 20.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_taxes_no_tax_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, 'test_invoice_taxes_no_tax_payment')
|
|
|
|
def test_invoice_taxes_exento_and_zero(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the invoice CFDI.
|
|
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_exento.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 2000.0,
|
|
'tax_ids': [Command.set(self.tax_0.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 3000.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_taxes_exento_and_zero_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, 'test_invoice_taxes_exento_and_zero_payment')
|
|
|
|
def test_invoice_taxes_withholding(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the invoice CFDI.
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 1000.0,
|
|
'tax_ids': [Command.set((self.tax_16 + self.tax_10_ret_isr + self.tax_10_67_ret).ids)],
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_invoice_taxes_withholding_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, 'test_invoice_taxes_withholding_payment')
|
|
|
|
def test_invoice_taxes_ieps(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the invoice CFDI.
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 1000.0,
|
|
'tax_ids': [Command.set((self.tax_8_ieps + self.tax_0).ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 2000.0,
|
|
'tax_ids': [Command.set((self.tax_53_ieps + 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_taxes_ieps_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, 'test_invoice_taxes_ieps_payment')
|
|
|
|
def test_invoice_taxes_local(self):
|
|
local_fixed_tax = self.env['account.tax'].create({
|
|
'name': 'local fixed tax',
|
|
'amount': 5.0,
|
|
'amount_type': 'fixed',
|
|
'l10n_mx_tax_type': 'local',
|
|
'l10n_mx_factor_type': 'Cuota',
|
|
'tax_group_id': self.local_tax_group.id,
|
|
})
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the invoice CFDI.
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 1000.0,
|
|
'tax_ids': [Command.set(self.local_tax_16_transferred.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 2000.0,
|
|
'tax_ids': [Command.set(self.local_tax_8_withholding.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 3000.0,
|
|
'tax_ids': [Command.set(self.local_tax_3_5_withholding.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 4000.0,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 2500.0,
|
|
'quantity': 2.0,
|
|
'tax_ids': [Command.set(local_fixed_tax.ids)],
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_invoice_taxes_local_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, 'test_invoice_taxes_local_payment')
|
|
|
|
def test_invoice_taxes_cuota(self):
|
|
self.env['decimal.precision'].search([('name', '=', 'Product Price')]).digits = 6
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
tax_cuota = self.fixed_tax(
|
|
name="Cuota 14.0163",
|
|
amount=14.0163,
|
|
l10n_mx_factor_type='Cuota',
|
|
l10n_mx_tax_type='ieps',
|
|
sequence=10.0,
|
|
)
|
|
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the first invoice CFDI.
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 523.448276,
|
|
'tax_ids': [Command.set(tax_cuota.ids)],
|
|
})
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_invoice_taxes_cuota_1_invoice')
|
|
|
|
# Test the first 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, 'test_invoice_taxes_cuota_1_payment')
|
|
|
|
# Test the second invoice CFDI.
|
|
tax_8_include_base_amount = self.tax_8.copy(default={'name': "tax_8_include_base_amount"})
|
|
tax_8_include_base_amount.include_base_amount = True
|
|
tax_8_include_base_amount.sequence = 9
|
|
self.tax_1_25_sale_withholding.sequence = 8
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 4000.0,
|
|
'tax_ids': [Command.set((tax_8_include_base_amount + tax_cuota + self.tax_1_25_sale_withholding).ids)],
|
|
})
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_invoice_taxes_cuota_2_invoice')
|
|
|
|
# Test the second 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, 'test_invoice_taxes_cuota_2_payment')
|
|
|
|
def test_invoice_taxes_cuota_with_custom_tax(self):
|
|
account_tax_python = self.env['ir.module.module']._get('account_tax_python')
|
|
if account_tax_python.state != 'installed':
|
|
return
|
|
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
tax_cuota = self.python_tax(
|
|
formula="6.4555 * quantity",
|
|
l10n_mx_factor_type='Cuota',
|
|
l10n_mx_tax_type='ieps',
|
|
price_include_override='tax_included',
|
|
include_base_amount=True,
|
|
sequence=1,
|
|
)
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
self.tax_16.sequence = 2
|
|
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Test the invoice CFDI.
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'quantity': 43775.0,
|
|
'price_unit': 18.33,
|
|
'tax_ids': [Command.set((tax_cuota + 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_taxes_cuota_with_custom_tax_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, 'test_invoice_taxes_cuota_with_custom_tax_payment')
|
|
|
|
def test_tax_objected_01(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'tax_ids': [],
|
|
})])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_tax_objected_01_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_tax_objected_01_pay')
|
|
|
|
def test_tax_objected_02(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'discount': 100.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_tax_objected_02_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_tax_objected_02_pay')
|
|
|
|
def test_tax_objected_03(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'l10n_mx_edi_tax_object': '03',
|
|
'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_tax_objected_03_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_tax_objected_03_pay')
|
|
|
|
def test_tax_objected_04(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'discount': 100.0,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
'l10n_mx_edi_tax_object': '04',
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'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_tax_objected_04_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_tax_objected_04_pay')
|
|
|
|
def test_tax_objected_05(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'l10n_mx_edi_tax_object': '05',
|
|
'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_tax_objected_05_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_tax_objected_05_pay')
|
|
|
|
def test_tax_objected_06(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'tax_ids': [Command.set(self.tax_10_ret_isr.ids)],
|
|
})])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_tax_objected_06_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_tax_objected_06_pay')
|
|
|
|
def test_tax_objected_07(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'tax_ids': [Command.set((self.tax_10_ret_isr + self.tax_8_ieps).ids)],
|
|
})])
|
|
# When not specified, tax object should be 07 if ieps breakdown
|
|
self.assertRecordValues(invoice.invoice_line_ids, [{'l10n_mx_edi_tax_object': '07'}])
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = False
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
# Generated cfdi should have tax object '07' and breakdown IEPS, even if it is not specified
|
|
self._assert_invoice_cfdi(invoice, 'test_tax_objected_07_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_tax_objected_07_pay')
|
|
|
|
def test_tax_objected_08(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'l10n_mx_edi_tax_object': '08',
|
|
'tax_ids': [Command.set((self.tax_10_ret_isr + self.tax_8_ieps).ids)],
|
|
})])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
# No IEPS breakdown even if specified
|
|
self._assert_invoice_cfdi(invoice, 'test_tax_objected_08_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_tax_objected_08_pay')
|
|
|
|
def test_global_invoice_ieps_breakdown(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'tax_ids': [Command.set((self.tax_10_ret_isr + self.tax_8_ieps).ids)]
|
|
})])
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
# IEPS should display even if not set
|
|
self._assert_global_invoice_cfdi_from_invoices(invoice, 'test_global_invoice_ieps_breakdown')
|
|
|
|
def test_invoice_addenda(self):
|
|
# The test data for complementos in this test are not recognized by the SAT as valid.
|
|
if EXTERNAL_MODE:
|
|
return
|
|
|
|
with self.mx_external_setup(self.frozen_today):
|
|
addenda_1, addenda_2, complementos_1, complementos_2 = self.env['l10n_mx_edi.addenda'].create([
|
|
{
|
|
'name': 'addenda simple',
|
|
'arch': """
|
|
<t t-xml-node="addenda">
|
|
<SimpleAddenda info="this is a simple addenda"/>
|
|
</t>
|
|
""",
|
|
},
|
|
{
|
|
'name': 'addenda with complex qweb',
|
|
'arch': """
|
|
<t><t t-xml-node="addenda"><t>
|
|
<t t-set="asdf" t-value="'this is a complex addenda'"/>
|
|
<t><t><ComplexAddenda t-att-info="asdf"/></t></t>
|
|
</t></t></t>
|
|
""",
|
|
},
|
|
{
|
|
'name': 'complementos simple',
|
|
'arch': """
|
|
<t t-xml-node="complemento">
|
|
<SimpleComplementos info="this is a simple complementos"/>
|
|
</t>
|
|
""",
|
|
},
|
|
{
|
|
'name': 'complementos with complex qweb',
|
|
'arch': """
|
|
<t t-xml-node="complemento"><t>
|
|
<t t-set="zxcv" t-value="'this is a complex complementos'"/>
|
|
<t><t><t><ComplexComplementos t-att-info="zxcv"/></t></t></t>
|
|
</t></t>
|
|
""",
|
|
},
|
|
])
|
|
|
|
for addenda_file, addenda_ids in (
|
|
('test_invoice_addenda_just_addenda', addenda_1 + addenda_2),
|
|
('test_invoice_addenda_just_complementos', complementos_1 + complementos_2),
|
|
('test_invoice_addenda_complex', addenda_1 + addenda_2 + complementos_1 + complementos_2),
|
|
):
|
|
with self.subTest(addenda_file=addenda_file):
|
|
self.partner_mx.l10n_mx_edi_addenda_ids = addenda_ids
|
|
invoice = self._create_invoice()
|
|
self.assertEqual(invoice.l10n_mx_edi_addenda_ids, addenda_ids)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, addenda_file)
|
|
|
|
def test_invoice_addenda_with_namespace(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
addenda_donat = self.env['l10n_mx_edi.addenda'].create([{
|
|
'name': 'Donatarias',
|
|
'arch': """
|
|
<?xml version="1.0"?>
|
|
<t>
|
|
<t t-xml-node="comprobante">
|
|
<t t-namespace-key="donat" t-namespace-url="http://www.sat.gob.mx/donat/"/>
|
|
<t t-schema-locations="https://www.schema_url_1.com/ https://www.schema_url_2.com/"/>
|
|
</t>
|
|
<t t-xml-node="addenda">
|
|
<t t-set="my_journal" t-value="record.journal_id"/>
|
|
<donat:Donatarias
|
|
t-att-testJournalName="my_journal.name"
|
|
testCertificate="325-SAT-09-IV-E-77917"
|
|
testDate="01/06/2005"
|
|
testText="hello world"/>
|
|
</t>
|
|
</t>
|
|
""",
|
|
}])
|
|
|
|
invoice = self._create_invoice()
|
|
invoice.l10n_mx_edi_addenda_ids = addenda_donat
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_invoice_addenda_with_namespace')
|
|
|
|
# Manually check if schema_locations are correctly saved in the document XML
|
|
# This is done because the previous assertXmlTreeEqual don't check inside the value inside xsi:schemaLocation
|
|
document = invoice.l10n_mx_edi_invoice_document_ids.filtered(lambda x: x.state == 'invoice_sent')[:1]
|
|
self.assertTrue(
|
|
expr=b'xsi:schemaLocation="https://www.schema_url_1.com/ https://www.schema_url_2.com/ ' in document.attachment_id.raw,
|
|
msg="The schema_locations string must be saved in the generated XML.",
|
|
)
|
|
|
|
def test_invoice_addenda_with_multi_schema_location(self):
|
|
""" Ensure the schema locations are sorted per groups and included in the end XML result. """
|
|
with self.mx_external_setup(self.frozen_today):
|
|
addenda_vals = []
|
|
schema_locations_list = [
|
|
"https://abcde.com https://zzzzz.com",
|
|
"https://zzzzz.com https://abcde.com",
|
|
"https://zzzzz.com https://zzzzz.com",
|
|
"https://abcde.com https://abcde.com",
|
|
"https://iiiii.com https://zzzzz.com",
|
|
"https://iiiii.com https://zzzzz.com", # duplicate lines will be removed
|
|
"https://abcde.com https://abcde.com",
|
|
]
|
|
for i, schema_locations in enumerate(schema_locations_list, start=1):
|
|
addenda_vals.append({
|
|
'name': f'test_addenda_{i}',
|
|
'arch': f"""
|
|
<t t-xml-node="comprobante">
|
|
<t t-schema-locations="{schema_locations}"/>
|
|
</t>
|
|
<t t-xml-node="addenda">
|
|
<TestAddenda{i}/>
|
|
</t>
|
|
""",
|
|
})
|
|
|
|
invoice = self._create_invoice()
|
|
invoice.l10n_mx_edi_addenda_ids = self.env['l10n_mx_edi.addenda'].create(addenda_vals)
|
|
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]
|
|
self.assertTrue(
|
|
expr=b'xsi:schemaLocation="https://abcde.com https://abcde.com https://abcde.com https://zzzzz.com https://iiiii.com https://zzzzz.com '
|
|
b'https://zzzzz.com https://abcde.com https://zzzzz.com https://zzzzz.com ' in document.attachment_id.raw,
|
|
msg="The schema_locations string must be filtered and sorted by line.",
|
|
)
|
|
|
|
def test_invoice_addenda_decode_errors(self):
|
|
invalid_cases = (
|
|
("<InvalidBecauseNotWrapped/>", "Arch must contain `<t t-xml-node="),
|
|
("<t t-xml-node='badvalue'><InvalidBecauseNotWrapped/></t>", "Arch value of t-xml-node must be either"),
|
|
("""
|
|
<t t-xml-node="comprobante">
|
|
<t t-schema-locations="value_one_have multiple_space_before_value_two"/>
|
|
</t>
|
|
<t t-xml-node="addenda">
|
|
<ValidAddendaButBadSchema/>
|
|
</t>
|
|
""", "Schema locations must be separated by only one whitespace character."),
|
|
("<t/>", "must contain either complemento or addenda"),
|
|
("<t t-xml-node='addenda'></t><t t-xml-node='complemento'></t>", "must contain either complemento or addenda"),
|
|
)
|
|
|
|
for (bad_arch, expected_regex) in invalid_cases:
|
|
with self.subTest(bad_arch=bad_arch):
|
|
with self.assertRaisesRegex(ValidationError, expected_regex):
|
|
self.env['l10n_mx_edi.addenda'].create([{
|
|
'name': 'invalid_addenda',
|
|
'arch': bad_arch,
|
|
}])
|
|
|
|
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_invoice_negative_lines_on_multiple_lines(self):
|
|
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,
|
|
'tax_ids': [Command.set(self.tax_0.ids)],
|
|
})
|
|
for quantity, price_unit in (
|
|
(1.0, 326.4),
|
|
(1.0, 24.0),
|
|
(1.0, 172.8),
|
|
(1.0, 691.2),
|
|
(-1.0, 1149.6),
|
|
)
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_invoice_negative_lines_on_multiple_lines')
|
|
|
|
def test_invoice_payment_policy(self):
|
|
""" Ensure the invoice payment policy isn't override by the partner payment policy. """
|
|
with self.mx_external_setup(self.frozen_today):
|
|
self.partner_mx.l10n_mx_edi_payment_policy = 'PUE'
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'quantity': 12.0,
|
|
'tax_ids': [],
|
|
}),
|
|
],
|
|
l10n_mx_edi_payment_policy='PPD',
|
|
)
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertEqual(invoice.l10n_mx_edi_payment_policy, 'PPD')
|
|
|
|
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_date_due=self.frozen_today,
|
|
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.wizard']\
|
|
.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):
|
|
with self.mx_external_setup(self.frozen_today - relativedelta(hours=1)):
|
|
self.env.company.write({
|
|
'child_ids': [Command.create({
|
|
'name': 'Branch A',
|
|
'zip': '85120',
|
|
})],
|
|
})
|
|
branch = self.env.company.child_ids
|
|
key = self.env['certificate.key'].create({
|
|
'content': base64.encodebytes(misc.file_open('l10n_mx_edi/demo/pac_credentials/certificate.key', 'rb').read()),
|
|
'password': '12345678a',
|
|
'company_id': branch.id,
|
|
})
|
|
certificate = self.env['certificate.certificate'].create({
|
|
'content': base64.encodebytes(misc.file_open('l10n_mx_edi/demo/pac_credentials/certificate.cer', 'rb').read()),
|
|
'private_key_id': key.id,
|
|
'company_id': branch.id,
|
|
})
|
|
branch.l10n_mx_edi_certificate_ids = certificate
|
|
self.cr.precommit.run() # load the CoA
|
|
|
|
self.assertRecordValues(self.env.company, [{
|
|
'l10n_mx_edi_global_invoice_sequence_id': False,
|
|
'l10n_mx_edi_global_invoice_sequence_prefix': 'GINV/',
|
|
}])
|
|
|
|
self.assertRecordValues(branch, [{
|
|
'l10n_mx_edi_global_invoice_sequence_id': False,
|
|
'l10n_mx_edi_global_invoice_sequence_prefix': 'GINV/',
|
|
}])
|
|
|
|
self.product.company_id = branch
|
|
|
|
# Invoice.
|
|
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_inv')
|
|
|
|
# Global invoice using the sequence of the root company.
|
|
invoice = self._create_invoice(company_id=branch.id, 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, 'test_invoice_company_branch_ginvoice_1')
|
|
|
|
# Global invoice with a custom global invoice sequence on the branch.
|
|
branch.l10n_mx_edi_global_invoice_sequence_prefix = "SAL/"
|
|
|
|
self.assertRecordValues(branch, [{
|
|
'l10n_mx_edi_global_invoice_sequence_prefix': 'SAL/',
|
|
}])
|
|
self.assertTrue(branch.l10n_mx_edi_global_invoice_sequence_id)
|
|
|
|
invoice = self._create_invoice(company_id=branch.id, 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, 'test_invoice_company_branch_ginvoice_2')
|
|
|
|
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():
|
|
wizard = self.env['account.move.send.wizard']\
|
|
.with_context(active_model=invoice._name, active_ids=invoice.ids)\
|
|
.create({})
|
|
wizard.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.with_mocked_pac_cancel_success():
|
|
self.env['account.move.send.wizard']\
|
|
.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.wizard']\
|
|
.with_context(active_model=invoice._name, active_ids=invoice.ids)\
|
|
.create({})
|
|
self.assertFalse(wizard.extra_edi_checkboxes and wizard.extra_edi_checkboxes.get('mx_cfdi'))
|
|
|
|
# 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.wizard']\
|
|
.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_global_invoice_foreign_currency(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
usd = self.setup_other_currency('USD', rates=[(self.frozen_today, 1 / 17.0398)])
|
|
invoice1 = self._create_invoice(currency_id=usd.id, l10n_mx_edi_cfdi_to_public=True)
|
|
invoice2 = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
invoices = invoice1 + invoice2
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
self.assertRaisesRegex(UserError, "You can only process invoices sharing the same currency.", invoices._l10n_mx_edi_cfdi_global_invoice_try_send)
|
|
invoice1._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
invoice2._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoice1, "test_global_invoice_foreign_currency")
|
|
|
|
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)],
|
|
}),
|
|
],
|
|
)
|
|
invoice.with_context(skip_invoice_sync=True)._generate_and_send(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,
|
|
'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_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(message_type='comment', 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_custom_tax_object(self):
|
|
file_name = "test_import_bill_custom_tax_object"
|
|
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': 1000.0,
|
|
'discount': 100.0,
|
|
'tax_ids': [],
|
|
'l10n_mx_edi_tax_object': '04',
|
|
},
|
|
{
|
|
'quantity': 1,
|
|
'price_unit': 1000.0,
|
|
'discount': 0.0,
|
|
'tax_ids': self.tax_16_purchase.ids,
|
|
'l10n_mx_edi_tax_object': '02',
|
|
},
|
|
))
|
|
|
|
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': True,
|
|
},
|
|
'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):
|
|
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, 'test_cfdi_rounding_1_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_cfdi_rounding_1_pay')
|
|
|
|
def test_cfdi_rounding_2(self):
|
|
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, 'test_cfdi_rounding_2_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, 'test_cfdi_rounding_2_pay')
|
|
|
|
def test_cfdi_rounding_3(self):
|
|
today = self.frozen_today
|
|
today_minus_1 = self.frozen_today - relativedelta(days=1)
|
|
usd = self.setup_other_currency('USD', rates=[(today_minus_1, 1 / 17.187), (today, 1 / 17.0357)])
|
|
|
|
with self.mx_external_setup(today_minus_1):
|
|
invoice = self._create_invoice(
|
|
currency_id=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, 'test_cfdi_rounding_3_inv')
|
|
|
|
with self.mx_external_setup(today):
|
|
payment = self._create_payment(
|
|
invoice,
|
|
payment_date=today,
|
|
currency_id=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, 'test_cfdi_rounding_3_pay')
|
|
|
|
def test_cfdi_rounding_4(self):
|
|
today = self.frozen_today
|
|
today_minus_1 = self.frozen_today - relativedelta(days=1)
|
|
usd = self.setup_other_currency('USD', rates=[(today_minus_1, 1 / 16.9912), (today, 1 / 17.068)])
|
|
|
|
with self.mx_external_setup(today_minus_1):
|
|
invoice1 = self._create_invoice(
|
|
currency_id=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, 'test_cfdi_rounding_4_inv_1')
|
|
|
|
with self.mx_external_setup(today):
|
|
invoice2 = self._create_invoice(
|
|
currency_id=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, 'test_cfdi_rounding_4_inv_2')
|
|
|
|
invoices = invoice1 + invoice2
|
|
with self.mx_external_setup(today):
|
|
payment = self._create_payment(
|
|
invoices,
|
|
amount=7276.68,
|
|
currency_id=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, 'test_cfdi_rounding_4_pay')
|
|
|
|
def test_cfdi_rounding_5(self):
|
|
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, 'test_cfdi_rounding_5_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_cfdi_rounding_5_pay')
|
|
|
|
def test_cfdi_rounding_6(self):
|
|
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, 'test_cfdi_rounding_6_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_cfdi_rounding_6_pay')
|
|
|
|
def test_cfdi_rounding_7(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
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, 'test_cfdi_rounding_7_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_cfdi_rounding_7_pay')
|
|
|
|
def test_cfdi_rounding_8(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
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, 'test_cfdi_rounding_8_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_cfdi_rounding_8_pay')
|
|
|
|
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.
|
|
usd = self.setup_other_currency('USD', rates=[(self.frozen_today, rate)])
|
|
invoice = self._create_invoice(
|
|
currency_id=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()
|
|
return invoice
|
|
|
|
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_other_currency('USD', rates=[(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, 'test_cfdi_rounding_9_pay')
|
|
|
|
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,
|
|
)
|
|
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
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, 'test_cfdi_rounding_10_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_cfdi_rounding_10_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, 'test_cfdi_rounding_10_ginvoice')
|
|
|
|
def test_cfdi_rounding_11(self):
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoices = self.env['account.move']
|
|
for price_unit in (2803.0, 1842.0, 2798.0, 3225.0, 3371.0):
|
|
invoices += self._create_invoice(
|
|
l10n_mx_edi_cfdi_to_public=True,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': price_unit,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoices._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoices, 'test_cfdi_rounding_11_ginvoice')
|
|
|
|
def test_cfdi_rounding_12(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 (
|
|
(7.54, self.tax_8_ieps),
|
|
(7.41, self.tax_8_ieps),
|
|
(5.27, self.tax_16),
|
|
(5.21, self.tax_16),
|
|
)
|
|
],
|
|
**kwargs,
|
|
)
|
|
|
|
with self.mx_external_setup(self.frozen_today):
|
|
self.partner_mx.l10n_mx_edi_ieps_breakdown = True
|
|
invoice = create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_12_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_cfdi_rounding_12_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, 'test_cfdi_rounding_12_ginvoice')
|
|
|
|
def test_cfdi_rounding_13(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 4000.0,
|
|
'tax_ids': [Command.set((self.tax_16 + self.local_tax_3_5_withholding).ids)],
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_13_inv')
|
|
|
|
def test_cfdi_rounding_14(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100.05,
|
|
'discount': 50.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_cfdi_rounding_14_inv')
|
|
|
|
def test_cfdi_rounding_15(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 18103.45,
|
|
'discount': 50.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_cfdi_rounding_15_inv')
|
|
|
|
def test_cfdi_rounding_16(self):
|
|
(self.tax_8_ieps + self.tax_26_5_ieps + self.tax_30_ieps + self.tax_53_ieps + self.tax_16).write({
|
|
'active': True,
|
|
'price_include_override': 'tax_included',
|
|
'include_base_amount': True,
|
|
})
|
|
product1 = self._create_product(
|
|
lst_price=489.57,
|
|
taxes_id=[Command.set((self.tax_26_5_ieps + self.tax_16).ids)],
|
|
)
|
|
product2 = self._create_product(
|
|
lst_price=789.57,
|
|
taxes_id=[Command.set((self.tax_30_ieps + self.tax_16).ids)],
|
|
)
|
|
product3 = self._create_product(
|
|
lst_price=7989.57,
|
|
taxes_id=[Command.set((self.tax_53_ieps + self.tax_16).ids)],
|
|
)
|
|
product4 = self._create_product(
|
|
lst_price=289.57,
|
|
taxes_id=[Command.set((self.tax_8_ieps + self.tax_16).ids)],
|
|
)
|
|
product5 = self._create_product(
|
|
lst_price=378.0,
|
|
taxes_id=[Command.set(self.tax_0.ids)],
|
|
)
|
|
product6 = self._create_product(
|
|
lst_price=1000.0,
|
|
taxes_id=[Command.set(self.tax_16.ids)],
|
|
)
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoices = self._create_invoice(
|
|
l10n_mx_edi_cfdi_to_public=True,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': product.id,
|
|
'quantity': quantity,
|
|
})
|
|
for product, quantity in (
|
|
(product1, 3),
|
|
(product2, 4),
|
|
(product3, 3),
|
|
(product4, 2),
|
|
(product5, 1),
|
|
(product6, 3),
|
|
)
|
|
],
|
|
)
|
|
invoices += self._create_invoice(
|
|
l10n_mx_edi_cfdi_to_public=True,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': product.id,
|
|
'quantity': quantity,
|
|
})
|
|
for product, quantity in (
|
|
(product1, 3),
|
|
(product2, 4),
|
|
(product3, 2),
|
|
(product4, 2),
|
|
(product5, 1),
|
|
(product6, 3),
|
|
)
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoices._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoices, 'test_cfdi_rounding_16_ginvoice')
|
|
|
|
def test_cfdi_rounding_17(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 3163.79,
|
|
'discount': 25.0,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 2992.41,
|
|
'discount': 25.0,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 3025.86,
|
|
'discount': 25.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_cfdi_rounding_17_inv')
|
|
|
|
def test_cfdi_rounding_18(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'l10n_mx_edi_tax_object': '03',
|
|
'product_id': self.product.id,
|
|
'price_unit': 50.00,
|
|
'tax_ids': [Command.set(self.tax_1_25_sale_withholding.ids)],
|
|
}),
|
|
])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_18_inv')
|
|
|
|
def test_cfdi_rounding_19(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'l10n_mx_edi_tax_object': '03',
|
|
'product_id': self.product.id,
|
|
'price_unit': 7.00,
|
|
'tax_ids': [Command.set(self.tax_1_25_sale_withholding.ids)],
|
|
}),
|
|
Command.create({
|
|
'l10n_mx_edi_tax_object': '03',
|
|
'product_id': self.product.id,
|
|
'price_unit': 43.00,
|
|
'tax_ids': [Command.set(self.tax_1_25_sale_withholding.ids)],
|
|
}),
|
|
])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_19_inv')
|
|
|
|
def test_cfdi_rounding_20(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'l10n_mx_edi_tax_object': '03',
|
|
'product_id': self.product.id,
|
|
'price_unit': 17.00,
|
|
'tax_ids': [Command.set(self.tax_1_25_sale_withholding.ids)],
|
|
}),
|
|
Command.create({
|
|
'l10n_mx_edi_tax_object': '03',
|
|
'product_id': self.product.id,
|
|
'price_unit': 33.00,
|
|
'tax_ids': [Command.set(self.tax_1_25_sale_withholding.ids)],
|
|
}),
|
|
])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_20_inv')
|
|
|
|
def test_cfdi_rounding_21(self):
|
|
rate = 1 / 20.4277
|
|
usd = self.setup_other_currency('USD', rates=[(self.frozen_today, rate)])
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 93.76,
|
|
'quantity': 172,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 74.18,
|
|
'quantity': 161,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 74.18,
|
|
'quantity': 162,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 93.76,
|
|
'quantity': 384,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 111.28,
|
|
'quantity': 178,
|
|
'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_cfdi_rounding_21_inv')
|
|
|
|
payment = self._create_payment(invoice, currency_id=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, 'test_cfdi_rounding_21_pay')
|
|
|
|
def test_cfdi_rounding_22(self):
|
|
today = self.frozen_today
|
|
today_minus_1 = self.frozen_today - relativedelta(days=1)
|
|
usd = self.setup_other_currency('USD', rates=[
|
|
(today_minus_1, 0.049216958195),
|
|
(today, 0.053418803419),
|
|
])
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
currency_id=usd.id,
|
|
date=today_minus_1,
|
|
invoice_date=today_minus_1,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 91,
|
|
'quantity': 64,
|
|
'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_cfdi_rounding_22_inv')
|
|
|
|
payment = self._create_payment(invoice, 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, 'test_cfdi_rounding_22_pay')
|
|
|
|
def test_cfdi_rounding_23(self):
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
|
|
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': discount,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
})
|
|
for quantity, price_unit, discount in (
|
|
(1.0, 454.0, 10.0),
|
|
(2.5, 452.01, 10.0),
|
|
(1.0, 209.99, 10.0),
|
|
(9.40, 27.25, 10.0),
|
|
(0.5, 1356.01, 10.0),
|
|
(20.0, 1.0, 0.0),
|
|
)
|
|
])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_23_inv')
|
|
|
|
def test_cfdi_rounding_24(self):
|
|
self.tax_16.price_include_override = 'tax_excluded'
|
|
|
|
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,
|
|
'price_unit': 47.25,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
'discount': 50,
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoice, 'test_cfdi_rounding_24_inv')
|
|
|
|
def test_cfdi_rounding_25(self):
|
|
self.env['decimal.precision'].search([('name', '=', 'Product Price')]).digits = 6
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100.032,
|
|
'discount': 50.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_cfdi_rounding_25_inv')
|
|
|
|
def test_cfdi_rounding_26(self):
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
|
|
def create_invoice():
|
|
return self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': price_unit,
|
|
'quantity': quantity,
|
|
'discount': discount,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
})
|
|
for price_unit, quantity, discount in (
|
|
(64.99, 6.60, 10.0),
|
|
(220.01, 1.0, 10.0),
|
|
(1.0, 12.0, 0.0),
|
|
(151.99, 1.0, 10.0),
|
|
)
|
|
])
|
|
|
|
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, 'test_cfdi_rounding_26_inv')
|
|
|
|
invoice = create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoice, 'test_cfdi_rounding_26_ginvoice')
|
|
|
|
def test_cfdi_rounding_27(self):
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
|
|
def create_invoice():
|
|
return self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': price_unit,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
})
|
|
for price_unit in (
|
|
1999.0, 1999.0, 1999.0,
|
|
1799.0, 1799.0,
|
|
649.0, 649.0, 649.0, 649.0, 649.0,
|
|
)
|
|
])
|
|
|
|
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, 'test_cfdi_rounding_27_inv')
|
|
|
|
invoice = create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoice, 'test_cfdi_rounding_27_ginvoice')
|
|
|
|
def test_cfdi_rounding_28(self):
|
|
self.tax_16.price_include_override = 'tax_included'
|
|
|
|
def create_invoice():
|
|
return self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': price_unit,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
})
|
|
for price_unit in (99.0, 99.0, 99.0, 399.0)
|
|
])
|
|
|
|
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, 'test_cfdi_rounding_28_inv')
|
|
|
|
invoice = create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self._assert_global_invoice_cfdi_from_invoices(invoice, 'test_cfdi_rounding_28_ginvoice')
|
|
|
|
def test_cfdi_rounding_negative_line_on_many_others(self):
|
|
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,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
})
|
|
for price_unit in (
|
|
723.77,
|
|
769.35,
|
|
1510.56,
|
|
1378.08,
|
|
851.40,
|
|
662.69,
|
|
1378.08,
|
|
1378.08,
|
|
77.80,
|
|
743.04,
|
|
503.28,
|
|
421.06,
|
|
743.04,
|
|
743.04,
|
|
63.47,
|
|
80.62,
|
|
80.62,
|
|
80.62,
|
|
80.62,
|
|
119.29,
|
|
11.27,
|
|
34.02,
|
|
16.60,
|
|
19.20,
|
|
19.20,
|
|
13.54,
|
|
15.79,
|
|
15.79,
|
|
5.37,
|
|
5.37,
|
|
0.01,
|
|
3.23,
|
|
32.01,
|
|
5.20,
|
|
68.54,
|
|
97.55,
|
|
121.36,
|
|
18.67,
|
|
32.51,
|
|
68.14,
|
|
15.33,
|
|
26.20,
|
|
46.89,
|
|
8.06,
|
|
8.06,
|
|
4.84,
|
|
8.06,
|
|
13.36,
|
|
-0.99,
|
|
)
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_rounding_negative_line_on_many_others')
|
|
|
|
def test_cfdi_cash_rounding(self):
|
|
"""Cash rounding lines (add_invoice_line strategy) must not appear as CFDI
|
|
concepts. The CFDI SubTotal/Total must reflect the pre-rounding amounts;
|
|
the rounding difference only belongs in the journal entry."""
|
|
cash_rounding = self.env['account.cash.rounding'].create({
|
|
'name': 'Redondeo',
|
|
'rounding': 1.0,
|
|
'strategy': 'add_invoice_line',
|
|
'profit_account_id': self.company_data['default_account_revenue'].copy().id,
|
|
'loss_account_id': self.company_data['default_account_expense'].copy().id,
|
|
'rounding_method': 'UP',
|
|
})
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
invoice_cash_rounding_id=cash_rounding.id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 86.034483,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
],
|
|
)
|
|
rounding_lines = invoice.line_ids.filtered(lambda l: l.display_type == 'rounding')
|
|
self.assertTrue(rounding_lines, "Expected a cash rounding line on the invoice")
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_cash_rounding')
|
|
|
|
def test_partial_payment_1(self):
|
|
date1 = self.frozen_today - relativedelta(days=2)
|
|
date2 = self.frozen_today - relativedelta(days=1)
|
|
date3 = self.frozen_today
|
|
chf = self.setup_other_currency('CHF', rates=[(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=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=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=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
|
|
chf = self.setup_other_currency('CHF', rates=[(date1, 16.0), (date2, 17.0), (date3, 18.0)])
|
|
usd = self.setup_other_currency('USD', rates=[(date1, 17.0), (date2, 16.5), (date3, 16.0)])
|
|
|
|
with self.mx_external_setup(date1):
|
|
invoice = self._create_invoice(
|
|
currency_id=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=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=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
|
|
usd = self.setup_other_currency('USD', rates=[(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=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=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=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=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=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=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_partial_payment_4(self):
|
|
""" Test the residual chain of a foreign currency invoice partially reconciled with a credit note,
|
|
then fully paid in the same foreign currency at another rate. The exchange difference created by the
|
|
payment reconciliation must not prevent the credit note from being deducted from the residual chain.
|
|
"""
|
|
date1 = self.frozen_today - relativedelta(days=2)
|
|
date_rate2 = self.frozen_today - relativedelta(days=1)
|
|
date2 = self.frozen_today
|
|
# The new rate is dated strictly before the payment (not the same day) so the exchange
|
|
# difference is guaranteed to be created regardless of the "rate as of date" lookup semantics.
|
|
usd = self.setup_other_currency('USD', rates=[(date1, 17.0), (date_rate2, 16.5)])
|
|
|
|
with self.mx_external_setup(date1):
|
|
# USD invoice: 19720 USD = 1160 MXN (1:17)
|
|
invoice = self._create_invoice(
|
|
currency_id=usd.id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 17000.0,
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
# Partial USD credit note at the same rate: 1972 USD = 116 MXN (1:17)
|
|
refund = self._create_invoice(
|
|
move_type='out_refund',
|
|
currency_id=usd.id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 1700.0,
|
|
}),
|
|
],
|
|
)
|
|
(refund + invoice).line_ids.filtered(lambda line: line.display_type == 'payment_term').reconcile()
|
|
self.assertRecordValues(invoice + refund, [
|
|
{'amount_residual': 17748.0},
|
|
{'amount_residual': 0.0},
|
|
])
|
|
|
|
with self.mx_external_setup(date2):
|
|
# Pay the remaining 17748 USD at rate 1:16.5 (dated 'date_rate2', strictly before this
|
|
# payment). The rate differs from the one used for the invoice/CN (1:17), so reconciling
|
|
# the payment is guaranteed to create an exchange difference move on the invoice/payment
|
|
# partial (1075.64 MXN paid - 1044.0 MXN residual).
|
|
# The credit note should be subtracted from the residual chain:
|
|
# ImpSaldoAnt="17748.00", ImpPagado="17748.00", ImpSaldoInsoluto="0.00"
|
|
payment = self._create_payment(
|
|
invoice,
|
|
amount=17748.0,
|
|
currency_id=usd.id,
|
|
payment_date=date2,
|
|
)
|
|
self.assertRecordValues(invoice, [{'amount_residual': 0.0}])
|
|
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_partial_payment_4_pay1')
|
|
|
|
def test_full_payment_rate(self):
|
|
date1 = fields.Date.today() - relativedelta(days=1)
|
|
date2 = fields.Date.today()
|
|
usd = self.setup_other_currency('USD', rates=[(date1, 0.049905678268), (date2, 0.049073733284)])
|
|
|
|
with self.mx_external_setup(date1):
|
|
invoice = self._create_invoice(
|
|
date=date1,
|
|
currency_id=usd.id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 5490.00,
|
|
'quantity': 1,
|
|
'discount': 0.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.assertEqual(invoice.l10n_mx_edi_cfdi_state, 'sent', f'Error: {invoice.l10n_mx_edi_document_ids.message}')
|
|
|
|
with self.mx_external_setup(date2):
|
|
payment = self._create_payment(
|
|
invoice,
|
|
amount=129772.07,
|
|
payment_date=date2,
|
|
currency_id=self.env.ref('base.MXN').id,
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertEqual(payment.move_id.l10n_mx_edi_cfdi_state, 'sent', f'Error: {payment.move_id.l10n_mx_edi_document_ids.message}')
|
|
self._assert_invoice_payment_cfdi(payment.move_id, 'test_full_payment_rate')
|
|
|
|
def test_statement_line_partially_reconciled_multiple_invoices(self):
|
|
payment_date = self.frozen_today
|
|
|
|
with self.mx_external_setup(payment_date):
|
|
invoice_1 = self._create_invoice(
|
|
invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100, # + tax(16%)
|
|
})],
|
|
)
|
|
invoice_2 = self._create_invoice(
|
|
invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100, # + tax(16%)
|
|
})],
|
|
)
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_1._l10n_mx_edi_cfdi_invoice_try_send()
|
|
invoice_2._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
st_line = self.env['account.bank.statement.line'].create({
|
|
'journal_id': self.company_data['default_journal_bank'].id,
|
|
'amount': 232,
|
|
'date': payment_date,
|
|
'payment_ref': 'test'
|
|
})
|
|
|
|
# Reconcile bank transaction with invoice_1
|
|
st_line.set_line_bank_statement_line(invoice_1.line_ids.filtered(lambda l: l.display_type == 'payment_term').ids)
|
|
self.assertRecordValues(st_line, [{'is_reconciled': False, 'l10n_mx_edi_cfdi_state': False}])
|
|
self.assertRecordValues(invoice_1, [{'payment_state': 'paid', 'l10n_mx_edi_update_payments_needed': False}])
|
|
self.assertRecordValues(invoice_2, [{'payment_state': 'not_paid', 'l10n_mx_edi_update_payments_needed': False}])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(st_line, [{'l10n_mx_edi_cfdi_state': False}])
|
|
|
|
# Reconcile bank transaction with invoice_2
|
|
st_line.set_line_bank_statement_line(invoice_2.line_ids.filtered(lambda l: l.display_type == 'payment_term').ids)
|
|
self.assertRecordValues(st_line, [{'is_reconciled': True, 'l10n_mx_edi_cfdi_state': False}])
|
|
self.assertRecordValues(invoice_1, [{'payment_state': 'paid', 'l10n_mx_edi_update_payments_needed': True}])
|
|
self.assertRecordValues(invoice_2, [{'payment_state': 'paid', 'l10n_mx_edi_update_payments_needed': True}])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_2.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(st_line, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
def test_payment_partially_reconciled_multiple_invoices(self):
|
|
payment_date = self.frozen_today
|
|
in_payment_state = self.env['account.move']._get_invoice_in_payment_state()
|
|
|
|
with self.mx_external_setup(payment_date):
|
|
invoice_1 = self._create_invoice(
|
|
invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100, # + tax(16%)
|
|
})],
|
|
)
|
|
invoice_2 = self._create_invoice(
|
|
invoice_line_ids=[Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100, # + tax(16%)
|
|
})],
|
|
)
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_1._l10n_mx_edi_cfdi_invoice_try_send()
|
|
invoice_2._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
payment = self.env['account.payment'].create({
|
|
'amount': 232.0,
|
|
'date': payment_date,
|
|
'payment_type': 'inbound',
|
|
'partner_type': 'customer',
|
|
'partner_id': invoice_1.partner_id.id,
|
|
})
|
|
payment.action_post()
|
|
payment.action_draft()
|
|
_liquidity_lines, counterpart_lines, _writeoff_lines = payment._seek_for_lines()
|
|
payment.move_id.line_ids = [
|
|
Command.update(counterpart_lines.id, {'balance': -116.0}),
|
|
Command.create({'account_id': counterpart_lines.account_id.id, 'balance': -116.0}),
|
|
]
|
|
payment.action_post()
|
|
_liquidity_lines, counterpart_lines, _writeoff_lines = payment._seek_for_lines()
|
|
|
|
# Reconcile payment with invoice_1
|
|
(counterpart_lines[0] + invoice_1.line_ids.filtered(lambda l: l.display_type == 'payment_term')).reconcile()
|
|
self.assertRecordValues(payment, [{'is_reconciled': False, 'l10n_mx_edi_cfdi_state': False}])
|
|
self.assertRecordValues(invoice_1, [{'payment_state': in_payment_state, 'l10n_mx_edi_update_payments_needed': False}])
|
|
self.assertRecordValues(invoice_2, [{'payment_state': 'not_paid', 'l10n_mx_edi_update_payments_needed': False}])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(payment, [{'l10n_mx_edi_cfdi_state': False}])
|
|
|
|
# Reconcile payment with invoice_2
|
|
(counterpart_lines[1] + invoice_2.line_ids.filtered(lambda l: l.display_type == 'payment_term')).reconcile()
|
|
self.assertRecordValues(payment, [{'is_reconciled': True, 'l10n_mx_edi_cfdi_state': False}])
|
|
self.assertRecordValues(invoice_1, [{'payment_state': in_payment_state, 'l10n_mx_edi_update_payments_needed': True}])
|
|
self.assertRecordValues(invoice_2, [{'payment_state': in_payment_state, 'l10n_mx_edi_update_payments_needed': True}])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_2.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(payment, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
def test_sw_finkok_CRP20211_usd_statement_in_mxn_journal_rounded_exchange_rate(self):
|
|
""" Test rounding of exchange rate in payment cfdi of a statement line with foreign currency
|
|
using Finkok, SW, or Solucion Factible does not trigger the CRP20211 error.
|
|
"""
|
|
payment_date = self.frozen_today
|
|
# Rates for how much USD for 1 MXN
|
|
usd = self.setup_other_currency('USD', rates=[(payment_date, 0.05)])
|
|
|
|
bank_journal = self.env['account.journal'].create({
|
|
'name': 'Bank 123456',
|
|
'code': 'BNK67',
|
|
'type': 'bank',
|
|
'bank_acc_number': '123456',
|
|
'l10n_mx_edi_payment_method_id': self.env.ref('l10n_mx_edi.payment_method_transferencia').id, # To default to this payment method
|
|
})
|
|
|
|
for pac in ['finkok', 'solfact']:
|
|
self.env.company.l10n_mx_edi_pac = pac
|
|
|
|
with self.mx_external_setup(payment_date):
|
|
invoice = self._create_invoice(
|
|
currency_id=usd.id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 11396.55, # + tax(16%) = 13220.0 USD
|
|
}),
|
|
],
|
|
)
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
with self.mx_external_setup(payment_date):
|
|
# Those are the important amount because
|
|
# 305147.51 MXN / 13220.0 USD = 23.082262481 ≃ 23.082262 MXN/USD
|
|
# 13220.0 USD * 23.082262 MXN/USD = 305147.50 MXN which is not 305147.51 MXN
|
|
st_line = self.env['account.bank.statement.line'].create({
|
|
'journal_id': bank_journal.id,
|
|
'amount_currency': 13220.00, # USD
|
|
'amount': 305147.51, # MXN
|
|
'foreign_currency_id': self.env.ref('base.USD').id,
|
|
'date': payment_date,
|
|
'payment_ref': 'test'
|
|
})
|
|
|
|
# Reconcile bank transaction with invoice
|
|
st_line.set_line_bank_statement_line(invoice.line_ids.filtered(lambda l: l.display_type == 'payment_term').ids)
|
|
self.assertRecordValues(st_line, [{'is_reconciled': True}])
|
|
self.assertRecordValues(invoice, [{'payment_state': 'paid'}])
|
|
|
|
# Generate payment cfdi file
|
|
with self.with_mocked_pac_sign_success():
|
|
st_line.move_id._l10n_mx_edi_cfdi_payment_try_send()
|
|
|
|
# Without fix, the generated cfdi payment file will be refused by Quadrum (finkok) due to CRP20211
|
|
self._assert_invoice_payment_cfdi(st_line.move_id, 'test_sw_finkok_CRP20211_usd_statement_in_mxn_journal_rounded_exchange_rate_pay')
|
|
|
|
def test_foreign_curr_payment_comp_curr_invoice_forced_balance(self):
|
|
date1 = self.frozen_today - relativedelta(days=1)
|
|
date2 = self.frozen_today
|
|
chf = self.setup_other_currency('CHF', rates=[(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': 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
|
|
chf = self.setup_other_currency('CHF', rates=[(date1, 16.0), (date2, 17.0)])
|
|
|
|
with self.mx_external_setup(date1):
|
|
invoice = self._create_invoice(
|
|
currency_id=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))
|
|
|
|
# Invoice created in the past with a date which was then in the future,
|
|
# which was already attempted to be sent in the past and which we try to resend today
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_date=date_in_the_past)
|
|
previous_send_time = (self.frozen_today - relativedelta(days=2)).replace(tzinfo=None)
|
|
invoice.l10n_mx_edi_post_time = previous_send_time
|
|
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=previous_send_time)
|
|
|
|
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_vendor_bill_payment_production_sign_flow_cancel_from_the_sat(self):
|
|
""" Test the case where the vendor bill is manually canceled from the SAT portal by the user (production environment). """
|
|
self.env.company.l10n_mx_edi_pac_test_env = False
|
|
self.env.company.l10n_mx_edi_pac_username = 'test'
|
|
self.env.company.l10n_mx_edi_pac_password = 'test'
|
|
|
|
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()
|
|
new_bill = self._upload_document_on_journal(
|
|
journal=self.company_data['default_journal_purchase'],
|
|
content=file_content,
|
|
filename=file_name,
|
|
)
|
|
|
|
# Not checking bill values since they are already checked in a different test, only SAT
|
|
self.assertEqual(new_bill.l10n_mx_edi_invoice_document_ids.state, 'invoice_received')
|
|
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)
|
|
|
|
# Manual cancellation from the SAT portal
|
|
with self.with_mocked_sat_call(lambda _x: 'cancelled'):
|
|
new_bill.l10n_mx_edi_cfdi_try_sat()
|
|
|
|
inv_cancel_doc_values = {
|
|
'move_id': new_bill.id,
|
|
'state': 'invoice_cancel',
|
|
'sat_state': 'cancelled',
|
|
}
|
|
inv_sent_doc_values = {
|
|
'move_id': new_bill.id,
|
|
'state': 'invoice_received',
|
|
'sat_state': 'valid',
|
|
}
|
|
self.assertRecordValues(new_bill.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
inv_cancel_doc_values,
|
|
inv_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(new_bill, [{
|
|
'state': 'cancel',
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': True,
|
|
'l10n_mx_edi_update_sat_needed': False,
|
|
'l10n_mx_edi_cfdi_sat_state': 'cancelled',
|
|
'l10n_mx_edi_cfdi_state': 'cancel',
|
|
}])
|
|
|
|
def test_cfdi_multi_relation_origin(self):
|
|
"Ensure that a CfdiRelacionados Node should be created for each relation"
|
|
cfdi_origin = "01|6c76a910-2115-4a2c-bf15-e67c1505dd21,02|6c76a910-2115-4a2c-bf15-e67c1505bb22"
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
l10n_mx_edi_cfdi_origin=cfdi_origin
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_multi_relation_inv')
|
|
|
|
# Testing with a relation with multiple uuids
|
|
cfdi_origin += ",7a86a910-3145-4a2c-bf15-e67c1505de30"
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(
|
|
l10n_mx_edi_cfdi_origin=cfdi_origin
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self._assert_invoice_cfdi(invoice, 'test_cfdi_multi_uuid_inv')
|
|
|
|
def test_cfdi_to_public_credit_note_g02_usage(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
credit_note = self._create_invoice(
|
|
move_type='out_refund',
|
|
l10n_mx_edi_cfdi_to_public=True,
|
|
l10n_mx_edi_usage='G02',
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 500.0,
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
credit_note._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
document = credit_note.l10n_mx_edi_invoice_document_ids.filtered(lambda x: x.state == 'invoice_sent')
|
|
cfdi_infos = self.env['l10n_mx_edi.document']._decode_cfdi_attachment(document.attachment_id.raw)
|
|
self.assertEqual(cfdi_infos['usage'], 'G02')
|
|
|
|
def test_extra_invoice_report_values(self):
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_date_due=self.frozen_today + relativedelta(months=1))
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
report_values = invoice._l10n_mx_edi_get_extra_invoice_report_values()
|
|
self.assertEqual(report_values['payment_method'], 'PPD')
|
|
self.assertEqual(report_values['payment_way'], '99 - Por definir')
|
|
|
|
def test_update_payments_rate(self):
|
|
""" This tests make sure that the document generated after updating payments show the correct payment amount and exchange rate used """
|
|
date1 = fields.Date.today()
|
|
usd = self.setup_other_currency('USD', rates=[(date1, 0.05)])
|
|
|
|
bank_journal = self.env['account.journal'].create({
|
|
'name': 'Bank 123456',
|
|
'code': 'BNK67',
|
|
'type': 'bank',
|
|
'bank_acc_number': '123456',
|
|
'currency_id': usd.id,
|
|
'l10n_mx_edi_payment_method_id': self.env.ref('l10n_mx_edi.payment_method_transferencia').id,
|
|
})
|
|
|
|
with self.mx_external_setup(date1):
|
|
invoice = self._create_invoice(
|
|
date=date1,
|
|
currency_id=self.env.ref('base.MXN').id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 300.00,
|
|
'quantity': 1,
|
|
'tax_ids': [],
|
|
})],
|
|
l10n_mx_edi_payment_policy='PPD',
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertEqual(invoice.l10n_mx_edi_cfdi_state, 'sent', f'Error: {invoice.l10n_mx_edi_document_ids.message}')
|
|
|
|
st_line = self.env['account.bank.statement.line'].create({
|
|
'journal_id': bank_journal.id,
|
|
'amount': 15.00,
|
|
'foreign_currency_id': usd.id,
|
|
'date': date1,
|
|
'payment_ref': 'test'
|
|
})
|
|
|
|
# Reconcile bank transaction with invoice
|
|
st_line.set_line_bank_statement_line(invoice.line_ids.filtered(lambda l: l.display_type == 'payment_term').ids)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
document = invoice.l10n_mx_edi_document_ids.filtered(lambda d: d.state == 'payment_sent')[0]
|
|
xml_tree = self.get_xml_tree_from_string(document.attachment_id.raw)
|
|
pago = xml_tree.xpath("//*[local-name()='Pago']")
|
|
self.assertEqual(len(pago), 1)
|
|
self.assertEqual(pago[0].get('Monto'), '15.00')
|
|
self.assertEqual(pago[0].get('MonedaP'), 'USD')
|
|
self.assertEqual(pago[0].get('TipoCambioP'), '20.000000')
|
|
|
|
def test_cfdi_future_payment(self):
|
|
"""
|
|
Ensure the l10n_mx_edi_update_payments_needed field is False
|
|
when having only payments in the future. It means that the 'Update Payments'
|
|
button will be invisible in the view.
|
|
"""
|
|
with self.mx_external_setup(self.frozen_today):
|
|
# Create a PDD invoice
|
|
invoice = self._create_invoice(
|
|
invoice_date_due=self.frozen_today + relativedelta(months=1, days=1),
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'quantity': 1.0,
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
# create a payment in the future
|
|
self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({
|
|
'amount': invoice.amount_total / 2,
|
|
'payment_date': self.frozen_today + relativedelta(days=1)
|
|
})._create_payments()
|
|
|
|
self.assertFalse(invoice.l10n_mx_edi_update_payments_needed)
|
|
|
|
# create a payment today
|
|
self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({
|
|
'payment_date': self.frozen_today
|
|
})._create_payments()
|
|
|
|
invoice.invalidate_recordset(['l10n_mx_edi_update_payments_needed'])
|
|
self.assertTrue(invoice.l10n_mx_edi_update_payments_needed)
|