From cbec2409a2012e6f42dd8e628f3788f5ebf1f144 Mon Sep 17 00:00:00 2001 From: Thomas Josse Date: Thu, 23 Sep 2021 13:29:48 +0000 Subject: [PATCH] [ADD] website_sale_slides: add default course product Purpose ======= This commit adds a default product called "Access Course" to the database when you have the `website_sale_slides` installed. This commit also starts the transition for courses products into `event ticket`-like products Specifications ============== This product is already created in order to help the users start to sell their courses. We also added a new type of product named `Courses`. This will be used to create courses and easily work with courses products. The commit adds the list of all the courses that the will gain access to when he will buy the product inside of the cart and in the right column whe it appears. It also adds the same list when the product is inserted into a sales order. The commit adds the choice of enroll policies in the front-end when `website_sales_slides` is installed and of the corresponding product if we are creating a paid course. Task-2636092 closes odoo/odoo#77990 Signed-off-by: Thibault Delavallee (tde) --- addons/website_sale_slides/__manifest__.py | 4 ++ .../website_sale_slides/controllers/slides.py | 23 +++++++- .../website_sale_slides/data/product_data.xml | 17 ++++++ .../website_sale_slides/data/product_demo.xml | 4 +- addons/website_sale_slides/models/__init__.py | 4 +- .../models/product_product.py | 10 +++- .../models/product_template.py | 17 ++++++ .../website_sale_slides/models/sale_order.py | 2 +- .../models/slide_channel.py | 8 ++- addons/website_sale_slides/models/website.py | 15 ++++++ .../static/img/default_course_product.svg | 1 + .../src/js/website_sale_slides_editor.js | 26 ++++++++++ .../static/src/xml/website_slides_channel.xml | 52 +++++++++++++++++++ .../views/slide_channel_views.xml | 2 +- .../views/website_sale_templates.xml | 11 ++++ .../static/src/js/website_slides.editor.js | 2 +- .../static/src/scss/website_slides.scss | 7 +++ .../static/src/xml/website_slides_channel.xml | 31 +++++------ 18 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 addons/website_sale_slides/data/product_data.xml create mode 100644 addons/website_sale_slides/models/product_template.py create mode 100644 addons/website_sale_slides/models/website.py create mode 100644 addons/website_sale_slides/static/img/default_course_product.svg create mode 100644 addons/website_sale_slides/static/src/js/website_sale_slides_editor.js create mode 100644 addons/website_sale_slides/static/src/xml/website_slides_channel.xml diff --git a/addons/website_sale_slides/__manifest__.py b/addons/website_sale_slides/__manifest__.py index 4a0ab8ae952..3ef082aca44 100644 --- a/addons/website_sale_slides/__manifest__.py +++ b/addons/website_sale_slides/__manifest__.py @@ -10,6 +10,7 @@ 'depends': ['website_slides', 'website_sale'], 'installable': True, 'data': [ + 'data/product_data.xml', 'report/sale_report_views.xml', 'views/website_slides_menu_views.xml', 'views/slide_channel_views.xml', @@ -25,6 +26,9 @@ 'web.assets_frontend': [ 'website_sale_slides/static/src/js/**/*', ], + 'web.assets_qweb': [ + 'website_sale_slides/static/src/xml/website_slides_channel.xml', + ] }, 'license': 'LGPL-3', } diff --git a/addons/website_sale_slides/controllers/slides.py b/addons/website_sale_slides/controllers/slides.py index 3debae40eef..67b76bd1f6a 100644 --- a/addons/website_sale_slides/controllers/slides.py +++ b/addons/website_sale_slides/controllers/slides.py @@ -2,11 +2,22 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.website_slides.controllers.main import WebsiteSlides -from odoo.http import request +from odoo.http import request, route +from odoo.tools import format_amount class WebsiteSaleSlides(WebsiteSlides): + @route('/slides/get_course_products', type='json', auth='user') + def get_course_products(self): + """Return a list of the course products values with formatted price.""" + products = request.env['product.product'].search([('detailed_type', '=', 'course')]) + + return [{ + 'id': product.id, + 'name': f'{product.name} ({format_amount(request.env, product.list_price, product.currency_id)})', + } for product in products] + def _prepare_additional_channel_values(self, values, **kwargs): values = super(WebsiteSaleSlides, self)._prepare_additional_channel_values(values, **kwargs) channel = values['channel'] @@ -15,3 +26,13 @@ class WebsiteSaleSlides(WebsiteSlides): 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 return values + + def _slide_channel_prepare_values(self, **kwargs): + """Parse the values posted when we create a new course + from website to add the selected product. + """ + channel = super(WebsiteSaleSlides, self)._slide_channel_prepare_values(**kwargs) + channel['enroll'] = kwargs.get('enroll') + if channel['enroll'] == 'payment' and 'product_id' in kwargs: + channel['product_id'] = int(kwargs['product_id']) + return channel diff --git a/addons/website_sale_slides/data/product_data.xml b/addons/website_sale_slides/data/product_data.xml new file mode 100644 index 00000000000..82d60e5339a --- /dev/null +++ b/addons/website_sale_slides/data/product_data.xml @@ -0,0 +1,17 @@ + + + + Courses + + + + Course Access + 99.99 + 99.99 + course + order + + + + + diff --git a/addons/website_sale_slides/data/product_demo.xml b/addons/website_sale_slides/data/product_demo.xml index 90ed00688f6..95f549df859 100644 --- a/addons/website_sale_slides/data/product_demo.xml +++ b/addons/website_sale_slides/data/product_demo.xml @@ -6,7 +6,7 @@ Taking care of Trees Course 150.0 150.0 - service + course order @@ -18,7 +18,7 @@ DIY Furniture Course 100.0 100.0 - service + course order diff --git a/addons/website_sale_slides/models/__init__.py b/addons/website_sale_slides/models/__init__.py index a7ae607df42..848605c053e 100644 --- a/addons/website_sale_slides/models/__init__.py +++ b/addons/website_sale_slides/models/__init__.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- from . import product_product -from . import slide_channel +from . import product_template from . import sale_order +from . import slide_channel +from . import website diff --git a/addons/website_sale_slides/models/product_product.py b/addons/website_sale_slides/models/product_product.py index 94fc0c6e82b..7d3cacf6d35 100644 --- a/addons/website_sale_slides/models/product_product.py +++ b/addons/website_sale_slides/models/product_product.py @@ -1,10 +1,18 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import _, fields, models class Product(models.Model): _inherit = "product.product" channel_ids = fields.One2many('slide.channel', 'product_id', string='Courses') + + def get_product_multiline_description_sale(self): + payment_channels = self.channel_ids.filtered(lambda course: course.enroll == 'payment') + + if not payment_channels: + return super(Product, self).get_product_multiline_description_sale() + + return _('Access to:\n%s', '\n'.join(payment_channels.mapped('name'))) diff --git a/addons/website_sale_slides/models/product_template.py b/addons/website_sale_slides/models/product_template.py new file mode 100644 index 00000000000..caad6854d95 --- /dev/null +++ b/addons/website_sale_slides/models/product_template.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + detailed_type = fields.Selection(selection_add=[ + ('course', 'Course'), + ], ondelete={'course': 'set service'}) + + def _detailed_type_mapping(self): + type_mapping = super(ProductTemplate, self)._detailed_type_mapping() + type_mapping['course'] = 'service' + return type_mapping diff --git a/addons/website_sale_slides/models/sale_order.py b/addons/website_sale_slides/models/sale_order.py index 03e345df922..08646c8df4c 100644 --- a/addons/website_sale_slides/models/sale_order.py +++ b/addons/website_sale_slides/models/sale_order.py @@ -17,7 +17,7 @@ class SaleOrder(models.Model): ) products = so_lines.mapped('product_id') related_channels = self.env['slide.channel'].search( - [('product_id', 'in', products.ids)] + [('product_id', 'in', products.ids), ('enroll', '=', 'payment')], ) channel_products = related_channels.mapped('product_id') diff --git a/addons/website_sale_slides/models/slide_channel.py b/addons/website_sale_slides/models/slide_channel.py index 37d158e0898..da503a966ef 100644 --- a/addons/website_sale_slides/models/slide_channel.py +++ b/addons/website_sale_slides/models/slide_channel.py @@ -7,10 +7,16 @@ from odoo import api, fields, models class Channel(models.Model): _inherit = 'slide.channel' + def _get_default_product_id(self): + product_courses = self.env['product.product'].search( + [('detailed_type', '=', 'course')], limit=2) + return product_courses.id if len(product_courses) == 1 else False + enroll = fields.Selection(selection_add=[ ('payment', 'On payment') ], ondelete={'payment': lambda recs: recs.write({'enroll': 'invite'})}) - product_id = fields.Many2one('product.product', 'Product', index=True) + product_id = fields.Many2one('product.product', 'Product', domain=[('detailed_type', '=', 'course')], + default=_get_default_product_id) product_sale_revenues = fields.Monetary( string='Total revenues', compute='_compute_product_sale_revenues', groups="sales_team.group_sale_salesman") diff --git a/addons/website_sale_slides/models/website.py b/addons/website_sale_slides/models/website.py new file mode 100644 index 00000000000..e110e51a26e --- /dev/null +++ b/addons/website_sale_slides/models/website.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo.osv import expression + + +class Website(models.Model): + _inherit = 'website' + + def sale_product_domain(self): + return expression.AND([ + super(Website, self).sale_product_domain(), + [('detailed_type', '!=', 'course')], + ]) diff --git a/addons/website_sale_slides/static/img/default_course_product.svg b/addons/website_sale_slides/static/img/default_course_product.svg new file mode 100644 index 00000000000..67682f913b3 --- /dev/null +++ b/addons/website_sale_slides/static/img/default_course_product.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/website_sale_slides/static/src/js/website_sale_slides_editor.js b/addons/website_sale_slides/static/src/js/website_sale_slides_editor.js new file mode 100644 index 00000000000..349ca3793c4 --- /dev/null +++ b/addons/website_sale_slides/static/src/js/website_sale_slides_editor.js @@ -0,0 +1,26 @@ +/** @odoo-module **/ + +import {ChannelCreateDialog} from '@website_slides/js/website_slides.editor'; + +ChannelCreateDialog.include({ + xmlDependencies: ChannelCreateDialog.prototype.xmlDependencies.concat( + ['/website_sale_slides/static/src/xml/website_slides_channel.xml'] + ), + + events: _.extend({}, ChannelCreateDialog.prototype.events, { + 'change input[name="enroll"]': '_onEnrollChanged', + }), + + willStart: async function () { + await this._super(); + + this.products = await this._rpc({route: '/slides/get_course_products'}); + }, + + /** + * @private + */ + _onEnrollChanged: function (ev) { + this.$('.o_wslides_course_products').toggleClass('d-none', ev.target.value !== 'payment'); + }, +}); diff --git a/addons/website_sale_slides/static/src/xml/website_slides_channel.xml b/addons/website_sale_slides/static/src/xml/website_slides_channel.xml new file mode 100644 index 00000000000..1b03ebccf96 --- /dev/null +++ b/addons/website_sale_slides/static/src/xml/website_slides_channel.xml @@ -0,0 +1,52 @@ + + + + + + +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ +
+
+
+
diff --git a/addons/website_sale_slides/views/slide_channel_views.xml b/addons/website_sale_slides/views/slide_channel_views.xml index d334b8931f2..3cb54e7316f 100644 --- a/addons/website_sale_slides/views/slide_channel_views.xml +++ b/addons/website_sale_slides/views/slide_channel_views.xml @@ -8,7 +8,7 @@ + context="{'default_detailed_type': 'course', 'default_invoice_policy': 'order', 'default_purchase_ok': False, 'default_sale_ok': True, 'default_website_published': True}"/>