# -*- 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()