[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
This commit is contained in:
Jérémy Hennecart (jeh)
2022-06-01 16:26:08 +02:00
parent 2e0490beb5
commit 5881195bfe
4 changed files with 86 additions and 17 deletions
@@ -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):
@@ -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")
@@ -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)
@@ -84,7 +84,7 @@
</template>
<template name="Buy Course Button" id="course_buy_course_button">
<t t-if="channel.product_id.website_published and not channel.is_member">
<t t-if="product_info and channel.product_id.website_published and not channel.is_member">
<div t-attf-class="text-center d-flex align-items-center text-center pb-1 #{'justify-content-between' if product_info['has_discounted_price'] else 'justify-content-around'}">
<div class="css_editable_mode_hidden">
<!-- real price -->