[IMP] account_edi: merge account_edi_extended
TASK ID: 2457873 closes odoo/odoo#65745 Related: odoo/enterprise#16226 Related: odoo/upgrade#2144 Signed-off-by: Laurent Smet <smetl@users.noreply.github.com>
This commit is contained in:
@@ -2,12 +2,13 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo.addons.account_edi_extended.models.account_edi_document import DEFAULT_BLOCKING_LEVEL
|
||||
from psycopg2 import OperationalError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_BLOCKING_LEVEL = 'error'
|
||||
|
||||
|
||||
class AccountEdiDocument(models.Model):
|
||||
_name = 'account.edi.document'
|
||||
@@ -19,6 +20,12 @@ class AccountEdiDocument(models.Model):
|
||||
attachment_id = fields.Many2one('ir.attachment', help='The file generated by edi_format_id when the invoice is posted (and this document is processed).')
|
||||
state = fields.Selection([('to_send', 'To Send'), ('sent', 'Sent'), ('to_cancel', 'To Cancel'), ('cancelled', 'Cancelled')])
|
||||
error = fields.Html(help='The text of the last error that happened during Electronic Invoice operation.')
|
||||
blocking_level = fields.Selection(
|
||||
selection=[('info', 'Info'), ('warning', 'Warning'), ('error', 'Error')],
|
||||
help="Blocks the document current operation depending on the error severity :\n"
|
||||
" * Info: the document is not blocked and everything is working as it should.\n"
|
||||
" * Warning : there is an error that doesn't prevent the current Electronic Invoicing operation to succeed.\n"
|
||||
" * Error : there is an error that blocks the current Electronic Invoicing operation.")
|
||||
|
||||
# == Not stored fields ==
|
||||
name = fields.Char(related='attachment_id.name')
|
||||
@@ -32,14 +39,6 @@ class AccountEdiDocument(models.Model):
|
||||
),
|
||||
]
|
||||
|
||||
def write(self, vals):
|
||||
''' If account_edi_extended is not installed, a default behaviour is used instead.
|
||||
'''
|
||||
if 'blocking_level' in vals and 'blocking_level' not in self.env['account.edi.document']._fields:
|
||||
vals.pop('blocking_level')
|
||||
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_jobs(self):
|
||||
"""Creates a list of jobs to be performed by '_process_job' for the documents in self.
|
||||
Each document represent a job, BUT if multiple documents have the same state, edi_format_id,
|
||||
@@ -53,10 +52,7 @@ class AccountEdiDocument(models.Model):
|
||||
|
||||
# Classify jobs by (edi_format, edi_doc.state, doc_type, move.company_id, custom_key)
|
||||
to_process = {}
|
||||
if 'blocking_level' in self.env['account.edi.document']._fields:
|
||||
documents = self.filtered(lambda d: d.state in ('to_send', 'to_cancel') and d.blocking_level != 'error')
|
||||
else:
|
||||
documents = self.filtered(lambda d: d.state in ('to_send', 'to_cancel'))
|
||||
documents = self.filtered(lambda d: d.state in ('to_send', 'to_cancel') and d.blocking_level != 'error')
|
||||
for edi_doc in documents:
|
||||
move = edi_doc.move_id
|
||||
edi_format = edi_doc.edi_format_id
|
||||
@@ -88,36 +84,6 @@ class AccountEdiDocument(models.Model):
|
||||
target.append((batch, doc_type))
|
||||
return invoices + payments
|
||||
|
||||
@api.model
|
||||
def _convert_to_old_jobs_format(self, jobs):
|
||||
""" See '_prepare_jobs' :
|
||||
Old format : ((edi_format, state, doc_type, company_id), documents)
|
||||
Since edi_format, state and company_id can be deduced from documents, this is redundant and more prone to unexpected behaviours.
|
||||
New format : (doc_type, documents).
|
||||
|
||||
However, for backward compatibility of 'process_jobs', we need a way to convert back to the old format.
|
||||
"""
|
||||
return [(
|
||||
(documents.edi_format_id, documents[0].state, doc_type, documents.move_id.company_id),
|
||||
documents
|
||||
) for documents, doc_type in jobs]
|
||||
|
||||
@api.model
|
||||
def _process_jobs(self, to_process):
|
||||
""" Deprecated, use _process_job instead.
|
||||
|
||||
:param to_process: A list of tuples (key, documents)
|
||||
* key: A tuple (edi_format_id, state, doc_type, company_id)
|
||||
** edi_format_id: The format to perform the operation with
|
||||
** state: The state of the documents of this job
|
||||
** doc_type: Are the moves of this job invoice or payments ?
|
||||
** company_id: The company the moves belong to
|
||||
* documents: The documents related to this job. If edi_format_id does not support batch, length is one
|
||||
"""
|
||||
for key, documents in to_process:
|
||||
dummy, dummy, doc_type, dummy = key
|
||||
self._process_job(documents, doc_type)
|
||||
|
||||
@api.model
|
||||
def _process_job(self, documents, doc_type):
|
||||
"""Post or cancel move_id (invoice or payment) by calling the related methods on edi_format_id.
|
||||
@@ -220,7 +186,8 @@ class AccountEdiDocument(models.Model):
|
||||
""" Post and cancel all the documents that don't need a web service.
|
||||
"""
|
||||
jobs = self.filtered(lambda d: not d.edi_format_id._needs_web_services())._prepare_jobs()
|
||||
self._process_jobs(self._convert_to_old_jobs_format(jobs))
|
||||
for documents, doc_type in jobs:
|
||||
self._process_job(documents, doc_type)
|
||||
|
||||
def _process_documents_web_services(self, job_count=None, with_commit=True):
|
||||
""" Post and cancel all the documents that need a web service. This is called by CRON.
|
||||
|
||||
@@ -98,12 +98,15 @@ class AccountEdiFormat(models.Model):
|
||||
# TO OVERRIDE
|
||||
return False
|
||||
|
||||
def _support_batching(self, move=None, state=None, company=None):
|
||||
def _support_batching(self, move, state, company):
|
||||
""" Indicate if we can send multiple documents in the same time to the web services.
|
||||
If True, the _post_%s_edi methods will get multiple documents in the same time.
|
||||
Otherwise, these methods will be called with only one record at a time.
|
||||
|
||||
:returns: True if batching is supported, False otherwise.
|
||||
:param move: The move that we are trying to batch.
|
||||
:param state: The EDI state of the move.
|
||||
:param company: The company with which we are sending the EDI.
|
||||
:returns: True if batching is supported, False otherwise.
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
return False
|
||||
@@ -112,8 +115,11 @@ class AccountEdiFormat(models.Model):
|
||||
""" Returns a tuple that will be used as key to partitionnate the invoices/payments when creating batches
|
||||
with multiple invoices/payments.
|
||||
The type of move (invoice or payment), its company_id, its edi state and the edi_format are used by default, if
|
||||
no further partition is needed for this format, this method should return ().
|
||||
no further partition is needed for this format, this method should return (). It's not necessary to repeat those
|
||||
fields in the custom key.
|
||||
|
||||
:param move: The move to batch.
|
||||
:param state: The EDI state of the move.
|
||||
:returns: The key to be used when partitionning the batches.
|
||||
"""
|
||||
move.ensure_one()
|
||||
@@ -122,7 +128,7 @@ class AccountEdiFormat(models.Model):
|
||||
def _check_move_configuration(self, move):
|
||||
""" Checks the move and relevant records for potential error (missing data, etc).
|
||||
|
||||
:param invoice: The move to check.
|
||||
:param move: The move to check.
|
||||
:returns: A list of error messages.
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
@@ -136,7 +142,7 @@ class AccountEdiFormat(models.Model):
|
||||
:returns: A dictionary with the invoice as key and as value, another dictionary:
|
||||
* attachment: The attachment representing the invoice in this edi_format if the edi was successfully posted.
|
||||
* error: An error if the edi was not successfully posted.
|
||||
* blocking_level: (optional, requires account_edi_extended) How bad is the error (how should the edi flow be blocked ?)
|
||||
* blocking_level: (optional) How bad is the error (how should the edi flow be blocked ?)
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
self.ensure_one()
|
||||
@@ -150,7 +156,7 @@ class AccountEdiFormat(models.Model):
|
||||
:returns: A dictionary with the invoice as key and as value, another dictionary:
|
||||
* success: True if the invoice was successfully cancelled.
|
||||
* error: An error if the edi was not successfully cancelled.
|
||||
* blocking_level: (optional, requires account_edi_extended) How bad is the error (how should the edi flow be blocked ?)
|
||||
* blocking_level: (optional) How bad is the error (how should the edi flow be blocked ?)
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
self.ensure_one()
|
||||
@@ -164,7 +170,7 @@ class AccountEdiFormat(models.Model):
|
||||
:returns: A dictionary with the payment as key and as value, another dictionary:
|
||||
* attachment: The attachment representing the payment in this edi_format if the edi was successfully posted.
|
||||
* error: An error if the edi was not successfully posted.
|
||||
* blocking_level: (optional, requires account_edi_extended) How bad is the error (how should the edi flow be blocked ?)
|
||||
* blocking_level: (optional) How bad is the error (how should the edi flow be blocked ?)
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
self.ensure_one()
|
||||
@@ -178,7 +184,7 @@ class AccountEdiFormat(models.Model):
|
||||
:returns: A dictionary with the payment as key and as value, another dictionary:
|
||||
* success: True if the payment was successfully cancelled.
|
||||
* error: An error if the edi was not successfully cancelled.
|
||||
* blocking_level: (optional, requires account_edi_extended) How bad is the error (how should the edi flow be blocked ?)
|
||||
* blocking_level: (optional) How bad is the error (how should the edi flow be blocked ?)
|
||||
"""
|
||||
# TO OVERRIDE
|
||||
self.ensure_one()
|
||||
@@ -376,10 +382,6 @@ class AccountEdiFormat(models.Model):
|
||||
except Exception as e:
|
||||
_logger.exception("Error importing attachment \"%s\" as invoice with format \"%s\"", file_data['filename'], edi_format.name, str(e))
|
||||
if res:
|
||||
if 'extract_state' in res:
|
||||
# Bypass the OCR to prevent overwriting data when an EDI was succesfully imported.
|
||||
# TODO : remove when we integrate the OCR to the EDI flow.
|
||||
res.write({'extract_state': 'done'})
|
||||
return res
|
||||
return self.env['account.move']
|
||||
|
||||
@@ -401,10 +403,6 @@ class AccountEdiFormat(models.Model):
|
||||
except Exception as e:
|
||||
_logger.exception("Error importing attachment \"%s\" as invoice with format \"%s\"", file_data['filename'], edi_format.name, str(e))
|
||||
if res:
|
||||
if 'extract_state' in res:
|
||||
# Bypass the OCR to prevent overwriting data when an EDI was succesfully imported.
|
||||
# TODO : remove when we integrate the OCR to the EDI flow.
|
||||
res.write({'extract_state': 'done'})
|
||||
return res
|
||||
return self.env['account.move']
|
||||
|
||||
|
||||
@@ -20,11 +20,18 @@ class AccountMove(models.Model):
|
||||
edi_error_count = fields.Integer(
|
||||
compute='_compute_edi_error_count',
|
||||
help='How many EDIs are in error for this move ?')
|
||||
edi_blocking_level = fields.Selection(
|
||||
selection=[('info', 'Info'), ('warning', 'Warning'), ('error', 'Error')],
|
||||
compute='_compute_edi_error_message')
|
||||
edi_error_message = fields.Html(
|
||||
compute='_compute_edi_error_message')
|
||||
edi_web_services_to_process = fields.Text(
|
||||
compute='_compute_edi_web_services_to_process',
|
||||
help="Technical field to display the documents that will be processed by the CRON")
|
||||
edi_show_cancel_button = fields.Boolean(
|
||||
compute='_compute_edi_show_cancel_button')
|
||||
edi_show_abandon_cancel_button = fields.Boolean(
|
||||
compute='_compute_edi_show_abandon_cancel_button')
|
||||
|
||||
@api.depends('edi_document_ids.state')
|
||||
def _compute_edi_state(self):
|
||||
@@ -46,14 +53,37 @@ class AccountMove(models.Model):
|
||||
for move in self:
|
||||
move.edi_error_count = len(move.edi_document_ids.filtered(lambda d: d.error))
|
||||
|
||||
@api.depends('edi_error_count', 'edi_document_ids.error', 'edi_document_ids.blocking_level')
|
||||
def _compute_edi_error_message(self):
|
||||
for move in self:
|
||||
if move.edi_error_count == 0:
|
||||
move.edi_error_message = None
|
||||
move.edi_blocking_level = None
|
||||
elif move.edi_error_count == 1:
|
||||
error_doc = move.edi_document_ids.filtered(lambda d: d.error)
|
||||
move.edi_error_message = error_doc.error
|
||||
move.edi_blocking_level = error_doc.blocking_level
|
||||
else:
|
||||
error_levels = set([doc.blocking_level for doc in move.edi_document_ids])
|
||||
if 'error' in error_levels:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing error(s)")
|
||||
move.edi_blocking_level = 'error'
|
||||
elif 'warning' in error_levels:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing warning(s)")
|
||||
move.edi_blocking_level = 'warning'
|
||||
else:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing info(s)")
|
||||
move.edi_blocking_level = 'info'
|
||||
|
||||
@api.depends(
|
||||
'edi_document_ids',
|
||||
'edi_document_ids.state',
|
||||
'edi_document_ids.blocking_level',
|
||||
'edi_document_ids.edi_format_id',
|
||||
'edi_document_ids.edi_format_id.name')
|
||||
def _compute_edi_web_services_to_process(self):
|
||||
for move in self:
|
||||
to_process = move.edi_document_ids.filtered(lambda d: d.state in ['to_send', 'to_cancel'])
|
||||
to_process = move.edi_document_ids.filtered(lambda d: d.state in ['to_send', 'to_cancel'] and d.blocking_level != 'error')
|
||||
format_web_services = to_process.edi_format_id.filtered(lambda f: f._needs_web_services())
|
||||
move.edi_web_services_to_process = ', '.join(f.name for f in format_web_services)
|
||||
|
||||
@@ -89,6 +119,18 @@ class AccountMove(models.Model):
|
||||
and doc.edi_format_id._is_required_for_invoice(move)
|
||||
for doc in move.edi_document_ids])
|
||||
|
||||
@api.depends(
|
||||
'state',
|
||||
'edi_document_ids.state',
|
||||
'edi_document_ids.attachment_id')
|
||||
def _compute_edi_show_abandon_cancel_button(self):
|
||||
for move in self:
|
||||
move.edi_show_abandon_cancel_button = any(doc.edi_format_id._needs_web_services()
|
||||
and doc.state == 'to_cancel'
|
||||
and move.is_invoice(include_receipts=True)
|
||||
and doc.edi_format_id._is_required_for_invoice(move)
|
||||
for doc in move.edi_document_ids)
|
||||
|
||||
####################################################
|
||||
# Export Electronic Document
|
||||
####################################################
|
||||
@@ -206,6 +248,23 @@ class AccountMove(models.Model):
|
||||
|
||||
to_cancel_documents.write({'state': 'to_cancel', 'error': False, 'blocking_level': False})
|
||||
|
||||
def button_abandon_cancel_posted_posted_moves(self):
|
||||
'''Cancel the request for cancellation of the EDI.
|
||||
'''
|
||||
documents = self.env['account.edi.document']
|
||||
for move in self:
|
||||
is_move_marked = False
|
||||
for doc in move.edi_document_ids:
|
||||
if doc.state == 'to_cancel' \
|
||||
and move.is_invoice(include_receipts=True) \
|
||||
and doc.edi_format_id._is_required_for_invoice(move):
|
||||
documents |= doc
|
||||
is_move_marked = True
|
||||
if is_move_marked:
|
||||
move.message_post(body=_("A request for cancellation of the EDI has been called off."))
|
||||
|
||||
documents.write({'state': 'sent'})
|
||||
|
||||
def _get_edi_document(self, edi_format):
|
||||
return self.edi_document_ids.filtered(lambda d: d.edi_format_id == edi_format)
|
||||
|
||||
@@ -233,11 +292,14 @@ class AccountMove(models.Model):
|
||||
####################################################
|
||||
|
||||
def action_process_edi_web_services(self):
|
||||
docs = self.edi_document_ids.filtered(lambda d: d.state in ('to_send', 'to_cancel'))
|
||||
if 'blocking_level' in self.env['account.edi.document']._fields:
|
||||
docs = docs.filtered(lambda d: d.blocking_level != 'error')
|
||||
docs = self.edi_document_ids.filtered(lambda d: d.state in ('to_send', 'to_cancel') and d.blocking_level != 'error')
|
||||
docs._process_documents_web_services(with_commit=False)
|
||||
|
||||
def action_retry_edi_documents_error(self):
|
||||
self.edi_document_ids.write({'error': False, 'blocking_level': False})
|
||||
self.action_process_edi_web_services()
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
|
||||
@@ -9,3 +9,7 @@ class AccountPayment(models.Model):
|
||||
|
||||
def action_process_edi_web_services(self):
|
||||
return self.move_id.action_process_edi_web_services()
|
||||
|
||||
def action_retry_edi_documents_error(self):
|
||||
self.ensure_one()
|
||||
return self.move_id.action_retry_edi_documents_error()
|
||||
|
||||
@@ -10,6 +10,68 @@ from unittest import mock
|
||||
import base64
|
||||
|
||||
|
||||
def _generate_mocked_needs_web_services(needs_web_services):
|
||||
return lambda edi_format: needs_web_services
|
||||
|
||||
|
||||
def _generate_mocked_support_batching(support_batching):
|
||||
return lambda edi_format, move, state, company: support_batching
|
||||
|
||||
|
||||
def _mocked_get_batch_key(edi_format, move, state):
|
||||
return ()
|
||||
|
||||
|
||||
def _mocked_check_move_configuration_success(edi_format, move):
|
||||
return []
|
||||
|
||||
|
||||
def _mocked_check_move_configuration_fail(edi_format, move):
|
||||
return ['Fake error (mocked)']
|
||||
|
||||
|
||||
def _mocked_post(edi_format, invoices, test_mode):
|
||||
res = {}
|
||||
for invoice in invoices:
|
||||
attachment = edi_format.env['ir.attachment'].create({
|
||||
'name': 'mock_simple.xml',
|
||||
'datas': base64.encodebytes(b"<?xml version='1.0' encoding='UTF-8'?><Invoice/>"),
|
||||
'mimetype': 'application/xml'
|
||||
})
|
||||
res[invoice] = {'attachment': attachment}
|
||||
return res
|
||||
|
||||
|
||||
def _mocked_post_two_steps(edi_format, invoices, test_mode):
|
||||
# For this test, we use the field ref to know if the first step is already done or not.
|
||||
# Typically, a technical field for the reference of the upload to the web-service will
|
||||
# be saved on the invoice.
|
||||
invoices_no_ref = invoices.filtered(lambda i: not i.ref)
|
||||
if len(invoices_no_ref) == len(invoices): # first step
|
||||
invoices_no_ref.ref = 'test_ref'
|
||||
return {invoice: {} for invoice in invoices}
|
||||
elif len(invoices_no_ref) == 0: # second step
|
||||
res = {}
|
||||
for invoice in invoices:
|
||||
attachment = edi_format.env['ir.attachment'].create({
|
||||
'name': 'mock_simple.xml',
|
||||
'datas': base64.encodebytes(b"<?xml version='1.0' encoding='UTF-8'?><Invoice/>"),
|
||||
'mimetype': 'application/xml'
|
||||
})
|
||||
res[invoice] = {'attachment': attachment}
|
||||
return res
|
||||
else:
|
||||
raise ValueError('wrong use of "_mocked_post_two_steps"')
|
||||
|
||||
|
||||
def _mocked_cancel_success(edi_format, invoices, test_mode):
|
||||
return {invoice: {'success': True} for invoice in invoices}
|
||||
|
||||
|
||||
def _mocked_cancel_failed(edi_format, invoices, test_mode):
|
||||
return {invoice: {'error': 'Faked error (mocked)'} for invoice in invoices}
|
||||
|
||||
|
||||
class AccountEdiTestCommon(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
@@ -31,6 +93,46 @@ class AccountEdiTestCommon(AccountTestInvoicingCommon):
|
||||
# EDI helpers
|
||||
####################################################
|
||||
|
||||
@contextmanager
|
||||
def mock_edi(self,
|
||||
_is_required_for_invoice_method=lambda edi_format, invoice: True,
|
||||
_is_required_for_payment_method=lambda edi_format, invoice: True,
|
||||
_support_batching_method=_generate_mocked_support_batching(False),
|
||||
_get_batch_key_method=_mocked_get_batch_key,
|
||||
_needs_web_services_method=_generate_mocked_needs_web_services(False),
|
||||
_check_move_configuration_method=_mocked_check_move_configuration_success,
|
||||
_post_invoice_edi_method=_mocked_post,
|
||||
_cancel_invoice_edi_method=_mocked_cancel_success,
|
||||
_post_payment_edi_method=_mocked_post,
|
||||
_cancel_payment_edi_method=_mocked_cancel_success,
|
||||
):
|
||||
|
||||
try:
|
||||
with patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._is_required_for_invoice',
|
||||
new=_is_required_for_invoice_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._is_required_for_payment',
|
||||
new=_is_required_for_payment_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._needs_web_services',
|
||||
new=_needs_web_services_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._support_batching',
|
||||
new=_support_batching_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._get_batch_key',
|
||||
new=_get_batch_key_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._check_move_configuration',
|
||||
new=_check_move_configuration_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._post_invoice_edi',
|
||||
new=_post_invoice_edi_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._cancel_invoice_edi',
|
||||
new=_cancel_invoice_edi_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._post_payment_edi',
|
||||
new=_post_payment_edi_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._cancel_payment_edi',
|
||||
new=_cancel_payment_edi_method):
|
||||
|
||||
yield
|
||||
finally:
|
||||
pass
|
||||
|
||||
def edi_cron(self):
|
||||
self.env['account.edi.document'].sudo().with_context(edi_test_mode=True).search([('state', 'in', ('to_send', 'to_cancel'))])._process_documents_web_services(with_commit=False)
|
||||
|
||||
@@ -39,8 +141,6 @@ class AccountEdiTestCommon(AccountTestInvoicingCommon):
|
||||
'move_type': 'in_invoice',
|
||||
'journal_id': self.company_data['default_journal_purchase'].id,
|
||||
})
|
||||
if 'extract_state' in invoice._fields:
|
||||
invoice.extract_state = 'done' # prevent ocr
|
||||
return invoice
|
||||
|
||||
def update_invoice_from_file(self, module_name, subfolder, filename, invoice):
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
|
||||
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon, _mocked_post_two_steps, _generate_mocked_needs_web_services, _mocked_cancel_failed, _generate_mocked_support_batching
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestAccountEdi(AccountEdiTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None, edi_format_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
|
||||
cls.invoice = cls.init_invoice('out_invoice', products=cls.product_a + cls.product_b)
|
||||
|
||||
def test_export_edi(self):
|
||||
invoice = self.init_invoice('out_invoice', products=self.product_a)
|
||||
self.assertEqual(len(invoice.edi_document_ids), 0)
|
||||
invoice.action_post()
|
||||
self.assertEqual(len(invoice.edi_document_ids), 1)
|
||||
self.assertEqual(len(self.invoice.edi_document_ids), 0)
|
||||
self.invoice.action_post()
|
||||
self.assertEqual(len(self.invoice.edi_document_ids), 1)
|
||||
|
||||
def test_prepare_jobs(self):
|
||||
|
||||
@@ -39,11 +44,155 @@ class TestAccountEdi(AccountEdiTestCommon):
|
||||
self.assertEqual(len(to_process), 2)
|
||||
|
||||
@patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._post_invoice_edi')
|
||||
def test_error(self, patched):
|
||||
def test_warning_is_retried(self, patched):
|
||||
with patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._needs_web_services',
|
||||
new=lambda edi_format: True):
|
||||
edi_docs = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs.error = 'Test Error'
|
||||
edi_docs.blocking_level = 'warning'
|
||||
|
||||
edi_docs.move_id.action_process_edi_web_services()
|
||||
patched.assert_called_once()
|
||||
|
||||
def test_edi_flow(self):
|
||||
with self.mock_edi():
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertFalse(doc)
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(len(doc), 1)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.invoice.button_draft()
|
||||
self.invoice.button_cancel()
|
||||
self.assertEqual(doc.state, 'cancelled')
|
||||
|
||||
def test_edi_flow_two_steps(self):
|
||||
with self.mock_edi(_post_invoice_edi_method=_mocked_post_two_steps,
|
||||
_needs_web_services_method=_generate_mocked_needs_web_services(True)):
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertFalse(doc)
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(len(doc), 1)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
|
||||
def test_edi_flow_request_cancel_success(self):
|
||||
with self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True)):
|
||||
self.assertEqual(self.invoice.state, 'draft')
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services()
|
||||
self.assertEqual(doc.state, 'cancelled')
|
||||
self.assertEqual(self.invoice.state, 'cancel')
|
||||
|
||||
def test_edi_flow_request_cancel_failed(self):
|
||||
with self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True),
|
||||
_cancel_invoice_edi_method=_mocked_cancel_failed):
|
||||
self.assertEqual(self.invoice.state, 'draft')
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
# Call off edi Cancellation
|
||||
self.invoice.button_abandon_cancel_posted_posted_moves()
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertFalse(doc.error)
|
||||
|
||||
# Failed cancel
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
|
||||
# Call off edi Cancellation
|
||||
self.invoice.button_abandon_cancel_posted_posted_moves()
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertIsNotNone(doc.error)
|
||||
|
||||
def test_edi_flow_two_step_cancel_with_call_off_request(self):
|
||||
def _mock_cancel(edi_format, invoices, test_mode):
|
||||
invoices_no_ref = invoices.filtered(lambda i: not i.ref)
|
||||
if len(invoices_no_ref) == len(invoices): # first step
|
||||
invoices_no_ref.ref = 'test_ref_cancel'
|
||||
return {invoice: {} for invoice in invoices}
|
||||
elif len(invoices_no_ref) == 0: # second step
|
||||
for invoice in invoices:
|
||||
invoice.ref = None
|
||||
return {invoice: {'success': True} for invoice in invoices}
|
||||
else:
|
||||
raise ValueError('wrong use of "_mocked_post_two_steps"')
|
||||
|
||||
def _is_needed_for_invoice(edi_format, invoice):
|
||||
return not bool(invoice.ref)
|
||||
|
||||
with self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True),
|
||||
_is_required_for_invoice_method=_is_needed_for_invoice,
|
||||
_cancel_invoice_edi_method=_mock_cancel):
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
|
||||
# Request Cancellation
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
doc._process_documents_web_services(with_commit=False) # first step of cancel
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
|
||||
# Call off edi Cancellation
|
||||
self.invoice.button_abandon_cancel_posted_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
|
||||
# If we cannot call off edi cancellation, only solution is to post again
|
||||
doc._process_documents_web_services(with_commit=False) # second step of cancel
|
||||
self.assertEqual(doc.state, 'cancelled')
|
||||
self.invoice.action_post()
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
|
||||
def test_batches(self):
|
||||
def _get_batch_key_method(edi_format, move, state):
|
||||
return (move.ref)
|
||||
|
||||
with self.mock_edi(_get_batch_key_method=_get_batch_key_method,
|
||||
_support_batching_method=_generate_mocked_support_batching(True)):
|
||||
edi_docs = self.env['account.edi.document']
|
||||
doc1 = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs |= doc1
|
||||
doc2 = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs |= doc2
|
||||
doc3 = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs |= doc3
|
||||
|
||||
to_process = edi_docs._prepare_jobs()
|
||||
self.assertEqual(len(to_process), 1)
|
||||
|
||||
doc1.move_id.ref = 'batch1'
|
||||
doc2.move_id.ref = 'batch2'
|
||||
doc3.move_id.ref = 'batch3'
|
||||
|
||||
to_process = edi_docs._prepare_jobs()
|
||||
self.assertEqual(len(to_process), 3)
|
||||
|
||||
doc2.move_id.ref = 'batch1'
|
||||
to_process = edi_docs._prepare_jobs()
|
||||
self.assertEqual(len(to_process), 2)
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
<field name="name">Account.edi.document.tree</field>
|
||||
<field name="model">account.edi.document</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree create="false" delete="false" edit="false">
|
||||
<tree create="false" delete="false" edit="false"
|
||||
decoration-info="blocking_level == 'info'"
|
||||
decoration-warning="blocking_level == 'warning'"
|
||||
decoration-danger="blocking_level == 'error'">
|
||||
<field name="edi_format_name" />
|
||||
<field name="error" />
|
||||
<field name="blocking_level" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -16,13 +16,21 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='button_cancel']" position="after">
|
||||
<field name="edi_show_cancel_button" invisible="1"/>
|
||||
<field name="edi_show_abandon_cancel_button" invisible="1"/>
|
||||
<button name="button_cancel_posted_moves"
|
||||
string="Request EDI Cancellation"
|
||||
type="object"
|
||||
groups="account.group_account_invoice"
|
||||
attrs="{'invisible' : [('edi_show_cancel_button', '=', False)]}"/>
|
||||
<button name="button_abandon_cancel_posted_posted_moves"
|
||||
string="Call off EDI Cancellation"
|
||||
type="object"
|
||||
groups="account.group_account_invoice"
|
||||
attrs="{'invisible' : [('edi_show_abandon_cancel_button', '=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="after">
|
||||
<field name="edi_blocking_level" invisible="1" />
|
||||
<field name="edi_error_count" invisible="1" />
|
||||
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_web_services_to_process', 'in', ['', False]), ('state', '=', 'draft')]}">
|
||||
<div>The invoice will be sent asynchronously to :
|
||||
@@ -30,11 +38,29 @@
|
||||
</div>
|
||||
<button name="action_process_edi_web_services" type="object" class="oe_link" string="Send now" />
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'error')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': [('edi_error_count', '=', 0)]}">
|
||||
<field name="edi_error_count"/>
|
||||
Electronic invoicing error(s)
|
||||
<button name="%(account_edi.action_open_edi_documents)d" type="action" class="oe_link oe_inline" string="⇒ See errors" />
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'warning')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'info')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='journal_div']" position="after">
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<field name="inherit_id" ref="account.view_account_payment_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position="after">
|
||||
<field name="edi_blocking_level" invisible="1" />
|
||||
<field name="edi_error_count" invisible="1" />
|
||||
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_web_services_to_process', 'in', ['', False]), ('state', '=', 'draft')]}">
|
||||
<div>The payment will be sent asynchronously to :
|
||||
@@ -22,11 +24,27 @@
|
||||
</div>
|
||||
<button name="action_process_edi_web_services" type="object" class="oe_link" string="Send now"/>
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'error')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': [('edi_error_count', '=', 0)]}">
|
||||
<field name="edi_error_count"/>
|
||||
Electronic invoicing error(s)
|
||||
<button name="%(account_edi.action_open_payment_edi_documents)d" type="action" class="oe_link oe_inline" string="⇒ See errors" />
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'warning')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'info')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='journal_id']" position="after">
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
from . import models
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
|
||||
def account_edi_block_level(cr, registery):
|
||||
''' The default value for blocking_level is 'error', but without this module,
|
||||
the behavior is the same as a blocking_level of 'warning' so we need to set
|
||||
all documents in error.
|
||||
'''
|
||||
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
env['account.edi.document'].search([('error', '!=', False)]).write({'blocking_level': 'warning'})
|
||||
@@ -1,19 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name' : 'Additionnal features for account_edi',
|
||||
'description':"""
|
||||
This module add features to account_edi to support new Edi formats.
|
||||
""",
|
||||
'version' : '1.0',
|
||||
'category': 'Accounting/Accounting',
|
||||
'depends' : ['account_edi'],
|
||||
'data': [
|
||||
'views/account_edi_document_views.xml',
|
||||
'views/account_move_views.xml',
|
||||
'views/account_payment_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
'post_init_hook': 'account_edi_block_level',
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * account_edi_extended
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 14.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-05 11:07+0000\n"
|
||||
"PO-Revision-Date: 2021-01-05 11:07+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: code:addons/account_edi_extended/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid " Electronic invoicing error(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: code:addons/account_edi_extended/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid " Electronic invoicing info(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: code:addons/account_edi_extended/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid " Electronic invoicing warning(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: code:addons/account_edi_extended/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "A request for cancellation of the EDI has been called off."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_edi_document__blocking_level
|
||||
msgid "Blocking Level"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,help:account_edi_extended.field_account_edi_document__blocking_level
|
||||
msgid ""
|
||||
"Blocks the document current operation depending on the error severity :\n"
|
||||
" * Info: the document is not blocked and everything is working as it should.\n"
|
||||
" * Warning : there is an error that doesn't prevent the current Electronic Invoicing operation to succeed.\n"
|
||||
" * Error : there is an error that blocks the current Electronic Invoicing operation."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model_terms:ir.ui.view,arch_db:account_edi_extended.view_move_form_inherit
|
||||
msgid "Call off EDI Cancellation"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_edi_document__display_name
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_move__display_name
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_payment__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_bank_statement_line__edi_blocking_level
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_move__edi_blocking_level
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_payment__edi_blocking_level
|
||||
msgid "Edi Blocking Level"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_bank_statement_line__edi_error_message
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_move__edi_error_message
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_payment__edi_error_message
|
||||
msgid "Edi Error Message"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_bank_statement_line__edi_show_abandon_cancel_button
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_move__edi_show_abandon_cancel_button
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_payment__edi_show_abandon_cancel_button
|
||||
msgid "Edi Show Abandon Cancel Button"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model,name:account_edi_extended.model_account_edi_document
|
||||
msgid "Electronic Document for an account.move"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields.selection,name:account_edi_extended.selection__account_edi_document__blocking_level__error
|
||||
#: model:ir.model.fields.selection,name:account_edi_extended.selection__account_move__edi_blocking_level__error
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_edi_document__id
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_move__id
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_payment__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields.selection,name:account_edi_extended.selection__account_edi_document__blocking_level__info
|
||||
#: model:ir.model.fields.selection,name:account_edi_extended.selection__account_move__edi_blocking_level__info
|
||||
msgid "Info"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: code:addons/account_edi_extended/models/account_edi_document.py:0
|
||||
#, python-format
|
||||
msgid "Invalid configuration:"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model,name:account_edi_extended.model_account_move
|
||||
msgid "Journal Entry"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_edi_document____last_update
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_move____last_update
|
||||
#: model:ir.model.fields,field_description:account_edi_extended.field_account_payment____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model,name:account_edi_extended.model_account_payment
|
||||
msgid "Payments"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model_terms:ir.ui.view,arch_db:account_edi_extended.view_move_form_inherit
|
||||
#: model_terms:ir.ui.view,arch_db:account_edi_extended.view_payment_form_inherit
|
||||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model:ir.model.fields.selection,name:account_edi_extended.selection__account_edi_document__blocking_level__warning
|
||||
#: model:ir.model.fields.selection,name:account_edi_extended.selection__account_move__edi_blocking_level__warning
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_edi_extended
|
||||
#: model_terms:ir.ui.view,arch_db:account_edi_extended.view_move_form_inherit
|
||||
msgid "⇒ See errors"
|
||||
msgstr ""
|
||||
@@ -1,3 +0,0 @@
|
||||
from . import account_edi_document
|
||||
from . import account_move
|
||||
from . import account_payment
|
||||
@@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import models, fields
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
DEFAULT_BLOCKING_LEVEL = 'warning' # Keep previous behavior. TODO : when account_edi_extended is merged with account_edi, should be 'error' (document will not be processed again until forced retry or reset to draft)
|
||||
|
||||
|
||||
class AccountEdiDocument(models.Model):
|
||||
_inherit = 'account.edi.document'
|
||||
|
||||
blocking_level = fields.Selection(selection=[('info', 'Info'), ('warning', 'Warning'), ('error', 'Error')],
|
||||
help="Blocks the document current operation depending on the error severity :\n"
|
||||
" * Info: the document is not blocked and everything is working as it should.\n"
|
||||
" * Warning : there is an error that doesn't prevent the current Electronic Invoicing operation to succeed.\n"
|
||||
" * Error : there is an error that blocks the current Electronic Invoicing operation.")
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, fields, _
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
edi_show_abandon_cancel_button = fields.Boolean(
|
||||
compute='_compute_edi_show_abandon_cancel_button')
|
||||
edi_error_message = fields.Html(compute='_compute_edi_error_message')
|
||||
edi_blocking_level = fields.Selection(selection=[('info', 'Info'), ('warning', 'Warning'), ('error', 'Error')], compute='_compute_edi_error_message')
|
||||
|
||||
@api.depends(
|
||||
'edi_document_ids',
|
||||
'edi_document_ids.state',
|
||||
'edi_document_ids.blocking_level',
|
||||
'edi_document_ids.edi_format_id',
|
||||
'edi_document_ids.edi_format_id.name')
|
||||
def _compute_edi_web_services_to_process(self):
|
||||
# OVERRIDE to take blocking_level into account
|
||||
for move in self:
|
||||
to_process = move.edi_document_ids.filtered(lambda d: d.state in ['to_send', 'to_cancel'] and d.blocking_level != 'error')
|
||||
format_web_services = to_process.edi_format_id.filtered(lambda f: f._needs_web_services())
|
||||
move.edi_web_services_to_process = ', '.join(f.name for f in format_web_services)
|
||||
|
||||
@api.depends(
|
||||
'state',
|
||||
'edi_document_ids.state',
|
||||
'edi_document_ids.attachment_id')
|
||||
def _compute_edi_show_abandon_cancel_button(self):
|
||||
for move in self:
|
||||
move.edi_show_abandon_cancel_button = any(doc.edi_format_id._needs_web_services()
|
||||
and doc.state == 'to_cancel'
|
||||
and move.is_invoice(include_receipts=True)
|
||||
and doc.edi_format_id._is_required_for_invoice(move)
|
||||
for doc in move.edi_document_ids)
|
||||
|
||||
@api.depends('edi_error_count', 'edi_document_ids.error', 'edi_document_ids.blocking_level')
|
||||
def _compute_edi_error_message(self):
|
||||
for move in self:
|
||||
if move.edi_error_count == 0:
|
||||
move.edi_error_message = None
|
||||
move.edi_blocking_level = None
|
||||
elif move.edi_error_count == 1:
|
||||
error_doc = move.edi_document_ids.filtered(lambda d: d.error)
|
||||
move.edi_error_message = error_doc.error
|
||||
move.edi_blocking_level = error_doc.blocking_level
|
||||
else:
|
||||
error_levels = set([doc.blocking_level for doc in move.edi_document_ids])
|
||||
if 'error' in error_levels:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing error(s)")
|
||||
move.edi_blocking_level = 'error'
|
||||
elif 'warning' in error_levels:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing warning(s)")
|
||||
move.edi_blocking_level = 'warning'
|
||||
else:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing info(s)")
|
||||
move.edi_blocking_level = 'info'
|
||||
|
||||
def action_retry_edi_documents_error(self):
|
||||
self.edi_document_ids.write({'error': False, 'blocking_level': False})
|
||||
self.action_process_edi_web_services()
|
||||
|
||||
def button_abandon_cancel_posted_posted_moves(self):
|
||||
'''Cancel the request for cancellation of the EDI.
|
||||
'''
|
||||
documents = self.env['account.edi.document']
|
||||
for move in self:
|
||||
is_move_marked = False
|
||||
for doc in move.edi_document_ids:
|
||||
if doc.state == 'to_cancel' \
|
||||
and move.is_invoice(include_receipts=True) \
|
||||
and doc.edi_format_id._is_required_for_invoice(move):
|
||||
documents |= doc
|
||||
is_move_marked = True
|
||||
if is_move_marked:
|
||||
move.message_post(body=_("A request for cancellation of the EDI has been called off."))
|
||||
|
||||
documents.write({'state': 'sent'})
|
||||
@@ -1,12 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
def action_retry_edi_documents_error(self):
|
||||
self.ensure_one()
|
||||
return self.move_id.action_retry_edi_documents_error()
|
||||
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_edi
|
||||
@@ -1,114 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.account_edi.tests.common import AccountEdiTestCommon
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import patch
|
||||
import base64
|
||||
|
||||
# TODO for the test to work, we need a chart template (COA) but we don't have any and don't want to add dependency (create empty coa ?)
|
||||
|
||||
|
||||
def _generate_mocked_needs_web_services(needs_web_services):
|
||||
return lambda edi_format: needs_web_services
|
||||
|
||||
|
||||
def _generate_mocked_support_batching(support_batching):
|
||||
return lambda edi_format, move, state, company: support_batching
|
||||
|
||||
|
||||
def _mocked_get_batch_key(edi_format, move, state):
|
||||
return ()
|
||||
|
||||
|
||||
def _mocked_check_move_configuration_success(edi_format, move):
|
||||
return []
|
||||
|
||||
|
||||
def _mocked_check_move_configuration_fail(edi_format, move):
|
||||
return ['Fake error (mocked)']
|
||||
|
||||
|
||||
def _mocked_post(edi_format, invoices, test_mode):
|
||||
res = {}
|
||||
for invoice in invoices:
|
||||
attachment = edi_format.env['ir.attachment'].create({
|
||||
'name': 'mock_simple.xml',
|
||||
'datas': base64.encodebytes(b"<?xml version='1.0' encoding='UTF-8'?><Invoice/>"),
|
||||
'mimetype': 'application/xml'
|
||||
})
|
||||
res[invoice] = {'attachment': attachment}
|
||||
return res
|
||||
|
||||
|
||||
def _mocked_post_two_steps(edi_format, invoices, test_mode):
|
||||
# For this test, we use the field ref to know if the first step is already done or not.
|
||||
# Typically, a technical field for the reference of the upload to the web-service will
|
||||
# be saved on the invoice.
|
||||
invoices_no_ref = invoices.filtered(lambda i: not i.ref)
|
||||
if len(invoices_no_ref) == len(invoices): # first step
|
||||
invoices_no_ref.ref = 'test_ref'
|
||||
return {invoice: {} for invoice in invoices}
|
||||
elif len(invoices_no_ref) == 0: # second step
|
||||
res = {}
|
||||
for invoice in invoices:
|
||||
attachment = edi_format.env['ir.attachment'].create({
|
||||
'name': 'mock_simple.xml',
|
||||
'datas': base64.encodebytes(b"<?xml version='1.0' encoding='UTF-8'?><Invoice/>"),
|
||||
'mimetype': 'application/xml'
|
||||
})
|
||||
res[invoice] = {'attachment': attachment}
|
||||
return res
|
||||
else:
|
||||
raise ValueError('wrong use of "_mocked_post_two_steps"')
|
||||
|
||||
|
||||
def _mocked_cancel_success(edi_format, invoices, test_mode):
|
||||
return {invoice: {'success': True} for invoice in invoices}
|
||||
|
||||
|
||||
def _mocked_cancel_failed(edi_format, invoices, test_mode):
|
||||
return {invoice: {'error': 'Faked error (mocked)'} for invoice in invoices}
|
||||
|
||||
|
||||
class AccountEdiExtendedTestCommon(AccountEdiTestCommon):
|
||||
|
||||
@contextmanager
|
||||
def mock_edi(self,
|
||||
_is_required_for_invoice_method=lambda edi_format, invoice: True,
|
||||
_is_required_for_payment_method=lambda edi_format, invoice: True,
|
||||
_support_batching_method=_generate_mocked_support_batching(False),
|
||||
_get_batch_key_method=_mocked_get_batch_key,
|
||||
_needs_web_services_method=_generate_mocked_needs_web_services(False),
|
||||
_check_move_configuration_method=_mocked_check_move_configuration_success,
|
||||
_post_invoice_edi_method=_mocked_post,
|
||||
_cancel_invoice_edi_method=_mocked_cancel_success,
|
||||
_post_payment_edi_method=_mocked_post,
|
||||
_cancel_payment_edi_method=_mocked_cancel_success,
|
||||
):
|
||||
|
||||
try:
|
||||
with patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._is_required_for_invoice',
|
||||
new=_is_required_for_invoice_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._is_required_for_payment',
|
||||
new=_is_required_for_payment_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._needs_web_services',
|
||||
new=_needs_web_services_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._support_batching',
|
||||
new=_support_batching_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._get_batch_key',
|
||||
new=_get_batch_key_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._check_move_configuration',
|
||||
new=_check_move_configuration_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._post_invoice_edi',
|
||||
new=_post_invoice_edi_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._cancel_invoice_edi',
|
||||
new=_cancel_invoice_edi_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._post_payment_edi',
|
||||
new=_post_payment_edi_method), \
|
||||
patch('odoo.addons.account_edi.models.account_edi_format.AccountEdiFormat._cancel_payment_edi',
|
||||
new=_cancel_payment_edi_method):
|
||||
|
||||
yield
|
||||
finally:
|
||||
pass
|
||||
@@ -1,157 +0,0 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.account_edi_extended.tests.common import AccountEdiExtendedTestCommon, _mocked_post_two_steps, _generate_mocked_needs_web_services, _mocked_cancel_failed, _generate_mocked_support_batching
|
||||
|
||||
|
||||
class TestAccountEdi(AccountEdiExtendedTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None, edi_format_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref)
|
||||
|
||||
cls.invoice = cls.init_invoice('out_invoice', products=cls.product_a + cls.product_b)
|
||||
|
||||
def test_edi_flow(self):
|
||||
with self.mock_edi():
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertFalse(doc)
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(len(doc), 1)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.invoice.button_draft()
|
||||
self.invoice.button_cancel()
|
||||
self.assertEqual(doc.state, 'cancelled')
|
||||
|
||||
def test_edi_flow_two_steps(self):
|
||||
with self.mock_edi(_post_invoice_edi_method=_mocked_post_two_steps,
|
||||
_needs_web_services_method=_generate_mocked_needs_web_services(True)):
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertFalse(doc)
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(len(doc), 1)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
|
||||
def test_edi_flow_request_cancel_success(self):
|
||||
with self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True)):
|
||||
self.assertEqual(self.invoice.state, 'draft')
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services()
|
||||
self.assertEqual(doc.state, 'cancelled')
|
||||
self.assertEqual(self.invoice.state, 'cancel')
|
||||
|
||||
def test_edi_flow_request_cancel_failed(self):
|
||||
with self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True),
|
||||
_cancel_invoice_edi_method=_mocked_cancel_failed):
|
||||
self.assertEqual(self.invoice.state, 'draft')
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
self.assertEqual(doc.state, 'to_send')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
# Call off edi Cancellation
|
||||
self.invoice.button_abandon_cancel_posted_posted_moves()
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertFalse(doc.error)
|
||||
|
||||
# Failed cancel
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
doc._process_documents_web_services()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
self.assertEqual(self.invoice.state, 'posted')
|
||||
|
||||
# Call off edi Cancellation
|
||||
self.invoice.button_abandon_cancel_posted_posted_moves()
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
self.assertIsNotNone(doc.error)
|
||||
|
||||
def test_edi_flow_two_step_cancel_with_call_off_request(self):
|
||||
def _mock_cancel(edi_format, invoices, test_mode):
|
||||
invoices_no_ref = invoices.filtered(lambda i: not i.ref)
|
||||
if len(invoices_no_ref) == len(invoices): # first step
|
||||
invoices_no_ref.ref = 'test_ref_cancel'
|
||||
return {invoice: {} for invoice in invoices}
|
||||
elif len(invoices_no_ref) == 0: # second step
|
||||
for invoice in invoices:
|
||||
invoice.ref = None
|
||||
return {invoice: {'success': True} for invoice in invoices}
|
||||
else:
|
||||
raise ValueError('wrong use of "_mocked_post_two_steps"')
|
||||
|
||||
def _is_needed_for_invoice(edi_format, invoice):
|
||||
return not bool(invoice.ref)
|
||||
|
||||
with self.mock_edi(_needs_web_services_method=_generate_mocked_needs_web_services(True),
|
||||
_is_required_for_invoice_method=_is_needed_for_invoice,
|
||||
_cancel_invoice_edi_method=_mock_cancel):
|
||||
self.invoice.action_post()
|
||||
doc = self.invoice._get_edi_document(self.edi_format)
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
|
||||
# Request Cancellation
|
||||
self.invoice.button_cancel_posted_moves()
|
||||
doc._process_documents_web_services(with_commit=False) # first step of cancel
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
|
||||
# Call off edi Cancellation
|
||||
self.invoice.button_abandon_cancel_posted_posted_moves()
|
||||
self.assertEqual(doc.state, 'to_cancel')
|
||||
|
||||
# If we cannot call off edi cancellation, only solution is to post again
|
||||
doc._process_documents_web_services(with_commit=False) # second step of cancel
|
||||
self.assertEqual(doc.state, 'cancelled')
|
||||
self.invoice.action_post()
|
||||
doc._process_documents_web_services(with_commit=False)
|
||||
self.assertEqual(doc.state, 'sent')
|
||||
|
||||
def test_batches(self):
|
||||
def _get_batch_key_method(edi_format, move, state):
|
||||
return (move.ref)
|
||||
|
||||
with self.mock_edi(_get_batch_key_method=_get_batch_key_method,
|
||||
_support_batching_method=_generate_mocked_support_batching(True)):
|
||||
edi_docs = self.env['account.edi.document']
|
||||
doc1 = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs |= doc1
|
||||
doc2 = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs |= doc2
|
||||
doc3 = self.create_edi_document(self.edi_format, 'to_send')
|
||||
edi_docs |= doc3
|
||||
|
||||
to_process = edi_docs._prepare_jobs()
|
||||
self.assertEqual(len(to_process), 1)
|
||||
|
||||
doc1.move_id.ref = 'batch1'
|
||||
doc2.move_id.ref = 'batch2'
|
||||
doc3.move_id.ref = 'batch3'
|
||||
|
||||
to_process = edi_docs._prepare_jobs()
|
||||
self.assertEqual(len(to_process), 3)
|
||||
|
||||
doc2.move_id.ref = 'batch1'
|
||||
to_process = edi_docs._prepare_jobs()
|
||||
self.assertEqual(len(to_process), 2)
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_tree_account_edi_document_inherit" model="ir.ui.view">
|
||||
<field name="name">Account.edi.document.tree.inherit</field>
|
||||
<field name="model">account.edi.document</field>
|
||||
<field name="inherit_id" ref="account_edi.view_tree_account_edi_document"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="inside">
|
||||
<field name="blocking_level" invisible="1" />
|
||||
</xpath>
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="decoration-info">blocking_level == 'info'</attribute>
|
||||
<attribute name="decoration-warning">blocking_level == 'warning'</attribute>
|
||||
<attribute name="decoration-danger">blocking_level == 'error'</attribute>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_move_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account.move.form.inherit</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account_edi.view_move_form_inherit" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='%(account_edi.action_open_edi_documents)d']" position="after">
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='button_cancel_posted_moves']" position="after">
|
||||
<field name="edi_show_abandon_cancel_button" invisible="1"/>
|
||||
<button name="button_abandon_cancel_posted_posted_moves"
|
||||
string="Call off EDI Cancellation"
|
||||
type="object"
|
||||
groups="account.group_account_invoice"
|
||||
attrs="{'invisible' : [('edi_show_abandon_cancel_button', '=', False)]}"/>
|
||||
</xpath>
|
||||
<!-- Nasty xpath to replace the error count warning banner. In master, it will be merged. -->
|
||||
<xpath expr="//div[hasclass('alert-warning')]" position="replace">
|
||||
<field name="edi_blocking_level" invisible="1" />
|
||||
<field name="edi_error_count" invisible="1" />
|
||||
<div class="alert alert-danger" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'error')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'warning')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('edi_error_count', '=', 0), ('edi_blocking_level', '!=', 'info')]}">
|
||||
<div class="o_row">
|
||||
<field name="edi_error_message" />
|
||||
<button name="%(account_edi.action_open_edi_documents)d" string="⇒ See errors" type="action" class="oe_link" attrs="{'invisible': [('edi_error_count', '=', 1)]}" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_payment_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account.payment.form.inherit</field>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account_edi.view_payment_form_inherit" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='%(account_edi.action_open_payment_edi_documents)d']" position="after">
|
||||
<button name="action_retry_edi_documents_error" type="object" class="oe_link oe_inline" string="Retry" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user