diff --git a/models/sale_order.py b/models/sale_order.py index a009b938c3d..e96e81a92f8 100644 --- a/models/sale_order.py +++ b/models/sale_order.py @@ -6,6 +6,44 @@ from odoo.http import request class SaleOrder(models.Model): _inherit = "sale.order" + @api.one + def _compute_website_order_line(self): + """ This method will merge multiple discount lines generated by a same program + into a single one (temporary line with `new()`). + This case will only occur when the program is a discount applied on multiple + products with different taxes. + In this case, each taxes will have their own discount line. This is required + to have correct amount of taxes according to the discount. + But we wan't these lines to be `visually` merged into a single one in the + e-commerce since the end user should only see one discount line. + This is only possible since we don't show taxes in cart. + eg: + line 1: 10% discount on product with tax `A` - $15 + line 2: 10% discount on product with tax `B` - $11.5 + line 3: 10% discount on product with tax `C` - $10 + would be `hidden` and `replaced` by + line 1: 10% discount - $36.5 + + Note: The line will be created without tax(es) and the amount will be computed + depending if B2B or B2C is enabled. + """ + super(SaleOrder, self)._compute_website_order_line() + for program in self._get_applied_programs_with_rewards_on_current_order(): + program_lines = self.order_line.filtered(lambda line: line.product_id == program.discount_line_product_id) + if len(program_lines) > 1: + price_unit = sum(l.price_subtotal if self.env.user.has_group('sale.group_show_price_subtotal') else l.price_total for l in program_lines) + self.website_order_line += self.env['sale.order.line'].new({ + 'product_id': program_lines[0].product_id.id, + 'price_unit': price_unit, + 'name': program_lines[0].name, + 'product_uom_qty': 1, + 'product_uom': program_lines[0].product_uom.id, + 'order_id': self.id, + 'is_reward_line': True, + }) + self.website_order_line -= program_lines + + def _compute_cart_info(self): super(SaleOrder, self)._compute_cart_info() for order in self: diff --git a/static/src/js/website_sale_coupon.test.js b/static/src/js/website_sale_coupon.test.js index cac0dd82e5b..3c98ec210a3 100644 --- a/static/src/js/website_sale_coupon.test.js +++ b/static/src/js/website_sale_coupon.test.js @@ -4,6 +4,7 @@ odoo.define('website_sale_coupon.test', function (require) { require("website_sale.tour"); var tour = require("web_tour.tour"); var base = require("web_editor.base"); +var ajax = require('web.ajax'); tour.register('shop_sale_coupon', { test: true, @@ -19,6 +20,11 @@ tour.register('shop_sale_coupon', { { content: "click on 'Show # found'", trigger: "#customize-menu a:contains(Show # found)", + run: function () { + if (!$('#customize-menu a:contains(Show # found) input').prop('checked')) { + $('#customize-menu a:contains(Show # found)').click(); + } + }, }, /* 1. Buy 1 iPad Mini, enable coupon code & insert 10% code */ { @@ -36,9 +42,9 @@ tour.register('shop_sale_coupon', { trigger: '.oe_product_cart a:contains("iPad Mini")', }, { - content: "add 3 iPad Mini into cart", + content: "add 2 iPad Mini into cart", trigger: '#product_details input[name="add_qty"]', - run: "text 3", + run: "text 2", }, { content: "click on 'Add to Cart' button", @@ -52,6 +58,11 @@ tour.register('shop_sale_coupon', { { content: "click on 'Promo Code'", trigger: "#customize-menu a:contains(Promo Code)", + run: function () { + if (!$('#customize-menu a:contains(Promo Code) input').prop('checked')) { + $('#customize-menu a:contains(Promo Code)').click(); + } + }, }, { content: "insert promo code '10pc'", @@ -68,61 +79,104 @@ tour.register('shop_sale_coupon', { trigger: '.reward_product:contains("10.0 % discount on total amount")', run: function () {}, // it's a check }, - /* 2. Add some iPad to get a free one, play with quantity */ + /* 2. Add a product with tax in the cart (which has different tax than iPad) and ensure only one discount line is shown */ + { + content: "go to shop", + trigger: '.reward_product:contains("10.0 % discount on total amount")', + run: function () { + ajax.jsonRpc('/web/dataset/call', 'call', { + model: 'account.tax', + method: 'create', + args: [{ + 'name':'15% tax incl', + 'amount': 15, + }], + }).then(function (tax_id) { + ajax.jsonRpc('/web/dataset/call', 'call', { + model: 'product.template', + method: 'create', + args: [{ + 'name': 'Taxed Product', + 'taxes_id': [([6, false, [tax_id]])], + 'list_price': 100, + 'website_published': true, + }], + }).then(function (data) { + location.href = '/shop'; + }); + }); + }, + }, + { + content: "type Taxed Product in search", + trigger: 'form input[name="search"]', + run: "text Taxed Product", + }, + { + content: "start search", + trigger: 'form:has(input[name="search"]) .oe_search_button', + }, + { + content: "select Taxed Product", + extra_trigger: '.oe_search_found', // Wait to be on search results or it sometimes throws concurent error (sent search form + click on product on /shop) + trigger: '.oe_product_cart a:containsExact("Taxed Product")', + }, + { + content: "click on 'Add to Cart' button", + trigger: "a:contains(Add to Cart)", + }, + { + content: "check reduction amount got recomputed and merged both discount lines into one only", + extra_trigger: '.oe_currency_value:contains("-74.00"):not(#cart_total .oe_currency_value:contains("-74.00"))', + trigger: '.oe_website_sale .oe_cart', + run: function () {}, // it's a check + }, + /* 3. Add some iPad to get a free one, play with quantity */ + { + content: "add one iPad", + trigger: '#cart_products input.js_quantity', + run: "text 3", + }, + { + content: "check reduction amount got recomputed when changing qty", + trigger: '.oe_currency_value:contains("-106.00")', + run: function () {}, // it's a check + }, { content: "add more iPad Mini into cart", trigger: '#cart_products input.js_quantity', run: "text 4", }, { - content: "check reduction amount got recomputed", - extra_trigger: '.oe_currency_value:contains("96.00")', // For some reason, "-96.00" won't work - trigger: '.reward_product:contains("Free Product - iPad Mini")', + content: "check free product is added", + trigger: '#wrap:has(.reward_product:contains("Free Product - iPad Mini"))', run: function () {}, // it's a check }, { content: "remove one iPad from cart", - trigger: '#cart_products a.js_add_cart_json:first', + trigger: '#cart_products input.js_quantity[value="4"]', + run: "text 3", }, { content: "check free product is removed", trigger: '#wrap:not(:has(.reward_product:contains("Free Product - iPad Mini")))', run: function () {}, // it's a check }, - /* 3. Empty cart and disable coupon */ + /* 4. Check /shop/payment does not break the `merged discount lines split per tax` (eg: with _compute_tax_id) */ { - content: "remove iPad Mini from cart", - trigger: '#cart_products input.js_quantity:first', - run: "text 0", + content: "go to checkout", + trigger: 'a[href="/shop/checkout"]', }, { - content: "check cart is empty", - trigger: '#wrap:not(:has(#cart_products))', + content: "Confirm checkout", + extra_trigger: "div.all_shipping .panel", + trigger: 'a[href="/shop/confirm_order"]', + }, + { + content: "check total is unchanged", + trigger: '.oe_currency_value:contains("967.50")', run: function () {}, // it's a check }, - /* 4. Disabled customize options coupon box & 'Show # found'*/ - { - content: "open customize menu", - extra_trigger: '.oe_website_sale .oe_cart', - trigger: '#customize-menu > a', - }, - { - content: "click on 'Promo Code'", - trigger: "#customize-menu a:contains(Promo Code)", - }, - { - content: "click on 'Continue Shopping'", - trigger: "a:contains(Continue Shopping)", - }, - { - content: "open customize menu", - extra_trigger: '.oe_website_sale #products_grid', - trigger: '#customize-menu > a', - }, - { - content: "click on 'Show # found'", - trigger: "#customize-menu a:contains(Show # found)", - }, ] ); @@ -135,7 +189,6 @@ if ('website_sale_options.website_sale' in odoo.__DEBUG__.services) { content: "click in modal on 'Proceed to Checkout' button", trigger: 'a:contains("Proceed to Checkout")', }); - break; } } }