add modules elearning
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 532 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 658 B After Width: | Height: | Size: 658 B |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 525 B After Width: | Height: | Size: 525 B |
|
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 540 B |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 872 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 979 B |
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 147 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "Elearning Slide Requirement",
|
||||||
|
'version': '1.0',
|
||||||
|
'summary': 'Requirement on specific slide',
|
||||||
|
'category': 'elearning',
|
||||||
|
'description': """
|
||||||
|
A product of Bac Ha Software allows to require user to learn specific slide
|
||||||
|
before able to learn next slides.
|
||||||
|
""",
|
||||||
|
"depends": ['website_slides', 'website_slides_survey'],
|
||||||
|
'data': [
|
||||||
|
'views/slides_templates.xml',
|
||||||
|
'views/slide_slide_view.xml',
|
||||||
|
],
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
# Author
|
||||||
|
'author': 'Bac Ha Software',
|
||||||
|
'website': 'https://bachasoftware.com',
|
||||||
|
'maintainer': 'Bac Ha Software',
|
||||||
|
'images': ['static/description/banner.png'],
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
'assets': {
|
||||||
|
'web.assets_frontend': [
|
||||||
|
'bhs_elearning_required_slide/static/src/scss/required_slide.scss',
|
||||||
|
'bhs_elearning_required_slide/static/src/js/slides_slide_toggle_is_required.js',
|
||||||
|
'bhs_elearning_required_slide/static/src/js/slides_slide_prevent_learning.js',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import main
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from ast import literal_eval
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import werkzeug
|
||||||
|
|
||||||
|
from odoo import http, tools, _
|
||||||
|
# from odoo.addons.http_routing.models.ir_http import slug, unslug
|
||||||
|
# from odoo.tools import slugify
|
||||||
|
from odoo.addons.website.controllers.main import QueryURL
|
||||||
|
from odoo.addons.website.models.ir_http import sitemap_qs2dom
|
||||||
|
from odoo.addons.website_profile.controllers.main import WebsiteProfile
|
||||||
|
from odoo.exceptions import AccessError, UserError
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.osv import expression
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WebsiteSlides(WebsiteProfile):
|
||||||
|
|
||||||
|
@http.route('/slides/slide/toggle_is_required', type='json', auth='user', website=True)
|
||||||
|
def toggle_slide_required(self, slide_id):
|
||||||
|
slide = request.env['slide.slide'].browse(int(slide_id))
|
||||||
|
if slide.channel_id.can_publish:
|
||||||
|
slide.is_required = not slide.is_required
|
||||||
|
return slide.is_required
|
||||||
|
|
||||||
|
@http.route('/slides/slide/required_slide', type='json', auth='public', website=True)
|
||||||
|
def get_required_slide(self, slide_id):
|
||||||
|
slide = request.env['slide.slide'].sudo().browse(int(slide_id))
|
||||||
|
channel_id = slide.channel_id
|
||||||
|
required_slide = channel_id.current_req_slide if slide.sequence > channel_id.current_req_slide.sequence else False
|
||||||
|
if not required_slide:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return {'id': required_slide.id,
|
||||||
|
'name': required_slide.name,
|
||||||
|
'url': required_slide.website_url,
|
||||||
|
'can_skip': slide.is_preview}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * bhs_elearning_required_slide
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 15.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2022-11-17 09:33+0000\n"
|
||||||
|
"PO-Revision-Date: 2022-11-17 09:33+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.missing_requirement_modal
|
||||||
|
msgid ""
|
||||||
|
"<br/>\n"
|
||||||
|
" before learning this slide."
|
||||||
|
msgstr ""
|
||||||
|
"<br/>\n"
|
||||||
|
" trước khi học bài học này."
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.slide_fullscreen_sidebar_category
|
||||||
|
msgid "<i class=\"fa fa-flag-checkered text-warning me-2\"/>Quiz"
|
||||||
|
msgstr "<i class=\"fa fa-flag-checkered text-warning me-2\"/>Đố vui"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.course_slides_required_slide
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge bh-badge-warning fw-normal px-2 py-1 "
|
||||||
|
"m-1\"><span>Required</span></span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge bh-badge-warning fw-normal px-2 py-1 "
|
||||||
|
"m-1\"><span>Bắt buộc</span></span>"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.slide_content_detailed_required
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"d-none d-sm-inline-block\">Next</span> <i class=\"fa fa-"
|
||||||
|
"chevron-right ms-2\"/>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"d-none d-sm-inline-block\">Tiếp</span> <i class=\"fa fa-"
|
||||||
|
"chevron-right ms-2\"/>"
|
||||||
|
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.course_slides_required_slide
|
||||||
|
msgid "<span>Required</span>"
|
||||||
|
msgstr "<span>Bắt buộc</span>"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model:ir.model,name:bhs_elearning_required_slide.model_slide_channel
|
||||||
|
msgid "Course"
|
||||||
|
msgstr "Khóa học"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model:ir.model.fields,field_description:bhs_elearning_required_slide.field_slide_channel__current_req_slide
|
||||||
|
msgid "Current Req Slide"
|
||||||
|
msgstr "Bài học bắt buộc"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model:ir.model.fields,field_description:bhs_elearning_required_slide.field_slide_slide__missing_requirement
|
||||||
|
msgid "Missing requirement"
|
||||||
|
msgstr "Thiếu điều kiện"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.missing_requirement_modal
|
||||||
|
msgid ""
|
||||||
|
"Please complete the <b>Required Slide:</b>\n"
|
||||||
|
" <br/>"
|
||||||
|
msgstr ""
|
||||||
|
"Vui lòng hoàn thành <b>Bài học Bắt buộc:</b>\n"
|
||||||
|
" <br/>"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model:ir.model.fields,field_description:bhs_elearning_required_slide.field_slide_slide__is_required
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Bắt buộc"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: model_terms:ir.ui.view,arch_db:bhs_elearning_required_slide.missing_requirement_modal
|
||||||
|
msgid "Understood"
|
||||||
|
msgstr "Đã hiểu"
|
||||||
|
|
||||||
|
#. module: bhs_elearning_required_slide
|
||||||
|
#: code:addons/bhs_elearning_required_slide/models/slide.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "You need to learn required slide first."
|
||||||
|
msgstr "Bạn cần học bài học bắt buộc trước."
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import slide
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from odoo import api, fields, models, tools, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Channel(models.Model):
|
||||||
|
_inherit = 'slide.channel'
|
||||||
|
|
||||||
|
current_req_slide = fields.Many2one('slide.slide', compute='_get_current_required_slide')
|
||||||
|
|
||||||
|
@api.depends('slide_ids.sequence', 'slide_ids.is_required')
|
||||||
|
@api.depends_context('uid')
|
||||||
|
def _get_current_required_slide(self):
|
||||||
|
for channel in self:
|
||||||
|
required_slides = channel.slide_ids.filtered('is_required')
|
||||||
|
required_slides_learned = self.env['slide.slide.partner'].sudo().search([('slide_id', 'in', required_slides.ids),
|
||||||
|
('partner_id', '=', self.env.user.partner_id.id),
|
||||||
|
('completed', '=', True)])
|
||||||
|
required_slides_not_learned = required_slides - required_slides_learned.mapped('slide_id')
|
||||||
|
channel.current_req_slide = required_slides_not_learned[0] if required_slides_not_learned else False
|
||||||
|
|
||||||
|
|
||||||
|
class Slide(models.Model):
|
||||||
|
_inherit = 'slide.slide'
|
||||||
|
|
||||||
|
is_required = fields.Boolean('Required', default=False)
|
||||||
|
missing_requirement = fields.Boolean('Missing requirement', compute='_get_missing_requirement')
|
||||||
|
|
||||||
|
@api.depends('sequence', 'channel_id.current_req_slide')
|
||||||
|
@api.depends_context('uid')
|
||||||
|
def _get_missing_requirement(self):
|
||||||
|
for slide in self:
|
||||||
|
req_slide = slide.channel_id.current_req_slide
|
||||||
|
slide.missing_requirement = req_slide and not slide.is_preview and slide.sequence > req_slide.sequence
|
||||||
|
|
||||||
|
|
||||||
|
class SlidePartner(models.Model):
|
||||||
|
_inherit = 'slide.slide.partner'
|
||||||
|
|
||||||
|
@api.constrains('slide_id', 'partner_id')
|
||||||
|
def required_slide_constrain(self):
|
||||||
|
for record in self:
|
||||||
|
slide = record.slide_id
|
||||||
|
if slide.missing_requirement:
|
||||||
|
raise UserError(_("You need to learn required slide first."))
|
||||||
|
After Width: | Height: | Size: 610 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M79.375 20.4583C75.555 16.5996 71.0049 13.5402 65.9902 11.4585C60.9754 9.37673 55.5963 8.31434 50.1667 8.33326C27.4167 8.33326 8.87499 26.8749 8.87499 49.6249C8.87499 56.9166 10.7917 63.9999 14.375 70.2499L8.54166 91.6666L30.4167 85.9166C36.4583 89.2083 43.25 90.9583 50.1667 90.9583C72.9167 90.9583 91.4583 72.4166 91.4583 49.6666C91.4583 38.6249 87.1667 28.2499 79.375 20.4583ZM50.1667 83.9583C44 83.9583 37.9583 82.2916 32.6667 79.1666L31.4167 78.4166L18.4167 81.8333L21.875 69.1666L21.0417 67.8749C17.6156 62.404 15.7964 56.0801 15.7917 49.6249C15.7917 30.7083 31.2083 15.2916 50.125 15.2916C59.2917 15.2916 67.9167 18.8749 74.375 25.3749C77.5728 28.5581 80.1071 32.3443 81.8308 36.5142C83.5545 40.684 84.4335 45.1545 84.4167 49.6666C84.5 68.5833 69.0833 83.9583 50.1667 83.9583ZM69 58.2916C67.9583 57.7916 62.875 55.2916 61.9583 54.9166C61 54.5833 60.3333 54.4166 59.625 55.4166C58.9167 56.4583 56.9583 58.7916 56.375 59.4583C55.7917 60.1666 55.1667 60.2499 54.125 59.7083C53.0833 59.2083 49.75 58.0833 45.8333 54.5833C42.75 51.8333 40.7083 48.4583 40.0833 47.4166C39.5 46.3749 40 45.8333 40.5417 45.2916C41 44.8333 41.5833 44.0833 42.0833 43.4999C42.5833 42.9166 42.7917 42.4583 43.125 41.7916C43.4583 41.0833 43.2917 40.4999 43.0417 39.9999C42.7917 39.4999 40.7083 34.4166 39.875 32.3333C39.0417 30.3333 38.1667 30.5833 37.5417 30.5416H35.5417C34.8333 30.5416 33.75 30.7916 32.7917 31.8333C31.875 32.8749 29.2083 35.3749 29.2083 40.4583C29.2083 45.5416 32.9167 50.4583 33.4167 51.1249C33.9167 51.8333 40.7083 62.2499 51.0417 66.7083C53.5 67.7916 55.4167 68.4166 56.9167 68.8749C59.375 69.6666 61.625 69.5416 63.4167 69.2916C65.4167 68.9999 69.5417 66.7916 70.375 64.3749C71.25 61.9583 71.25 59.9166 70.9583 59.4583C70.6667 58.9999 70.0417 58.7916 69 58.2916Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 532 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 658 B |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="84" height="82" viewBox="0 0 84 82" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M79.625 24.2082L42 0.666504L4.37501 24.2082C1.95834 25.6665 0.333344 28.2915 0.333344 31.3332V72.9998C0.333344 77.5832 4.08334 81.3332 8.66668 81.3332H75.3333C79.9167 81.3332 83.6667 77.5832 83.6667 72.9998V31.3332C83.6667 28.2915 82.0417 25.6665 79.625 24.2082ZM75.3333 72.9998H8.66668V39.6665L42 60.4998L75.3333 39.6665V72.9998ZM42 52.1665L8.66668 31.3332L42 10.4998L75.3333 31.3332L42 52.1665Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 525 B |
|
After Width: | Height: | Size: 540 B |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 872 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 979 B |
|
After Width: | Height: | Size: 772 B |
@@ -0,0 +1,216 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<div class="container" style="padding: 1rem !important; margin-bottom: 1rem !important;">
|
||||||
|
<div class="row" style="border-bottom: 1px solid #d5d5d5;">
|
||||||
|
<div class="col-sm-8 col-md-8 col-lg-8 d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<img src="./imgs/logo.png" style="width: auto !important;height: 86px !important;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4 col-md-4 col-lg-4 text-end">
|
||||||
|
<div class="mt-3 text-end" style="font-size: 18px;">
|
||||||
|
<b>TOP SOFTWARE COMPANY IN VIETNAM</b>
|
||||||
|
<div>hello@bachasoftware.com</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" style="padding: 0rem 1.5rem 0rem 1.5rem;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12 col-lg-12" style="padding: 4rem 3rem !important;">
|
||||||
|
<h2 style="font-weight: 700; font-size: 36px; text-align: center;">
|
||||||
|
Elearning Slide Requirement
|
||||||
|
</h2>
|
||||||
|
<p style="font-weight: 400; font-size: 18px; text-align: center;">
|
||||||
|
A product of Bac Ha Software allows to require user to learn specific slide
|
||||||
|
before able to learn next slides.
|
||||||
|
</p>
|
||||||
|
<img src="banner.png" class="img img-fluid mx-auto img-thumbnail" style="height:350px;">
|
||||||
|
|
||||||
|
<section class="oe_container" style="padding-top: 6rem">
|
||||||
|
<h2 style="text-align: center;"><span style="font-weight: 700; font-size: 28px; color: #FF8000;">
|
||||||
|
Featured Highlight
|
||||||
|
</span></h2>
|
||||||
|
|
||||||
|
<div style="padding-top: 3rem;" class="row">
|
||||||
|
<div class="text-center">
|
||||||
|
<p style="font-weight: 700; font-size: 22px; text-align: center">Simple Setup</p>
|
||||||
|
<p style="font-weight: 400; font-size: 18px">You can easily configure required slides in each course.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container pt-5">
|
||||||
|
<h2 style="text-align: center;"><span style="font-weight: 700; font-size: 28px; color: #FF8000;">
|
||||||
|
Key Features
|
||||||
|
</span></h2>
|
||||||
|
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-lg-6 pt16 pb16 o_colored_level">
|
||||||
|
<img src="./imgs/screen/require.png" class="img img-fluid mx-auto img-thumbnail" style="width: 75%; float: right"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 pt16 pb16 o_colored_level" style="width: 40%">
|
||||||
|
<p style="font-weight: 700; font-size: 24px; text-align: center">Slide Requirement</p>
|
||||||
|
<p style="font-size: 18px;">
|
||||||
|
Require user to learn specific slide before able to learn next slides.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container pt-5">
|
||||||
|
<h2 style="text-align: center;"><span style="font-weight: 700; font-size: 28px; color: #FF8000;">
|
||||||
|
Other Modules
|
||||||
|
</span></h2>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row my-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/bhs_employee_user/" target="_blank">
|
||||||
|
<div style="border-radius:10px" class="shadow-sm">
|
||||||
|
<img src="./imgs/other_module/auto_create_user_from_employee.png" class="img-fluid" />
|
||||||
|
<h4 class="mt0 text-truncate"
|
||||||
|
style="padding:6% 4%; text-align:center; width:100%; font-weight:bold">
|
||||||
|
Auto Create User From Employee
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/bhs_aws_ses_mail_tracking/" target="_blank">
|
||||||
|
<div style="border-radius:10px" class="shadow-sm">
|
||||||
|
<img src="./imgs/other_module/aws_ses_mail_tracking.png" class="img-fluid"/>
|
||||||
|
<h4 class="mt0 text-truncate"
|
||||||
|
style="padding:6% 4%; text-align:center; width:100%; font-weight:bold">
|
||||||
|
AWS SES Mail Tracking
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/bhs_marketing_automation/" target="_blank">
|
||||||
|
<div style="border-radius:10px" class="shadow-sm">
|
||||||
|
<img src="./imgs/other_module/advance_marketing_automation.png" class="img-fluid"/>
|
||||||
|
<h4 class="mt0 text-truncate"
|
||||||
|
style="padding:6% 4%; text-align:center; width:100%; font-weight:bold">
|
||||||
|
Advance Marketing Automation
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/bhs_hr_attendance/" target="_blank">
|
||||||
|
<div style="border-radius:10px" class="shadow-sm">
|
||||||
|
<img src="./imgs/other_module/hr_attendance_management.png" class="img-fluid" />
|
||||||
|
<h4 class="mt0 text-truncate"
|
||||||
|
style="padding:6% 4%; text-align:center; width:100%; font-weight:bold">
|
||||||
|
HR Attendance Management
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/bhs_resignation/" target="_blank">
|
||||||
|
<div style="border-radius:10px" class="shadow-sm">
|
||||||
|
<img src="./imgs/other_module/hr_resignation_management.png" class="img-fluid"/>
|
||||||
|
<h4 class="mt0 text-truncate"
|
||||||
|
style="padding:6% 4%; text-align:center; width:100%; font-weight:bold">
|
||||||
|
HR Resignation Management
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/bhs_project_task_checklist/" target="_blank">
|
||||||
|
<div style="border-radius:10px" class="shadow-sm">
|
||||||
|
<img src="./imgs/other_module/project_task_check_list.png" class="img-fluid"/>
|
||||||
|
<h4 class="mt0 text-truncate"
|
||||||
|
style="padding:6% 4%; text-align:center; width:100%; font-weight:bold">
|
||||||
|
Project Task Check List
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-3">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<a href="https://apps.odoo.com/apps/modules/browse?order=Newest&author=Bac+Ha+Software" target="_blank">
|
||||||
|
<h4 class="mt0 text-truncate py-3 text-end">
|
||||||
|
<span class="blink_me" style="font-weight: 700; font-size: 24px; color: #FF8000;">
|
||||||
|
See more of our modules
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container pt-5">
|
||||||
|
<div style="padding: 2rem 2rem 2rem 2rem; background-color: #5FB645; color: white!important;">
|
||||||
|
<h2 style="text-align: center; font-weight: 700; font-size: 28px; color: white">
|
||||||
|
Need Help?
|
||||||
|
</h2>
|
||||||
|
<p style="font-size: 18px; font-weight: 400; text-align: center">
|
||||||
|
Need assistance with setup or have any concerns?
|
||||||
|
Contact Bac Ha Software directly for prompt and dedicated support:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-lg-6 pt16 pb16 o_colored_level" style="width: 50%">
|
||||||
|
<img src="./imgs/mail.png" class="img img-fluid mx-auto"/>
|
||||||
|
<p style="font-weight: 400; font-size: 18px; text-align: center; margin-bottom: 0; margin-top: 1rem;">Drop us a message:</p>
|
||||||
|
<p style="font-weight: 700; font-size: 18px; text-align: center"><a href="mailto:odoo@bachasoftware.com" style="color: white" target="_blank" rel="noreferrer">odoo@bachasoftware.com</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 pt16 pb16 o_colored_level" style="width: 50%">
|
||||||
|
<img src="./imgs/call.png" class="img img-fluid mx-auto"/>
|
||||||
|
<p style="font-weight: 400; font-size: 18px; text-align: center; margin-bottom: 0; margin-top: 1rem;">Set up a quick call:</p>
|
||||||
|
<p style="font-weight: 700; font-size: 18px; text-align: center">+84 88 616 0880 / +84 88 636 0880</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="container px-1 pb-1 pt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 col-md-4 col-lg-4 justify-content-between">
|
||||||
|
<div>
|
||||||
|
<img src="./imgs/logo.png" style="width: auto !important;height: 150px !important;">
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<img src="./imgs/linkedin_icon.png" style="width: auto !important;height: 32px !important;">
|
||||||
|
<img src="./imgs/twitter_icon.png" style="width: auto !important;height: 26px !important;margin-left: 0.3rem;">
|
||||||
|
<img src="./imgs/facebook_icon.png" style="width: auto !important;height: 28px !important;margin-left: 0.3rem;">
|
||||||
|
<img src="./imgs/youtube_icon.png" style="width: auto !important;height: 32px !important;margin-left: 0.3rem;">
|
||||||
|
<img src="./imgs/instagram_icon.png" style="width: auto !important;height: 32px !important;margin-left: 0.3rem;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-md-4 col-lg-4 justify-content-between">
|
||||||
|
<div class="my-5">
|
||||||
|
<div class="mt-2"><img src="./imgs/mail_footer.png" style="width: auto !important;height: 16px !important;display: inline-block;" class="img-fluid img o_we_custom_image"> <span class="ms-2"><a href="mailto:odoo@bachasoftware.com" target="_blank" rel="noreferrer">odoo@bachasoftware.com</a></span></div>
|
||||||
|
<div class="mt-2"><img src="./imgs/skype_footer.png" style="width: auto !important;height: 20px !important;display: inline-block;" class="img-fluid img o_we_custom_image"> <span class="ms-2">bachasoftware</span></div>
|
||||||
|
<div class="mt-2"><img src="./imgs/whatsapp_icon.png" style="width: auto !important;height: 23px !important;display: inline-block;" class="img-fluid img o_we_custom_image"> <span class="ms-2">+84 88 616 0880/ +84 88 636 0880</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-md-4 col-lg-4 justify-content-between">
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-md-4 my-1"><img src="./imgs/Implementation_icon.png" style="width: auto !important;height: 50px !important;" class="img-fluid img o_we_custom_image"> <div class="text-center">Odoo Implementation</div></div>
|
||||||
|
<div class="col-md-4 my-1"><img src="./imgs/Customization_icon.png" style="width: auto !important;height: 50px !important;" class="img-fluid img o_we_custom_image"> <div class="text-center">Odoo Customization</div></div>
|
||||||
|
<div class="col-md-4 my-1"><img src="./imgs/Development_icon.png" style="width: auto !important;height: 50px !important;" class="img-fluid img o_we_custom_image"> <div class="text-center">Odoo Development</div></div>
|
||||||
|
<div class="col-md-4 my-1"><img src="./imgs/Training_icon.png" style="width: auto !important;height: 50px !important;" class="img-fluid img o_we_custom_image"> <div class="text-center">Odoo Training</div></div>
|
||||||
|
<div class="col-md-4 my-1"><img src="./imgs/Migration_icon.png" style="width: auto !important;height: 50px !important;" class="img-fluid img o_we_custom_image"> <div class="text-center">Odoo Migration</div></div>
|
||||||
|
<div class="col-md-4 my-1"><img src="./imgs/Support_icon.png" style="width: auto !important;height: 50px !important;" class="img-fluid img o_we_custom_image"> <div class="text-center">Odoo Support</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import publicWidget from '@web/legacy/js/public/public_widget';
|
||||||
|
import Fullscreen from '@website_slides/js/slides_course_fullscreen_player';
|
||||||
|
import { useService } from "@web/core/utils/hooks";
|
||||||
|
|
||||||
|
var getReqSlide = function (obj, slideId){
|
||||||
|
return obj.rpc('/slides/slide/required_slide',{slide_id: slideId});
|
||||||
|
};
|
||||||
|
|
||||||
|
publicWidget.registry.websiteSlidesSlidePreventLearningAdmin = publicWidget.Widget.extend({
|
||||||
|
selector: '.o_wslides_js_slides_list_slide_link,.o_wslides_lesson_aside_list_link a,.next-slide-link',
|
||||||
|
xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'],
|
||||||
|
events: {
|
||||||
|
'click': '_onMissingRequirementSlideClick',
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.rpc = this.bindService("rpc");
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMissingRequirementSlideClick: async function (ev) {
|
||||||
|
console.log('_onMissingRequirementSlideClick');
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
var dataId = $(ev.currentTarget).data('id');
|
||||||
|
var slideHref = $(ev.currentTarget).attr('href')
|
||||||
|
if (dataId) {
|
||||||
|
var slideId = parseInt(dataId)
|
||||||
|
}else {
|
||||||
|
var slideLink = slideHref.replace('?fullscreen=1', '');
|
||||||
|
if(slideLink.includes('-')) {
|
||||||
|
var slideSplit = slideLink.split('-');
|
||||||
|
}else{
|
||||||
|
var slideSplit = slideLink.split('/');
|
||||||
|
}
|
||||||
|
// var slideSplit = slideLink.split('-');
|
||||||
|
console.log(slideSplit);
|
||||||
|
var slideId = parseInt(slideSplit[slideSplit.length - 1]);
|
||||||
|
console.log(slideId);
|
||||||
|
}
|
||||||
|
const reqSlide = await getReqSlide(this, slideId);
|
||||||
|
console.log('reqSlide: ', reqSlide);
|
||||||
|
if (reqSlide && !reqSlide.can_skip) {
|
||||||
|
console.log('cant skip');
|
||||||
|
var url = reqSlide.url;
|
||||||
|
if (slideHref.includes('?fullscreen=1')) {url = url + '?fullscreen=1'}
|
||||||
|
$("#modal_required_slide").attr('href', url);
|
||||||
|
$("#modal_required_slide").html(reqSlide.name);
|
||||||
|
$("#missing_requirement_modal").modal('show');
|
||||||
|
}
|
||||||
|
else if (slideHref) {location.href = slideHref;}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var BHSidebar = publicWidget.Widget.extend({
|
||||||
|
events: {
|
||||||
|
"click .o_wslides_fs_sidebar_list_item": '_onClickTab',
|
||||||
|
"click .o_wslides_fs_slide_link": '_onClickLink',
|
||||||
|
// "click #modal_required_slide": '_onClickModalLink',
|
||||||
|
},
|
||||||
|
init: function (parent, slideList, defaultSlide) {
|
||||||
|
var result = this._super.apply(this, arguments);
|
||||||
|
this.slideEntries = slideList;
|
||||||
|
this.set('slideEntry', defaultSlide);
|
||||||
|
this.rpc = this.bindService("rpc");
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
start: function (){
|
||||||
|
var self = this;
|
||||||
|
this.on('change:slideEntry', this, this._onChangeCurrentSlide);
|
||||||
|
return this._super.apply(this, arguments).then(function (){
|
||||||
|
$(document).keydown(self._onKeyDown.bind(self));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
$(document).unbind('keydown', this._onKeyDown.bind(this));
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
openRequiredSlideModal: function (reqSlide) {
|
||||||
|
var self = this;
|
||||||
|
$("#modal_required_slide").attr('data-id', reqSlide.id);
|
||||||
|
$("#modal_required_slide span").html(reqSlide.name);
|
||||||
|
$("#missing_requirement_modal").modal('show');
|
||||||
|
$("#modal_required_slide").click(function () {
|
||||||
|
var slide = findSlide(self.slideEntries, {id: reqSlide.id, isQuiz: false});
|
||||||
|
self.set('slideEntry', slide);
|
||||||
|
$("#missing_requirement_modal").modal('hide');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the current slide with the next one (if there is one).
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
goNext: async function () {
|
||||||
|
var currentIndex = this._getCurrentIndex();
|
||||||
|
if (currentIndex < this.slideEntries.length-1) {
|
||||||
|
var nextSlide = this.slideEntries[currentIndex+1];
|
||||||
|
const reqSlide = await getReqSlide(this, nextSlide.id);
|
||||||
|
if (reqSlide && !reqSlide.can_skip) {this.openRequiredSlideModal(reqSlide)}
|
||||||
|
else {this.set('slideEntry', nextSlide);}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Change the current slide with the previous one (if there is one).
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
goPrevious: function () {
|
||||||
|
var currentIndex = this._getCurrentIndex();
|
||||||
|
if (currentIndex >= 1) {
|
||||||
|
this.set('slideEntry', this.slideEntries[currentIndex-1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Greens up the bullet when the slide is completed
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {Integer} slideId
|
||||||
|
*/
|
||||||
|
setSlideCompleted: async function (slideId) {
|
||||||
|
var $elem = this.$('.fa-circle-thin[data-slide-id="'+slideId+'"],.fa-lock[data-slide-id="'+slideId+'"]');
|
||||||
|
$elem.removeClass('fa-circle-thin').removeClass('fa-lock').addClass('fa-check text-success o_wslides_slide_completed');
|
||||||
|
|
||||||
|
const reqSlide = await getReqSlide(this, slideId);
|
||||||
|
if (!reqSlide) {this.updateRequirement()}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Updates the progressbar whenever a lesson is completed
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {*} channelCompletion
|
||||||
|
*/
|
||||||
|
updateProgressbar: function (channelCompletion) {
|
||||||
|
var completion = Math.min(100, channelCompletion);
|
||||||
|
this.$('.progress-bar').css('width', completion + "%" );
|
||||||
|
this.$('.o_wslides_progress_percentage').text(completion);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRequirement: function () {
|
||||||
|
var self = this;
|
||||||
|
var allSlides = this.slideEntries;
|
||||||
|
var nextSlides = allSlides.slice(this._getCurrentIndex() + 1, allSlides.length - 1);
|
||||||
|
for (const slide of nextSlides) {
|
||||||
|
var slideId = slide.id;
|
||||||
|
var $slideName = self.$('li[data-id="'+slideId+'"] div.o_wslides_fs_slide_name');
|
||||||
|
var $slideLink = self.$('li[data-id="'+slideId+'"] a.o_wslides_fs_slide_link');
|
||||||
|
$slideName.removeClass('text-600');
|
||||||
|
$slideLink.removeClass('text-600');
|
||||||
|
if (slide.hasQuestion && !slide.isQuiz) {
|
||||||
|
var $slideQuiz = self.$('li[data-id="'+slideId+'"] a.o_wslides_fs_slide_quiz');
|
||||||
|
$slideQuiz.removeClass('text-600');
|
||||||
|
};
|
||||||
|
if (slide.required === 'True') {break}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Get the index of the current slide entry (slide and/or quiz)
|
||||||
|
*/
|
||||||
|
_getCurrentIndex: function () {
|
||||||
|
var slide = this.get('slideEntry');
|
||||||
|
var currentIndex = this.slideEntries.findIndex(entry =>{
|
||||||
|
return entry.id === slide.id && entry.isQuiz === slide.isQuiz;
|
||||||
|
});
|
||||||
|
return currentIndex;
|
||||||
|
},
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Handler
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* Handler called whenever the user clicks on a sub-quiz which is linked to a slide.
|
||||||
|
* This does NOT handle the case of a slide of type "quiz".
|
||||||
|
* By going through this handler, the widget will be able to determine that it has to render
|
||||||
|
* the associated quiz and not the main content.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {*} ev
|
||||||
|
*/
|
||||||
|
_onClickMiniQuiz: function (ev){
|
||||||
|
var slideID = parseInt($(ev.currentTarget).data().slide_id);
|
||||||
|
this.set('slideEntry',{
|
||||||
|
slideID: slideID,
|
||||||
|
isMiniQuiz: true
|
||||||
|
});
|
||||||
|
this.trigger_up('change_slide', this.get('slideEntry'));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler called when the user clicks on a normal slide tab
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {*} ev
|
||||||
|
*/
|
||||||
|
_onClickTab: async function (ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
var self = this;
|
||||||
|
var $elem = $(ev.currentTarget);
|
||||||
|
var slideID = parseInt($elem.data('id'));
|
||||||
|
console.log('aaaa');
|
||||||
|
const reqSlide = await getReqSlide(this, slideID);
|
||||||
|
if ($elem.data('canAccess') === 'True') {
|
||||||
|
if (!reqSlide || (reqSlide && reqSlide.can_skip)) {
|
||||||
|
var isQuiz = $elem.data('isQuiz');
|
||||||
|
var slide = findSlide(this.slideEntries, {id: slideID, isQuiz: isQuiz});
|
||||||
|
this.set('slideEntry', slide);
|
||||||
|
}
|
||||||
|
else {this.openRequiredSlideModal(reqSlide)};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onClickLink: async function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var self = this;
|
||||||
|
var $elem = $(ev.currentTarget);
|
||||||
|
var $slideElem = $elem.closest('li.o_wslides_fs_sidebar_list_item');
|
||||||
|
var slideID = parseInt($slideElem.data('id'));
|
||||||
|
const reqSlide = await getReqSlide(this, slideID);
|
||||||
|
if ($slideElem.data('canAccess') === 'True') {
|
||||||
|
if (!reqSlide || (reqSlide && reqSlide.can_skip)) {
|
||||||
|
window.open($elem.attr('href'), '_blank').focus();
|
||||||
|
}
|
||||||
|
else {this.openRequiredSlideModal(reqSlide)};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actively changes the active tab in the sidebar so that it corresponds
|
||||||
|
* the slide currently displayed
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onChangeCurrentSlide: function () {
|
||||||
|
var slide = this.get('slideEntry');
|
||||||
|
this.$('.o_wslides_fs_sidebar_list_item.active').removeClass('active');
|
||||||
|
var selector = '.o_wslides_fs_sidebar_list_item[data-id='+slide.id+'][data-is-quiz!="1"]';
|
||||||
|
|
||||||
|
this.$(selector).addClass('active');
|
||||||
|
this.trigger_up('change_slide', this.get('slideEntry'));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds left and right arrow to allow the user to navigate between slides
|
||||||
|
*
|
||||||
|
* @param {*} ev
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onKeyDown: function (ev){
|
||||||
|
switch (ev.key){
|
||||||
|
case "ArrowLeft":
|
||||||
|
this.goPrevious();
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
this.goNext();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//var findSlide = function (slideList, matcher) {
|
||||||
|
// var slideMatch = _.matcher(matcher);
|
||||||
|
// return _.find(slideList, slideMatch);
|
||||||
|
//};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: Get the slide dict matching the given criteria
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Array<Object>} slideList List of dict reprensenting a slide
|
||||||
|
* @param {[string] : any} matcher
|
||||||
|
*/
|
||||||
|
var findSlide = function (slideList, matcher) {
|
||||||
|
return slideList.find((slide) => {
|
||||||
|
return Object.keys(matcher).every((key) => matcher[key] === slide[key]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var ShareButton = publicWidget.Widget.extend({
|
||||||
|
events: {
|
||||||
|
"click .o_wslides_fs_share": '_onClickShareSlide'
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function (el, slide) {
|
||||||
|
var result = this._super.apply(this, arguments);
|
||||||
|
this.slide = slide;
|
||||||
|
this.rpc = this.bindService("rpc");
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
_openDialog: function() {
|
||||||
|
return new ShareDialog(this, {}, this.slide).open();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onClickShareSlide: function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this._openDialog();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onChangeSlide: function (currentSlide) {
|
||||||
|
this.slide = currentSlide;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var BHFullscreen = Fullscreen.include({
|
||||||
|
init: function (parent, slides, defaultSlideId, channelData) {
|
||||||
|
var result = this._super.apply(this,arguments);
|
||||||
|
this.initialSlideID = defaultSlideId;
|
||||||
|
this.slides = this._preprocessSlideData(slides);
|
||||||
|
this.channel = channelData;
|
||||||
|
var slide;
|
||||||
|
const urlParams = new URL(window.location).searchParams;
|
||||||
|
if (defaultSlideId) {
|
||||||
|
slide = findSlide(this.slides, {id: defaultSlideId, isQuiz: String(urlParams.get("quiz")) === "1" });
|
||||||
|
} else {
|
||||||
|
slide = this.slides[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('slide', slide);
|
||||||
|
|
||||||
|
this.sidebar = new BHSidebar(this, this.slides, slide);
|
||||||
|
this.shareButton = new ShareButton(this, slide);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
websiteSlidesSlidePreventLearningAdmin: publicWidget.registry.websiteSlidesSlidePreventLearningAdmin
|
||||||
|
};
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/** @odoo-module **/
|
||||||
|
|
||||||
|
//import { useService } from "@web/core/utils/hooks";
|
||||||
|
//import browser from "@web/core/browser/browser";
|
||||||
|
//import publicWidget from '@web/legacy/js/public/public_widget';
|
||||||
|
//
|
||||||
|
//publicWidget.registry.websiteSlidesSlideToggleIsRequired = publicWidget.Widget.extend({
|
||||||
|
// selector: '.o_wslides_js_slide_toggle_is_required',
|
||||||
|
// xmlDependencies: ['/website_slides/static/src/xml/slide_management.xml'],
|
||||||
|
// events: {
|
||||||
|
// 'click': '_onRequiredSlideClick',
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// _updateMissingReq: function ($liParent) {
|
||||||
|
// var slides = $liParent.closest('.o_wslides_slides_list');
|
||||||
|
// var allLi = slides.find('li.o_wslides_slides_list_slide');
|
||||||
|
// var missingReq = false;
|
||||||
|
// allLi.each(function (){
|
||||||
|
// var $slideLink = $(this).find('.o_wslides_js_slides_list_slide_link');
|
||||||
|
// var muted = $slideLink.hasClass('text-muted');
|
||||||
|
// var slideReq = Boolean($(this).find('.o_wslides_js_slide_toggle_is_required.bh-badge-warning').length);
|
||||||
|
// var slideLearned = Boolean($(this).find('.fa-check-circle').length);
|
||||||
|
// var isPreview = Boolean($(this).find('.o_wslides_js_slide_toggle_is_preview.badge-success').length);
|
||||||
|
// if (missingReq && !isPreview && !muted) {
|
||||||
|
// $slideLink.addClass('text-muted');
|
||||||
|
// }
|
||||||
|
// else if (!missingReq && muted) {
|
||||||
|
// $slideLink.removeClass('text-muted');
|
||||||
|
// };
|
||||||
|
// if (slideReq && !slideLearned) {
|
||||||
|
// missingReq = slideReq;
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// _toggleSlideRequired: function($slideTarget) {
|
||||||
|
// var self = this
|
||||||
|
// this.rpc = useService('rpc');
|
||||||
|
// this.rpc('/slides/slide/toggle_is_required',
|
||||||
|
// {
|
||||||
|
// slide_id: $slideTarget.data('slideId')
|
||||||
|
// }
|
||||||
|
// ).then(function (isRequired) {
|
||||||
|
// var $liParent = $slideTarget.closest('li.o_wslides_slides_list_slide');
|
||||||
|
// var learned = Boolean($liParent.find('.fa-check-circle').length);
|
||||||
|
// if (isRequired) {
|
||||||
|
// $slideTarget.removeClass('badge-light badge-hide border text-bg-light');
|
||||||
|
// $slideTarget.addClass('bh-badge-warning');
|
||||||
|
// } else {
|
||||||
|
// $slideTarget.removeClass('bh-badge-warning');
|
||||||
|
// $slideTarget.addClass('badge-light badge-hide border text-bg-light');
|
||||||
|
// };
|
||||||
|
// if (!learned) {
|
||||||
|
// self._updateMissingReq($liParent);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// _onRequiredSlideClick: function (ev) {
|
||||||
|
// ev.preventDefault();
|
||||||
|
// this._toggleSlideRequired($(ev.currentTarget));
|
||||||
|
// },
|
||||||
|
//});
|
||||||
|
//
|
||||||
|
//export default {
|
||||||
|
// websiteSlidesSlideToggleIsRequired: publicWidget.registry.websiteSlidesSlideToggleIsRequired
|
||||||
|
//};
|
||||||
|
|
||||||
|
import publicWidget from '@web/legacy/js/public/public_widget';
|
||||||
|
|
||||||
|
publicWidget.registry.websiteSlidesSlideToggleIsRequired = publicWidget.Widget.extend({
|
||||||
|
selector: '.o_wslides_js_slide_toggle_is_required',
|
||||||
|
events: {
|
||||||
|
'click': '_onRequiredSlideClick',
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.rpc = this.bindService("rpc");
|
||||||
|
},
|
||||||
|
|
||||||
|
_toggleSlideRequired: function($slideTarget) {
|
||||||
|
this.rpc('/slides/slide/toggle_is_required', {
|
||||||
|
slide_id: $slideTarget.data('slideId')
|
||||||
|
}).then(function (isRequired) {
|
||||||
|
var $liParent = $slideTarget.closest('li.o_wslides_slides_list_slide');
|
||||||
|
var learned = Boolean($liParent.find('.fa-check-circle').length);
|
||||||
|
if (isRequired) {
|
||||||
|
$slideTarget.removeClass('badge-light badge-hide border text-bg-light');
|
||||||
|
$slideTarget.addClass('bh-badge-warning');
|
||||||
|
} else {
|
||||||
|
$slideTarget.removeClass('bh-badge-warning');
|
||||||
|
$slideTarget.addClass('badge-light badge-hide border text-bg-light');
|
||||||
|
};
|
||||||
|
if (!learned) {
|
||||||
|
var slides = $liParent.closest('.o_wslides_slides_list');
|
||||||
|
var allLi = slides.find('li.o_wslides_slides_list_slide');
|
||||||
|
var missingReq = false;
|
||||||
|
allLi.each(function (){
|
||||||
|
var $slideLink = $(this).find('.o_wslides_js_slides_list_slide_link');
|
||||||
|
var muted = $slideLink.hasClass('text-muted');
|
||||||
|
var slideReq = Boolean($(this).find('.o_wslides_js_slide_toggle_is_required.bh-badge-warning').length);
|
||||||
|
var slideLearned = Boolean($(this).find('.fa-check-circle').length);
|
||||||
|
var isPreview = Boolean($(this).find('.o_wslides_js_slide_toggle_is_preview.badge-success').length);
|
||||||
|
if (missingReq && !isPreview && !muted) {
|
||||||
|
$slideLink.addClass('text-muted');
|
||||||
|
}
|
||||||
|
else if (!missingReq && muted) {
|
||||||
|
$slideLink.removeClass('text-muted');
|
||||||
|
};
|
||||||
|
if (slideReq && !slideLearned) {
|
||||||
|
missingReq = slideReq;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRequiredSlideClick: function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this._toggleSlideRequired($(ev.currentTarget));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
websiteSlidesSlideToggleIsPreview: publicWidget.registry.websiteSlidesSlideToggleIsPreview
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
.bh-badge-warning{
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #F99C19;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_wslides_js_slide_toggle_is_required p {
|
||||||
|
margin-bottom: 0 !important;;
|
||||||
|
}
|
||||||
|
|
||||||
|
.o_wslides_js_slide_toggle_is_preview.badge-success{
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #09D5AC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bh_text_popup a {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 150%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#missing_requirement_modal .btn.btn-primary{
|
||||||
|
background-color: #2F64E4;
|
||||||
|
border-color: #2F64E4;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
/* required box dia*/
|
||||||
|
.bh_img_icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding-bottom: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bh_button_popup {
|
||||||
|
background: #2F64E4;
|
||||||
|
width: 49%;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: solid 1px #405089;
|
||||||
|
color: #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bh_text_popup {
|
||||||
|
padding-bottom: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bh_text_popup p {
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 150%;
|
||||||
|
text-align: center;
|
||||||
|
color: #262D57;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bh_modal-box {
|
||||||
|
width: 70%;
|
||||||
|
top: 25%;
|
||||||
|
left: 15%;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bh_text_login {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="slide_slide_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">slide.slide.view.form.inherit.required.slide</field>
|
||||||
|
<field name="model">slide.slide</field>
|
||||||
|
<field name="inherit_id" ref="website_slides.view_slide_slide_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='slide_resource_downloadable']" position="after">
|
||||||
|
<field name="is_required"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="slide_channel_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">slide.channel.view.form.required.slide</field>
|
||||||
|
<field name="model">slide.channel</field>
|
||||||
|
<field name="inherit_id" ref="website_slides.view_slide_channel_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//field[@name='slide_ids']//field[@name='is_preview']" position="after">
|
||||||
|
<field name="is_required"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<template id="bhs_elearning_required_slide.missing_requirement_modal" name="Slide missing requirement modal">
|
||||||
|
<div class="modal fade" id="missing_requirement_modal" role="dialog">
|
||||||
|
<div class="modal-dialog bh_modal_dialog">
|
||||||
|
<div class="modal-content bh_modal-box">
|
||||||
|
<div class="bh_text_popup pt-4">
|
||||||
|
<p>Please complete the <b>Required Slide:</b>
|
||||||
|
<br/>
|
||||||
|
<a href="#" id="modal_required_slide"><span></span></a>
|
||||||
|
<br/>
|
||||||
|
before learning this slide.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-primary px-5 mb-2" data-bs-dismiss="modal">Understood</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="course_slides_list_modal" name="Modal for Training Course content: list" inherit_id="website_slides.course_slides_list">
|
||||||
|
<xpath expr="//div[@t-field='channel.description_html']" position="after">
|
||||||
|
<t t-call="bhs_elearning_required_slide.missing_requirement_modal"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="slide_aside_training_modal" name="Modal for Slide: Sidebar in Training" inherit_id="website_slides.slide_aside_training">
|
||||||
|
<xpath expr="//div[@class='o_wslides_lesson_aside_list position-relative bg-white border-bottom mt-4']" position="after">
|
||||||
|
<t t-call="bhs_elearning_required_slide.missing_requirement_modal"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="slide_fullscreen_modal" name="Modal for Slide Fullscreen" inherit_id="website_slides.slide_fullscreen">
|
||||||
|
<xpath expr="//div[@class='o_wslides_fs_container d-flex position-relative overflow-hidden flex-grow-1']" position="after">
|
||||||
|
<t t-call="bhs_elearning_required_slide.missing_requirement_modal"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="slide_fullscreen_sidebar_category" name="Fullscreen view side bar - remove slide link when not accessible" inherit_id="website_slides.slide_fullscreen_sidebar_category">
|
||||||
|
<xpath expr="//t[@t-set='can_access']" position="after">
|
||||||
|
<t t-set="missing_requirement" t-value="slide.missing_requirement"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//a//div[@class='o_wslides_fs_slide_name text-truncate']" position="replace">
|
||||||
|
<div t-attf-class="o_wslides_fs_slide_name #{'text-600 text-truncate' if missing_requirement else 'text-truncate'}" t-esc="slide.name"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//li[@t-att-data-embed-code]" position="attributes">
|
||||||
|
<attribute name="t-att-data-required">slide.is_required</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//a[@t-att-href='link.link']" position="replace">
|
||||||
|
<a t-if="can_access" t-attf-class="o_wslides_fs_slide_link #{'text-600 text-truncate' if missing_requirement else 'text-truncate'}" t-att-href="link.link" target="_blank">
|
||||||
|
<i class="fa fa-link me-2"/><span t-esc="link.name"/>
|
||||||
|
</a>
|
||||||
|
</xpath>
|
||||||
|
<!-- <xpath expr="//a[@t-att-href='resource._get_download_url()']" position="replace">-->
|
||||||
|
<!-- <a t-attf-class="o_wslides_fs_slide_link #{'text-600' if missing_requirement else ''}" t-att-href="resource._get_download_url()">-->
|
||||||
|
<!-- <xpath expr="//a[@t-attf-href='/web/content/slide.slide.resource/#{resource.id}/data?download=true']" position="replace">-->
|
||||||
|
<!-- <a t-attf-class="o_wslides_fs_slide_link #{'text-600' if missing_requirement else ''}" t-attf-href="/web/content/slide.slide.resource/#{resource.id}/data?download=true">-->
|
||||||
|
<!-- <i class="fa fa-download me-2"/><span t-esc="resource.name"/>-->
|
||||||
|
<!-- </a>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
<xpath expr="//a[hasclass('o_wslides_fs_slide_quiz')]" position="replace">
|
||||||
|
<a t-if="can_access" t-attf-class="o_wslides_fs_slide_quiz o_wslides_fs_slide_name #{'text-600 text-truncate' if missing_requirement else 'text-truncate'}" href="#" t-att-index="i">
|
||||||
|
<i class="fa fa-flag-checkered text-warning me-2"/>Quiz
|
||||||
|
</a>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="course_slides_required_slide" name="Slides template with required slide" inherit_id="website_slides_survey.course_slides_list_slide_inherit_survey">
|
||||||
|
<xpath expr="//div[@class='text-truncate me-auto']" position="replace">
|
||||||
|
<div class="text-truncate me-auto">
|
||||||
|
<a t-if="slide.is_preview or channel.is_member or channel.can_publish"
|
||||||
|
t-attf-class="o_wslides_js_slides_list_slide_link #{'text-muted' if slide.missing_requirement else ''}"
|
||||||
|
t-attf-href="#{'/website_slides_survey/slide/get_certification_url?slide_id=%s' %(slide.id) if slide.slide_type == 'certification' and slide.channel_id.total_slides == 1 else '/slides/slide/%s' %(slug(slide))}"
|
||||||
|
t-att-data-slide-id="slide.id">
|
||||||
|
<span t-field="slide.name"/>
|
||||||
|
</a>
|
||||||
|
<span t-else="">
|
||||||
|
<span t-esc="slide.name"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//a[@name='o_wslides_list_slide_add_quizz']" position="before">
|
||||||
|
<a t-if="channel.can_upload" href="#">
|
||||||
|
<span t-att-data-slide-id="slide.id" t-attf-class="o_wslides_js_slide_toggle_is_required badge #{'bh-badge-warning' if slide.is_required else 'badge-light badge-hide border text-bg-light'} fw-normal px-2 py-1 m-1"><p>Required</p></span>
|
||||||
|
</a>
|
||||||
|
<t t-elif="slide.is_required">
|
||||||
|
<span class="badge bh-badge-warning fw-normal px-2 py-1 m-1"><span>Required</span></span>
|
||||||
|
</t>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="slide_sidebar_done_button_required" inherit_id="website_slides.slide_sidebar_done_button" name="Sidebar Done Button">
|
||||||
|
<xpath expr="//button[@t-if]//i[@t-else]" position="replace">
|
||||||
|
<i t-elif="slide.is_required" class="fa fa-lock fa-fw fa-lg" t-att-data-slide-id="slide.id" title="Mark as done"/>
|
||||||
|
<i t-else="" class="fa fa-circle-thin fa-fw fa-lg" t-att-data-slide-id="slide.id" title="Mark as done"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//button[@t-else]//i[@t-if]" position="replace">
|
||||||
|
<i t-if="not slide_completed and slide.is_required" class="fa fa-lock fa-fw fa-lg" t-att-data-slide-id="slide.id" title="Can not be marked as done"/>
|
||||||
|
<i t-elif="not slide_completed" class="fa fa-circle-thin fa-fw fa-lg" t-att-data-slide-id="slide.id" title="Can not be marked as done"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="slide_content_detailed_required" inherit_id="website_slides.slide_content_detailed" name="Slide: Detailed Content">
|
||||||
|
<xpath expr="//i[@class='oi oi-chevron-right ms-2']/parent::a" position="replace">
|
||||||
|
<a t-att-class="'btn btn-light border %s' % ('disabled' if not next_slide else 'next-slide-link')"
|
||||||
|
role="button" t-att-aria-disabled="'disabled' if not next_slide else None"
|
||||||
|
t-att-href="'/slides/slide/%s' % (slug(next_slide)) if next_slide else '#'">
|
||||||
|
<span class="d-none d-sm-inline-block">Next</span> <i class="oi oi-chevron-right ms-2"></i>
|
||||||
|
</a>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'HR Skill eLearning',
|
||||||
|
'author': 'Bac Ha Software',
|
||||||
|
'website': 'https://bachasoftware.com',
|
||||||
|
'maintainer': 'Bac Ha Software',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Website',
|
||||||
|
'sequence': 101,
|
||||||
|
'summary': 'HR Skill eLearning',
|
||||||
|
'description': """
|
||||||
|
A product of Bac Ha Software allows to record online courses that
|
||||||
|
each employee has/is taking in employee information.
|
||||||
|
""",
|
||||||
|
'images': [],
|
||||||
|
'depends': ['website_slides', 'hr_skills_slides'],
|
||||||
|
'assets': {
|
||||||
|
'web.assets_qweb': [
|
||||||
|
'bhs_hr_skill_elearning/static/src/xml/resume_templates.xml',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'data': [
|
||||||
|
'views/hr_view.xml',
|
||||||
|
],
|
||||||
|
'demo': [],
|
||||||
|
"external_dependencies": {},
|
||||||
|
'images': ['static/description/banner.png'],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
'qweb': [],
|
||||||
|
'license': 'LGPL-3'
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Translation of Odoo Server.
|
||||||
|
# This file contains the translation of the following modules:
|
||||||
|
# * bhs_hr_skill_elearning
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Odoo Server 15.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2022-12-02 02:26+0000\n"
|
||||||
|
"PO-Revision-Date: 2022-12-02 02:26+0000\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: \n"
|
||||||
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. module: bhs_hr_skill_elearning
|
||||||
|
#: model:ir.model,name:bhs_hr_skill_elearning.model_slide_channel_partner
|
||||||
|
msgid "Channel / Partners (Members)"
|
||||||
|
msgstr "Kênh / Đối tác (Thành viên)"
|
||||||
|
|
||||||
|
#. module: bhs_hr_skill_elearning
|
||||||
|
#: model:ir.model.fields,field_description:bhs_hr_skill_elearning.field_hr_resume_line__completed
|
||||||
|
msgid "Completed"
|
||||||
|
msgstr "Đã hoàn thành"
|
||||||
|
|
||||||
|
#. module: bhs_hr_skill_elearning
|
||||||
|
#: code:addons/bhs_hr_skill_elearning/models/slide_chanel_partner.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Completed course: %s"
|
||||||
|
msgstr "Hoàn thành khóa: %s"
|
||||||
|
|
||||||
|
#. module: bhs_hr_skill_elearning
|
||||||
|
#: model:ir.model,name:bhs_hr_skill_elearning.model_hr_resume_line
|
||||||
|
msgid "Resumé line of an employee"
|
||||||
|
msgstr "Dòng sơ yếu lí lịch của nhân viên"
|
||||||
|
|
||||||
|
#. module: bhs_hr_skill_elearning
|
||||||
|
#: code:addons/bhs_hr_skill_elearning/models/slide_chanel_partner.py:0
|
||||||
|
#, python-format
|
||||||
|
msgid "Studying course: %s"
|
||||||
|
msgstr "Đang học khóa: %s"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import slide_chanel_partner
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import models, fields, api,_
|
||||||
|
from odoo.tools import html2plaintext
|
||||||
|
|
||||||
|
class ResumeLine(models.Model):
|
||||||
|
_inherit = 'hr.resume.line'
|
||||||
|
|
||||||
|
# Trạng thái
|
||||||
|
completed = fields.Boolean(string="Completed", default=True)
|
||||||
|
|
||||||
|
class SlideChannelPartnerInherit(models.Model):
|
||||||
|
|
||||||
|
_inherit = 'slide.channel.partner'
|
||||||
|
|
||||||
|
def _recompute_completion(self):
|
||||||
|
# NOTE: KHI KẾ THỪA HÀM NÀY CẦN COPY LẠI GHI ĐÈ CẢ HÀM NÀY
|
||||||
|
# CODE BASE WEBSITE SLIDE:website_slides/slide_channel.py
|
||||||
|
""" This method computes the completion and member_status of attendees that are neither
|
||||||
|
'invited' nor 'completed'. Indeed, once completed, membership should remain so.
|
||||||
|
We do not do any update on the 'invited' records.
|
||||||
|
One should first set member_status to 'joined' before recomputing those values
|
||||||
|
when enrolling an invited or archived attendee.
|
||||||
|
It takes into account the previous completion value to add or remove karma for
|
||||||
|
completing the course to the attendee (see _post_completion_update_hook)
|
||||||
|
"""
|
||||||
|
read_group_res = self.env['slide.slide.partner'].sudo()._read_group(
|
||||||
|
['&', '&', ('channel_id', 'in', self.mapped('channel_id').ids),
|
||||||
|
('partner_id', 'in', self.mapped('partner_id').ids),
|
||||||
|
('completed', '=', True),
|
||||||
|
('slide_id.is_published', '=', True),
|
||||||
|
('slide_id.active', '=', True)],
|
||||||
|
['channel_id', 'partner_id'],
|
||||||
|
aggregates=['__count'])
|
||||||
|
mapped_data = {
|
||||||
|
(channel.id, partner.id): count
|
||||||
|
for channel, partner, count in read_group_res
|
||||||
|
}
|
||||||
|
|
||||||
|
completed_records = self.env['slide.channel.partner']
|
||||||
|
uncompleted_records = self.env['slide.channel.partner']
|
||||||
|
for record in self:
|
||||||
|
if record.member_status in ('completed', 'invited'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# GHI NHẬN RESUME LINE
|
||||||
|
employee_id = self.env['hr.employee'].sudo().search(
|
||||||
|
[('user_id.partner_id', '=', record.partner_id.id)])
|
||||||
|
for emp in employee_id:
|
||||||
|
# Nếu chưa ghi nhận thì tạo
|
||||||
|
check_resume_data = self.env['hr.resume.line'].search(
|
||||||
|
[('employee_id', '=', emp.id), ('channel_id', '=', record.channel_id.id)])
|
||||||
|
if not check_resume_data:
|
||||||
|
line_type = self.env.ref('hr_skills_slides.resume_type_training', raise_if_not_found=False)
|
||||||
|
self.env['hr.resume.line'].create({
|
||||||
|
'employee_id': emp and emp.id,
|
||||||
|
'name': _('Studying course: %s') % record.channel_id.name,
|
||||||
|
'date_start': fields.Date.today(),
|
||||||
|
'description': html2plaintext(record.channel_id.description),
|
||||||
|
'line_type_id': line_type and line_type.id,
|
||||||
|
'display_type': 'course',
|
||||||
|
'channel_id': record.channel_id.id,
|
||||||
|
'completed': False
|
||||||
|
})
|
||||||
|
|
||||||
|
was_finished = record.completion == 100
|
||||||
|
record.completed_slides_count = mapped_data.get((record.channel_id.id, record.partner_id.id), 0)
|
||||||
|
record.completion = round(100.0 * record.completed_slides_count / (record.channel_id.total_slides or 1))
|
||||||
|
|
||||||
|
if not record.channel_id.active:
|
||||||
|
continue
|
||||||
|
elif not was_finished and record.completed_slides_count >= record.channel_id.total_slides:
|
||||||
|
completed_records += record
|
||||||
|
elif was_finished and record.completed_slides_count < record.channel_id.total_slides:
|
||||||
|
uncompleted_records += record
|
||||||
|
|
||||||
|
if record.completion == 100:
|
||||||
|
record.member_status = 'completed'
|
||||||
|
elif record.completion == 0:
|
||||||
|
record.member_status = 'joined'
|
||||||
|
else:
|
||||||
|
record.member_status = 'ongoing'
|
||||||
|
|
||||||
|
if completed_records:
|
||||||
|
# CODE GHI NHẬN HOÀN THÀNH KHÓA HỌC:
|
||||||
|
for completed_data in completed_records:
|
||||||
|
employee_completed_id = self.env['hr.employee'].sudo().search(
|
||||||
|
[('user_id.partner_id', '=', completed_data.partner_id.id)])
|
||||||
|
resume_data = self.env['hr.resume.line'].search([('employee_id', 'in', employee_completed_id.ids),
|
||||||
|
('channel_id', '=', completed_data.channel_id.id),
|
||||||
|
('completed', '!=', True)])
|
||||||
|
resume_data.write({'name': _('Completed course: %s') % completed_data.channel_id.name,
|
||||||
|
'date_end': fields.Date.today(), 'completed': True})
|
||||||
|
|
||||||
|
completed_records._post_completion_update_hook(completed=True)
|
||||||
|
completed_records._send_completed_mail()
|
||||||
|
|
||||||
|
if uncompleted_records:
|
||||||
|
uncompleted_records._post_completion_update_hook(completed=False)
|
||||||
|
|
||||||
|
# def _recompute_completion(self):
|
||||||
|
# # NOTE: KHI KẾ THỪA HÀM NÀY CẦN COPY LẠI GHI ĐÈ CẢ HÀM NÀY
|
||||||
|
# # CODE BASE WEBSITE SLIDE:website_slides/slide_channel.py
|
||||||
|
# read_group_res = self.env['slide.slide.partner'].sudo().read_group(
|
||||||
|
# ['&', '&', ('channel_id', 'in', self.mapped('channel_id').ids),
|
||||||
|
# ('partner_id', 'in', self.mapped('partner_id').ids),
|
||||||
|
# ('completed', '=', True),
|
||||||
|
# ('slide_id.is_published', '=', True),
|
||||||
|
# ('slide_id.active', '=', True)],
|
||||||
|
# ['channel_id', 'partner_id'],
|
||||||
|
# groupby=['channel_id', 'partner_id'], lazy=False)
|
||||||
|
# mapped_data = dict()
|
||||||
|
# for item in read_group_res:
|
||||||
|
# mapped_data.setdefault(item['channel_id'][0], dict())
|
||||||
|
# mapped_data[item['channel_id'][0]][item['partner_id'][0]] = item['__count']
|
||||||
|
#
|
||||||
|
# completed_records = self.env['slide.channel.partner']
|
||||||
|
# for record in self:
|
||||||
|
# # GHI NHẬN RESUME LINE
|
||||||
|
# employee_id = self.env['hr.employee'].sudo().search(
|
||||||
|
# [('user_id.partner_id', '=', record.partner_id.id)])
|
||||||
|
# for emp in employee_id:
|
||||||
|
# # Nếu chưa ghi nhận thì tạo
|
||||||
|
# check_resume_data = self.env['hr.resume.line'].search([('employee_id', '=', emp.id), ('channel_id', '=', record.channel_id.id)])
|
||||||
|
# if not check_resume_data:
|
||||||
|
# line_type = self.env.ref('hr_skills_slides.resume_type_training', raise_if_not_found=False)
|
||||||
|
# self.env['hr.resume.line'].create({
|
||||||
|
# 'employee_id': emp and emp.id,
|
||||||
|
# 'name': _('Studying course: %s') % record.channel_id.name,
|
||||||
|
# 'date_start': fields.Date.today(),
|
||||||
|
# 'description': html2plaintext(record.channel_id.description),
|
||||||
|
# 'line_type_id': line_type and line_type.id,
|
||||||
|
# 'display_type': 'course',
|
||||||
|
# 'channel_id': record.channel_id.id,
|
||||||
|
# 'completed': False
|
||||||
|
# })
|
||||||
|
# record.completed_slides_count = mapped_data.get(record.channel_id.id, dict()).get(record.partner_id.id, 0)
|
||||||
|
# record.completion = 100.0 if record.completed else round(
|
||||||
|
# 100.0 * record.completed_slides_count / (record.channel_id.total_slides or 1))
|
||||||
|
#
|
||||||
|
# if not record.completed and record.channel_id.active and record.completed_slides_count >= record.channel_id.total_slides:
|
||||||
|
# completed_records += record
|
||||||
|
#
|
||||||
|
# if completed_records:
|
||||||
|
# # CODE GHI NHẬN HOÀN THÀNH KHÓA HỌC:
|
||||||
|
# for completed_data in completed_records:
|
||||||
|
# employee_completed_id = self.env['hr.employee'].sudo().search(
|
||||||
|
# [('user_id.partner_id', '=', completed_data.partner_id.id)])
|
||||||
|
# resume_data = self.env['hr.resume.line'].search([('employee_id', 'in', employee_completed_id.ids), ('channel_id', '=', completed_data.channel_id.id), ('completed', '!=', True)])
|
||||||
|
# resume_data.write({'name': _('Completed course: %s') % completed_data.channel_id.name, 'date_end': fields.Date.today(), 'completed': True})
|
||||||
|
#
|
||||||
|
# completed_records._set_as_completed()
|
||||||
|
# completed_records._send_completed_mail()
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 573 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 30 KiB |