[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) <tde@openerp.com>
This commit is contained in:
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<odoo><data>
|
||||
<record id="product_category_courses" model="product.category">
|
||||
<field name="parent_id" ref="product.product_category_1"/>
|
||||
<field name="name">Courses</field>
|
||||
</record>
|
||||
|
||||
<record id="default_product_course" model="product.product">
|
||||
<field name="name">Course Access</field>
|
||||
<field name="standard_price">99.99</field>
|
||||
<field name="list_price">99.99</field>
|
||||
<field name="detailed_type">course</field>
|
||||
<field name="invoice_policy">order</field>
|
||||
<field name="is_published" eval="True"/>
|
||||
<field name="image_1920" type="base64" file="website_sale_slides/static/img/default_course_product.svg"/>
|
||||
<field name="categ_id" ref="product_category_courses"/>
|
||||
</record>
|
||||
</data></odoo>
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="name">Taking care of Trees Course</field>
|
||||
<field name="standard_price">150.0</field>
|
||||
<field name="list_price">150.0</field>
|
||||
<field name="detailed_type">service</field>
|
||||
<field name="detailed_type">course</field>
|
||||
<field name="invoice_policy">order</field>
|
||||
<field name="is_published" eval="True"/>
|
||||
<field name="image_1920" type="base64" file="website_sale_slides/static/img/channel_demo_tree_1.jpg"/>
|
||||
@@ -18,7 +18,7 @@
|
||||
<field name="name">DIY Furniture Course</field>
|
||||
<field name="standard_price">100.0</field>
|
||||
<field name="list_price">100.0</field>
|
||||
<field name="detailed_type">service</field>
|
||||
<field name="detailed_type">course</field>
|
||||
<field name="invoice_policy">order</field>
|
||||
<field name="is_published" eval="True"/>
|
||||
<field name="image_1920" type="base64" file="website_sale_slides/static/img/product_course.png"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')))
|
||||
|
||||
@@ -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
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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')],
|
||||
])
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg id="solid" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><title/><path d="M407.951,343.116a7.2,7.2,0,0,1,.048.733c.121,13.4-15.891,29.5-41.725,41.981C345.344,395.944,309.222,408,256,408s-89.344-12.056-110.274-22.17C119.892,373.348,103.88,357.246,104,343.849a7.2,7.2,0,0,1,.048-.733L115.1,243.624a7,7,0,0,1,9.82-5.615L242.911,290.9a32.043,32.043,0,0,0,26.179,0l117.986-52.89a7,7,0,0,1,9.82,5.615ZM474.988,153.4l-205.9-92.3a32.037,32.037,0,0,0-26.179,0l-205.9,92.3a16,16,0,0,0,0,29.2l205.9,92.3a32.043,32.043,0,0,0,26.179,0L440,198.284v59.874A15.98,15.98,0,0,0,432,272V376h32V272a15.98,15.98,0,0,0-8-13.842V191.111l18.987-8.511a16,16,0,0,0,0-29.2Z" style="fill:#3b3b3b"/></svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -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');
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<templates>
|
||||
<t t-extend="website.slide.channel.create">
|
||||
<t t-jquery="form > div:last" t-operation="after">
|
||||
<label for="enroll">Enroll Policy</label>
|
||||
<div class="form-row" id="enroll_policies">
|
||||
<div class="form-group col-3">
|
||||
<div class="form-check mx-0">
|
||||
<input class="form-check-input"
|
||||
type="radio" name="enroll"
|
||||
id="enroll_public" value="public"
|
||||
checked="checked"/>
|
||||
<label class="form-check-label font-weight-normal"
|
||||
for="enroll_public">
|
||||
Public
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-3">
|
||||
<div class="form-check mx-0">
|
||||
<input class="form-check-input"
|
||||
type="radio" name="enroll"
|
||||
id="enroll_invitation" value="invite"/>
|
||||
<label class="form-check-label font-weight-normal"
|
||||
for="enroll_invitation">
|
||||
On Invitation
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-3">
|
||||
<div class="form-check mx-0">
|
||||
<input class="form-check-input"
|
||||
type="radio" name="enroll"
|
||||
id="enroll_payment" value="payment"/>
|
||||
<label class="form-check-label font-weight-normal"
|
||||
for="enroll_payment">
|
||||
On Payment
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="o_wslides_course_products d-none">Product</label>
|
||||
<div class="form-group o_wslides_course_products d-none">
|
||||
<select class="form-control" name="product_id" id="product_id">
|
||||
<option t-foreach="widget.products" t-as="product"
|
||||
t-att-value="product.id" t-esc="product.name"/>
|
||||
</select>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -8,7 +8,7 @@
|
||||
<xpath expr="//field[@name='enroll']" position="after">
|
||||
<field name="product_id"
|
||||
attrs="{'invisible': [('enroll', '!=', 'payment')], 'required': [('enroll', '=', 'payment')]}"
|
||||
context="{'default_detailed_type': 'service', 'default_invoice_policy': 'order', 'default_purchase_ok': False, 'default_sale_ok': True, 'default_website_published': True}"/>
|
||||
context="{'default_detailed_type': 'course', 'default_invoice_policy': 'order', 'default_purchase_ok': False, 'default_sale_ok': True, 'default_website_published': True}"/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='action_redirect_to_members']" position="after">
|
||||
<button name="action_view_sales"
|
||||
|
||||
@@ -34,4 +34,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="cart_summary_inherit_website_sale_slides"
|
||||
inherit_id="website_sale.cart_summary"
|
||||
name="Course Cart right column">
|
||||
<xpath expr="//td[hasclass('td-product_name')]/div/strong" position="after">
|
||||
<div t-if="line.product_id.channel_ids"
|
||||
t-foreach="line.product_id.channel_ids.filtered(lambda course: course.enroll == 'payment')"
|
||||
t-as="course"
|
||||
t-esc="course.name"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data></odoo>
|
||||
|
||||
@@ -7,7 +7,7 @@ import SlidesCourseTagAdd from '@website_slides/js/slides_course_tag_add';
|
||||
|
||||
const TagCourseDialog = SlidesCourseTagAdd.TagCourseDialog;
|
||||
|
||||
var ChannelCreateDialog = Dialog.extend({
|
||||
export const ChannelCreateDialog = Dialog.extend({
|
||||
template: 'website.slide.channel.create',
|
||||
xmlDependencies: Dialog.prototype.xmlDependencies.concat(
|
||||
['/website_slides/static/src/xml/website_slides_channel.xml',
|
||||
|
||||
@@ -573,6 +573,13 @@ $truncate-limits: 2, 3, 10;
|
||||
box-shadow: 3px 3px 5px gray;
|
||||
}
|
||||
|
||||
#slide_channel_add_form label img {
|
||||
// make the layout preview images size depending on the screen size
|
||||
// so we do not need to scroll on most resolutions
|
||||
cursor: pointer;
|
||||
height: 20vh;
|
||||
}
|
||||
|
||||
// Embed PDFViewer
|
||||
// **************************************************
|
||||
#PDFViewer.o_wslides_fs_pdf_viewer {
|
||||
|
||||
@@ -16,30 +16,25 @@
|
||||
<input type="text" class="form-control" name="tag_ids" id="tag_ids" placeholder="Tags"/>
|
||||
</div>
|
||||
<label for="channel_type">Choose a layout</label>
|
||||
<div class="form-row">
|
||||
<div class="form-group col-6">
|
||||
<div class="form-check px-0">
|
||||
<input class="form-check-input d-none" type="radio" name="channel_type" id="channel_type1" value="training" checked="checked"/>
|
||||
<label for="channel_type1">
|
||||
<img class="w-100" src="/website_slides/static/src/img/channel-training-layout.png" alt="Training Layout"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-6">
|
||||
<div class="form-check px-0">
|
||||
<input class="form-check-input d-none" type="radio" name="channel_type" id="channel_type2" value="documentation"/>
|
||||
<label for="channel_type2">
|
||||
<img class="w-100" src="/website_slides/static/src/img/channel-documentation-layout.png" alt="Documentation Layout"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row justify-content-around">
|
||||
<input class="form-check-input d-none" type="radio" name="channel_type" id="channel_type1" value="training" checked="checked"/>
|
||||
<label for="channel_type1">
|
||||
<img src="/website_slides/static/src/img/channel-training-layout.png"
|
||||
alt="Training Layout"/>
|
||||
</label>
|
||||
<input class="form-check-input d-none" type="radio" name="channel_type" id="channel_type2" value="documentation"/>
|
||||
<label for="channel_type2">
|
||||
<img src="/website_slides/static/src/img/channel-documentation-layout.png"
|
||||
alt="Documentation Layout"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="title">Description</label>
|
||||
<textarea rows="2" class="form-control" name="description" id="description"
|
||||
<textarea rows="1" class="form-control" name="description" id="description"
|
||||
placeholder="Common tasks for a computer scientist is asking the right questions and answering
|
||||
questions. In this course, you'll study those topics with activities about mathematics, science and logic." />
|
||||
</div>
|
||||
<label id="communication-label">Review</label>
|
||||
<div class="form-group">
|
||||
<div class="o_wslide_channel_communication_type">
|
||||
<div class="form-check">
|
||||
|
||||
Reference in New Issue
Block a user