From 98bdb241ded7beb4a2d958387ad2d5ec620feb3e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= <tde@odoo.com>
Date: Thu, 25 Jul 2019 10:50:35 +0000
Subject: [PATCH] [IMP] website_slides: add an overview (kanban view) on course
 model
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

PURPOSE

eLearning should have its own application in manager. Indeed this is becoming
a big application and having it embedded inside Website application is not
enough anymore. It should also hold updated and easy-to-use menus,
navigation, actions and views in backend.

SPECIFICATIONS

Add course (slide.channel) kanban view. Each tile should contain

  * Title of the course (make it big, o_primary)
  * Tags below the title
  * Primary button "New Lesson"
  * Statistics :
    * Attendees
    * Running (progress != 100%)
    * (if one content is certificate) Certified or Finished
  * See
    * v1: https://drive.google.com/a/odoo.com/file/d/13K8FmkbwmBG4JQ5gH3Q5T8OlWPs3blcu/view?usp=drivesdk

With FP remarks https://drive.google.com/a/odoo.com/file/d/1mb7LUT0VUNs5fMcKB9c0Bfaqk2-XFFuu/view?usp=drivesdk
  * remove globe, type and visibility of course, lessen padding between rating
    views and watch time;
  * for rating, instead of 8.00 => 5 reviews (4.5/5);
  * replace "View Lesson" / "Invite" by "View Course" => Send to the front-end
  * In the menu of the card, add:
    * Invite (same behavior than before, when it was a primary btn)
  * click on x Contents:  land on the contents, group by category with sequence
  * re-order of the 3 infos/Stats at the bottom of the card: 1. Content,
    2. Attendee, 3. Certified
    * + number: font-weight: 500 + font-size 1.2em
    * + labels: remove text-uppercase, + text-color: #666666
    * + the "whole box" is clickable

LINKS

Task 1978729
PR #35061

Co-Authored-By: Thibault Delavallée <tde@odoo.com>
Co-Authored-By: Jérémy Hennecart <jeh@odoo.com>
---
 addons/website_slides/__manifest__.py         |  1 +
 .../models/res_config_settings.py             |  4 +-
 addons/website_slides/models/slide_channel.py | 64 +++++++++++--
 .../static/src/scss/slide_channel_views.scss  |  7 ++
 addons/website_slides/views/assets.xml        |  1 +
 .../views/res_config_settings_views.xml       |  6 +-
 .../views/res_partner_views.xml               |  5 +
 .../views/slide_channel_views.xml             | 96 ++++++++++++++++++-
 .../views/slide_channel_views.xml             | 15 +++
 9 files changed, 182 insertions(+), 17 deletions(-)
 create mode 100644 addons/website_slides/static/src/scss/slide_channel_views.scss

diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py
index 5373f5bd6de9..1da8adc4eeed 100644
--- a/addons/website_slides/__manifest__.py
+++ b/addons/website_slides/__manifest__.py
@@ -29,6 +29,7 @@ Featuring
         'views/res_config_settings_views.xml',
         'views/res_partner_views.xml',
         'views/rating_rating_views.xml',
+        'views/res_partner_views.xml',
         'views/slide_question_views.xml',
         'views/slide_slide_views.xml',
         'views/slide_channel_partner_views.xml',
diff --git a/addons/website_slides/models/res_config_settings.py b/addons/website_slides/models/res_config_settings.py
index fa9e7508afcb..8085cd407c8f 100644
--- a/addons/website_slides/models/res_config_settings.py
+++ b/addons/website_slides/models/res_config_settings.py
@@ -8,6 +8,6 @@ class ResConfigSettings(models.TransientModel):
     _inherit = "res.config.settings"
 
     website_slide_google_app_key = fields.Char(related='website_id.website_slide_google_app_key', readonly=False)
-    module_website_sale_slides = fields.Boolean(string="Sell courses")
-    module_website_slides_forum = fields.Boolean(string="Forum on Courses")
+    module_website_sale_slides = fields.Boolean(string="Sell on eCommerce")
+    module_website_slides_forum = fields.Boolean(string="Forum")
     module_website_slides_survey = fields.Boolean(string="Certifications")
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index 4496c59b391c..78c32b55b741 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -73,6 +73,7 @@ class ChannelUsersRelation(models.Model):
     def _post_completion_hook(self):
         pass
 
+
 class Channel(models.Model):
     """ A channel is a container of slides. """
     _name = 'slide.channel'
@@ -96,6 +97,7 @@ class Channel(models.Model):
         string="Course type", default="documentation", required=True)
     sequence = fields.Integer(default=10, help='Display order')
     user_id = fields.Many2one('res.users', string='Responsible', default=lambda self: self.env.uid)
+    color = fields.Integer('Color Index', default=0, help='Used to decorate kanban view')
     tag_ids = fields.Many2many(
         'slide.channel.tag', 'slide_channel_tag_rel', 'channel_id', 'tag_id',
         string='Tags', help='Used to categorize and filter displayed channels/courses')
@@ -151,6 +153,7 @@ class Channel(models.Model):
         'res.partner', 'slide_channel_partner', 'channel_id', 'partner_id',
         string='Members', help="All members of the channel.", context={'active_test': False})
     members_count = fields.Integer('Attendees count', compute='_compute_members_count')
+    members_done_count = fields.Integer('Attendees Done Count', compute='_compute_members_done_count')
     is_member = fields.Boolean(string='Is Member', compute='_compute_is_member')
     channel_partner_ids = fields.One2many('slide.channel.partner', 'channel_id', string='Members Information', groups='website.group_website_publisher')
     upload_group_ids = fields.Many2many(
@@ -184,6 +187,13 @@ class Channel(models.Model):
         for channel in self:
             channel.members_count = data.get(channel.id, 0)
 
+    @api.depends('channel_partner_ids.channel_id', 'channel_partner_ids.completed')
+    def _compute_members_done_count(self):
+        read_group_res = self.env['slide.channel.partner'].sudo().read_group(['&', ('channel_id', 'in', self.ids), ('completed', '=', True)], ['channel_id'], 'channel_id')
+        data = dict((res['channel_id'][0], res['channel_id_count']) for res in read_group_res)
+        for channel in self:
+            channel.members_done_count = data.get(channel.id, 0)
+
     @api.depends('channel_partner_ids.partner_id')
     @api.model
     def _compute_is_member(self):
@@ -366,21 +376,34 @@ class Channel(models.Model):
     # Business / Actions
     # ---------------------------------------------------------
 
-    def action_redirect_to_members(self):
-        action = self.env.ref('website_slides.slide_channel_partner_action').read()[0]
-        action['view_mode'] = 'tree'
-        action['domain'] = [('channel_id', 'in', self.ids)]
+    def action_redirect_to_members(self, state=None):
+        action = self.env.ref('website_slides.res_partner_action_slide_channel').read()[0]
+        action['context'] = {'active_test': False}
+        if state == 'running':
+            action['domain'] = [('id', 'in', self.env['slide.channel.partner'].sudo().search([
+                ('channel_id', 'in', self.ids),
+                ('completed', '=', False)
+            ]).mapped('partner_id').ids)]
+        elif state == 'completed':
+            action['domain'] = [('id', 'in', self.env['slide.channel.partner'].sudo().search([
+                ('channel_id', 'in', self.ids),
+                ('completed', '=', True)
+            ]).mapped('partner_id').ids)]
+        else:
+            action['domain'] = [('id', 'in', self.mapped('partner_ids').ids)]
         if len(self) == 1:
-            action['context'] = {'default_channel_id': self.id}
-
+            action['display_name'] = _('Attendees of %s') % self.name
+            action['context'] = {'active_test': False, 'default_channel_id': self.id}
         return action
 
-    def action_channel_invite(self):
-        self.ensure_one()
+    def action_redirect_to_running_members(self):
+        return self.action_redirect_to_members('running')
 
-        if self.enroll != 'invite':
-            raise UserError(_("You cannot send invitations for channels that are not set as 'invite'."))
+    def action_redirect_to_done_members(self):
+        return self.action_redirect_to_members('completed')
 
+    def action_channel_invite(self):
+        self.ensure_one()
         template = self.env.ref('website_slides.mail_template_slide_channel_invite', raise_if_not_found=False)
 
         local_context = dict(
@@ -474,6 +497,27 @@ class Channel(models.Model):
         if removed_channel_partner_domain:
             self.env['slide.channel.partner'].sudo().search(removed_channel_partner_domain).unlink()
 
+    def action_view(self):
+        return {
+            'type': 'ir.actions.act_window',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'slide.channel',
+            'res_id': self.id,
+        }
+
+    def action_view_slides(self):
+        action = self.env.ref('website_slides.slide_slide_action').read()[0]
+        action['context'] = {'default_channel_id': self.id}
+        action['domain'] = [('channel_id', "=", self.id)]
+        return action
+
+    def action_view_ratings(self):
+        action = self.env.ref('website_slides.rating_rating_action_slide_channel').read()[0]
+        action['name'] = _('Rating of %s') % (self.name)
+        action['domain'] = [('res_id', 'in', self.ids)]
+        return action
+
     # ---------------------------------------------------------
     # Rating Mixin API
     # ---------------------------------------------------------
diff --git a/addons/website_slides/static/src/scss/slide_channel_views.scss b/addons/website_slides/static/src/scss/slide_channel_views.scss
new file mode 100644
index 000000000000..3bd8203c4eb0
--- /dev/null
+++ b/addons/website_slides/static/src/scss/slide_channel_views.scss
@@ -0,0 +1,7 @@
+$o-kanban-large-record-width: 400px;
+
+.o_kanban_view.o_slide_channel_kanban {
+    .o_kanban_record {
+    	width: $o-kanban-large-record-width
+    }
+}
diff --git a/addons/website_slides/views/assets.xml b/addons/website_slides/views/assets.xml
index 1c990e7ad502..62129e58e6bf 100644
--- a/addons/website_slides/views/assets.xml
+++ b/addons/website_slides/views/assets.xml
@@ -4,6 +4,7 @@
         <template id="assets_backend" name="Slides Backend Assets" inherit_id="web.assets_backend">
             <xpath expr="." position="inside">
                 <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/rating_rating_views.scss"/>
+                <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_channel_views.scss"/>
             </xpath>
         </template>
 
diff --git a/addons/website_slides/views/res_config_settings_views.xml b/addons/website_slides/views/res_config_settings_views.xml
index 6925fbc33b58..3e5a64010f05 100644
--- a/addons/website_slides/views/res_config_settings_views.xml
+++ b/addons/website_slides/views/res_config_settings_views.xml
@@ -38,7 +38,7 @@
                             <div class="o_setting_right_pane">
                                 <label for="module_website_slides_forum"/>
                                 <div class="text-muted">
-                                    Allow Forum on Courses
+                                    Create a community and let the members help each others
                                 </div>
                             </div>
                         </div>
@@ -50,7 +50,7 @@
                             <div class="o_setting_right_pane">
                                 <label for="module_website_slides_survey"/>
                                 <div class="text-muted">
-                                    Allow to take certifications
+                                    Evaluate your students and certify them
                                 </div>
                             </div>
                         </div>
@@ -62,7 +62,7 @@
                             <div class="o_setting_right_pane">
                                 <label for="module_website_sale_slides"/>
                                 <div class="text-muted">
-                                    Sell courses on your website
+                                    Generate revenues thanks to your courses
                                 </div>
                             </div>
                         </div>
diff --git a/addons/website_slides/views/res_partner_views.xml b/addons/website_slides/views/res_partner_views.xml
index a5e7161c00b5..5455a0195f5e 100644
--- a/addons/website_slides/views/res_partner_views.xml
+++ b/addons/website_slides/views/res_partner_views.xml
@@ -36,4 +36,9 @@
         </field>
     </record>
 
+    <record id="res_partner_action_slide_channel" model="ir.actions.act_window">
+        <field name="name">Attendees</field>
+        <field name="res_model">res.partner</field>
+        <field name="view_mode">kanban,tree,form</field>
+    </record>
 </data></odoo>
diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml
index edd662541e57..898599ede254 100644
--- a/addons/website_slides/views/slide_channel_views.xml
+++ b/addons/website_slides/views/slide_channel_views.xml
@@ -218,11 +218,103 @@
             </field>
         </record>
 
+        <record id="slide_channel_view_kanban" model="ir.ui.view">
+            <field name="name">slide.channel.view.kanban</field>
+            <field name="model">slide.channel</field>
+            <field name="arch" type="xml">
+                <kanban string="eLearning Overview" class="o_emphasize_colors o_kanban_dashboard o_slide_channel_kanban breadcrumb_item active" edit="false">
+                    <field name="color"/>
+                    <field name="website_published"/>
+                    <templates>
+                        <t t-name="kanban-box">
+                            <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
+                                <div class="o_dropdown_kanban dropdown">
+                                    <a role="button" class="dropdown-toggle o-no-caret btn" data-toggle="dropdown" href="#" aria-label="Dropdown menu" title="Dropdown menu">
+                                        <span class="fa fa-ellipsis-v" aria-hidden="false"/>
+                                    </a>
+                                    <div class="dropdown-menu" role="menu">
+                                        <ul class="oe_kanban_colorpicker" data-field="color"/>
+                                        <t t-if="widget.deletable">
+                                            <a class="dropdown-item" role="menuitem" type="delete">Delete</a>
+                                        </t>
+                                        <a class="dropdown-item" role="menuitem" type="edit">
+                                            Edit
+                                        </a>
+                                        <a class="dropdown-item" name="action_view_slides" role="menuitem" type="object">
+                                            Lessons
+                                        </a>
+                                        <a class="dropdown-item" name="action_channel_invite" role="menuitem" type="object">
+                                            Invite
+                                        </a>
+                                    </div>
+                                </div>
+                                <div class="o_kanban_card_header">
+                                    <div class="o_kanban_card_header_title mb16">
+                                        <div class="o_primary">
+                                            <a name="action_view" type="object" class="mr-auto">
+                                                <span><field name="name" class="o_primary"/></span>
+                                            </a>
+                                        </div>
+                                        <div t-if="record.tag_ids">
+                                            <field name="tag_ids" widget="many2many_tags"/>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="container o_kanban_card_content mt0">
+                                    <div class="row">
+                                        <div class="col-6 o_kanban_primary_left">
+                                            <button class="btn btn-primary" name="open_website_url" type="object">View course</button>
+                                        </div>
+                                        <div class="col-6 o_kanban_primary_right">
+                                            <div class="d-flex">
+                                                <span class="mr-auto"><label for="rating_avg" class="mb0">Rating</label></span>
+                                                <a name="action_view_ratings" type="object">
+                                                    <field name="rating_count"/><span class="ml-2">reviews</span> (<field name="rating_avg_stars"/>/5)
+                                                </a>
+                                            </div>
+                                            <div class="d-flex">
+                                                <span class="mr-auto"><label for="total_views" class="mb0">Views</label></span>
+                                                <field name="total_views"/>
+                                            </div>
+                                            <div class="d-flex">
+                                                <span class="mr-auto"><label for="total_time" class="mb0">Watch Time</label></span>
+                                                <field name="total_time" widget="float_time"/>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row mt3">
+                                        <div class="col-4 border-right">
+                                            <a name="action_view_slides" type="object" class="d-flex flex-column align-items-center">
+                                                <span class="font-weight-bold"><field name="total_slides"/></span>
+                                                <span class="text-secondary">Contents</span>
+                                            </a>
+                                        </div>
+                                        <div class="col-4 border-right">
+                                            <a name="action_redirect_to_members" type="object" class="d-flex flex-column align-items-center">
+                                                <span class="font-weight-bold"><field name="members_count"/></span>
+                                                <span class="text-secondary">Attendees</span>
+                                            </a>
+                                        </div>
+                                        <div class="col-4">
+                                            <a name="action_redirect_to_done_members" type="object" class="d-flex flex-column align-items-center">
+                                                <span class="font-weight-bold"><field name="members_done_count"/></span>
+                                                <span class="text-secondary">Finished</span>
+                                            </a>
+                                        </div>
+                                    </div>
+                                </div>
+                             </div>
+                        </t>
+                    </templates>
+                </kanban>
+            </field>
+        </record>
+
         <record id="slide_channel_action_overview" model="ir.actions.act_window">
             <field name="name">eLearning Overview</field>
             <field name="res_model">slide.channel</field>
-            <field name="view_mode">tree,form</field>
-            <field name="view_id" ref="slide_channel_view_tree"/>
+            <field name="view_mode">kanban,tree,form</field>
+            <field name="view_id" ref="slide_channel_view_kanban"/>
             <field name="help" type="html">
                 <p class="o_view_nocontent_smiling_face">
                     Create a course
diff --git a/addons/website_slides_survey/views/slide_channel_views.xml b/addons/website_slides_survey/views/slide_channel_views.xml
index e28b67997cf0..2b93ef0dd2af 100644
--- a/addons/website_slides_survey/views/slide_channel_views.xml
+++ b/addons/website_slides_survey/views/slide_channel_views.xml
@@ -10,4 +10,19 @@
             </xpath>
         </field>
     </record>
+
+    <record id="slide_channel_view_kanban" model="ir.ui.view">
+        <field name="name">slide.channel.view.kanban.inherit.survey</field>
+        <field name="model">slide.channel</field>
+        <field name="inherit_id" ref="website_slides.slide_channel_view_kanban"/>
+        <field name="arch" type="xml">
+            <xpath expr="//field[@name='members_running_count']" position="after">
+                <field name="nbr_certification"/>
+            </xpath>
+            <xpath expr="//a[@name='action_redirect_to_done_members']/span" position="replace">
+                <t t-if="record.nbr_certification.raw_value"><span class="text-uppercase">Certified</span></t>
+                <t t-else=""><span class="text-uppercase">Finished</span></t>
+            </xpath>
+        </field>
+    </record>
 </odoo>
-- 
GitLab