diff options
| author | Yannick Tivisse <yti@odoo.com> | 2017-05-24 11:40:19 +0200 |
|---|---|---|
| committer | Yannick Tivisse <yti@odoo.com> | 2017-06-02 15:31:47 +0200 |
| commit | 4aa2fad313112a7a45a86b67e806f5a3af22d73b (patch) | |
| tree | ae84130085545019a33b0e6f92687686f24a07d6 | |
| parent | 4fe4c1e8518f3adbd7406f2a75f98d7c25aa9a90 (diff) | |
[IMP] payment: Remove auto_confirm field and simplify views.
PURPOSE
=======
The field auto_confirm is complicated to understand for common users. Furthermore, on of its options is only useful for authorize module.
SPECIFICATION
=============
Remove the auto_confirm field. The destinies of its options are the following:
- none: Simply disappear.
- authorize: Become capture_manually. It has nothing to do with the auto_confirm field has it's related to the autorize module (And could be extended to other payment acquirers too).
- confirm_so: Remove it. Will be automatic, and we will always validate the sales order and generate the accounting entries on acquirer validation.
- generate_and_pay_invoice: Is linked to the journal_id. The field journal_id is always set and we will use it to validate the sales order and generate the accounting entries on acquirer validation.
Bonus: website_sale: Allow to create/validate invoice automatically on `Mark as Paid`
18 files changed, 174 insertions, 78 deletions
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 @@ <record id="payment_acquirer_custom" model="payment.acquirer"> <field name="name">Custom</field> - <field name="auto_confirm">none</field> <field name="company_id" ref="base.main_company"/> <field name="view_template_id" ref="default_acquirer_button"/> <field name="module_id" ref="base.module_payment_transfer"/> 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 @@ <field name="environment" invisible='1'/> <field name="fees_implemented" invisible='1'/> <field name="token_implemented" invisible='1'/> + <field name="authorize_implemented" invisible="1"/> <sheet> <field name="module_id" invisible="1"/> <field name="module_state" invisible="1"/> <div class="oe_button_box" name="button_box" attrs="{'invisible': ['&', ('module_id', '!=', False), ('module_state', '!=', 'installed')]}"> - <button name="toggle_website_published" - class="oe_stat_button" - type="object" icon="fa-globe"> - <field name="website_published" widget="boolean_button" - options='{"terminology": {"string_true": "Published", "hover_false": "Publish"}}'/> - </button> <!-- transfer referenced here due to view inheritance issue in current master (post-saas-16) --> <button name="toggle_environment_value" attrs="{'invisible': ['|', ('environment', '=', 'test'), ('provider', '=', 'transfer')]}" @@ -46,6 +41,12 @@ <span class="o_stat_text">Environment</span> </div> </button> + <button name="toggle_website_published" + class="oe_stat_button" + type="object" icon="fa-globe"> + <field name="website_published" widget="boolean_button" + options='{"terminology": {"string_true": "Published", "hover_false": "Publish"}}'/> + </button> </div> <field name="image" widget='image' class="oe_avatar" options='{"preview_image": "image_medium"}'/> <div class="oe_title"> @@ -80,10 +81,16 @@ </page> <page string="Configuration"> <group name="acquirer_config"> - <field name="auto_confirm" widget="radio"/> + <field name="capture_manually" attrs="{'invisible': [('authorize_implemented', '=', False)]}"/> <field name="save_token" widget="radio" attrs="{'invisible': [('token_implemented', '=', False)]}"/> - <field name="journal_id" attrs="{'required': [('auto_confirm', '=', 'generate_and_pay_invoice')], 'invisible': [('auto_confirm', '!=', 'generate_and_pay_invoice')]}"/> - <field name="country_ids" widget="many2many_tags"/> + <field name="fees_active" attrs="{'invisible': [('fees_implemented', '=', False)]}"/> + <field name="fees_dom_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/> + <field name="fees_dom_var" attrs="{'invisible': [('fees_active', '=', False)]}"/> + <field name="fees_int_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/> + <field name="fees_int_var" attrs="{'invisible': [('fees_active', '=', False)]}"/> + <field name="journal_id" context="{'default_type': 'bank'}" required="1"/> + <field name="specific_countries"/> + <field name="country_ids" widget="many2many_tags" attrs="{'invisible': [('specific_countries', '=', False)]}"/> <label for="view_template_id" groups="base.group_no_one"/> <div groups="base.group_no_one"> <field name="view_template_id" nolabel="1"/> @@ -106,13 +113,6 @@ </div> <field name="registration_view_template_id" groups="base.group_no_one"/> </group> - <group name="fees_config" attrs="{'invisible': [('fees_implemented', '=', False)]}"> - <field name="fees_active"/> - <field name="fees_dom_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/> - <field name="fees_dom_var" attrs="{'invisible': [('fees_active', '=', False)]}"/> - <field name="fees_int_fixed" attrs="{'invisible': [('fees_active', '=', False)]}"/> - <field name="fees_int_var" attrs="{'invisible': [('fees_active', '=', False)]}"/> - </group> </page> </notebook> </sheet> 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 @@ <field name="provider">transfer</field> <field name="company_id" ref="base.main_company"/> <field name="view_template_id" ref="transfer_acquirer_button"/> - <field name="auto_confirm">none</field> <field name="environment">test</field> <field name="website_published" eval="True"/> <field name="pre_msg"><![CDATA[ diff --git a/addons/payment_transfer/views/payment_views.xml b/addons/payment_transfer/views/payment_views.xml index 127ab7b1238..a126bb67626 100644 --- a/addons/payment_transfer/views/payment_views.xml +++ b/addons/payment_transfer/views/payment_views.xml @@ -17,9 +17,6 @@ <field name="error_msg" position="attributes"> <attribute name="attrs">{'invisible': [('provider', '=', 'transfer')]}</attribute> </field> - <field name="auto_confirm" position="attributes"> - <attribute name="attrs">{'invisible': [('provider', '=', 'transfer')]}</attribute> - </field> </field> </record> </odoo> 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 @@ <button name="action_quotation_send" string="Send by Email" type="object" states="draft" class="btn-primary"/> <button name="action_quotation_send" type="object" string="Send PRO-FORMA" groups="sale.group_proforma_sales" class="btn-primary" attrs="{'invisible': ['|', ('state', 'in', ['sent', 'sale']), ('invoice_count','>=',1)]}" context="{'proforma': True}"/> <button name="print_quotation" string="Print" type="object" states="draft" class="btn-primary o_sale_print"/> - <button name="action_confirm" states="sent" string="Confirm Sale" class="btn-primary" type="object" /> + <button name="action_confirm" id="action_confirm" states="sent" string="Confirm Sale" class="btn-primary" type="object" /> <button name="action_confirm" states="draft" string="Confirm Sale" type="object" /> <button name="action_quotation_send" type="object" string="Send PRO-FORMA" groups="sale.group_proforma_sales" attrs="{'invisible': ['|', ('state', '=', 'draft'), ('invoice_count','>=',1)]}" context="{'proforma': True}"/> <button name="print_quotation" string="Print" type="object" states="sent,sale" class="o_sale_print"/> diff --git a/addons/website_portal_sale/models/payment.py b/addons/website_portal_sale/models/payment.py index 9842567abb1..7d442871c5d 100644 --- a/addons/website_portal_sale/models/payment.py +++ b/addons/website_portal_sale/models/payment.py @@ -11,6 +11,7 @@ class PaymentTransaction(models.Model): _inherit = 'payment.transaction' # link with the sales order + # YTI FIXME: The auto_join seems useless sale_order_id = fields.Many2one('sale.order', string='Sales Order', auto_join=True) def _generate_and_pay_invoice(self, tx, acquirer_name): @@ -29,13 +30,15 @@ class PaymentTransaction(models.Model): acquirer_name, created_invoice.name, created_invoice.id, tx.sale_order_id.name, tx.sale_order_id.id) created_invoice.action_invoice_open() - if tx.acquirer_id.journal_id: + if not tx.acquirer_id.journal_id: + default_journal = self.env['account.journal'].search([('type', '=', 'bank')], limit=1) + if not default_journal: + _logger.warning('<%s> transaction completed, could not auto-generate payment for %s (ID %s) (no journal set on acquirer)', + acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id) + tx.acquirer_id.journal_id = default_journal created_invoice.pay_and_reconcile(tx.acquirer_id.journal_id, pay_amount=created_invoice.amount_total) if created_invoice.payment_ids: created_invoice.payment_ids[0].payment_transaction_id = tx - else: - _logger.warning('<%s> transaction completed, could not auto-generate payment for %s (ID %s) (no journal set on acquirer)', - acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id) else: _logger.warning('<%s> transaction completed, could not auto-generate invoice for %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id) @@ -69,15 +72,13 @@ class PaymentTransaction(models.Model): if amount_matches: if not acquirer_name: acquirer_name = tx.sale_order_id.payment_acquirer_id.provider or 'unknown' - if tx.state == 'authorized' and tx.acquirer_id.auto_confirm == 'authorize': + if tx.state == 'authorized' and tx.acquirer_id.capture_manually: _logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id) tx.sale_order_id.with_context(send_email=True).action_confirm() - if tx.state == 'done' and tx.acquirer_id.auto_confirm in ['confirm_so', 'generate_and_pay_invoice']: + if tx.state == 'done': _logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id) tx.sale_order_id.with_context(send_email=True).action_confirm() - - if tx.acquirer_id.auto_confirm == 'generate_and_pay_invoice': - self._generate_and_pay_invoice(tx, acquirer_name) + self._generate_and_pay_invoice(tx, acquirer_name) elif tx.state not in ['cancel', 'error'] and tx.sale_order_id.state == 'draft': _logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id) tx.sale_order_id.force_quotation_send() diff --git a/addons/website_portal_sale/models/sale_order.py b/addons/website_portal_sale/models/sale_order.py index 8ec2592ab8d..ecbdbef107a 100644 --- a/addons/website_portal_sale/models/sale_order.py +++ b/addons/website_portal_sale/models/sale_order.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, exceptions, fields, models +from odoo import api, exceptions, fields, models, _ + +from odoo.exceptions import ValidationError class SaleOrder(models.Model): @@ -9,11 +11,18 @@ class SaleOrder(models.Model): _inherit = 'sale.order' website_url = fields.Char('Website URL', compute='_website_url', help='The full URL to access the document through the website.') + payment_transaction_count = fields.Integer(compute='_compute_payment_transaction_count', string="Number of payment transactions") def _website_url(self): for so in self: so.website_url = '/my/orders/%s' % (so.id) + def _compute_payment_transaction_count(self): + transaction_data = self.env['payment.transaction'].read_group([('sale_order_id', 'in', self.ids)], ['sale_order_id'], ['sale_order_id']) + mapped_data = dict([(m['sale_order_id'][0], m['sale_order_id_count']) for m in transaction_data]) + for order in self: + order.payment_transaction_count = mapped_data.get(order.id, 0) + @api.multi def get_access_action(self): """ Instead of the classic form view, redirect to the online quote for @@ -62,6 +71,24 @@ class SaleOrder(models.Model): model=self._name, res_id=self.id)[self.partner_id.id] + def action_view_transaction(self): + action = { + 'type': 'ir.actions.act_window', + 'name': 'Payment Transactions', + 'res_model': 'payment.transaction', + } + if self.payment_transaction_count == 1: + action.update({ + 'res_id': self.env['payment.transaction'].search([('sale_order_id', '=', self.id)]).id, + 'view_mode': 'form', + }) + else: + action.update({ + 'view_mode': 'tree,form', + 'domain': [('sale_order_id', '=', self.id)], + }) + return action + class SaleOrderLine(models.Model): diff --git a/addons/website_portal_sale/views/sale_order_views.xml b/addons/website_portal_sale/views/sale_order_views.xml index 2075deb64c9..0a06daf69fd 100644 --- a/addons/website_portal_sale/views/sale_order_views.xml +++ b/addons/website_portal_sale/views/sale_order_views.xml @@ -1,15 +1,26 @@ <?xml version="1.0"?> <odoo> + <record id="view_account_invoice_filter_share" model="ir.ui.view"> + <field name="name">account.invoice.select.share</field> + <field name="model">account.invoice</field> + <field name="inherit_id" ref="account.view_account_invoice_filter"/> + <field name="arch" type="xml"> + <xpath expr='//field[@name="user_id"]' position="attributes"> + <attribute name="domain">[('share','=', False)]</attribute> + </xpath> + </field> + </record> - <record id="view_account_invoice_filter_share" model="ir.ui.view"> - <field name="name">account.invoice.select.share</field> - <field name="model">account.invoice</field> - <field name="inherit_id" ref="account.view_account_invoice_filter"/> - <field name="arch" type="xml"> - <xpath expr='//field[@name="user_id"]' position="attributes"> - <attribute name="domain">[('share','=', False)]</attribute> - </xpath> - </field> - </record> - + <record id="sale_order_view_form" model="ir.ui.view"> + <field name="name">sale.order.form</field> + <field name="model">sale.order</field> + <field name="inherit_id" ref="sale.view_order_form"/> + <field name="arch" type="xml"> + <xpath expr="//div[@name='button_box']" position="inside"> + <button name="action_view_transaction" type="object" class="oe_stat_button" icon="fa-money" attrs="{'invisible': [('payment_transaction_count', '=', 0)]}"> + <field name="payment_transaction_count" widget="statinfo" string="Transactions"/> + </button> + </xpath> + </field> + </record> </odoo> 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 @@ <td> <strong>Payment Method:</strong> <t t-esc="doc.payment_acquirer_id.name"/><br /> <strong>Payment Status:</strong> - <t t-if="doc.payment_acquirer_id.auto_confirm == 'none'"> - <t t-raw="doc.payment_acquirer_id.pending_msg"/> - </t> - <t t-if="doc.payment_acquirer_id.auto_confirm in ['confirm_so', 'generate_and_pay_invoice']"> + <t t-if="doc.payment_acquirer_id.journal_id"> <t t-if="doc.payment_tx_id.state == 'pending'"> <t t-raw="doc.payment_acquirer_id.pending_msg"/> </t> 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 @@ <div class="o_setting_right_pane"> <label for="module_account_invoicing"/> <div class="text-muted"> - Send invoices to customers + Issue invoices to customers </div> - <div class="content-group"> + <div class="content-group" attrs="{'invisible': [('module_account_invoicing', '=', False)]}"> <div class="mt16"> <field name="default_invoice_policy" class="o_light_label" widget="radio"/> </div> </div> </div> </div> + <div class="col-xs-12 col-md-6 o_setting_box" attrs="{'invisible': ['|', ('default_invoice_policy', '=', 'delivery'), ('module_account_invoicing', '=', False)]}"> + <div class="o_setting_left_pane"> + <field name="automatic_invoice" nolabel="1"/> + </div> + <div class="o_setting_right_pane"> + <label for="automatic_invoice"/> + <div class="text-muted"> + Issue the invoice automatically at the order confirmation + </div> + </div> + </div> </div> <h2>Shipping</h2> <div class="row mt16 o_settings_container" id="sale_shipping_settings"> 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"/> + <record id="sale_order_view_form" model="ir.ui.view"> + <field name="name">sale.order.form</field> + <field name="model">sale.order</field> + <field name="inherit_id" ref="sale.view_order_form"/> + <field name="arch" type="xml"> + <xpath expr="//button[@name='action_quotation_send']" position="after"> + <button name="action_mark_as_paid" type="object" string="Mark as Paid" states="sent,sale" class="btn-primary" attrs="{'invisible': [('can_directly_mark_as_paid', '=', False)]}"/> + </xpath> + <field name="partner_id" position="after"> + <field name="can_directly_mark_as_paid" invisible="1"/> + </field> + <xpath expr="//button[@id='action_confirm']" position="attributes"> + <attribute name="attrs">{'invisible': [('can_directly_mark_as_paid', '=', True)]}</attribute> + </xpath> + </field> + </record> + </odoo> |
