mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-23 14:42:05 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -1,5 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
|
@ -18,7 +23,7 @@ class AccountMove(models.Model):
|
|||
help='The aggregated state of all the EDIs with web-service of this move')
|
||||
edi_error_count = fields.Integer(
|
||||
compute='_compute_edi_error_count',
|
||||
help='How many EDIs are in error for this move ?')
|
||||
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')
|
||||
|
|
@ -31,6 +36,8 @@ class AccountMove(models.Model):
|
|||
compute='_compute_edi_show_cancel_button')
|
||||
edi_show_abandon_cancel_button = fields.Boolean(
|
||||
compute='_compute_edi_show_abandon_cancel_button')
|
||||
edi_show_force_cancel_button = fields.Boolean(
|
||||
compute='_compute_edi_show_force_cancel_button')
|
||||
|
||||
@api.depends('edi_document_ids.state')
|
||||
def _compute_edi_state(self):
|
||||
|
|
@ -47,6 +54,11 @@ class AccountMove(models.Model):
|
|||
else:
|
||||
move.edi_state = False
|
||||
|
||||
@api.depends('edi_document_ids.state')
|
||||
def _compute_edi_show_force_cancel_button(self):
|
||||
for move in self:
|
||||
move.edi_show_force_cancel_button = move._can_force_cancel()
|
||||
|
||||
@api.depends('edi_document_ids.error')
|
||||
def _compute_edi_error_count(self):
|
||||
for move in self:
|
||||
|
|
@ -64,14 +76,15 @@ class AccountMove(models.Model):
|
|||
move.edi_blocking_level = error_doc.blocking_level
|
||||
else:
|
||||
error_levels = set([doc.blocking_level for doc in move.edi_document_ids])
|
||||
count = str(move.edi_error_count)
|
||||
if 'error' in error_levels:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing error(s)")
|
||||
move.edi_error_message = _("%(count)s Electronic invoicing error(s)", count=count)
|
||||
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_error_message = _("%(count)s Electronic invoicing warning(s)", count=count)
|
||||
move.edi_blocking_level = 'warning'
|
||||
else:
|
||||
move.edi_error_message = str(move.edi_error_count) + _(" Electronic invoicing info(s)")
|
||||
move.edi_error_message = _("%(count)s Electronic invoicing info(s)", count=count)
|
||||
move.edi_blocking_level = 'info'
|
||||
|
||||
@api.depends(
|
||||
|
|
@ -126,7 +139,7 @@ class AccountMove(models.Model):
|
|||
def _compute_edi_show_abandon_cancel_button(self):
|
||||
for move in self:
|
||||
move.edi_show_abandon_cancel_button = False
|
||||
for doc in move.edi_document_ids:
|
||||
for doc in move.sudo().edi_document_ids:
|
||||
move_applicability = doc.edi_format_id._get_move_applicability(move)
|
||||
if doc.edi_format_id._needs_web_services() \
|
||||
and doc.state == 'to_cancel' \
|
||||
|
|
@ -168,22 +181,6 @@ class AccountMove(models.Model):
|
|||
grouping_key to aggregate tax values together. The returned dictionary is added
|
||||
to each tax details in order to retrieve the full grouping_key later.
|
||||
|
||||
:param compute_mode: Optional parameter to specify the method used to allocate the tax line amounts
|
||||
among the invoice lines:
|
||||
'tax_details' (the default) uses the AccountMove._get_query_tax_details method.
|
||||
'compute_all' uses the AccountTax._compute_all method.
|
||||
|
||||
The 'tax_details' method takes the tax line balance and allocates it among the
|
||||
invoice lines to which that tax applies, proportionately to the invoice lines'
|
||||
base amounts. This always ensures that the sum of the tax amounts equals the
|
||||
tax line's balance, which, depending on the constraints of a particular
|
||||
localization, can be more appropriate when 'Round Globally' is set.
|
||||
|
||||
The 'compute_all' method returns, for each invoice line, the exact tax amounts
|
||||
corresponding to the taxes applied to the invoice line. Depending on the
|
||||
constraints of the particular localization, this can be more appropriate when
|
||||
'Round per Line' is set.
|
||||
|
||||
:return: The full tax details for the current invoice and for each invoice line
|
||||
separately. The returned dictionary is the following:
|
||||
|
||||
|
|
@ -222,73 +219,6 @@ class AccountMove(models.Model):
|
|||
grouping_key_generator=grouping_key_generator,
|
||||
)
|
||||
|
||||
def _prepare_edi_vals_to_export(self):
|
||||
''' The purpose of this helper is to prepare values in order to export an invoice through the EDI system.
|
||||
This includes the computation of the tax details for each invoice line that could be very difficult to
|
||||
handle regarding the computation of the base amount.
|
||||
|
||||
:return: A python dict containing default pre-processed values.
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
res = {
|
||||
'record': self,
|
||||
'balance_multiplicator': -1 if self.is_inbound() else 1,
|
||||
'invoice_line_vals_list': [],
|
||||
}
|
||||
|
||||
# Invoice lines details.
|
||||
for index, line in enumerate(self.invoice_line_ids.filtered(lambda line: line.display_type == 'product'), start=1):
|
||||
line_vals = line._prepare_edi_vals_to_export()
|
||||
line_vals['index'] = index
|
||||
res['invoice_line_vals_list'].append(line_vals)
|
||||
|
||||
# Totals.
|
||||
res.update({
|
||||
'total_price_subtotal_before_discount': sum(x['price_subtotal_before_discount'] for x in res['invoice_line_vals_list']),
|
||||
'total_price_discount': sum(x['price_discount'] for x in res['invoice_line_vals_list']),
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
def _update_payments_edi_documents(self):
|
||||
''' Update the edi documents linked to the current journal entries. These journal entries must be linked to an
|
||||
account.payment of an account.bank.statement.line. This additional method is needed because the payment flow is
|
||||
not the same as the invoice one. Indeed, the edi documents must be created when the payment is fully reconciled
|
||||
with invoices.
|
||||
'''
|
||||
payments = self.filtered(lambda move: move.payment_id or move.statement_line_id)
|
||||
edi_document_vals_list = []
|
||||
to_remove = self.env['account.edi.document']
|
||||
for payment in payments:
|
||||
edi_formats = payment._get_reconciled_invoices().journal_id.edi_format_ids | payment.edi_document_ids.edi_format_id
|
||||
for edi_format in edi_formats:
|
||||
# Only recreate document when cancelled before.
|
||||
existing_edi_document = payment.edi_document_ids.filtered(lambda x: x.edi_format_id == edi_format)
|
||||
if existing_edi_document.state == 'sent':
|
||||
continue
|
||||
move_applicability = edi_format._get_move_applicability(payment)
|
||||
|
||||
if move_applicability:
|
||||
if existing_edi_document:
|
||||
existing_edi_document.write({
|
||||
'state': 'to_send',
|
||||
'error': False,
|
||||
'blocking_level': False,
|
||||
})
|
||||
else:
|
||||
edi_document_vals_list.append({
|
||||
'edi_format_id': edi_format.id,
|
||||
'move_id': payment.id,
|
||||
'state': 'to_send',
|
||||
})
|
||||
elif existing_edi_document:
|
||||
to_remove |= existing_edi_document
|
||||
|
||||
to_remove.unlink()
|
||||
self.env['account.edi.document'].create(edi_document_vals_list)
|
||||
payments.edi_document_ids._process_documents_no_web_services()
|
||||
|
||||
def _is_ready_to_be_sent(self):
|
||||
# OVERRIDE
|
||||
# Prevent a mail to be sent to the customer if the EDI document is not sent.
|
||||
|
|
@ -313,7 +243,7 @@ class AccountMove(models.Model):
|
|||
if move_applicability:
|
||||
errors = edi_format._check_move_configuration(move)
|
||||
if errors:
|
||||
raise UserError(_("Invalid invoice configuration:\n\n%s") % '\n'.join(errors))
|
||||
raise UserError(_("Invalid invoice configuration:\n\n%s", '\n'.join(errors)))
|
||||
|
||||
existing_edi_document = move.edi_document_ids.filtered(lambda x: x.edi_format_id == edi_format)
|
||||
if existing_edi_document:
|
||||
|
|
@ -330,9 +260,18 @@ class AccountMove(models.Model):
|
|||
|
||||
self.env['account.edi.document'].create(edi_document_vals_list)
|
||||
posted.edi_document_ids._process_documents_no_web_services()
|
||||
self.env.ref('account_edi.ir_cron_edi_network')._trigger()
|
||||
if not self.env.context.get('skip_account_edi_cron_trigger'):
|
||||
self.env.ref('account_edi.ir_cron_edi_network')._trigger()
|
||||
return posted
|
||||
|
||||
def button_force_cancel(self):
|
||||
""" Cancel the invoice without waiting for the cancellation request to succeed.
|
||||
"""
|
||||
for move in self:
|
||||
to_cancel_edi_documents = move.edi_document_ids.filtered(lambda doc: doc.state == 'to_cancel')
|
||||
move.message_post(body=_("This invoice was canceled while the EDIs %s still had a pending cancellation request.", ", ".join(to_cancel_edi_documents.mapped('edi_format_id.name'))))
|
||||
self.button_cancel()
|
||||
|
||||
def button_cancel(self):
|
||||
# OVERRIDE
|
||||
# Set the electronic document to be canceled and cancel immediately for synchronous formats.
|
||||
|
|
@ -355,8 +294,8 @@ class AccountMove(models.Model):
|
|||
if not move._edi_allow_button_draft():
|
||||
raise UserError(_(
|
||||
"You can't edit the following journal entry %s because an electronic document has already been "
|
||||
"sent. Please use the 'Request EDI Cancellation' button instead."
|
||||
) % move.display_name)
|
||||
"sent. Please use the 'Request EDI Cancellation' button instead.",
|
||||
move.display_name))
|
||||
|
||||
res = super().button_draft()
|
||||
|
||||
|
|
@ -370,7 +309,7 @@ class AccountMove(models.Model):
|
|||
'''
|
||||
to_cancel_documents = self.env['account.edi.document']
|
||||
for move in self:
|
||||
move._check_fiscalyear_lock_date()
|
||||
move._check_fiscal_lock_dates()
|
||||
is_move_marked = False
|
||||
for doc in move.edi_document_ids:
|
||||
move_applicability = doc.edi_format_id._get_move_applicability(move)
|
||||
|
|
@ -407,27 +346,11 @@ class AccountMove(models.Model):
|
|||
def _get_edi_attachment(self, edi_format):
|
||||
return self._get_edi_document(edi_format).sudo().attachment_id
|
||||
|
||||
####################################################
|
||||
# Import Electronic Document
|
||||
####################################################
|
||||
|
||||
def _get_create_document_from_attachment_decoders(self):
|
||||
# OVERRIDE
|
||||
res = super()._get_create_document_from_attachment_decoders()
|
||||
res.append((10, self.env['account.edi.format'].search([])._create_document_from_attachment))
|
||||
return res
|
||||
|
||||
def _get_update_invoice_from_attachment_decoders(self, invoice):
|
||||
# OVERRIDE
|
||||
res = super()._get_update_invoice_from_attachment_decoders(invoice)
|
||||
res.append((10, self.env['account.edi.format'].search([])._update_invoice_from_attachment))
|
||||
return res
|
||||
|
||||
# this override is to make sure that the main attachment is not the edi xml otherwise the attachment viewer will not work correctly
|
||||
def _message_set_main_attachment_id(self, attachment_ids):
|
||||
if self.message_main_attachment_id and len(attachment_ids) > 1 and self.message_main_attachment_id in self.edi_document_ids.attachment_id:
|
||||
self.message_main_attachment_id = self.env['ir.attachment']
|
||||
super()._message_set_main_attachment_id(attachment_ids)
|
||||
def _message_set_main_attachment_id(self, attachments, force=False, filter_xml=True):
|
||||
if not force and len(attachments) > 1 and self.message_main_attachment_id in self.edi_document_ids.attachment_id:
|
||||
force = True
|
||||
super()._message_set_main_attachment_id(attachments, force=force, filter_xml=filter_xml)
|
||||
|
||||
####################################################
|
||||
# Business operations
|
||||
|
|
@ -441,60 +364,26 @@ class AccountMove(models.Model):
|
|||
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=with_commit)
|
||||
|
||||
def _retry_edi_documents_error_hook(self):
|
||||
''' Hook called when edi_documents are retried. For example, when it's needed to clean a field.
|
||||
TO OVERRIDE
|
||||
def _retry_edi_documents_error(self):
|
||||
'''Called when edi_documents need to be retried.
|
||||
'''
|
||||
return
|
||||
self.edi_document_ids.write({'error': False, 'blocking_level': False})
|
||||
|
||||
def action_retry_edi_documents_error(self):
|
||||
self._retry_edi_documents_error_hook()
|
||||
self.edi_document_ids.write({'error': False, 'blocking_level': False})
|
||||
self._retry_edi_documents_error()
|
||||
self.action_process_edi_web_services()
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
####################################################
|
||||
# Export Electronic Document
|
||||
# Mailing
|
||||
####################################################
|
||||
|
||||
def _prepare_edi_vals_to_export(self):
|
||||
''' The purpose of this helper is the same as '_prepare_edi_vals_to_export' but for a single invoice line.
|
||||
This includes the computation of the tax details for each invoice line or the management of the discount.
|
||||
Indeed, in some EDI, we need to provide extra values depending the discount such as:
|
||||
- the discount as an amount instead of a percentage.
|
||||
- the price_unit but after subtraction of the discount.
|
||||
|
||||
:return: A python dict containing default pre-processed values.
|
||||
'''
|
||||
self.ensure_one()
|
||||
|
||||
if self.discount == 100.0:
|
||||
gross_price_subtotal = self.currency_id.round(self.price_unit * self.quantity)
|
||||
else:
|
||||
gross_price_subtotal = self.currency_id.round(self.price_subtotal / (1 - self.discount / 100.0))
|
||||
|
||||
res = {
|
||||
'line': self,
|
||||
'price_unit_after_discount': self.currency_id.round(self.price_unit * (1 - (self.discount / 100.0))),
|
||||
'price_subtotal_before_discount': gross_price_subtotal,
|
||||
'price_subtotal_unit': self.currency_id.round(self.price_subtotal / self.quantity) if self.quantity else 0.0,
|
||||
'price_total_unit': self.currency_id.round(self.price_total / self.quantity) if self.quantity else 0.0,
|
||||
'price_discount': gross_price_subtotal - self.price_subtotal,
|
||||
'price_discount_unit': (gross_price_subtotal - self.price_subtotal) / self.quantity if self.quantity else 0.0,
|
||||
'gross_price_total_unit': self.currency_id.round(gross_price_subtotal / self.quantity) if self.quantity else 0.0,
|
||||
'unece_uom_code': self.product_id.product_tmpl_id.uom_id._get_unece_code(),
|
||||
}
|
||||
return res
|
||||
|
||||
def reconcile(self):
|
||||
# OVERRIDE
|
||||
# In some countries, the payments must be sent to the government under some condition. One of them could be
|
||||
# there is at least one reconciled invoice to the payment. Then, we need to update the state of the edi
|
||||
# documents during the reconciliation.
|
||||
all_lines = self + self.matched_debit_ids.debit_move_id + self.matched_credit_ids.credit_move_id
|
||||
res = super().reconcile()
|
||||
all_lines.move_id._update_payments_edi_documents()
|
||||
return res
|
||||
def _process_attachments_for_template_post(self, mail_template):
|
||||
""" Add Edi attachments to templates. """
|
||||
result = super()._process_attachments_for_template_post(mail_template)
|
||||
for move in self.filtered('edi_document_ids'):
|
||||
move_result = result.setdefault(move.id, {})
|
||||
for edi_doc in move.edi_document_ids:
|
||||
edi_attachments = edi_doc._filter_edi_attachments_for_mailing()
|
||||
move_result.setdefault('attachment_ids', []).extend(edi_attachments.get('attachment_ids', []))
|
||||
move_result.setdefault('attachments', []).extend(edi_attachments.get('attachments', []))
|
||||
return result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue