diff --git a/addons/payment/data/payment_acquirer_data.xml b/addons/payment/data/payment_acquirer_data.xml index 0271aebdd52..174c8b4a998 100644 --- a/addons/payment/data/payment_acquirer_data.xml +++ b/addons/payment/data/payment_acquirer_data.xml @@ -155,7 +155,6 @@ Custom - none diff --git a/addons/payment/models/account_payment.py b/addons/payment/models/account_payment.py index 60ddcc60b4d..5a15e71e6e5 100644 --- a/addons/payment/models/account_payment.py +++ b/addons/payment/models/account_payment.py @@ -10,7 +10,7 @@ class AccountPayment(models.Model): _inherit = 'account.payment' payment_transaction_id = fields.Many2one('payment.transaction', string="Payment Transaction") - payment_token_id = fields.Many2one('payment.token', string="Saved payment token", domain=[('acquirer_id.auto_confirm', '!=', 'authorize')], + payment_token_id = fields.Many2one('payment.token', string="Saved payment token", domain=[('acquirer_id.capture_manually', '=', False)], help="Note that tokens from acquirers set to only authorize transactions (instead of capturing the amount) are not available.") @api.onchange('partner_id') @@ -18,14 +18,14 @@ class AccountPayment(models.Model): res = {} if self.partner_id: partners = self.partner_id | self.partner_id.commercial_partner_id | self.partner_id.commercial_partner_id.child_ids - res['domain'] = {'payment_token_id': [('partner_id', 'in', partners.ids), ('acquirer_id.auto_confirm', '!=', 'authorize')]} + res['domain'] = {'payment_token_id': [('partner_id', 'in', partners.ids), ('acquirer_id.capture_manually', '=', False)]} return res @api.onchange('payment_method_id', 'journal_id') def _onchange_payment_method(self): if self.payment_method_code == 'electronic': - self.payment_token_id = self.env['payment.token'].search([('partner_id', '=', self.partner_id.id), ('acquirer_id.auto_confirm', '!=', 'authorize')], limit=1) + self.payment_token_id = self.env['payment.token'].search([('partner_id', '=', self.partner_id.id), ('acquirer_id.capture_manually', '=', False)], limit=1) else: self.payment_token_id = False @@ -38,7 +38,7 @@ class AccountPayment(models.Model): return account_payment def _do_payment(self): - if self.payment_token_id.acquirer_id.auto_confirm == 'authorize': + if self.payment_token_id.acquirer_id.capture_manually: raise ValidationError(_('This feature is not available for payment acquirers set to the "Authorize" mode.\n' 'Please use a token from another provider than %s.') % self.payment_token_id.acquirer_id.name) reference = "P-%s-%s" % (self.id, datetime.datetime.now().strftime('%y%m%d_%H%M%S')) diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 92f0da0bc74..e36b344a52d 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -71,15 +71,22 @@ class PaymentAcquirer(models.Model): website_published = fields.Boolean( 'Visible in Portal / Website', copy=False, help="Make this payment acquirer available (Customer invoices, etc.)") - auto_confirm = fields.Selection([ - ('none', 'No automatic confirmation'), - ('authorize', 'Authorize the amount and confirm the order on acquirer confirmation (capture manually)'), - ('confirm_so', 'Authorize & capture the amount and confirm the order on acquirer confirmation'), - ('generate_and_pay_invoice', 'Authorize & capture the amount, confirm the order and auto-validate the invoice on acquirer confirmation')], - string='Order Confirmation', default='confirm_so', required=True) + # Formerly associated to `authorize` option from auto_confirm + capture_manually = fields.Boolean(string="Capture Amount Manually", + help="Capture the amount from Odoo, when the delivery is completed.") + # Formerly associated to `generate_and_pay_invoice` option from auto_confirm journal_id = fields.Many2one( - 'account.journal', 'Payment Journal', - help="Account journal used for automatic payment reconciliation.") + 'account.journal', 'Payment Journal', domain=[('type', '=', 'bank')], + default=lambda self: self.env['account.journal'].search([('type', '=', 'bank')], limit=1), + help="""Payments will be registered into this journal. If you get paid straight on your bank account, + select your bank account. If you get paid in batch for several transactions, create a specific + payment journal for this payment acquirer to easily manage the bank reconciliation. You hold + the amount in a temporary transfer account of your books (created automatically when you create + the payment journal). Then when you get paid on your bank account by the payment acquirer, you + reconcile the bank statement line with this temporary transfer account. Use reconciliation + templates to do it in one-click.""") + specific_countries = fields.Boolean(string="Specific Countries", + help="If you leave it empty, the payment acquirer will be available for all the countries.") country_ids = fields.Many2many( 'res.country', 'payment_country_rel', 'payment_id', 'country_id', 'Countries', @@ -116,7 +123,7 @@ class PaymentAcquirer(models.Model): "If you manage subscriptions (recurring invoicing), you need it to automatically charge the customer when you " "issue an invoice.") token_implemented = fields.Boolean('Saving Card Data supported', compute='_compute_feature_support') - + authorize_implemented = fields.Boolean('Authorize Mechanism Supported', compute='_compute_feature_support') fees_implemented = fields.Boolean('Fees Computation Supported', compute='_compute_feature_support') fees_active = fields.Boolean('Add Extra Fees') fees_dom_fixed = fields.Float('Fixed domestic fees') @@ -142,11 +149,11 @@ class PaymentAcquirer(models.Model): "resized as a 64x64px image, with aspect ratio preserved. " "Use this field anywhere a small image is required.") - @api.multi def _compute_feature_support(self): feature_support = self._get_feature_support() for acquirer in self: acquirer.fees_implemented = acquirer.provider in feature_support['fees'] + acquirer.authorize_implemented = acquirer.provider in feature_support['authorize'] acquirer.token_implemented = acquirer.provider in feature_support['tokenize'] @api.multi @@ -158,13 +165,6 @@ class PaymentAcquirer(models.Model): return False return True - @api.constrains('auto_confirm') - def _check_authorization_support(self): - for acquirer in self: - if acquirer.auto_confirm == 'authorize' and acquirer.provider not in self._get_feature_support()['authorize']: - raise ValidationError(_('You cannot capture payments manually with this payment method. Please choose another Order Confirmation mode (in Configuration tab).')) - return True - _constraints = [ (_check_required_if_provider, 'Required fields not filled', []), ] diff --git a/addons/payment/views/payment_views.xml b/addons/payment/views/payment_views.xml index 86d795802b4..ef611b73e81 100644 --- a/addons/payment/views/payment_views.xml +++ b/addons/payment/views/payment_views.xml @@ -16,16 +16,11 @@ +
-
+
@@ -80,10 +81,16 @@ - + - - + + + + + + + + - - - - - - - diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index a80627dbf35..24d57cb0db7 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -66,7 +66,7 @@ class PaymentAcquirerAuthorize(models.Model): 'x_trans_key': self.authorize_transaction_key, 'x_amount': str(values['amount']), 'x_show_form': 'PAYMENT_FORM', - 'x_type': 'AUTH_CAPTURE' if self.auto_confirm != 'authorize' else 'AUTH_ONLY', + 'x_type': 'AUTH_CAPTURE' if not self.capture_manually else 'AUTH_ONLY', 'x_method': 'CC', 'x_fp_sequence': '%s%s' % (self.id, int(time.time())), 'x_version': '3.1', @@ -246,7 +246,7 @@ class TxAuthorize(models.Model): def authorize_s2s_do_transaction(self, **data): self.ensure_one() transaction = AuthorizeAPI(self.acquirer_id) - if self.acquirer_id.auto_confirm != "authorize": + if not self.acquirer_id.capture_manually: res = transaction.auth_and_capture(self.payment_token_id, self.amount, self.reference) else: res = transaction.authorize(self.payment_token_id, self.amount, self.reference) diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py index d19690284a8..b02d3d81209 100644 --- a/addons/payment_authorize/tests/test_authorize.py +++ b/addons/payment_authorize/tests/test_authorize.py @@ -25,7 +25,7 @@ class AuthorizeCommon(PaymentAcquirerCommon): # get the authorize account self.authorize = self.env.ref('payment.payment_acquirer_authorize') # Be sure to be in 'capture' mode - self.authorize.auto_confirm = 'confirm_so' + # self.authorize.auto_confirm = 'confirm_so' @odoo.tests.common.at_install(True) @@ -219,7 +219,7 @@ class AuthorizeForm(AuthorizeCommon): # switch to 'authorize only' # create authorize only s2s transaction & capture it - self.authorize.auto_confirm = 'authorize' + self.authorize.capture_manually = True transaction = self.env['payment.transaction'].create({ 'amount': 500, 'acquirer_id': authorize.id, @@ -236,7 +236,7 @@ class AuthorizeForm(AuthorizeCommon): self.assertEqual(transaction.state, 'done') # create authorize only s2s transaction & void it - self.authorize.auto_confirm = 'authorize' + self.authorize.capture_manually = True transaction = self.env['payment.transaction'].create({ 'amount': 500, 'acquirer_id': authorize.id, diff --git a/addons/payment_transfer/data/payment_acquirer_data.xml b/addons/payment_transfer/data/payment_acquirer_data.xml index 77ff2e2381d..0eee54a9259 100644 --- a/addons/payment_transfer/data/payment_acquirer_data.xml +++ b/addons/payment_transfer/data/payment_acquirer_data.xml @@ -8,7 +8,6 @@ transfer - none test {'invisible': [('provider', '=', 'transfer')]} - - {'invisible': [('provider', '=', 'transfer')]} - diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index ca5c95153df..136c4dc043f 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -157,7 +157,7 @@ + + + diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 8af8e148a9e..ae104629215 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -768,7 +768,7 @@ class WebsiteSale(http.Controller): tx.partner_id == tx.sale_order_id.partner_id): try: s2s_result = tx.s2s_do_transaction() - valid_state = 'authorized' if tx.acquirer_id.auto_confirm == 'authorize' else 'done' + valid_state = 'authorized' if tx.acquirer_id.capture_manually else 'done' if not s2s_result or tx.state != valid_state: return dict(success=False, error=_("Payment transaction failed (%s)") % tx.state_message) else: diff --git a/addons/website_sale/models/res_config.py b/addons/website_sale/models/res_config.py index 629c4f77a52..c6eeb9d328c 100644 --- a/addons/website_sale/models/res_config.py +++ b/addons/website_sale/models/res_config.py @@ -70,9 +70,10 @@ class WebsiteConfigSettings(models.TransientModel): group='base.group_portal,base.group_user,base.group_public') default_invoice_policy = fields.Selection([ - ('order', 'Invoice what is ordered (automatic)'), - ('delivery', 'Invoice what is delivered (manual)') + ('order', 'Invoice what is ordered'), + ('delivery', 'Invoice what is delivered') ], 'Invoicing Policy', default='order') + automatic_invoice = fields.Boolean("Automatic Invoice") group_multi_currency = fields.Boolean(string='Multi-Currencies', implied_group='base.group_multi_currency') @@ -81,6 +82,16 @@ class WebsiteConfigSettings(models.TransientModel): ('subtotal', 'Tax-Excluded Prices')], "Product Prices", default='total') + @api.multi + def set_automatic_invoice(self): + value = self.module_account_invoicing and self.default_invoice_policy == 'order' and self.automatic_invoice + self.env['ir.config_parameter'].sudo().set_param('website_sale.automatic_invoice', value) + + @api.model + def get_default_automatic_invoice(self, fields): + value = self.env['ir.config_parameter'].sudo().get_param('website_sale.automatic_invoice', default=False) + return {'automatic_invoice': value} + @api.model def get_default_sale_delivery_settings(self, fields): sale_delivery_settings = 'none' diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 4b007b59bd2..0779899f999 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -23,6 +23,16 @@ class SaleOrder(models.Model): payment_acquirer_id = fields.Many2one('payment.acquirer', string='Payment Acquirer', copy=False) payment_tx_id = fields.Many2one('payment.transaction', string='Last Transaction', copy=False) only_services = fields.Boolean(compute='_compute_cart_info', string='Only Services') + can_directly_mark_as_paid = fields.Boolean(compute='_compute_can_directly_mark_as_paid', + string="Can be directly marked as paid", store=True, + help="""Checked if the sales order can directly be marked as paid, i.e. if the quotation + is sent or confirmed and if the payment acquire is of the type transfer or manual""") + + @api.depends('state', 'payment_tx_id', 'payment_tx_id.state', + 'payment_acquirer_id', 'payment_acquirer_id.provider') + def _compute_can_directly_mark_as_paid(self): + for order in self: + order.can_directly_mark_as_paid = order.state in ['sent', 'sale'] and order.payment_tx_id.state != 'done' and order.payment_acquirer_id.provider in ['transfer', 'manual'] @api.multi @api.depends('website_order_line.product_uom_qty', 'website_order_line.product_id') @@ -166,6 +176,22 @@ class SaleOrder(models.Model): accessory_products -= order.website_order_line.mapped('product_id') return random.sample(accessory_products, len(accessory_products)) + def action_mark_as_paid(self): + """ Mark directly a sales order as paid if: + - State: Quotation Sent, or sales order + - Provider: wire transfer or manual config + The transaction is marked as done + The invoice may be generated and marked as paid if configured in the website settings + """ + self.ensure_one() + if self.can_directly_mark_as_paid: + self.action_confirm() + if self.env['ir.config_parameter'].sudo().get_param('website_sale.automatic_invoice', default=False): + self.payment_tx_id._generate_and_pay_invoice(self.payment_tx_id, self.payment_acquirer_id.provider) + self.payment_tx_id.state = 'done' + else: + raise ValidationError(_("The quote should be sent and the payment acquirer type should be manual or wire transfer")) + class ResCountry(models.Model): _inherit = 'res.country' diff --git a/addons/website_sale/views/report_shop_saleorder.xml b/addons/website_sale/views/report_shop_saleorder.xml index 753e902397a..f8fd2600c7b 100644 --- a/addons/website_sale/views/report_shop_saleorder.xml +++ b/addons/website_sale/views/report_shop_saleorder.xml @@ -15,10 +15,7 @@ Payment Method:
Payment Status: - - - - + diff --git a/addons/website_sale/views/res_config_view.xml b/addons/website_sale/views/res_config_view.xml index cb270375b89..56ea5e36c31 100644 --- a/addons/website_sale/views/res_config_view.xml +++ b/addons/website_sale/views/res_config_view.xml @@ -227,15 +227,26 @@
+
+
+ +
+
+
+

Shipping

diff --git a/addons/website_sale/views/sale_order_views.xml b/addons/website_sale/views/sale_order_views.xml index 0a4e11f5d7a..49b701e3111 100644 --- a/addons/website_sale/views/sale_order_views.xml +++ b/addons/website_sale/views/sale_order_views.xml @@ -133,4 +133,21 @@ action="product.variants_action" parent="menu_product_settings" groups="product.group_product_variant" sequence="2"/> + + sale.order.form + sale.order + + + +