odoo18/addons/account_peppol/models/account_move_send.py

236 lines
12 KiB
Python

from base64 import b64encode
from datetime import timedelta
from odoo import fields, models, _
from odoo.addons.account.models.company import PEPPOL_LIST
from odoo.addons.account_edi_proxy_client.models.account_edi_proxy_user import AccountEdiProxyError
class AccountMoveSend(models.AbstractModel):
_inherit = 'account.move.send'
# -------------------------------------------------------------------------
# ALERTS
# -------------------------------------------------------------------------
def _get_alerts(self, moves, moves_data):
# EXTENDS 'account'
def peppol_partner(moves):
return moves.partner_id.commercial_partner_id
def filter_peppol_state(moves, states):
return peppol_partner(moves.filtered(
lambda m: self.env['res.partner']._get_peppol_verification_state(
peppol_partner(m).peppol_endpoint,
peppol_partner(m).peppol_eas,
moves_data[m]['invoice_edi_format']) in states))
alerts = super()._get_alerts(moves, moves_data)
# Check for invalid peppol partners.
peppol_moves = moves.filtered(lambda m: 'peppol' in moves_data[m]['sending_methods'])
invalid_partners = filter_peppol_state(peppol_moves, ['not_valid_format'])
if invalid_partners and not 'account_edi_ubl_cii_configure_partner' in alerts:
alerts['account_peppol_warning_partner'] = {
'message': _("Customer is on Peppol but did not enable receiving documents."),
'action_text': _("View Partner(s)"),
'action': invalid_partners._get_records_action(name=_("Check Partner(s)")),
}
not_peppol_moves = moves.filtered(lambda m: 'peppol' not in moves_data[m]['sending_methods'])
what_is_peppol_alert = {
'level': 'info',
'action_text': _("Why should you use it ?"),
'action': {
'name': _("Why should I use PEPPOL ?"),
'type': 'ir.actions.client',
'tag': 'account_peppol.what_is_peppol',
'target': 'new',
'context': {
'footer': False,
'dialog_size': 'medium',
'action_on_activate': self.action_what_is_peppol_activate(moves),
},
},
}
info_always_on_countries = {'BE', 'FI', 'LU', 'LV', 'NL', 'NO', 'SE'}
can_send = self.env['account_edi_proxy_client.user']._get_can_send_domain()
any_moves_not_sent_peppol = any(move.peppol_move_state not in ('processing', 'done') for move in moves)
always_on_companies = moves.company_id.filtered(
lambda c: c.country_code in info_always_on_countries and c.account_peppol_proxy_state not in can_send
)
if always_on_companies and any_moves_not_sent_peppol and not filter_peppol_state(moves, ['not_valid', 'not_verified']):
alerts.pop('account_edi_ubl_cii_configure_company', False)
alerts['account_peppol_what_is_peppol'] = {
'message': _("You can send this invoice electronically via Peppol."),
**what_is_peppol_alert,
}
elif (peppol_not_selected_partners := filter_peppol_state(not_peppol_moves, ['valid'])) and any_moves_not_sent_peppol:
# Check for not peppol partners that are on the network.
if len(peppol_not_selected_partners) == 1:
alerts['account_peppol_partner_want_peppol'] = {
'message': _(
"%s has requested electronic invoices reception on Peppol.",
peppol_not_selected_partners.display_name
),
**what_is_peppol_alert,
}
return alerts
# -------------------------------------------------------------------------
# SENDING METHODS
# -------------------------------------------------------------------------
def _get_default_invoice_edi_format(self, move, **kwargs) -> str:
# EXTENDS 'account' - default on bis3 if Peppol is set but no format on the partner
invoice_edi_format = super()._get_default_invoice_edi_format(move, **kwargs)
if 'peppol' in kwargs.get('sending_methods', {}):
return move.partner_id.with_company(move.company_id)._get_peppol_edi_format()
return invoice_edi_format
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 _do_peppol_pre_send(self, moves):
if len(moves.company_id) == 1:
can_send = self.env['account_edi_proxy_client.user']._get_can_send_domain()
if moves.company_id.account_peppol_proxy_state not in can_send:
registration_wizard = self.env['peppol.registration'].create({'company_id': moves.company_id.id})
return registration_wizard._action_open_peppol_form(reopen=False)
for move in moves:
if move.peppol_move_state in ('ready', False):
move.peppol_move_state = 'to_send'
def _is_applicable_to_company(self, method, company):
# EXTENDS 'account'
if method == 'peppol':
return company.country_code in PEPPOL_LIST and company.account_peppol_proxy_state != 'rejected'
else:
return super()._is_applicable_to_company(method, company)
def _is_applicable_to_move(self, method, move, **move_data):
# EXTENDS 'account'
if method == 'peppol':
partner = move.partner_id.commercial_partner_id.with_company(move.company_id)
invoice_edi_format = move_data.get('invoice_edi_format') or partner._get_peppol_edi_format()
return all([
self._is_applicable_to_company(method, move.company_id),
self.env['res.partner']._get_peppol_verification_state(partner.peppol_endpoint, partner.peppol_eas, invoice_edi_format) == 'valid',
move.company_id.account_peppol_proxy_state != 'rejected',
move._need_ubl_cii_xml(invoice_edi_format)
or move.ubl_cii_xml_id and move.peppol_move_state not in ('processing', 'done'),
])
else:
return super()._is_applicable_to_move(method, move, **move_data)
def _hook_if_errors(self, moves_data, allow_raising=True):
# EXTENDS 'account'
# to update `peppol_move_state` as `error` 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 'peppol' in move_data['sending_methods'] and move_data.get('blocking_error'):
moves_failed_file_generation |= move
moves_failed_file_generation.peppol_move_state = 'error'
return super()._hook_if_errors(moves_data, allow_raising=allow_raising)
def _call_web_service_after_invoice_pdf_render(self, invoices_data):
# EXTENDS 'account'
super()._call_web_service_after_invoice_pdf_render(invoices_data)
params = {'documents': []}
invoices_data_peppol = {}
for invoice, invoice_data in invoices_data.items():
partner = invoice.partner_id.commercial_partner_id.with_company(invoice.company_id)
if 'peppol' in invoice_data['sending_methods']:
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 self.env['res.partner']._get_peppol_verification_state(partner.peppol_endpoint, partner.peppol_eas, invoice_data['invoice_edi_format']) != 'valid':
invoice.peppol_move_state = 'error'
invoice_data['error'] = _('Please verify partner configuration in partner settings.')
continue
if not self._is_applicable_to_move('peppol', invoice, **invoice_data):
continue
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', 'done'):
xml_file = invoice.ubl_cii_xml_id.raw
filename = invoice.ubl_cii_xml_id.name
else:
invoice.peppol_move_state = 'error'
builder = invoice.partner_id.commercial_partner_id._get_edi_builder(invoice_data['invoice_edi_format'])
invoice_data['error'] = _(
"Errors occurred while creating the EDI document (format: %s):",
builder._description
)
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._call_peppol_proxy(
"/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={invoice.id: log_message for invoice in invoices})
self.env.ref('account_peppol.ir_cron_peppol_get_message_status')._trigger(at=fields.Datetime.now() + timedelta(minutes=5))
if self._can_commit():
self._cr.commit()
def action_what_is_peppol_activate(self, moves):
companies = moves.company_id
can_send = self.env['account_edi_proxy_client.user']._get_can_send_domain()
if len(companies) == 1 and companies.account_peppol_proxy_state not in can_send:
action = self.env['peppol.registration']._action_open_peppol_form()
action['context'].update({
'active_model': 'account.move',
'active_ids': moves.ids,
'dialog_size': 'medium',
})
return action
else:
return moves.action_send_and_print()