File: payment_capture_wizard.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (152 lines) | stat: -rw-r--r-- 7,421 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# 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.