From 5881195bfe1680fc26763ca90cebdf54d04a104a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Hennecart=20=28jeh=29?= Date: Fri, 6 May 2022 07:31:34 +0000 Subject: [PATCH] [FIX] website_sale_slides: fix sync between product and course Before, each time we created a course "on payment". The product linked to it was automatically unpublished. If multiple course share the same product this leads to issues. Indeed, with the product unpublished all the courses depending on the product are no longer buyable. To avoid this, we synchronise the published value when the course is published which published the product. If we create a new course or unpublished a previous course linked to a product, we don't update the published value of the product unless all courses linked to this product are unpublished. We also fix a Forbidden error raised when the product was unpublished but not the course. This was leading to a previous member to not have access to a course he previously buyed. task-2842624 Part-of: odoo/odoo#90736 --- .../website_sale_slides/controllers/slides.py | 13 ++-- .../models/slide_channel.py | 16 ++++- .../tests/test_course_purchase_flow.py | 72 ++++++++++++++++--- .../views/website_slides_templates.xml | 2 +- 4 files changed, 86 insertions(+), 17 deletions(-) diff --git a/addons/website_sale_slides/controllers/slides.py b/addons/website_sale_slides/controllers/slides.py index 67b76bd1f6a..2734480713b 100644 --- a/addons/website_sale_slides/controllers/slides.py +++ b/addons/website_sale_slides/controllers/slides.py @@ -21,10 +21,15 @@ class WebsiteSaleSlides(WebsiteSlides): def _prepare_additional_channel_values(self, values, **kwargs): values = super(WebsiteSaleSlides, self)._prepare_additional_channel_values(values, **kwargs) channel = values['channel'] - if channel.enroll == 'payment' and channel.product_id: - pricelist = request.website.get_current_pricelist() - values['product_info'] = channel.product_id.product_tmpl_id._get_combination_info(product_id=channel.product_id.id, pricelist=pricelist) - values['product_info']['currency_id'] = request.website.currency_id + if channel.enroll == 'payment': + # search the product to apply ACLs, notably on published status, to avoid access errors + product = request.env['product.product'].search([('id', '=', channel.product_id.id)]) if channel.product_id else request.env['product.product'] + if product: + pricelist = request.website.get_current_pricelist() + values['product_info'] = channel.product_id.product_tmpl_id._get_combination_info(product_id=channel.product_id.id, pricelist=pricelist) + values['product_info']['currency_id'] = request.website.currency_id + else: + values['product_info'] = False return values def _slide_channel_prepare_values(self, **kwargs): diff --git a/addons/website_sale_slides/models/slide_channel.py b/addons/website_sale_slides/models/slide_channel.py index e1817ba25cd..de0c3dddb4e 100644 --- a/addons/website_sale_slides/models/slide_channel.py +++ b/addons/website_sale_slides/models/slide_channel.py @@ -52,10 +52,24 @@ class Channel(models.Model): return res def _synchronize_product_publish(self): + """ + Ensure that when publishing a course that its linked product is also published + If all courses linked to a product are unpublished, we also unpublished the product + """ if not self: return self.filtered(lambda channel: channel.is_published and not channel.product_id.is_published).sudo().product_id.write({'is_published': True}) - self.filtered(lambda channel: not channel.is_published and channel.product_id.is_published).sudo().product_id.write({'is_published': False}) + + unpublished_channel_products = self.filtered(lambda channel: not channel.is_published).product_id + group_data = self.read_group( + [('is_published', '=', True), ('product_id', 'in', unpublished_channel_products.ids)], + ['product_id'], + ['product_id'], + ) + used_product_ids = [product['product_id'][0] for product in group_data if product['product_id_count'] > 0] + product_to_unpublish = unpublished_channel_products.filtered(lambda product: product.id not in used_product_ids) + if product_to_unpublish: + product_to_unpublish.sudo().write({'is_published': False}) def action_view_sales(self): action = self.env["ir.actions.actions"]._for_xml_id("website_sale_slides.sale_report_action_slides") diff --git a/addons/website_sale_slides/tests/test_course_purchase_flow.py b/addons/website_sale_slides/tests/test_course_purchase_flow.py index 27e53561fcc..fb30c904847 100644 --- a/addons/website_sale_slides/tests/test_course_purchase_flow.py +++ b/addons/website_sale_slides/tests/test_course_purchase_flow.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.website_slides.tests import common +from odoo.tests.common import users class TestCoursePurchaseFlow(common.SlidesCase): @@ -15,9 +16,7 @@ class TestCoursePurchaseFlow(common.SlidesCase): 'groups_id': [(6, 0, cls.env.ref('sales_team.group_sale_salesman').ids)], }) - def test_course_purchase_flow(self): - # Step1: create a course product and assign it to 2 slide.channels - course_product = self.env['product.product'].create({ + cls.course_product = cls.env['product.product'].create({ 'name': "Course Product", 'standard_price': 100, 'list_price': 150, @@ -26,15 +25,17 @@ class TestCoursePurchaseFlow(common.SlidesCase): 'is_published': True, }) + def test_course_purchase_flow(self): + # Step1: assign a course product to 2 slide.channels self.channel.write({ 'enroll': 'payment', - 'product_id': course_product.id + 'product_id': self.course_product.id }) self.channel_2 = self.env['slide.channel'].with_user(self.user_officer).create({ 'name': 'Test Channel', 'enroll': 'payment', - 'product_id': course_product.id, + 'product_id': self.course_product.id, 'is_published': True, }) @@ -43,10 +44,10 @@ class TestCoursePurchaseFlow(common.SlidesCase): 'partner_id': self.customer.id, 'order_line': [ (0, 0, { - 'name': course_product.name, - 'product_id': course_product.id, + 'name': self.course_product.name, + 'product_id': self.course_product.id, 'product_uom_qty': 1, - 'price_unit': course_product.list_price, + 'price_unit': self.course_product.list_price, }) ], }) @@ -62,10 +63,10 @@ class TestCoursePurchaseFlow(common.SlidesCase): 'partner_id': self.user_portal.partner_id.id, 'order_line': [ (0, 0, { - 'name': course_product.name, - 'product_id': course_product.id, + 'name': self.course_product.name, + 'product_id': self.course_product.id, 'product_uom_qty': 1, - 'price_unit': course_product.list_price, + 'price_unit': self.course_product.list_price, }) ], }) @@ -74,3 +75,52 @@ class TestCoursePurchaseFlow(common.SlidesCase): self.assertIn(self.user_portal.partner_id, self.channel.partner_ids) self.assertIn(self.user_portal.partner_id, self.channel_2.partner_ids) + + @users('user_officer') + def test_course_product_published_synch(self): + """ Test the synchronization between a course and its product """ + course_1 = self.env['slide.channel'].create({ + 'name': 'Test Channel 1', + 'enroll': 'payment', + 'product_id': self.course_product.id, + }) + course_2 = self.env['slide.channel'].create({ + 'name': 'Test Channel 2', + 'enroll': 'payment', + 'product_id': self.course_product.id, + 'is_published': True, + }) + + # The course_1 is not published by default which doesn't impact the product + self.assertFalse(course_1.is_published) + self.assertTrue(self.course_product.is_published) + + course_1.is_published = True + + # The course_1 and the product are published + self.assertTrue(course_1.is_published) + self.assertTrue(self.course_product.is_published) + + self.course_product.is_published = False + + # Unpublishing the product should not change the course_1 + self.assertTrue(course_1.is_published) + self.assertFalse(self.course_product.is_published) + + course_1.is_published = False + + self.assertFalse(course_1.is_published) + self.assertFalse(self.course_product.is_published) + + course_1.is_published = True + + # Publishing the course_1 should publish the product + self.assertTrue(course_1.is_published) + self.assertTrue(self.course_product.is_published) + + (course_1 + course_2).write({'is_published': False}) + + # If all course linked to a product are unpublished, we unpublished the product + self.assertFalse(course_1.is_published) + self.assertFalse(course_2.is_published) + self.assertFalse(self.course_product.is_published) diff --git a/addons/website_sale_slides/views/website_slides_templates.xml b/addons/website_sale_slides/views/website_slides_templates.xml index 553846536b6..0718abed84a 100644 --- a/addons/website_sale_slides/views/website_slides_templates.xml +++ b/addons/website_sale_slides/views/website_slides_templates.xml @@ -84,7 +84,7 @@