229 lines
10 KiB
Python
229 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from base64 import b64encode
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError
|
|
|
|
|
|
class AccountMoveSend(models.TransientModel):
|
|
_inherit = 'account.move.send'
|
|
|
|
checkbox_send_peppol = fields.Boolean(
|
|
string='Send via PEPPOL',
|
|
compute='_compute_checkbox_send_peppol', store=True, readonly=False,
|
|
help='Send the invoice via PEPPOL',
|
|
)
|
|
enable_peppol = fields.Boolean(compute='_compute_enable_peppol')
|
|
# technical field needed for computing a warning text about the peppol configuration
|
|
peppol_warning = fields.Char(
|
|
string="Warning",
|
|
compute="_compute_peppol_warning",
|
|
)
|
|
account_peppol_edi_mode_info = fields.Char(compute='_compute_account_peppol_edi_mode_info')
|
|
|
|
def _get_wizard_values(self):
|
|
# EXTENDS 'account'
|
|
values = super()._get_wizard_values()
|
|
values['send_peppol'] = self.checkbox_send_peppol
|
|
return values
|
|
|
|
@api.model
|
|
def _get_wizard_vals_restrict_to(self, only_options):
|
|
# EXTENDS 'account'
|
|
values = super()._get_wizard_vals_restrict_to(only_options)
|
|
return {
|
|
'checkbox_send_peppol': False,
|
|
**values,
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# COMPUTE METHODS
|
|
# -------------------------------------------------------------------------
|
|
|
|
@api.depends('enable_peppol')
|
|
def _compute_checkbox_send_peppol(self):
|
|
for wizard in self:
|
|
wizard.checkbox_send_peppol = wizard.enable_peppol and not wizard.peppol_warning
|
|
|
|
def _compute_checkbox_send_mail(self):
|
|
super()._compute_checkbox_send_mail()
|
|
for wizard in self:
|
|
if wizard.checkbox_send_mail and wizard.checkbox_send_peppol:
|
|
wizard.checkbox_send_mail = False
|
|
|
|
@api.depends('checkbox_send_peppol')
|
|
def _compute_checkbox_ubl_cii_xml(self):
|
|
# extends 'account_edi_ubl_cii'
|
|
super()._compute_checkbox_ubl_cii_xml()
|
|
for wizard in self:
|
|
if wizard.checkbox_send_peppol and wizard.enable_ubl_cii_xml and not wizard.checkbox_ubl_cii_xml:
|
|
wizard.checkbox_ubl_cii_xml = True
|
|
|
|
@api.depends('checkbox_send_peppol')
|
|
def _compute_mail_attachments_widget(self):
|
|
# EXTENDS 'account' - add depends
|
|
super()._compute_mail_attachments_widget()
|
|
|
|
@api.depends('move_ids')
|
|
def _compute_peppol_warning(self):
|
|
for wizard in self:
|
|
invalid_partners = wizard.move_ids.partner_id.commercial_partner_id.filtered(
|
|
lambda partner: not partner.account_peppol_is_endpoint_valid)
|
|
if not invalid_partners:
|
|
wizard.peppol_warning = False
|
|
else:
|
|
names = ', '.join(invalid_partners[:5].mapped('display_name'))
|
|
wizard.peppol_warning = _("The following partners are not correctly configured to receive Peppol documents. "
|
|
"Please check and verify their Peppol endpoint and the Electronic Invoicing format: "
|
|
"%s", names)
|
|
|
|
@api.depends('enable_ubl_cii_xml')
|
|
def _compute_enable_peppol(self):
|
|
for wizard in self:
|
|
# show peppol option if either the ubl option is available or any move already has a ubl file generated
|
|
# and moves are not processing/done and if partners have an edi format set to one that works for peppol
|
|
invalid_partners = wizard.move_ids.partner_id.commercial_partner_id.filtered(
|
|
lambda partner: not partner.is_peppol_edi_format
|
|
)
|
|
wizard.enable_peppol = (
|
|
wizard.company_id.account_peppol_proxy_state == 'active'
|
|
and (
|
|
wizard.enable_ubl_cii_xml
|
|
or any(m.ubl_cii_xml_id and m.peppol_move_state not in ('processing', 'done') for m in wizard.move_ids)
|
|
)
|
|
and not invalid_partners
|
|
)
|
|
|
|
@api.depends('company_id.account_edi_proxy_client_ids.edi_mode')
|
|
def _compute_account_peppol_edi_mode_info(self):
|
|
mode_strings = {
|
|
'test': _('Test'),
|
|
'demo': _('Demo'),
|
|
}
|
|
for wizard in self:
|
|
edi_user = wizard.company_id.sudo().account_edi_proxy_client_ids.filtered(
|
|
lambda usr: usr.proxy_type == 'peppol'
|
|
)
|
|
mode = mode_strings.get(edi_user.edi_mode)
|
|
wizard.account_peppol_edi_mode_info = f' ({mode})' if mode else ''
|
|
|
|
# -------------------------------------------------------------------------
|
|
# ATTACHMENTS
|
|
# -------------------------------------------------------------------------
|
|
|
|
def _needs_ubl_cii_placeholder(self):
|
|
return super()._needs_ubl_cii_placeholder() and not self.checkbox_send_peppol
|
|
|
|
# -------------------------------------------------------------------------
|
|
# BUSINESS ACTIONS
|
|
# -------------------------------------------------------------------------
|
|
|
|
@api.model
|
|
def _get_mail_layout(self):
|
|
# EXTENDS 'account'
|
|
# TODO remove the fallback in master
|
|
if self.env.ref('account_peppol.mail_notification_layout_with_responsible_signature_and_peppol', raise_if_not_found=False):
|
|
return 'account_peppol.mail_notification_layout_with_responsible_signature_and_peppol'
|
|
return super()._get_mail_layout()
|
|
|
|
def action_send_and_print(self, force_synchronous=False, allow_fallback_pdf=False, **kwargs):
|
|
# Extends 'account' to force ubl xml checkbox if sending via peppol
|
|
self.ensure_one()
|
|
|
|
if all([self.checkbox_send_peppol, self.enable_peppol, self.enable_ubl_cii_xml, not self.checkbox_ubl_cii_xml]):
|
|
self.checkbox_ubl_cii_xml = True
|
|
if self.checkbox_send_peppol and self.enable_peppol:
|
|
for move in self.move_ids:
|
|
if not move.peppol_move_state or move.peppol_move_state == 'ready':
|
|
move.sudo().peppol_move_state = 'to_send'
|
|
|
|
return super().action_send_and_print(force_synchronous=force_synchronous, allow_fallback_pdf=allow_fallback_pdf, **kwargs)
|
|
|
|
def _hook_if_errors(self, moves_data, from_cron=False, allow_fallback_pdf=False):
|
|
# Extends account
|
|
# to update `peppol_move_state` as `skipped` to show users that something went wrong
|
|
# because those moves that failed XML/PDF files generation are not sent via Peppol
|
|
moves_failed_file_generation = self.env['account.move']
|
|
for move, move_data in moves_data.items():
|
|
if move_data.get('send_peppol') and move_data.get('blocking_error'):
|
|
moves_failed_file_generation |= move
|
|
|
|
moves_failed_file_generation.peppol_move_state = 'skipped'
|
|
|
|
return super()._hook_if_errors(moves_data, from_cron=from_cron, allow_fallback_pdf=allow_fallback_pdf)
|
|
|
|
@api.model
|
|
def _call_web_service_after_invoice_pdf_render(self, invoices_data):
|
|
# Overrides 'account'
|
|
super()._call_web_service_after_invoice_pdf_render(invoices_data)
|
|
|
|
params = {'documents': []}
|
|
invoices_data_peppol = {}
|
|
for invoice, invoice_data in invoices_data.items():
|
|
if invoice_data.get('send_peppol'):
|
|
if invoice_data.get('ubl_cii_xml_attachment_values'):
|
|
xml_file = invoice_data['ubl_cii_xml_attachment_values']['raw']
|
|
filename = invoice_data['ubl_cii_xml_attachment_values']['name']
|
|
elif invoice.ubl_cii_xml_id and invoice.peppol_move_state not in ('processing', 'canceled', 'done'):
|
|
xml_file = invoice.ubl_cii_xml_id.raw
|
|
filename = invoice.ubl_cii_xml_id.name
|
|
else:
|
|
invoice.peppol_move_state = 'skipped'
|
|
continue
|
|
|
|
partner = invoice.partner_id.commercial_partner_id
|
|
if not partner.peppol_eas or not partner.peppol_endpoint:
|
|
invoice.peppol_move_state = 'error'
|
|
invoice_data['error'] = _('The partner is missing Peppol EAS and/or Endpoint identifier.')
|
|
continue
|
|
|
|
if not partner.account_peppol_is_endpoint_valid:
|
|
invoice.peppol_move_state = 'error'
|
|
invoice_data['error'] = _('Please verify partner configuration in partner settings.')
|
|
continue
|
|
|
|
receiver_identification = f"{partner.peppol_eas}:{partner.peppol_endpoint}"
|
|
params['documents'].append({
|
|
'filename': filename,
|
|
'receiver': receiver_identification,
|
|
'ubl': b64encode(xml_file).decode(),
|
|
})
|
|
invoices_data_peppol[invoice] = invoice_data
|
|
|
|
if not params['documents']:
|
|
return
|
|
|
|
edi_user = next(iter(invoices_data)).company_id.account_edi_proxy_client_ids.filtered(
|
|
lambda u: u.proxy_type == 'peppol')
|
|
|
|
try:
|
|
response = edi_user._make_request(
|
|
f"{edi_user._get_server_url()}/api/peppol/1/send_document",
|
|
params=params,
|
|
)
|
|
except AccountEdiProxyError as e:
|
|
for invoice, invoice_data in invoices_data_peppol.items():
|
|
invoice.peppol_move_state = 'error'
|
|
invoice_data['error'] = e.message
|
|
else:
|
|
if response.get('error'):
|
|
# at the moment the only error that can happen here is ParticipantNotReady error
|
|
for invoice, invoice_data in invoices_data_peppol.items():
|
|
invoice.peppol_move_state = 'error'
|
|
invoice_data['error'] = response['error']['message']
|
|
else:
|
|
# the response only contains message uuids,
|
|
# so we have to rely on the order to connect peppol messages to account.move
|
|
invoices = self.env['account.move']
|
|
for message, (invoice, invoice_data) in zip(response['messages'], invoices_data_peppol.items()):
|
|
invoice.peppol_message_uuid = message['message_uuid']
|
|
invoice.peppol_move_state = 'processing'
|
|
invoices |= invoice
|
|
log_message = _('The document has been sent to the Peppol Access Point for processing')
|
|
invoices._message_log_batch(bodies=dict((invoice.id, log_message) for invoice in invoices))
|
|
|
|
if self._can_commit():
|
|
self._cr.commit()
|