Odoo18-Base/addons/payment/wizards/payment_capture_wizard.py

153 lines
7.2 KiB
Python
Raw Permalink Normal View History

2025-01-06 10:57:38 +07:00
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import format_amount
class PaymentCaptureWizard(models.TransientModel):
_name = 'payment.capture.wizard'
_description = "Payment Capture Wizard"
transaction_ids = fields.Many2many( # All the source txs related to the capture request
comodel_name='payment.transaction',
default=lambda self: self.env.context.get('active_ids'),
readonly=True,
)
authorized_amount = fields.Monetary(
string="Authorized Amount", compute='_compute_authorized_amount'
)
captured_amount = fields.Monetary(string="Already Captured", compute='_compute_captured_amount')
voided_amount = fields.Monetary(string="Already Voided", compute='_compute_voided_amount')
available_amount = fields.Monetary(
string="Maximum Capture Allowed", compute='_compute_available_amount'
)
amount_to_capture = fields.Monetary(
compute='_compute_amount_to_capture', store=True, readonly=False
)
is_amount_to_capture_valid = fields.Boolean(compute='_compute_is_amount_to_capture_valid')
void_remaining_amount = fields.Boolean()
currency_id = fields.Many2one(related='transaction_ids.currency_id')
support_partial_capture = fields.Boolean(
help="Whether each of the transactions' provider supports the partial capture.",
compute='_compute_support_partial_capture',
compute_sudo=True,
)
has_draft_children = fields.Boolean(compute='_compute_has_draft_children')
has_remaining_amount = fields.Boolean(compute='_compute_has_remaining_amount')
#=== COMPUTE METHODS ===#
@api.depends('transaction_ids')
def _compute_authorized_amount(self):
for wizard in self:
wizard.authorized_amount = sum(wizard.transaction_ids.mapped('amount'))
@api.depends('transaction_ids')
def _compute_captured_amount(self):
for wizard in self:
full_capture_txs = wizard.transaction_ids.filtered(
lambda tx: tx.state == 'done' and not tx.child_transaction_ids
) # Transactions that have been fully captured in a single capture operation.
partial_capture_child_txs = wizard.transaction_ids.child_transaction_ids.filtered(
lambda tx: tx.state == 'done'
) # Transactions that represent a partial capture of their source transaction.
wizard.captured_amount = sum(
(full_capture_txs | partial_capture_child_txs).mapped('amount')
)
@api.depends('transaction_ids')
def _compute_voided_amount(self):
for wizard in self:
void_child_txs = wizard.transaction_ids.child_transaction_ids.filtered(
lambda tx: tx.state == 'cancel'
)
wizard.voided_amount = sum(void_child_txs.mapped('amount'))
@api.depends('authorized_amount', 'captured_amount', 'voided_amount')
def _compute_available_amount(self):
for wizard in self:
wizard.available_amount = wizard.authorized_amount \
- wizard.captured_amount \
- wizard.voided_amount
@api.depends('available_amount')
def _compute_amount_to_capture(self):
""" Set the default amount to capture to the amount available for capture. """
for wizard in self:
wizard.amount_to_capture = wizard.available_amount
@api.depends('amount_to_capture', 'available_amount')
def _compute_is_amount_to_capture_valid(self):
for wizard in self:
is_valid = 0 < wizard.amount_to_capture <= wizard.available_amount
wizard.is_amount_to_capture_valid = is_valid
@api.depends('transaction_ids')
def _compute_support_partial_capture(self):
for wizard in self:
wizard.support_partial_capture = all(
tx.provider_id.support_manual_capture == 'partial' for tx in wizard.transaction_ids
)
@api.depends('transaction_ids')
def _compute_has_draft_children(self):
for wizard in self:
wizard.has_draft_children = bool(wizard.transaction_ids.child_transaction_ids.filtered(
lambda tx: tx.state == 'draft'
))
@api.depends('available_amount', 'amount_to_capture')
def _compute_has_remaining_amount(self):
for wizard in self:
wizard.has_remaining_amount = wizard.amount_to_capture < wizard.available_amount
if not wizard.has_remaining_amount:
wizard.void_remaining_amount = False
#=== CONSTRAINT METHODS ===#
@api.constrains('amount_to_capture')
def _check_amount_to_capture_within_boundaries(self):
for wizard in self:
if not wizard.is_amount_to_capture_valid:
formatted_amount = format_amount(
self.env, wizard.available_amount, wizard.currency_id
)
raise ValidationError(_(
"The amount to capture must be positive and cannot be superior to %s.",
formatted_amount
))
if not wizard.support_partial_capture \
and wizard.amount_to_capture != wizard.available_amount:
raise ValidationError(_(
"Some of the transactions you intend to capture can only be captured in full. "
"Handle the transactions individually to capture a partial amount."
))
#=== ACTION METHODS ===#
def action_capture(self):
for wizard in self:
remaining_amount_to_capture = wizard.amount_to_capture
for source_tx in wizard.transaction_ids.filtered(lambda tx: tx.state == 'authorized'):
partial_capture_child_txs = wizard.transaction_ids.child_transaction_ids.filtered(
lambda tx: tx.source_transaction_id == source_tx and tx.state == 'done'
) # We can void all the remaining amount only at once => don't check cancel state.
source_tx_remaining_amount = source_tx.currency_id.round(
source_tx.amount - sum(partial_capture_child_txs.mapped('amount'))
)
if remaining_amount_to_capture:
amount_to_capture = min(source_tx_remaining_amount, remaining_amount_to_capture)
# In sudo mode because we need to be able to read on provider fields.
source_tx.sudo()._send_capture_request(amount_to_capture=amount_to_capture)
remaining_amount_to_capture -= amount_to_capture
source_tx_remaining_amount -= amount_to_capture
if source_tx_remaining_amount and wizard.void_remaining_amount:
# The source tx isn't fully captured and the user wants to void the remaining.
# In sudo mode because we need to be able to read on provider fields.
source_tx.sudo()._send_void_request(amount_to_void=source_tx_remaining_amount)
elif not remaining_amount_to_capture and not wizard.void_remaining_amount:
# The amount to capture has been completely captured.
break # Skip the remaining transactions.