mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-22 20:42:08 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -11,7 +11,7 @@ from odoo.addons.payment.controllers import portal as payment_portal
|
|||
|
||||
class PaymentPortal(payment_portal.PaymentPortal):
|
||||
|
||||
@route('/invoice/transaction/<int:invoice_id>', type='json', auth='public')
|
||||
@route('/invoice/transaction/<int:invoice_id>', type='jsonrpc', auth='public')
|
||||
def invoice_transaction(self, invoice_id, access_token, **kwargs):
|
||||
""" Create a draft transaction and return its processing values.
|
||||
|
||||
|
|
@ -30,13 +30,43 @@ class PaymentPortal(payment_portal.PaymentPortal):
|
|||
except AccessError:
|
||||
raise ValidationError(_("The access token is invalid."))
|
||||
|
||||
kwargs['reference_prefix'] = None # Allow the reference to be computed based on the invoice
|
||||
logged_in = not request.env.user._is_public()
|
||||
partner = request.env.user.partner_id if logged_in else invoice_sudo.partner_id
|
||||
kwargs['partner_id'] = partner.id
|
||||
kwargs.pop('custom_create_values', None) # Don't allow passing arbitrary create values
|
||||
partner_sudo = request.env.user.partner_id if logged_in else invoice_sudo.partner_id
|
||||
self._validate_transaction_kwargs(kwargs, additional_allowed_keys={'name_next_installment'})
|
||||
return self._process_transaction(partner_sudo.id, invoice_sudo.currency_id.id, [invoice_id], False, **kwargs)
|
||||
|
||||
@route('/invoice/transaction/overdue', type='jsonrpc', auth='public')
|
||||
def overdue_invoices_transaction(self, payment_reference, **kwargs):
|
||||
""" Create a draft transaction for overdue invoices and return its processing values.
|
||||
|
||||
:param str payment_reference: The reference to the current payment
|
||||
:param dict kwargs: Locally unused data passed to `_create_transaction`
|
||||
:return: The mandatory values for the processing of the transaction
|
||||
:rtype: dict
|
||||
:raise: ValidationError if the user is not logged in, or all the overdue invoices don't share the same currency.
|
||||
"""
|
||||
logged_in = not request.env.user._is_public()
|
||||
if not logged_in:
|
||||
raise ValidationError(_("Please log in to pay your overdue invoices"))
|
||||
partner = request.env.user.partner_id
|
||||
overdue_invoices = request.env['account.move'].search(self._get_overdue_invoices_domain())
|
||||
currencies = overdue_invoices.mapped('currency_id')
|
||||
if not all(currency == currencies[0] for currency in currencies):
|
||||
raise ValidationError(_("Impossible to pay all the overdue invoices if they don't share the same currency."))
|
||||
self._validate_transaction_kwargs(kwargs)
|
||||
return self._process_transaction(partner.id, currencies[0].id, overdue_invoices.ids, payment_reference, **kwargs)
|
||||
|
||||
def _process_transaction(self, partner_id, currency_id, invoice_ids, payment_reference, **kwargs):
|
||||
kwargs.update({
|
||||
'currency_id': currency_id,
|
||||
'partner_id': partner_id,
|
||||
'reference_prefix': payment_reference,
|
||||
}) # Inject the create values taken from the invoice into the kwargs.
|
||||
tx_sudo = self._create_transaction(
|
||||
custom_create_values={'invoice_ids': [Command.set([invoice_id])]}, **kwargs,
|
||||
custom_create_values={
|
||||
'invoice_ids': [Command.set(invoice_ids)],
|
||||
},
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return tx_sudo._get_processing_values()
|
||||
|
|
@ -47,9 +77,6 @@ class PaymentPortal(payment_portal.PaymentPortal):
|
|||
def payment_pay(self, *args, amount=None, invoice_id=None, access_token=None, **kwargs):
|
||||
""" Override of `payment` to replace the missing transaction values by that of the invoice.
|
||||
|
||||
This is necessary for the reconciliation as all transaction values, excepted the amount,
|
||||
need to match exactly that of the invoice.
|
||||
|
||||
:param str amount: The (possibly partial) amount to pay used to check the access token.
|
||||
:param str invoice_id: The invoice for which a payment id made, as an `account.move` id.
|
||||
:param str access_token: The access token used to authenticate the partner.
|
||||
|
|
@ -73,48 +100,53 @@ class PaymentPortal(payment_portal.PaymentPortal):
|
|||
raise ValidationError(_("The provided parameters are invalid."))
|
||||
|
||||
kwargs.update({
|
||||
# To display on the payment form; will be later overwritten when creating the tx.
|
||||
'reference': invoice_sudo.name,
|
||||
# To fix the currency if incorrect and avoid mismatches when creating the tx.
|
||||
'currency_id': invoice_sudo.currency_id.id,
|
||||
# To fix the partner if incorrect and avoid mismatches when creating the tx.
|
||||
'partner_id': invoice_sudo.partner_id.id,
|
||||
'company_id': invoice_sudo.company_id.id,
|
||||
'invoice_id': invoice_id,
|
||||
})
|
||||
return super().payment_pay(*args, amount=amount, access_token=access_token, **kwargs)
|
||||
|
||||
def _get_custom_rendering_context_values(self, invoice_id=None, **kwargs):
|
||||
""" Override of `payment` to add the invoice id in the custom rendering context values.
|
||||
def _get_extra_payment_form_values(self, invoice_id=None, access_token=None, **kwargs):
|
||||
""" Override of `payment` to reroute the payment flow to the portal view of the invoice.
|
||||
|
||||
:param int invoice_id: The invoice for which a payment id made, as an `account.move` id.
|
||||
:param dict kwargs: Optional data. This parameter is not used here.
|
||||
:param str invoice_id: The invoice for which a payment id made, as an `account.move` id.
|
||||
:param str access_token: The portal or payment access token, respectively if we are in a
|
||||
portal or payment link flow.
|
||||
:return: The extended rendering context values.
|
||||
:rtype: dict
|
||||
"""
|
||||
rendering_context_values = super()._get_custom_rendering_context_values(
|
||||
invoice_id=invoice_id, **kwargs
|
||||
form_values = super()._get_extra_payment_form_values(
|
||||
invoice_id=invoice_id, access_token=access_token, **kwargs
|
||||
)
|
||||
if invoice_id:
|
||||
rendering_context_values['invoice_id'] = invoice_id
|
||||
invoice_id = self._cast_as_int(invoice_id)
|
||||
|
||||
try: # Check document access against what could be a portal access token.
|
||||
invoice_sudo = self._document_check_access('account.move', invoice_id, access_token)
|
||||
except AccessError: # It is a payment access token computed on the payment context.
|
||||
if not payment_utils.check_access_token(
|
||||
access_token,
|
||||
kwargs.get('partner_id'),
|
||||
kwargs.get('amount'),
|
||||
kwargs.get('currency_id'),
|
||||
):
|
||||
raise
|
||||
invoice_sudo = request.env['account.move'].sudo().browse(invoice_id)
|
||||
|
||||
# Interrupt the payment flow if the invoice has been canceled.
|
||||
invoice_sudo = request.env['account.move'].sudo().browse(invoice_id)
|
||||
if invoice_sudo.state == 'cancel':
|
||||
rendering_context_values['amount'] = 0.0
|
||||
form_values['amount'] = 0.0
|
||||
|
||||
return rendering_context_values
|
||||
|
||||
def _create_transaction(self, *args, invoice_id=None, custom_create_values=None, **kwargs):
|
||||
""" Override of `payment` to add the invoice id in the custom create values.
|
||||
|
||||
:param int invoice_id: The invoice for which a payment id made, as an `account.move` id.
|
||||
:param dict custom_create_values: Additional create values overwriting the default ones.
|
||||
:param dict kwargs: Optional data. This parameter is not used here.
|
||||
:return: The result of the parent method.
|
||||
:rtype: recordset of `payment.transaction`
|
||||
"""
|
||||
if invoice_id:
|
||||
if custom_create_values is None:
|
||||
custom_create_values = {}
|
||||
custom_create_values['invoice_ids'] = [Command.set([int(invoice_id)])]
|
||||
|
||||
return super()._create_transaction(
|
||||
*args, invoice_id=invoice_id, custom_create_values=custom_create_values, **kwargs
|
||||
)
|
||||
# Reroute the next steps of the payment flow to the portal view of the invoice.
|
||||
form_values.update({
|
||||
'transaction_route': f'/invoice/transaction/{invoice_id}',
|
||||
'landing_route': f'{invoice_sudo.access_url}'
|
||||
f'?access_token={invoice_sudo._portal_ensure_token()}',
|
||||
'access_token': invoice_sudo.access_token,
|
||||
})
|
||||
return form_values
|
||||
|
|
|
|||
|
|
@ -1,68 +1,176 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, fields, http
|
||||
from odoo.exceptions import AccessError, MissingError, ValidationError
|
||||
from odoo.http import request
|
||||
|
||||
from odoo.addons.account.controllers import portal
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
from odoo.addons.payment.controllers.portal import PaymentPortal
|
||||
from odoo.addons.portal.controllers.portal import _build_url_w_params
|
||||
|
||||
|
||||
class PortalAccount(portal.PortalAccount):
|
||||
class PortalAccount(portal.PortalAccount, PaymentPortal):
|
||||
|
||||
def _invoice_get_page_view_values(self, invoice, access_token, **kwargs):
|
||||
values = super()._invoice_get_page_view_values(invoice, access_token, **kwargs)
|
||||
def _invoice_get_page_view_values(self, invoice, access_token, payment=False, amount=None, **kwargs):
|
||||
# EXTENDS account
|
||||
|
||||
values = super()._invoice_get_page_view_values(invoice, access_token, amount=amount, **kwargs)
|
||||
|
||||
if not invoice._has_to_be_paid():
|
||||
# Do not compute payment-related stuff if given invoice doesn't have to be paid.
|
||||
return {
|
||||
**values,
|
||||
'payment': payment, # We want to show the dialog even when everything has been paid (with a custom message)
|
||||
}
|
||||
|
||||
epd = values.get('epd_discount_amount_currency', 0.0)
|
||||
discounted_amount = invoice.amount_residual - epd
|
||||
|
||||
common_view_values = self._get_common_page_view_values(
|
||||
invoices_data={
|
||||
'partner': invoice.partner_id,
|
||||
'company': invoice.company_id,
|
||||
'total_amount': invoice.amount_total,
|
||||
'currency': invoice.currency_id,
|
||||
'amount_residual': discounted_amount,
|
||||
'landing_route': invoice.get_portal_url(),
|
||||
'transaction_route': f'/invoice/transaction/{invoice.id}',
|
||||
},
|
||||
access_token=access_token,
|
||||
**kwargs)
|
||||
|
||||
amount_custom = float(amount or 0.0)
|
||||
values |= {
|
||||
**common_view_values,
|
||||
'amount_custom': amount_custom,
|
||||
'payment': payment,
|
||||
'invoice_id': invoice.id,
|
||||
'invoice_name': invoice.name,
|
||||
'show_epd': epd,
|
||||
}
|
||||
return values
|
||||
|
||||
@http.route(['/my/invoices/overdue'], type='http', auth='public', methods=['GET'], website=True, sitemap=False)
|
||||
def portal_my_overdue_invoices(self, access_token=None, **kw):
|
||||
try:
|
||||
request.env['account.move'].check_access('read')
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
overdue_invoices = request.env['account.move'].search(self._get_overdue_invoices_domain())
|
||||
|
||||
values = self._overdue_invoices_get_page_view_values(overdue_invoices, **kw)
|
||||
return request.render("account_payment.portal_overdue_invoices_page", values) if 'payment' in values else request.redirect('/my/invoices')
|
||||
|
||||
def _overdue_invoices_get_page_view_values(self, overdue_invoices, **kwargs):
|
||||
values = {'page_name': 'overdue_invoices'}
|
||||
|
||||
if len(overdue_invoices) == 0:
|
||||
return values
|
||||
|
||||
first_invoice = overdue_invoices[0]
|
||||
partner = first_invoice.partner_id
|
||||
company = first_invoice.company_id
|
||||
currency = first_invoice.currency_id
|
||||
|
||||
if any(invoice.partner_id != partner for invoice in overdue_invoices):
|
||||
raise ValidationError(_("Overdue invoices should share the same partner."))
|
||||
if any(invoice.company_id != company for invoice in overdue_invoices):
|
||||
raise ValidationError(_("Overdue invoices should share the same company."))
|
||||
if any(invoice.currency_id != currency for invoice in overdue_invoices):
|
||||
raise ValidationError(_("Overdue invoices should share the same currency."))
|
||||
|
||||
total_amount = sum(overdue_invoices.mapped('amount_total'))
|
||||
amount_residual = sum(overdue_invoices.mapped('amount_residual'))
|
||||
batch_name = company.get_next_batch_payment_communication() if len(overdue_invoices) > 1 else first_invoice.name
|
||||
values.update({
|
||||
'payment': {
|
||||
'date': fields.Date.today(),
|
||||
'reference': batch_name,
|
||||
'amount': total_amount,
|
||||
'currency': currency,
|
||||
},
|
||||
'amount': total_amount,
|
||||
})
|
||||
|
||||
common_view_values = self._get_common_page_view_values(
|
||||
invoices_data={
|
||||
'partner': partner,
|
||||
'company': company,
|
||||
'total_amount': total_amount,
|
||||
'currency': currency,
|
||||
'amount_residual': amount_residual,
|
||||
'payment_reference': batch_name,
|
||||
'landing_route': '/my/invoices/',
|
||||
'transaction_route': '/invoice/transaction/overdue',
|
||||
},
|
||||
**kwargs)
|
||||
values |= common_view_values
|
||||
return values
|
||||
|
||||
def _get_common_page_view_values(self, invoices_data, access_token=None, **kwargs):
|
||||
logged_in = not request.env.user._is_public()
|
||||
# We set partner_id to the partner id of the current user if logged in, otherwise we set it
|
||||
# to the invoice partner id. We do this to ensure that payment tokens are assigned to the
|
||||
# correct partner and to avoid linking tokens to the public user.
|
||||
partner_sudo = request.env.user.partner_id if logged_in else invoice.partner_id
|
||||
invoice_company = invoice.company_id or request.env.company
|
||||
partner_sudo = request.env.user.partner_id if logged_in else invoices_data['partner']
|
||||
invoice_company = invoices_data['company'] or request.env.company
|
||||
|
||||
availability_report = {}
|
||||
# Select all the payment methods and tokens that match the payment context.
|
||||
providers_sudo = request.env['payment.provider'].sudo()._get_compatible_providers(
|
||||
invoice_company.id,
|
||||
partner_sudo.id,
|
||||
invoice.amount_total,
|
||||
currency_id=invoice.currency_id.id
|
||||
) # In sudo mode to read the fields of providers and partner (if not logged in)
|
||||
tokens = request.env['payment.token'].search(
|
||||
[('provider_id', 'in', providers_sudo.ids), ('partner_id', '=', partner_sudo.id)]
|
||||
) # Tokens are cleared at the end if the user is not logged in
|
||||
invoices_data['total_amount'],
|
||||
currency_id=invoices_data['currency'].id,
|
||||
report=availability_report,
|
||||
**kwargs,
|
||||
) # In sudo mode to read the fields of providers and partner (if logged out).
|
||||
payment_methods_sudo = request.env['payment.method'].sudo()._get_compatible_payment_methods(
|
||||
providers_sudo.ids,
|
||||
partner_sudo.id,
|
||||
currency_id=invoices_data['currency'].id,
|
||||
report=availability_report,
|
||||
) # In sudo mode to read the fields of providers.
|
||||
tokens_sudo = request.env['payment.token'].sudo()._get_available_tokens(
|
||||
providers_sudo.ids, partner_sudo.id
|
||||
) # In sudo mode to read the partner's tokens (if logged out) and provider fields.
|
||||
|
||||
# Make sure that the partner's company matches the invoice's company.
|
||||
if not PaymentPortal._can_partner_pay_in_company(partner_sudo, invoice_company):
|
||||
providers_sudo = request.env['payment.provider'].sudo()
|
||||
tokens = request.env['payment.token']
|
||||
company_mismatch = not PaymentPortal._can_partner_pay_in_company(
|
||||
partner_sudo, invoice_company
|
||||
)
|
||||
|
||||
fees_by_provider = {
|
||||
pro_sudo: pro_sudo._compute_fees(
|
||||
invoice.amount_residual, invoice.currency_id, invoice.partner_id.country_id
|
||||
) for pro_sudo in providers_sudo.filtered('fees_active')
|
||||
portal_page_values = {
|
||||
'company_mismatch': company_mismatch,
|
||||
'expected_company': invoice_company,
|
||||
}
|
||||
values.update({
|
||||
'providers': providers_sudo,
|
||||
'tokens': tokens,
|
||||
'fees_by_provider': fees_by_provider,
|
||||
'show_tokenize_input': PaymentPortal._compute_show_tokenize_input_mapping(
|
||||
providers_sudo, logged_in=logged_in
|
||||
payment_form_values = {
|
||||
'show_tokenize_input_mapping': PaymentPortal._compute_show_tokenize_input_mapping(
|
||||
providers_sudo
|
||||
),
|
||||
'amount': invoice.amount_residual,
|
||||
'currency': invoice.currency_id,
|
||||
}
|
||||
payment_context = {
|
||||
'currency': invoices_data['currency'],
|
||||
'partner_id': partner_sudo.id,
|
||||
'providers_sudo': providers_sudo,
|
||||
'payment_methods_sudo': payment_methods_sudo,
|
||||
'tokens_sudo': tokens_sudo,
|
||||
'availability_report': availability_report,
|
||||
'transaction_route': invoices_data['transaction_route'],
|
||||
'landing_route': invoices_data['landing_route'],
|
||||
'access_token': access_token,
|
||||
'transaction_route': f'/invoice/transaction/{invoice.id}/',
|
||||
'landing_route': _build_url_w_params(invoice.access_url, {'access_token': access_token})
|
||||
})
|
||||
if not logged_in:
|
||||
# Don't display payment tokens of the invoice partner if the user is not logged in, but
|
||||
# inform that logging in will make them available.
|
||||
values.update({
|
||||
'existing_token': bool(tokens),
|
||||
'tokens': request.env['payment.token'],
|
||||
})
|
||||
'payment_reference': invoices_data.get('payment_reference', False),
|
||||
}
|
||||
# Merge the dictionaries while allowing the redefinition of keys.
|
||||
values = portal_page_values | payment_form_values | payment_context | self._get_extra_payment_form_values(**kwargs)
|
||||
return values
|
||||
|
||||
@http.route()
|
||||
def portal_my_invoice_detail(self, invoice_id, payment_token=None, amount=None, **kw):
|
||||
# EXTENDS account
|
||||
|
||||
# If we have a custom payment amount, make sure it hasn't been tampered with
|
||||
if amount and not payment_utils.check_access_token(payment_token, invoice_id, amount):
|
||||
return request.redirect('/my')
|
||||
return super().portal_my_invoice_detail(invoice_id, amount=amount, **kw)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue