2233 lines
96 KiB
Python
2233 lines
96 KiB
Python
# -*- coding: utf-8 -*-
|
|
from contextlib import contextmanager
|
|
from datetime import timedelta
|
|
|
|
from .common import TestMxEdiCommon
|
|
from odoo import Command, fields
|
|
from odoo.addons.mail.tools.discuss import Store
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests import tagged
|
|
from odoo.tools import misc
|
|
from odoo.tools.misc import file_open
|
|
|
|
from freezegun import freeze_time
|
|
|
|
@tagged('post_install_l10n', 'post_install', '-at_install')
|
|
class TestCFDIInvoiceWorkflow(TestMxEdiCommon):
|
|
|
|
def test_invoice_workflow(self):
|
|
with freeze_time('2017-01-01'):
|
|
invoice = self._create_invoice(invoice_date_due='2017-02-01') # Force PPD
|
|
|
|
# No pac found.
|
|
self.env.company.l10n_mx_edi_pac = None
|
|
with freeze_time('2017-01-05'):
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [
|
|
{
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-05 00:00:00'),
|
|
'message': "No PAC specified.",
|
|
'state': 'invoice_sent_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': None}])
|
|
|
|
# Set back the PAC but make it raising an error.
|
|
self.env.company.l10n_mx_edi_pac = 'solfact'
|
|
with freeze_time('2017-01-06'), self.with_mocked_pac_sign_error():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [
|
|
{
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-06 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'invoice_sent_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': None}])
|
|
|
|
# The failing attachment remains accessible for the user.
|
|
self.assertTrue(invoice.l10n_mx_edi_invoice_document_ids.attachment_id)
|
|
|
|
# Sign.
|
|
with freeze_time('2017-01-07'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-07 00:00:00'),
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
self.assertTrue(invoice.l10n_mx_edi_cfdi_attachment_id)
|
|
self.assertTrue(invoice.l10n_mx_edi_invoice_document_ids.attachment_id)
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Cancel failed.
|
|
self.env.company.l10n_mx_edi_pac = None
|
|
with freeze_time('2017-02-01'):
|
|
self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '02'}) \
|
|
.action_cancel_invoice()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
{
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-02-01 00:00:00'),
|
|
'message': "No PAC specified.",
|
|
'state': 'invoice_cancel_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Set back the PAC but make it raising an error.
|
|
self.env.company.l10n_mx_edi_pac = 'solfact'
|
|
with freeze_time('2017-02-06'), self.with_mocked_pac_cancel_error():
|
|
invoice.l10n_mx_edi_invoice_document_ids.sorted()[0].action_retry()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
{
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-02-06 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'invoice_cancel_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Cancel.
|
|
with freeze_time('2017-02-07'), self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '02'}) \
|
|
.action_cancel_invoice()
|
|
|
|
invoice.l10n_mx_edi_invoice_document_ids.invalidate_recordset(fnames=['cancel_button_needed'])
|
|
sent_doc_values['cancel_button_needed'] = False
|
|
|
|
cancel_doc_values = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-02-07 00:00:00'),
|
|
'message': False,
|
|
'state': 'invoice_cancel',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': False,
|
|
}
|
|
sent_doc_values['sat_state'] = 'skip'
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'cancel',
|
|
'l10n_mx_edi_cfdi_state': 'cancel',
|
|
}])
|
|
|
|
# Sign again.
|
|
invoice.button_draft()
|
|
invoice.action_post()
|
|
with freeze_time('2017-03-10'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
sent_doc_values2 = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-03-10 00:00:00'),
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Sat.
|
|
with freeze_time('2017-04-01'), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
sent_doc_values2['sat_state'] = 'valid'
|
|
cancel_doc_values['sat_state'] = 'valid'
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Payment fully match but failed.
|
|
payment1 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 100.0,
|
|
})\
|
|
._create_payments()
|
|
with freeze_time('2017-06-02'), self.with_mocked_pac_sign_error():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
{
|
|
'move_id': payment1.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-02 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'payment_sent_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Unreconcile the payment.
|
|
# The document should disappear.
|
|
payment1.move_id.line_ids.remove_move_reconcile()
|
|
with freeze_time('2017-06-02'):
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Redo the reconciliation.
|
|
# The document should be created again.
|
|
(invoice + payment1.move_id).line_ids\
|
|
.filtered(lambda x: x.account_type == 'asset_receivable')\
|
|
.reconcile()
|
|
with freeze_time('2017-06-03'), self.with_mocked_pac_sign_error():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
{
|
|
'move_id': payment1.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-03 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'payment_sent_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# In case of success, the document is updated.
|
|
with freeze_time('2017-06-04'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
payment1_doc_values1 = {
|
|
'move_id': payment1.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-04 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Call again the method shouldn't change anything.
|
|
with freeze_time('2017-06-04'), self.with_mocked_pac_sign_error():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Create another payment and unreconcile the first payment.
|
|
payment2 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 100.0,
|
|
})\
|
|
._create_payments()
|
|
payment1.move_id.line_ids.remove_move_reconcile()
|
|
with freeze_time('2017-07-01'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
payment2_doc_values1 = {
|
|
'move_id': payment2.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-07-01 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment2_doc_values1,
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Fail to cancel the invoice.
|
|
with freeze_time('2017-07-10'), self.with_mocked_pac_cancel_error():
|
|
self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '02'}) \
|
|
.action_cancel_invoice()
|
|
cancel_doc_values2 = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-07-10 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'invoice_cancel_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values2,
|
|
payment2_doc_values1,
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Reconcile again the first payment. Since this reconciliation has already been sent to the SAT, nothing
|
|
# is updated.
|
|
(invoice + payment1.move_id).line_ids\
|
|
.filtered(lambda x: x.account_type == 'asset_receivable')\
|
|
.reconcile()
|
|
with freeze_time('2017-07-11'), self.with_mocked_pac_sign_error():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(
|
|
invoice.l10n_mx_edi_invoice_document_ids.sorted(lambda x: (x.datetime, x.move_id.id), reverse=True),
|
|
[
|
|
cancel_doc_values2,
|
|
payment2_doc_values1,
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
],
|
|
)
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Cancel successfully the invoice.
|
|
with freeze_time('2017-07-12'), self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '02'}) \
|
|
.action_cancel_invoice()
|
|
|
|
invoice.l10n_mx_edi_invoice_document_ids.invalidate_recordset(fnames=['cancel_button_needed'])
|
|
sent_doc_values2['cancel_button_needed'] = False
|
|
|
|
cancel_doc_values2.update({
|
|
'datetime': fields.Datetime.from_string('2017-07-12 00:00:00'),
|
|
'message': False,
|
|
'state': 'invoice_cancel',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': False,
|
|
})
|
|
self.assertRecordValues(
|
|
invoice.l10n_mx_edi_invoice_document_ids.sorted(lambda x: (x.datetime, x.move_id.id), reverse=True),
|
|
[
|
|
cancel_doc_values2,
|
|
payment2_doc_values1,
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
],
|
|
)
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'cancel'}])
|
|
|
|
with freeze_time('2017-07-12'), self.with_mocked_sat_call(lambda x: 'cancelled' if x.move_id == invoice else 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
cancel_doc_values2['sat_state'] = 'cancelled'
|
|
payment2_doc_values1['sat_state'] = 'valid'
|
|
payment1_doc_values1['sat_state'] = 'valid'
|
|
self.assertRecordValues(
|
|
invoice.l10n_mx_edi_invoice_document_ids.sorted(lambda x: (x.datetime, x.move_id.id), reverse=True),
|
|
[
|
|
cancel_doc_values2,
|
|
payment2_doc_values1,
|
|
payment1_doc_values1,
|
|
sent_doc_values2,
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
],
|
|
)
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'cancel'}])
|
|
|
|
def test_invoice_sent_after_paid(self):
|
|
invoice = self._create_invoice(invoice_date_due='2017-01-01')
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_payment_policy': 'PUE'}])
|
|
|
|
# Pay.
|
|
payment = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 100.0,
|
|
})\
|
|
._create_payments()
|
|
|
|
# Sign.
|
|
with freeze_time('2017-01-07'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-07 00:00:00'),
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_update_payments_needed': True}])
|
|
|
|
with freeze_time('2017-06-02'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
{
|
|
'move_id': payment.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-02 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_sent_pue',
|
|
'sat_state': False,
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': False,
|
|
},
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_update_payments_needed': False}])
|
|
|
|
def test_invoice_advanced_payment_flows(self):
|
|
invoice = self._create_invoice()
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_payment_policy': 'PPD'}])
|
|
|
|
# Sign.
|
|
with freeze_time('2017-01-07'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-07 00:00:00'),
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
|
|
# Pay.
|
|
payment = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 100.0,
|
|
})\
|
|
._create_payments()
|
|
|
|
# Try sending payment CFDI but force an error.
|
|
with freeze_time('2017-06-03'), self.with_mocked_pac_sign_error():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
{
|
|
'move_id': payment.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-03 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'payment_sent_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
},
|
|
sent_doc_values,
|
|
])
|
|
|
|
with freeze_time('2017-06-04'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
payment_doc_values = {
|
|
'move_id': payment.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-04 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Cancel the payment.
|
|
with freeze_time('2017-08-01'), self.with_mocked_pac_cancel_error():
|
|
payment.l10n_mx_edi_payment_document_ids.action_cancel()
|
|
payment_doc_cancel_values = {
|
|
'move_id': payment.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-08-01 00:00:00'),
|
|
'message': "turlututu",
|
|
'state': 'payment_cancel_failed',
|
|
'sat_state': False,
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': True,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment_doc_cancel_values,
|
|
payment_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Retry.
|
|
with freeze_time('2017-08-02'), self.with_mocked_pac_cancel_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
payment_doc_cancel_values.update({
|
|
'datetime': fields.Datetime.from_string('2017-08-02 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_cancel',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': False,
|
|
})
|
|
payment_doc_values['sat_state'] = 'skip'
|
|
payment_doc_values['cancel_button_needed'] = False
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment_doc_cancel_values,
|
|
payment_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# New payment.
|
|
payment2 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-08-01',
|
|
})\
|
|
._create_payments()
|
|
invoice.invalidate_recordset(fnames=['l10n_mx_edi_update_payments_needed'])
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
with freeze_time('2017-08-03'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
payment2_doc_values = {
|
|
'move_id': payment2.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-08-03 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment2_doc_values,
|
|
payment_doc_cancel_values,
|
|
payment_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Remove it and again a new payment.
|
|
payment2.move_id.line_ids.remove_move_reconcile()
|
|
payment3 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-08-04',
|
|
})\
|
|
._create_payments()
|
|
|
|
with freeze_time('2017-08-04'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
payment3_doc_values = {
|
|
'move_id': payment3.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-08-04 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': False,
|
|
'cancel_button_needed': True,
|
|
'retry_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment3_doc_values,
|
|
payment2_doc_values,
|
|
payment_doc_cancel_values,
|
|
payment_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Cancel payment2
|
|
with freeze_time('2017-08-05'), self.with_mocked_pac_cancel_success():
|
|
payment2.l10n_mx_edi_payment_document_ids.action_cancel()
|
|
payment2_cancel_doc_values = {
|
|
'move_id': payment2.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-08-05 00:00:00'),
|
|
'message': False,
|
|
'state': 'payment_cancel',
|
|
'sat_state': 'not_defined',
|
|
'cancellation_reason': '02',
|
|
'cancel_button_needed': False,
|
|
'retry_button_needed': False,
|
|
}
|
|
payment2_doc_values['sat_state'] = 'skip'
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment2_cancel_doc_values,
|
|
payment3_doc_values,
|
|
payment2_doc_values,
|
|
payment_doc_cancel_values,
|
|
payment_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
def test_payment_on_multiple_invoices(self):
|
|
invoice1 = self._create_invoice_with_amount('2017-01-01', self.comp_curr, 1000.0)
|
|
invoice2 = self._create_invoice_with_amount('2017-01-01', self.comp_curr, 1000.0)
|
|
|
|
# Sign.
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice1._l10n_mx_edi_cfdi_invoice_try_send()
|
|
inv1_sent_doc_values = {
|
|
'move_id': invoice1.id,
|
|
'invoice_ids': invoice1.ids,
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids, [inv1_sent_doc_values])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Create a payment with an higher amount than invoice1 and reconcile it.
|
|
payment = self.env['account.payment'].create({
|
|
'partner_id': self.partner_mx.id,
|
|
'payment_type': 'inbound',
|
|
'partner_type': 'customer',
|
|
'date': invoice1.date,
|
|
'amount': 1500.0,
|
|
'currency_id': self.comp_curr.id,
|
|
})
|
|
payment.action_post()
|
|
payment1_rec_line = payment.move_id.line_ids.filtered(lambda x: x.account_type == 'asset_receivable')
|
|
invoice1_rec_line = invoice1.line_ids.filtered(lambda x: x.account_type == 'asset_receivable')
|
|
(payment1_rec_line + invoice1_rec_line).reconcile()
|
|
|
|
# Nothing change since the payment is not fully reconciled.
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids, [inv1_sent_doc_values])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Fully reconcile the payment.
|
|
invoice2_rec_line = invoice2.line_ids.filtered(lambda x: x.account_type == 'asset_receivable')
|
|
(payment1_rec_line + invoice2_rec_line).reconcile()
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Nothing change since invoice2 is not signed.
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids, [inv1_sent_doc_values])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Sign invoice2 and retry the payments.
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice2._l10n_mx_edi_cfdi_invoice_try_send()
|
|
invoice1.invalidate_recordset(fnames=['l10n_mx_edi_update_payments_needed'])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
|
|
pay_sent_doc_values1 = {
|
|
'move_id': payment.move_id.id,
|
|
'invoice_ids': (invoice1 + invoice2).ids,
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv1_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
inv2_sent_doc_values = {
|
|
'move_id': invoice2.id,
|
|
'invoice_ids': invoice2.ids,
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice2.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv2_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Updating again the payment shouldn't do anything since the reconciliation hasn't changed.
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_error():
|
|
invoice1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv1_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice2.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv2_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Change the reconciliation.
|
|
invoice3 = self._create_invoice_with_amount('2017-01-01', self.comp_curr, 1000.0)
|
|
invoice2_rec_line.remove_move_reconcile()
|
|
invoice3_rec_line = invoice3.line_ids.filtered(lambda x: x.account_type == 'asset_receivable')
|
|
(payment1_rec_line + invoice3_rec_line).reconcile()
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice3._l10n_mx_edi_cfdi_invoice_try_send()
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_error():
|
|
invoice1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
pay_sent_doc_values2 = {
|
|
'move_id': payment.move_id.id,
|
|
'invoice_ids': (invoice1 + invoice3).ids,
|
|
'message': "turlututu",
|
|
'state': 'payment_sent_failed',
|
|
'sat_state': False,
|
|
}
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values2,
|
|
pay_sent_doc_values1,
|
|
inv1_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
self.assertRecordValues(invoice2.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv2_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
inv3_sent_doc_values = {
|
|
'move_id': invoice3.id,
|
|
'invoice_ids': invoice3.ids,
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice3.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values2,
|
|
inv3_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice3, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
|
|
# Change again the reconciliation.
|
|
invoice4 = self._create_invoice_with_amount('2017-01-01', self.comp_curr, 1000.0)
|
|
invoice1_rec_line.remove_move_reconcile()
|
|
invoice4_rec_line = invoice4.line_ids.filtered(lambda x: x.account_type == 'asset_receivable')
|
|
(payment1_rec_line + invoice4_rec_line).reconcile()
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice4._l10n_mx_edi_cfdi_invoice_try_send()
|
|
with freeze_time('2017-01-02'), self.with_mocked_pac_sign_success():
|
|
invoice4.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
pay_sent_doc_values2.update({
|
|
'invoice_ids': (invoice3 + invoice4).ids,
|
|
'message': False,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'not_defined',
|
|
})
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv1_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice3.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values2,
|
|
inv3_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice3, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
inv4_sent_doc_values = {
|
|
'move_id': invoice4.id,
|
|
'invoice_ids': invoice4.ids,
|
|
'message': False,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice4.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values2,
|
|
inv4_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice4, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
# Try to cancel the payment but it failed.
|
|
self.assertRecordValues(payment.l10n_mx_edi_payment_document_ids.sorted(), [
|
|
pay_sent_doc_values2,
|
|
pay_sent_doc_values1,
|
|
])
|
|
payment_doc = payment.l10n_mx_edi_payment_document_ids.sorted()[0]
|
|
with freeze_time('2017-01-03'), self.with_mocked_pac_cancel_error():
|
|
payment_doc.action_cancel()
|
|
pay_cancel_doc_values = {
|
|
'move_id': payment.move_id.id,
|
|
'invoice_ids': (invoice3 + invoice4).ids,
|
|
'message': "turlututu",
|
|
'state': 'payment_cancel_failed',
|
|
'sat_state': False,
|
|
}
|
|
self.assertRecordValues(payment.l10n_mx_edi_payment_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
pay_sent_doc_values2,
|
|
pay_sent_doc_values1,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice3.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
pay_sent_doc_values2,
|
|
inv3_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice3, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
self.assertRecordValues(invoice4.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
pay_sent_doc_values2,
|
|
inv4_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice4, [{
|
|
'l10n_mx_edi_update_payments_needed': True,
|
|
}])
|
|
|
|
# Change the reconciliation and successfully cancel.
|
|
invoice3_rec_line.remove_move_reconcile()
|
|
(payment1_rec_line + invoice1_rec_line).reconcile()
|
|
payment_doc = payment.l10n_mx_edi_payment_document_ids.sorted()[0]
|
|
with freeze_time('2017-01-03'), self.with_mocked_pac_cancel_success():
|
|
payment_doc.action_retry()
|
|
pay_cancel_doc_values.update({
|
|
'invoice_ids': (invoice3 + invoice4).ids,
|
|
'message': False,
|
|
'state': 'payment_cancel',
|
|
'sat_state': 'not_defined',
|
|
})
|
|
pay_sent_doc_values2['sat_state'] = 'skip'
|
|
self.assertRecordValues(payment.l10n_mx_edi_payment_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
pay_sent_doc_values2,
|
|
pay_sent_doc_values1,
|
|
])
|
|
self.assertRecordValues(invoice1.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_sent_doc_values1,
|
|
inv1_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice2, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice3.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
pay_sent_doc_values2,
|
|
inv3_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice3, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
self.assertRecordValues(invoice4.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
pay_sent_doc_values2,
|
|
inv4_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice4, [{
|
|
'l10n_mx_edi_update_payments_needed': False,
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_invoice_cancel_in_locked_period(self):
|
|
invoice = self._create_invoice(invoice_date_due='2017-02-01')
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
payment = self.env['account.payment.register'] \
|
|
.with_context(active_model='account.move', active_ids=invoice.ids) \
|
|
.create({}) \
|
|
._create_payments()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(payment, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
|
|
# Lock the period.
|
|
invoice.company_id.fiscalyear_lock_date = '2017-01-01'
|
|
|
|
# Cancel the invoice.
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '03'}) \
|
|
.action_cancel_invoice()
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'cancel'}])
|
|
|
|
# Cancel the payment.
|
|
with self.with_mocked_pac_cancel_success():
|
|
payment.l10n_mx_edi_payment_document_ids.action_cancel()
|
|
self.assertRecordValues(payment, [{'l10n_mx_edi_cfdi_state': 'cancel'}])
|
|
|
|
def test_invoice_payment_production_sign_flow_cancel_from_the_sat(self):
|
|
""" Test the case the invoice/payment is signed but the user manually cancel the document from the SAT portal (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'
|
|
|
|
with freeze_time('2017-01-01'):
|
|
invoice = self._create_invoice(invoice_date_due='2017-02-01') # Force PPD
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
inv_sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'valid',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [inv_sent_doc_values])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'valid',
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
}])
|
|
|
|
# Register a payment and sign it.
|
|
with freeze_time('2017-06-01'):
|
|
payment = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({'payment_date': '2017-06-01'})\
|
|
._create_payments()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
pay_sent_doc_values = {
|
|
'move_id': payment.move_id.id,
|
|
'state': 'payment_sent',
|
|
'sat_state': 'valid',
|
|
}
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
payment.move_id.l10n_mx_edi_cfdi_try_sat()
|
|
self.assertRecordValues(payment.move_id.l10n_mx_edi_payment_document_ids, [pay_sent_doc_values])
|
|
self.assertRecordValues(payment.move_id, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'valid',
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
}])
|
|
|
|
# Manual cancellation from the SAT portal.
|
|
with self.with_mocked_sat_call(lambda _x: 'cancelled'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
|
|
inv_cancel_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_cancel',
|
|
'sat_state': 'cancelled',
|
|
}
|
|
pay_cancel_doc_values = {
|
|
'move_id': payment.move_id.id,
|
|
'state': 'payment_cancel',
|
|
'sat_state': 'cancelled',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
pay_cancel_doc_values,
|
|
inv_cancel_doc_values,
|
|
pay_sent_doc_values,
|
|
inv_sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'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',
|
|
}])
|
|
self.assertRecordValues(payment.move_id, [{
|
|
'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_global_invoice_production_with_account_user(self):
|
|
""" Test the global invoice creation made by a user that isn't in the group 'base.group_system' """
|
|
self.simple_accountman.email = 'simple_accountman@test.com'
|
|
|
|
with freeze_time('2017-01-01'):
|
|
invoice = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice.with_user(self.simple_accountman)._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'global_sent',
|
|
}])
|
|
|
|
def test_global_invoice_production_sign_flow_cancel_from_the_sat(self):
|
|
""" Test the case the global invoice is signed but the user manually cancel the document from the SAT portal (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'
|
|
|
|
with freeze_time('2017-01-01'):
|
|
invoice = self._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()
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
sent_doc_values = {
|
|
'invoice_ids': invoice.ids,
|
|
'state': 'ginvoice_sent',
|
|
'sat_state': 'valid',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'valid',
|
|
'l10n_mx_edi_cfdi_state': 'global_sent',
|
|
}])
|
|
|
|
# Manual cancellation from the SAT portal.
|
|
with self.with_mocked_sat_call(lambda _x: 'cancelled'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
|
|
cancel_doc_values = {
|
|
'invoice_ids': invoice.ids,
|
|
'state': 'ginvoice_cancel',
|
|
'sat_state': 'cancelled',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_update_sat_needed': False,
|
|
'l10n_mx_edi_cfdi_sat_state': 'cancelled',
|
|
'l10n_mx_edi_cfdi_state': 'global_cancel',
|
|
'show_reset_to_draft_button': True,
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_invoice_production_sign_flow_cancel_from_odoo(self):
|
|
""" Test the case the invoice is signed and the user request a cancellation in Odoo (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'
|
|
|
|
invoice = self._create_invoice(invoice_date_due='2017-02-01') # Force PPD
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
|
|
# Approval of the sat.
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
sent_doc_values['sat_state'] = 'valid'
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'valid',
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
}])
|
|
|
|
# Request Cancel.
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(invoice.button_request_cancel()['context'])\
|
|
.create({'cancellation_reason': '02'})\
|
|
.action_cancel_invoice()
|
|
cancel_request_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_cancel_requested',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_request_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'not_defined',
|
|
'l10n_mx_edi_cfdi_state': 'cancel_requested',
|
|
}])
|
|
|
|
# The SAT rejected the cancellation.
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
cancel_request_doc_values['sat_state'] = 'valid'
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_request_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'valid',
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
}])
|
|
|
|
# Request Cancel again!
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(invoice.button_request_cancel()['context'])\
|
|
.create({'cancellation_reason': '02'})\
|
|
.action_cancel_invoice()
|
|
cancel_request_doc_values_2 = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_cancel_requested',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_request_doc_values_2,
|
|
cancel_request_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'not_defined',
|
|
'l10n_mx_edi_cfdi_state': 'cancel_requested',
|
|
}])
|
|
|
|
# The SAT approved the cancellation.
|
|
with self.with_mocked_sat_call(lambda _x: 'cancelled'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
cancel_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_cancel',
|
|
'sat_state': 'cancelled',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values,
|
|
cancel_request_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'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',
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_invoice_test_sign_flow_cancel_from_odoo(self):
|
|
""" Test the case the invoice is signed and the user request a cancellation in Odoo (testing environment). """
|
|
invoice = self._create_invoice(invoice_date_due='2017-02-01') # Force PPD
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
|
|
# Approval of the sat.
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoice.l10n_mx_edi_cfdi_try_sat()
|
|
sent_doc_values['sat_state'] = 'valid'
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'posted',
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'valid',
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
}])
|
|
|
|
# Request Cancel.
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(invoice.button_request_cancel()['context'])\
|
|
.create({'cancellation_reason': '02'})\
|
|
.action_cancel_invoice()
|
|
cancel_doc_values = {
|
|
'move_id': invoice.id,
|
|
'state': 'invoice_cancel',
|
|
'sat_state': 'not_defined',
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
self.assertRecordValues(invoice, [{
|
|
'state': 'cancel',
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': True,
|
|
'l10n_mx_edi_update_sat_needed': True,
|
|
'l10n_mx_edi_cfdi_sat_state': 'not_defined',
|
|
'l10n_mx_edi_cfdi_state': 'cancel',
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_payment_to_multiple_invoices_with_different_rfc(self):
|
|
invoice1 = self._create_invoice(partner_id=self.partner_mx.id, invoice_date_due='2017-02-01')
|
|
invoice2 = self._create_invoice(partner_id=self.partner_us.id, invoice_date_due='2017-02-01')
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice1._l10n_mx_edi_cfdi_invoice_try_send()
|
|
invoice2._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
payment = self.env['account.payment'].create({
|
|
'payment_type': 'inbound',
|
|
'partner_type': 'customer',
|
|
'amount': sum((invoice1 + invoice2).mapped('amount_total')),
|
|
'date': '2017-01-01',
|
|
})
|
|
payment.action_post()
|
|
(invoice1 + invoice2 + payment.move_id).line_ids\
|
|
.filtered(lambda x: x.account_type == 'asset_receivable')\
|
|
.reconcile()
|
|
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice1.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
self.assertRecordValues(payment.l10n_mx_edi_payment_document_ids.sorted(), [
|
|
{
|
|
'move_id': payment.move_id.id,
|
|
'invoice_ids': (invoice1 + invoice2).ids,
|
|
'message': "You can't register a payment for invoices having different RFCs.",
|
|
'state': 'payment_sent_failed',
|
|
},
|
|
])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_invoice_to_public_flow(self):
|
|
invoice = self._create_invoice(
|
|
partner_id=self.partner_mx.id,
|
|
l10n_mx_edi_cfdi_to_public=True,
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
self.assertRecordValues(invoice, [{'l10n_mx_edi_cfdi_state': 'sent'}])
|
|
self.assertEqual(len(invoice.l10n_mx_edi_invoice_document_ids), 1)
|
|
|
|
self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({})\
|
|
._create_payments()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
|
|
self.assertEqual(len(invoice.l10n_mx_edi_invoice_document_ids), 2)
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_invoice_cancellation_01(self):
|
|
# Create and send the invoice.
|
|
invoice = self._create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
'l10n_mx_edi_invoice_cancellation_reason': False,
|
|
'l10n_mx_edi_cfdi_origin': False,
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'state': 'posted',
|
|
}])
|
|
|
|
# Create a replacement invoice.
|
|
action_results = self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({}) \
|
|
.action_create_replacement_invoice()
|
|
new_invoice = self.env['account.move'].browse(action_results['res_id'])
|
|
|
|
invoice.invalidate_recordset(fnames=['need_cancel_request', 'l10n_mx_edi_cfdi_cancel_id'])
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
'l10n_mx_edi_invoice_cancellation_reason': False,
|
|
'l10n_mx_edi_cfdi_origin': False,
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': False,
|
|
'l10n_mx_edi_cfdi_cancel_id': new_invoice.id,
|
|
'state': 'posted',
|
|
}])
|
|
self.assertRecordValues(new_invoice, [{
|
|
'l10n_mx_edi_cfdi_state': False,
|
|
'l10n_mx_edi_invoice_cancellation_reason': False,
|
|
'l10n_mx_edi_cfdi_origin': f'04|{invoice.l10n_mx_edi_cfdi_uuid}',
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': False,
|
|
'state': 'draft',
|
|
}])
|
|
|
|
# Sign the replacement invoice, which automatically cancels the old invoice.
|
|
new_invoice.action_post()
|
|
with self.with_mocked_pac_sign_success(), self.with_mocked_pac_cancel_success():
|
|
new_invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
invoice.invalidate_recordset(fnames=['need_cancel_request', 'l10n_mx_edi_cfdi_cancel_id'])
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'cancel',
|
|
'l10n_mx_edi_invoice_cancellation_reason': '01',
|
|
'l10n_mx_edi_cfdi_origin': False,
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': True,
|
|
'l10n_mx_edi_cfdi_cancel_id': new_invoice.id,
|
|
'state': 'cancel',
|
|
}])
|
|
|
|
self.assertRecordValues(new_invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
'l10n_mx_edi_invoice_cancellation_reason': False,
|
|
'l10n_mx_edi_cfdi_origin': f'04|{invoice.l10n_mx_edi_cfdi_uuid}',
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'state': 'posted',
|
|
}])
|
|
|
|
# Cancel the replacement invoice.
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(new_invoice.button_request_cancel()['context'])\
|
|
.create({'cancellation_reason': '02'})\
|
|
.action_cancel_invoice()
|
|
|
|
self.assertRecordValues(new_invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'cancel',
|
|
'l10n_mx_edi_invoice_cancellation_reason': '02',
|
|
'l10n_mx_edi_cfdi_origin': f'04|{invoice.l10n_mx_edi_cfdi_uuid}',
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': True,
|
|
'state': 'cancel',
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_invoice_cancellation_02(self):
|
|
invoice = self._create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'sent',
|
|
'l10n_mx_edi_invoice_cancellation_reason': False,
|
|
'l10n_mx_edi_cfdi_origin': False,
|
|
'need_cancel_request': True,
|
|
'show_reset_to_draft_button': False,
|
|
'state': 'posted',
|
|
}])
|
|
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '02'})\
|
|
.action_cancel_invoice()
|
|
self.assertRecordValues(invoice, [{
|
|
'l10n_mx_edi_cfdi_state': 'cancel',
|
|
'l10n_mx_edi_invoice_cancellation_reason': '02',
|
|
'l10n_mx_edi_cfdi_origin': False,
|
|
'need_cancel_request': False,
|
|
'show_reset_to_draft_button': True,
|
|
'state': 'cancel',
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_global_invoice_cancellation_01(self):
|
|
# Create and send the invoice.
|
|
invoice1 = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
invoice2 = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
invoices = invoice1 + invoice2
|
|
|
|
# Sign as a global invoice.
|
|
with self.with_mocked_pac_sign_success():
|
|
invoices._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
sent_doc_values1 = {
|
|
'invoice_ids': invoices.ids,
|
|
'state': 'ginvoice_sent',
|
|
'attachment_uuid': invoices[0].l10n_mx_edi_cfdi_uuid,
|
|
'attachment_origin': False,
|
|
'cancellation_reason': False,
|
|
}
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids, [sent_doc_values1])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_cfdi_state': 'global_sent',
|
|
'l10n_mx_edi_cfdi_uuid': sent_doc_values1['attachment_uuid'],
|
|
}])
|
|
|
|
# Request a replacement for the global invoice, which automatically cancels the original global invoice.
|
|
gi_doc1 = invoices.l10n_mx_edi_invoice_document_ids
|
|
with self.with_mocked_pac_sign_success(), self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(gi_doc1.action_request_cancel()['context'])\
|
|
.create({})\
|
|
.action_create_replacement_invoice()
|
|
sent_doc_values2 = {
|
|
'invoice_ids': invoices.ids,
|
|
'state': 'ginvoice_sent',
|
|
'attachment_uuid': invoices[0].l10n_mx_edi_cfdi_uuid,
|
|
'attachment_origin': f"04|{sent_doc_values1['attachment_uuid']}",
|
|
'cancellation_reason': False,
|
|
}
|
|
cancel_doc_values = {
|
|
'invoice_ids': invoices.ids,
|
|
'state': 'ginvoice_cancel',
|
|
'attachment_uuid': sent_doc_values1['attachment_uuid'],
|
|
'attachment_origin': False,
|
|
'cancellation_reason': '01',
|
|
}
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values,
|
|
sent_doc_values2,
|
|
sent_doc_values1,
|
|
])
|
|
self.assertRecordValues(invoice1, [{
|
|
'l10n_mx_edi_cfdi_state': 'global_sent',
|
|
'l10n_mx_edi_cfdi_uuid': sent_doc_values2['attachment_uuid'],
|
|
}])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_global_invoice_then_replacement_then_cancel_replacement_then_cancel_gi(self):
|
|
invoice1 = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
invoice2 = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
invoices = invoice1 + invoice2
|
|
|
|
# Failed to send the global invoice.
|
|
with self.with_mocked_pac_sign_error():
|
|
invoices._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
sent_doc_values1 = {
|
|
'move_id': None,
|
|
'invoice_ids': invoices.ids,
|
|
'message': "turlututu",
|
|
'state': 'ginvoice_sent_failed',
|
|
'sat_state': False,
|
|
'attachment_uuid': False,
|
|
'attachment_origin': False,
|
|
'cancellation_reason': False,
|
|
'retry_button_needed': True,
|
|
'cancel_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids, [sent_doc_values1])
|
|
self.assertTrue(invoices.l10n_mx_edi_invoice_document_ids.attachment_id)
|
|
|
|
# Successfully create the global invoice.
|
|
with self.with_mocked_pac_sign_success():
|
|
invoices.l10n_mx_edi_invoice_document_ids.action_retry()
|
|
gi_attachment = invoices.l10n_mx_edi_cfdi_attachment_id
|
|
self.assertEqual(len(gi_attachment), 1)
|
|
sent_doc_values1.update({
|
|
'message': False,
|
|
'state': 'ginvoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'attachment_id': gi_attachment.id,
|
|
'attachment_uuid': invoices[0].l10n_mx_edi_cfdi_uuid,
|
|
'attachment_origin': False,
|
|
'retry_button_needed': False,
|
|
'cancel_button_needed': True,
|
|
})
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids, [sent_doc_values1])
|
|
self.assertRecordValues(invoices, [{'l10n_mx_edi_update_sat_needed': True}] * 2)
|
|
|
|
with self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
invoices[0].l10n_mx_edi_cfdi_try_sat()
|
|
sent_doc_values1['sat_state'] = 'valid'
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids, [sent_doc_values1])
|
|
|
|
# Request a replacement for the global invoice, automatically try to cancel the original invoice but fail.
|
|
gi_doc1 = invoices.l10n_mx_edi_invoice_document_ids
|
|
with self.with_mocked_pac_sign_success(), self.with_mocked_pac_cancel_error():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(gi_doc1.action_request_cancel()['context'])\
|
|
.create({})\
|
|
.action_create_replacement_invoice()
|
|
|
|
gi_doc2 = invoices.l10n_mx_edi_invoice_document_ids.sorted()[1]
|
|
self.assertTrue(gi_doc2.attachment_id)
|
|
sent_doc_values2 = {
|
|
'move_id': None,
|
|
'invoice_ids': invoices.ids,
|
|
'message': False,
|
|
'state': 'ginvoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'attachment_id': gi_doc2.attachment_id.id,
|
|
'attachment_uuid': invoices[0].l10n_mx_edi_cfdi_uuid,
|
|
'attachment_origin': f'04|{gi_doc1.attachment_uuid}',
|
|
'cancellation_reason': False,
|
|
'retry_button_needed': False,
|
|
'cancel_button_needed': True,
|
|
}
|
|
gi_doc3 = invoices.l10n_mx_edi_invoice_document_ids.sorted()[0]
|
|
self.assertTrue(gi_doc3.attachment_id)
|
|
sent_doc_values3 = {
|
|
'move_id': None,
|
|
'invoice_ids': invoices.ids,
|
|
'message': "turlututu",
|
|
'state': 'ginvoice_cancel_failed',
|
|
'sat_state': False,
|
|
'attachment_id': gi_doc3.attachment_id.id,
|
|
'attachment_uuid': gi_doc3.attachment_uuid, # In this case, UUID does not become UUID of the invoices
|
|
'attachment_origin': False,
|
|
'cancellation_reason': '01',
|
|
'retry_button_needed': True,
|
|
'cancel_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
sent_doc_values3,
|
|
sent_doc_values2,
|
|
sent_doc_values1,
|
|
])
|
|
|
|
# Failed to cancel the second global invoice with cancellation reason 02.
|
|
with self.with_mocked_pac_cancel_error():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(gi_doc2.action_request_cancel()['context'])\
|
|
.create({'cancellation_reason': '02'})\
|
|
.action_cancel_invoice()
|
|
cancel_doc_values1 = {
|
|
'move_id': None,
|
|
'invoice_ids': invoices.ids,
|
|
'message': "turlututu",
|
|
'state': 'ginvoice_cancel_failed',
|
|
'sat_state': False,
|
|
'attachment_id': gi_doc2.attachment_id.id,
|
|
'attachment_uuid': gi_doc2.attachment_uuid,
|
|
'attachment_origin': f'04|{gi_doc1.attachment_uuid}',
|
|
'cancellation_reason': '02',
|
|
'retry_button_needed': True,
|
|
'cancel_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values1,
|
|
sent_doc_values2,
|
|
sent_doc_values1,
|
|
])
|
|
|
|
# Retry the cancellation of the second global invoice.
|
|
with self.with_mocked_pac_cancel_success():
|
|
invoices.l10n_mx_edi_invoice_document_ids.sorted()[0].action_retry()
|
|
|
|
cancel_doc_values1.update({
|
|
'message': False,
|
|
'state': 'ginvoice_cancel',
|
|
'sat_state': 'not_defined',
|
|
'retry_button_needed': False,
|
|
'cancel_button_needed': False,
|
|
})
|
|
sent_doc_values2['sat_state'] = 'skip'
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values1,
|
|
sent_doc_values2,
|
|
sent_doc_values1,
|
|
])
|
|
|
|
# Successfully cancel the first global invoice.
|
|
with self.with_mocked_pac_cancel_success():
|
|
self.env['l10n_mx_edi.invoice.cancel']\
|
|
.with_context(gi_doc1.action_request_cancel()['context'])\
|
|
.create({})\
|
|
.action_cancel_invoice()
|
|
|
|
cancel_doc_values2 = {
|
|
'move_id': None,
|
|
'invoice_ids': invoices.ids,
|
|
'message': False,
|
|
'state': 'ginvoice_cancel',
|
|
'sat_state': 'not_defined',
|
|
'attachment_id': gi_doc1.attachment_id.id,
|
|
'attachment_uuid': gi_doc1.attachment_uuid,
|
|
'attachment_origin': False,
|
|
'cancellation_reason': '01',
|
|
'retry_button_needed': False,
|
|
'cancel_button_needed': False,
|
|
}
|
|
self.assertRecordValues(invoices.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
cancel_doc_values2,
|
|
cancel_doc_values1,
|
|
sent_doc_values2,
|
|
sent_doc_values1,
|
|
])
|
|
|
|
@freeze_time('2017-01-01')
|
|
def test_global_invoice_after_failing_send_invoice(self):
|
|
invoice = self._create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
|
|
with self.with_mocked_pac_sign_error():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertEqual(len(invoice.l10n_mx_edi_invoice_document_ids), 1)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
self.assertEqual(len(invoice.l10n_mx_edi_invoice_document_ids), 1)
|
|
|
|
def test_invoice_cancellation_then_replacement_in_foreign_currency(self):
|
|
date_1 = '2017-01-01'
|
|
date_2 = '2017-01-02'
|
|
usd = self.setup_other_currency('USD', rates=[(date_1, 1 / 17.187), (date_2, 1 / 17.0357)])
|
|
|
|
with freeze_time(date_1):
|
|
# create an invoice in USD when currency rate is 0.05
|
|
invoice = self._create_invoice(
|
|
invoice_date=date_1,
|
|
date=date_1,
|
|
currency_id=usd.id,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 100,
|
|
'quantity': 1,
|
|
'tax_ids': [Command.set(self.tax_16.ids)],
|
|
}),
|
|
],
|
|
)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
self.assertRecordValues(invoice.line_ids, [
|
|
{
|
|
'amount_currency': -100.0,
|
|
'balance': -1718.7,
|
|
'debit': 0.0,
|
|
'credit': 1718.7,
|
|
},
|
|
{
|
|
'amount_currency': -16.0,
|
|
'balance': -274.99,
|
|
'debit': 0.0,
|
|
'credit': 274.99,
|
|
},
|
|
{
|
|
'amount_currency': 116.0,
|
|
'balance': 1993.69,
|
|
'debit': 1993.69,
|
|
'credit': 0.0,
|
|
},
|
|
])
|
|
|
|
# create a replacement invoice when currency rate is 17.0357
|
|
with freeze_time(date_2):
|
|
action_results = self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({}) \
|
|
.action_create_replacement_invoice()
|
|
new_invoice = self.env['account.move'].browse(action_results['res_id'])
|
|
|
|
# the amounts of the replacement invoice should use the current rate
|
|
self.assertRecordValues(new_invoice.line_ids, [
|
|
{
|
|
'amount_currency': -100.0,
|
|
'balance': -1703.57,
|
|
'debit': 0.0,
|
|
'credit': 1703.57,
|
|
'tax_base_amount': 0.0,
|
|
},
|
|
{
|
|
'amount_currency': -16.0,
|
|
'balance': -272.57,
|
|
'debit': 0.0,
|
|
'credit': 272.57,
|
|
'tax_base_amount': -1703.57,
|
|
},
|
|
{
|
|
'amount_currency': 116.0,
|
|
'balance': 1976.14,
|
|
'debit': 1976.14,
|
|
'credit': 0.0,
|
|
'tax_base_amount': 0.0,
|
|
},
|
|
])
|
|
|
|
def test_cannot_delete_edi_document(self):
|
|
invoice = self._create_invoice(invoice_date_due='2017-01-01')
|
|
|
|
with freeze_time('2017-01-07'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
self.assertEqual(len(invoice.l10n_mx_edi_invoice_document_ids), 1)
|
|
|
|
with self.assertRaises(UserError, msg="You can't unlink an attachment being an EDI document sent to the government."):
|
|
invoice.l10n_mx_edi_invoice_document_ids.attachment_id.unlink()
|
|
|
|
@freeze_time('2017-01-01 10:00:00')
|
|
def test_global_invoice_year_month_format(self):
|
|
""" Test that 'meses' and 'anno' are correctly set since we ignore them in
|
|
other tests to allow to dynamically generate documents.
|
|
"""
|
|
invoice = self._create_invoice(
|
|
l10n_mx_edi_cfdi_to_public=True,
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
'price_unit': 1000.0,
|
|
'tax_ids': [],
|
|
}),
|
|
],
|
|
)
|
|
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_global_invoice_year_month_format')
|
|
|
|
def test_payment_workflow(self):
|
|
""" Test the payment workflow, here is the flow we test :
|
|
1) create and sign an invoice (PPD)
|
|
2) create a first payment and send it
|
|
3) unreconcile the first payment
|
|
4) create a second payment with cfdi_origin referring the first payment uuid and send it
|
|
5) cancel the first payment
|
|
6) cancel the second payment
|
|
"""
|
|
# Create invoice
|
|
invoice = self._create_invoice()
|
|
|
|
# Sign.
|
|
with freeze_time('2017-01-07'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-07 00:00:00'),
|
|
'state': 'invoice_sent',
|
|
'cancellation_reason': False,
|
|
'attachment_origin': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
|
|
# Create new payment
|
|
payment1 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 1160.0,
|
|
})\
|
|
._create_payments()
|
|
|
|
with freeze_time('2017-06-02'), self.with_mocked_pac_sign_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
|
|
payment1_doc_values = {
|
|
'move_id': payment1.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-02 00:00:00'),
|
|
'state': 'payment_sent',
|
|
'cancellation_reason': False,
|
|
'attachment_origin': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment1_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Unreconcile the first payment
|
|
payment1.move_id.line_ids.remove_move_reconcile()
|
|
|
|
# Create a new payment with a cfdi origin containing the uuid of the first payment, which automatically cancels the first payment
|
|
payment2 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 1160.0,
|
|
'l10n_mx_edi_cfdi_origin': f'04|{payment1.l10n_mx_edi_cfdi_uuid}',
|
|
})\
|
|
._create_payments()
|
|
|
|
with freeze_time('2017-06-02'), self.with_mocked_pac_sign_success(), self.with_mocked_pac_cancel_success():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
|
|
payment2_doc_values = {
|
|
'move_id': payment2.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-02 00:00:00'),
|
|
'state': 'payment_sent',
|
|
'cancellation_reason': False,
|
|
'attachment_origin': f'04|{payment1.l10n_mx_edi_cfdi_uuid}',
|
|
}
|
|
payment1_cancel_doc_values = {
|
|
'move_id': payment1.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-06-02 00:00:00'),
|
|
'state': 'payment_cancel',
|
|
'cancellation_reason': '01',
|
|
'attachment_origin': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment1_cancel_doc_values,
|
|
payment2_doc_values,
|
|
payment1_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
# Cancel the second payment, it should apply the cancellation reason '2'
|
|
with freeze_time('2017-08-06'), self.with_mocked_pac_cancel_success():
|
|
payment2.l10n_mx_edi_payment_document_ids.action_cancel()
|
|
|
|
payment2_cancel_doc_values = {
|
|
'move_id': payment2.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-08-06 00:00:00'),
|
|
'state': 'payment_cancel',
|
|
'cancellation_reason': '02',
|
|
'attachment_origin': f'04|{payment1.l10n_mx_edi_cfdi_uuid}',
|
|
}
|
|
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment2_cancel_doc_values,
|
|
payment1_cancel_doc_values,
|
|
payment2_doc_values,
|
|
payment1_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
def test_access_invoice_form_with_second_user(self):
|
|
def create_invoice(**kwargs):
|
|
return self._create_invoice(
|
|
invoice_line_ids=[
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
})
|
|
],
|
|
**kwargs
|
|
)
|
|
|
|
with self.mx_external_setup(self.frozen_today):
|
|
second_partner = self.env['res.partner'].create({
|
|
'name': 'second user',
|
|
})
|
|
second_user = self.env['res.users'].create({
|
|
'login': 'seconduser',
|
|
'partner_id': second_partner.id,
|
|
'group_ids': [self.env.ref('account.group_account_manager').id]
|
|
})
|
|
|
|
invoice = create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
invoice_for_global = create_invoice(l10n_mx_edi_cfdi_to_public=True)
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_for_global._l10n_mx_edi_cfdi_global_invoice_try_send()
|
|
|
|
self.env.invalidate_all() # remove values from cache to check access rights
|
|
request_list = ['activities', 'attachments', 'followers', 'scheduledMessages', 'suggestedRecipients']
|
|
store = Store()
|
|
store.add(invoice.with_user(second_user), [], as_thread=True, request_list=request_list)
|
|
store.add(invoice_for_global.with_user(second_user), [], as_thread=True, request_list=request_list)
|
|
checksums = [attachment_data['checksum'] for attachment_data in store.data['ir.attachment'].values()]
|
|
self.assertTrue(invoice.l10n_mx_edi_cfdi_attachment_id.checksum in checksums)
|
|
self.assertTrue(invoice_for_global.l10n_mx_edi_cfdi_attachment_id.checksum in checksums)
|
|
|
|
def test_cancel_payment(self):
|
|
""" Test the payment cancelation workflow, here is the flow we test :
|
|
1) create and sign an invoice
|
|
2) create a payment and send it - it needs to fail in order to reset it to draft without requesting cancelation
|
|
3) reset the payment to draft
|
|
4) cancel the payment
|
|
"""
|
|
# Create invoice
|
|
invoice = self._create_invoice()
|
|
|
|
# Sign.
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
sent_doc_values = {
|
|
'move_id': invoice.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-01 00:00:00'),
|
|
'state': 'invoice_sent',
|
|
'cancellation_reason': False,
|
|
'attachment_origin': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids, [sent_doc_values])
|
|
|
|
# Create new payment with `Por Definir` payment method in order to fail the cfdi
|
|
payment1 = self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-01-01',
|
|
'amount': 1160.0,
|
|
'l10n_mx_edi_payment_method_id': self.env.ref('l10n_mx_edi.payment_method_otros').id,
|
|
})\
|
|
._create_payments()
|
|
|
|
with freeze_time('2017-01-01'), self.with_mocked_pac_sign_error():
|
|
invoice.l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
|
|
payment1_doc_values = {
|
|
'move_id': payment1.move_id.id,
|
|
'datetime': fields.Datetime.from_string('2017-01-01 00:00:00'),
|
|
'state': 'payment_sent_failed',
|
|
'cancellation_reason': False,
|
|
'attachment_origin': False,
|
|
}
|
|
self.assertRecordValues(invoice.l10n_mx_edi_invoice_document_ids.sorted(), [
|
|
payment1_doc_values,
|
|
sent_doc_values,
|
|
])
|
|
|
|
payment1.action_draft()
|
|
payment1.action_cancel()
|
|
|
|
self.assertFalse(payment1.move_id)
|
|
self.assertEqual(payment1.state, 'canceled')
|
|
|
|
def test_import_bill_write_l10n_mx_edi_cfdi_sat_state(self):
|
|
"""
|
|
Test that when importing a cfdi bill, the field l10n_mx_edi_cfdi_sat_state is correctly computed
|
|
"""
|
|
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,
|
|
)
|
|
self.assertEqual(new_bill.l10n_mx_edi_cfdi_sat_state, 'not_defined')
|
|
|
|
def test_note_and_section_copied_new_invoice(self):
|
|
""" Test that notes and sections are carry over when the new invoice is created after a cfdi cancel"""
|
|
with self.mx_external_setup(self.frozen_today):
|
|
invoice = self._create_invoice(invoice_line_ids=[
|
|
Command.create({
|
|
'display_type': 'line_section',
|
|
'name': 'section',
|
|
}),
|
|
Command.create({
|
|
'display_type': 'line_note',
|
|
'name': 'note',
|
|
}),
|
|
Command.create({
|
|
'product_id': self.product.id,
|
|
}),
|
|
])
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
with self.with_mocked_pac_cancel_success():
|
|
action_results = self.env['l10n_mx_edi.invoice.cancel'] \
|
|
.with_context(invoice.button_request_cancel()['context']) \
|
|
.create({'cancellation_reason': '01'}) \
|
|
.action_create_replacement_invoice()
|
|
|
|
new_invoice = self.env['account.move'].browse(action_results['res_id'])
|
|
self.assertEqual(new_invoice.line_ids[0].name, 'section')
|
|
self.assertEqual(new_invoice.line_ids[1].name, 'note')
|
|
|
|
def test_cron_update_sat_state_write_date(self):
|
|
""" Test that the sat_state write_date is updated on every cron run,
|
|
retriggering within 4h skips recently checked documents, and vendor
|
|
bills older than 7 days are excluded.
|
|
"""
|
|
|
|
@contextmanager
|
|
def freeze_now(ref_datetime):
|
|
with freeze_time(ref_datetime):
|
|
self.patch(self.env.cr, 'now', lambda: ref_datetime)
|
|
yield
|
|
|
|
ref_datetime = fields.Datetime.from_string('2017-01-01 00:00:00')
|
|
|
|
ref_datetime_minus_2 = ref_datetime - timedelta(days=2)
|
|
with freeze_now(ref_datetime_minus_2):
|
|
invoice_2 = self._create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_2._l10n_mx_edi_cfdi_invoice_try_send()
|
|
doc_2 = invoice_2.l10n_mx_edi_invoice_document_ids
|
|
doc_2.invalidate_recordset()
|
|
|
|
ref_datetime_minus_1 = ref_datetime - timedelta(days=1)
|
|
with freeze_now(ref_datetime_minus_1):
|
|
invoice_1 = self._create_invoice()
|
|
with self.with_mocked_pac_sign_success():
|
|
invoice_1._l10n_mx_edi_cfdi_invoice_try_send()
|
|
doc_1 = invoice_1.l10n_mx_edi_invoice_document_ids
|
|
doc_1.invalidate_recordset()
|
|
|
|
self.assertRecordValues(doc_1 + doc_2, [
|
|
{'state': 'invoice_sent', 'write_date': ref_datetime_minus_1, 'sat_state': 'not_defined'},
|
|
{'state': 'invoice_sent', 'write_date': ref_datetime_minus_2, 'sat_state': 'not_defined'},
|
|
])
|
|
|
|
# Step 1: Run batch_size=1 where sat_state doesn't change (returns 'not_defined').
|
|
# doc_2 (oldest create_date) is processed. write_date must update even if sat_state is unchanged.
|
|
ref_datetime_plus_1 = ref_datetime + timedelta(days=1)
|
|
with freeze_now(ref_datetime_plus_1), self.with_mocked_sat_call(lambda _x: 'not_defined'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(batch_size=1, extra_domain=[('id', 'in', (doc_1 + doc_2).ids)])
|
|
doc_1.invalidate_recordset()
|
|
doc_2.invalidate_recordset()
|
|
self.assertRecordValues(doc_1 + doc_2, [
|
|
{'state': 'invoice_sent', 'write_date': ref_datetime_minus_1, 'sat_state': 'not_defined'},
|
|
{'state': 'invoice_sent', 'write_date': ref_datetime_plus_1, 'sat_state': 'not_defined'},
|
|
])
|
|
|
|
cron = self.env.ref('l10n_mx_edi.ir_cron_update_pac_status_invoice')
|
|
cron_trigger = self.env['ir.cron.trigger'].search([
|
|
('cron_id', '=', cron.id)
|
|
])
|
|
self.assertEqual(
|
|
cron_trigger.call_at,
|
|
ref_datetime_plus_1,
|
|
"The cron hasn't been retriggered for the remaining invoice !"
|
|
)
|
|
|
|
# Step 2: Immediate retrigger (1 minute later, < 4 hours).
|
|
# doc_2 was updated 1m ago, so it must be skipped by write_date <= now - 4h filter.
|
|
# doc_1 is processed instead.
|
|
ref_datetime_plus_1_1m = ref_datetime_plus_1 + timedelta(minutes=1)
|
|
with freeze_now(ref_datetime_plus_1_1m), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(batch_size=1, extra_domain=[('id', 'in', (doc_1 + doc_2).ids)])
|
|
doc_1.invalidate_recordset()
|
|
doc_2.invalidate_recordset()
|
|
self.assertRecordValues(doc_1 + doc_2, [
|
|
{'state': 'invoice_sent', 'write_date': ref_datetime_plus_1_1m, 'sat_state': 'valid'},
|
|
{'state': 'invoice_sent', 'write_date': ref_datetime_plus_1, 'sat_state': 'not_defined'},
|
|
])
|
|
|
|
# Step 3: Retrigger again 2 minutes later (< 4 hours).
|
|
# doc_2 is skipped (< 4h since write_date), doc_1 is skipped (sat_state is 'valid').
|
|
# No documents are fetched, so no cron retrigger occurs.
|
|
cron_trigger.unlink()
|
|
ref_datetime_plus_1_2m = ref_datetime_plus_1 + timedelta(minutes=2)
|
|
with freeze_now(ref_datetime_plus_1_2m), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(batch_size=1, extra_domain=[('id', 'in', (doc_1 + doc_2).ids)])
|
|
new_cron_trigger = self.env['ir.cron.trigger'].search([('cron_id', '=', cron.id)])
|
|
self.assertFalse(new_cron_trigger, "The cron should not retrigger when no remaining documents match domain")
|
|
|
|
# Step 4: After > 4 hours (5 hours later), doc_2's write_date is > 4h ago, so it becomes eligible again.
|
|
ref_datetime_plus_1_5h = ref_datetime_plus_1 + timedelta(hours=5)
|
|
with freeze_now(ref_datetime_plus_1_5h), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(batch_size=1, extra_domain=[('id', 'in', (doc_1 + doc_2).ids)])
|
|
doc_2.invalidate_recordset()
|
|
self.assertEqual(doc_2.sat_state, 'valid')
|
|
self.assertEqual(doc_2.write_date, ref_datetime_plus_1_5h)
|
|
|
|
# Step 5: Test vendor bill (invoice_received) 7-day cutoff vs customer invoice 60-day cutoff
|
|
ref_datetime_minus_10 = ref_datetime - timedelta(days=10)
|
|
with freeze_now(ref_datetime_minus_10):
|
|
vendor_bill_doc = self.env['l10n_mx_edi.document'].create({
|
|
'state': 'invoice_received',
|
|
'sat_state': 'not_defined',
|
|
'datetime': ref_datetime_minus_10,
|
|
'move_id': self._create_invoice().id,
|
|
})
|
|
customer_inv_doc = self.env['l10n_mx_edi.document'].create({
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'datetime': ref_datetime_minus_10,
|
|
'move_id': self._create_invoice().id,
|
|
})
|
|
|
|
ref_datetime_check = ref_datetime + timedelta(days=10)
|
|
with freeze_now(ref_datetime_check), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(
|
|
extra_domain=[('id', 'in', (vendor_bill_doc + customer_inv_doc).ids)]
|
|
)
|
|
vendor_bill_doc.invalidate_recordset()
|
|
customer_inv_doc.invalidate_recordset()
|
|
|
|
# Vendor bill created 20 days prior to check date (> 7 days) is NOT updated
|
|
self.assertEqual(vendor_bill_doc.sat_state, 'not_defined')
|
|
# Customer invoice created 20 days prior to check date (<= 60 days) IS updated
|
|
self.assertEqual(customer_inv_doc.sat_state, 'valid')
|
|
|
|
# Step 6: Test vendor bill write_date re-check threshold (12 hours) vs customer invoice (4 hours)
|
|
ref_datetime_vb_create = ref_datetime - timedelta(days=2)
|
|
with freeze_now(ref_datetime_vb_create):
|
|
vendor_bill_recent = self.env['l10n_mx_edi.document'].create({
|
|
'state': 'invoice_received',
|
|
'sat_state': 'not_defined',
|
|
'datetime': ref_datetime_vb_create,
|
|
'move_id': self._create_invoice().id,
|
|
})
|
|
customer_inv_recent = self.env['l10n_mx_edi.document'].create({
|
|
'state': 'invoice_sent',
|
|
'sat_state': 'not_defined',
|
|
'datetime': ref_datetime_vb_create,
|
|
'move_id': self._create_invoice().id,
|
|
})
|
|
|
|
# Initial check at ref_datetime: both are processed and write_date becomes ref_datetime
|
|
ref_datetime_vb_run = ref_datetime
|
|
with freeze_now(ref_datetime_vb_run), self.with_mocked_sat_call(lambda _x: 'not_defined'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(
|
|
extra_domain=[('id', 'in', (vendor_bill_recent + customer_inv_recent).ids)]
|
|
)
|
|
vendor_bill_recent.invalidate_recordset()
|
|
customer_inv_recent.invalidate_recordset()
|
|
self.assertEqual(vendor_bill_recent.write_date, ref_datetime_vb_run)
|
|
self.assertEqual(customer_inv_recent.write_date, ref_datetime_vb_run)
|
|
|
|
# 5 hours later: customer invoice IS re-checked (> 4h), vendor bill IS NOT re-checked (< 12h)
|
|
ref_datetime_5h_later = ref_datetime_vb_run + timedelta(hours=5)
|
|
with freeze_now(ref_datetime_5h_later), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(
|
|
extra_domain=[('id', 'in', (vendor_bill_recent + customer_inv_recent).ids)]
|
|
)
|
|
vendor_bill_recent.invalidate_recordset()
|
|
customer_inv_recent.invalidate_recordset()
|
|
|
|
self.assertEqual(customer_inv_recent.sat_state, 'valid')
|
|
self.assertEqual(vendor_bill_recent.sat_state, 'not_defined')
|
|
|
|
# 13 hours later (> 12h): vendor bill IS now re-checked
|
|
ref_datetime_13h_later = ref_datetime_vb_run + timedelta(hours=13)
|
|
with freeze_now(ref_datetime_13h_later), self.with_mocked_sat_call(lambda _x: 'valid'):
|
|
self.env['l10n_mx_edi.document']._fetch_and_update_sat_status(
|
|
extra_domain=[('id', 'in', (vendor_bill_recent + customer_inv_recent).ids)]
|
|
)
|
|
vendor_bill_recent.invalidate_recordset()
|
|
|
|
self.assertEqual(vendor_bill_recent.sat_state, 'valid')
|
|
|
|
def test_payment_cfdi_with_leaked_default_type_context(self):
|
|
"""Test CFDI payment document creation with leaked context (default_type) from account.journal
|
|
Requires documents_account to be installed (skipped otherwise).
|
|
"""
|
|
if self.env['ir.module.module']._get('documents_account').state != 'installed':
|
|
self.skipTest("documents_account is not installed")
|
|
|
|
# Journal-folder sync is needed so the CFDI attachment creation triggers document creation
|
|
bank_journal = self.company_data['default_journal_bank']
|
|
folder = self.env['documents.document'].sudo().create({'name': 'Test Folder', 'type': 'folder'})
|
|
if setting := self.env['documents.account.folder.setting'].sudo().search([
|
|
('journal_id', '=', bank_journal.id),
|
|
('company_id', '=', self.env.company.id)], limit=1):
|
|
setting.folder_id = folder
|
|
else:
|
|
self.env['documents.account.folder.setting'].sudo().create({
|
|
'folder_id': folder.id,
|
|
'journal_id': bank_journal.id
|
|
})
|
|
|
|
invoice = self._create_invoice()
|
|
with freeze_time('2017-01-07'), self.with_mocked_pac_sign_success():
|
|
invoice._l10n_mx_edi_cfdi_invoice_try_send()
|
|
|
|
self.env['account.payment.register']\
|
|
.with_context(active_model='account.move', active_ids=invoice.ids)\
|
|
.create({
|
|
'payment_date': '2017-06-01',
|
|
'amount': 1160.0,
|
|
'journal_id': bank_journal.id,
|
|
})\
|
|
._create_payments()
|
|
|
|
# Sign the payment CFDI with `default_type='sale'` context
|
|
with freeze_time('2017-06-02'), self.with_mocked_pac_sign_success():
|
|
invoice.with_context(default_type='sale').l10n_mx_edi_cfdi_invoice_try_update_payments()
|
|
|
|
payment_doc = invoice.l10n_mx_edi_invoice_document_ids.filtered(lambda d: d.state == 'payment_sent')
|
|
self.assertTrue(payment_doc, "Payment CFDI document should have been created")
|
|
|
|
cfdi_attachment = payment_doc.attachment_id
|
|
self.assertTrue(cfdi_attachment, "Payment CFDI attachment should exist")
|
|
doc = self.env['documents.document'].search([('attachment_id', '=', cfdi_attachment.id)])
|
|
self.assertTrue(doc, "A documents.document should have been created for the CFDI attachment")
|
|
self.assertEqual(
|
|
doc.type, 'binary',
|
|
"Document type should be 'binary', not the leaked 'sale' value from the accounting context",
|
|
)
|