19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:07 +01:00
parent ba20ce7443
commit 768b70e05e
2357 changed files with 1057103 additions and 712486 deletions

View file

@ -1,8 +1,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from odoo import api, fields, models
from odoo.tools import format_date, str2bool
from odoo.tools.translate import _
from odoo.addons.payment import utils as payment_utils
from odoo.tools.image import image_data_uri
class AccountMove(models.Model):
@ -14,7 +19,11 @@ class AccountMove(models.Model):
readonly=True, copy=False)
authorized_transaction_ids = fields.Many2many(
string="Authorized Transactions", comodel_name='payment.transaction',
compute='_compute_authorized_transaction_ids', readonly=True, copy=False)
compute='_compute_authorized_transaction_ids', readonly=True, copy=False,
compute_sudo=True)
transaction_count = fields.Integer(
string="Transaction Count", compute='_compute_transaction_count'
)
amount_paid = fields.Monetary(
string="Amount paid",
compute='_compute_amount_paid'
@ -27,6 +36,11 @@ class AccountMove(models.Model):
lambda tx: tx.state == 'authorized'
)
@api.depends('transaction_ids')
def _compute_transaction_count(self):
for invoice in self:
invoice.transaction_count = len(invoice.transaction_ids)
@api.depends('transaction_ids')
def _compute_amount_paid(self):
""" Sum all the transaction amount for which state is in 'authorized' or 'done'
@ -41,34 +55,72 @@ class AccountMove(models.Model):
def _has_to_be_paid(self):
self.ensure_one()
transactions = self.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))
pending_manual_txs = transactions.filtered(lambda tx: tx.state == 'pending' and tx.provider_code in ('none', 'custom'))
return bool(
(
self.amount_residual
or not transactions
pending_transactions = transactions.filtered(
lambda tx: tx.state in {'pending', 'authorized'}
and tx.provider_code not in {'none', 'custom'})
enabled_feature = str2bool(
self.env['ir.config_parameter'].sudo().get_param(
'account_payment.enable_portal_payment'
)
)
return enabled_feature and bool(
(self.amount_residual or not transactions)
and self.state == 'posted'
and self.payment_state in ('not_paid', 'partial')
and self.payment_state in ('not_paid', 'in_payment', 'partial')
and not self.currency_id.is_zero(self.amount_residual)
and self.amount_total
and self.move_type == 'out_invoice'
and (pending_manual_txs or not transactions or self.amount_paid < self.amount_total)
and not pending_transactions
)
def _get_online_payment_error(self):
"""
Returns the appropriate error message to be displayed if _has_to_be_paid() method returns False.
"""
self.ensure_one()
transactions = self.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))
pending_transactions = transactions.filtered(
lambda tx: tx.state in {'pending', 'authorized'}
and tx.provider_code not in {'none', 'custom'})
enabled_feature = str2bool(
self.env['ir.config_parameter'].sudo().get_param(
'account_payment.enable_portal_payment'
)
)
errors = []
if not enabled_feature:
errors.append(_("This invoice cannot be paid online."))
if transactions or self.currency_id.is_zero(self.amount_residual):
errors.append(_("There is no amount to be paid."))
if self.state != 'posted':
errors.append(_("This invoice isn't posted."))
if self.currency_id.is_zero(self.amount_residual):
errors.append(_("This invoice has already been paid."))
if self.move_type != 'out_invoice':
errors.append(_("This is not an outgoing invoice."))
if pending_transactions:
errors.append(_("There are pending transactions for this invoice."))
return '\n'.join(errors)
@api.private
def get_portal_last_transaction(self):
self.ensure_one()
return self.with_context(active_test=False).transaction_ids._get_last()
return self.with_context(active_test=False).sudo().transaction_ids._get_last()
def payment_action_capture(self):
""" Capture all transactions linked to this invoice. """
self.ensure_one()
payment_utils.check_rights_on_recordset(self)
# In sudo mode because we need to be able to read on provider fields.
self.authorized_transaction_ids.sudo().action_capture()
# In sudo mode to bypass the checks on the rights on the transactions.
return self.sudo().transaction_ids.action_capture()
def payment_action_void(self):
""" Void all transactions linked to this invoice. """
payment_utils.check_rights_on_recordset(self)
# In sudo mode because we need to be able to read on provider fields.
self.authorized_transaction_ids.sudo().action_void()
# In sudo mode to bypass the checks on the rights on the transactions.
self.sudo().authorized_transaction_ids.action_void()
def action_view_payment_transactions(self):
action = self.env['ir.actions.act_window']._for_xml_id('payment.action_payment_transaction')
@ -83,11 +135,48 @@ class AccountMove(models.Model):
return action
def _get_default_payment_link_values(self):
self.ensure_one()
next_payment_values = self._get_invoice_next_payment_values()
amount_max = next_payment_values.get('amount_due')
additional_info = {}
open_installments = []
installment_state = next_payment_values.get('installment_state')
next_amount_to_pay = next_payment_values.get('next_amount_to_pay')
if installment_state in ('next', 'overdue'):
open_installments = []
for installment in next_payment_values.get('not_reconciled_installments'):
data = {
'type': installment['type'],
'number': installment['number'],
'amount': installment['amount_residual_currency_unsigned'],
'date_maturity': format_date(self.env, installment['date_maturity']),
}
open_installments.append(data)
elif installment_state == 'epd':
amount_max = next_amount_to_pay # with epd, next_amount_to_pay is the invoice amount residual
additional_info.update({
'has_eligible_epd': True,
'discount_date': next_payment_values.get('discount_date')
})
return {
'description': self.payment_reference,
'amount': self.amount_residual,
'currency_id': self.currency_id.id,
'partner_id': self.partner_id.id,
'amount_max': self.amount_residual,
'open_installments': open_installments,
'amount': next_amount_to_pay,
'amount_max': amount_max,
**additional_info
}
def _generate_portal_payment_qr(self):
self.ensure_one()
portal_url = self._get_portal_payment_link()
barcode = self.env['ir.actions.report'].barcode(barcode_type="QR", value=portal_url, width=128, height=128, quiet=False)
return image_data_uri(base64.b64encode(barcode))
def _get_portal_payment_link(self):
self.ensure_one()
payment_link_wizard = self.env['payment.link.wizard'].with_context(
active_id=self.id, active_model=self._name
).create({})
return payment_link_wizard.link