summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorOdoo's Mergebot <xmo+roboto@odoo.com>2019-09-17 18:25:40 +0200
committerGitHub <noreply@github.com>2019-09-17 18:25:40 +0200
commit2476be9bf9e94834d59bcb8c8ec28d973fff5786 (patch)
tree93b39ef0d83b8eb26052a9fe0da8d48237fd8598 /addons
parentaa791a646b5d52f8c56b13ac7f01601cbb345d5d (diff)
parent83e67319f0d0e7999754562c757a32a9d87e2549 (diff)
[MERGE][ADD-IMP] website_* : apply multiple improvements on visitor
PURPOSE Perform a back2basics on recently-merged and recently-under-merged website visitor concept. Main points to add are sending emails and sms to visitors as well as improving integration. SPECIFICATIONS Applies various improvements: - Add graph view on visitors; - Remove welcome message if chat request usecase; - If a visitor is online: line is green / if offline: line is red (in the list view); - Visitors list view: add first / last connection fields and remove time_since_last_action; - Avoid useless leave notification if the livechat channel is empty; - Delete the first message "hello, how may i help you?" on a live chat request when I start a live chat; - Send mail to visitor linked to res.partner or linked to a crm.lead. New simplified wizard to post_message on lead or on res_partner if any (priority to lead). If the lead has a res_partner and not the visitor, set the partner on the visitor If the lead has no partner, create a partner based on the lead and link it to the visitor; - send a sms to a visitor if the visitor is linked to a least 1 lead that has a mobile number or a phone number or is linked to a res.partner with mobile number. As in contact form from website, there is only a phone field and not a mobile one, we allow to send sms if the lead phone field is filled; Fixes : - ACLS on website_visitor_page model (for im_livechat_group_user) - Update visitor lang if visitor change the website lang. - Create visitor twice when translated website (due to rerouting) See sub commit for more informations LINKS Task ID : 2070272 closes odoo/odoo#36290 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
Diffstat (limited to 'addons')
-rw-r--r--addons/crm/models/crm_lead.py17
-rw-r--r--addons/im_livechat/static/src/js/im_livechat.js6
-rw-r--r--addons/website/models/ir_http.py20
-rw-r--r--addons/website/models/website_visitor.py79
-rw-r--r--addons/website/views/website_visitor_views.xml24
-rw-r--r--addons/website_crm/models/crm_lead.py2
-rw-r--r--addons/website_crm/models/website_visitor.py44
-rw-r--r--addons/website_crm/security/ir.model.access.csv1
-rw-r--r--addons/website_crm_sms/__init__.py4
-rw-r--r--addons/website_crm_sms/__manifest__.py14
-rw-r--r--addons/website_crm_sms/models/__init__.py4
-rw-r--r--addons/website_crm_sms/models/website_visitor.py22
-rw-r--r--addons/website_livechat/controllers/main.py2
-rw-r--r--addons/website_livechat/models/im_livechat_channel.py2
-rw-r--r--addons/website_livechat/models/mail_channel.py15
-rw-r--r--addons/website_livechat/security/ir.model.access.csv1
-rw-r--r--addons/website_livechat/static/src/xml/thread.xml2
-rw-r--r--addons/website_sms/__init__.py4
-rw-r--r--addons/website_sms/__manifest__.py16
-rw-r--r--addons/website_sms/models/__init__.py4
-rw-r--r--addons/website_sms/models/website_visitor.py42
-rw-r--r--addons/website_sms/views/website_visitor_views.xml14
22 files changed, 308 insertions, 31 deletions
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index a165880e4e7..abe9e285c82 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -764,12 +764,7 @@ class Lead(models.Model):
if len(self.ids) <= 1:
raise UserError(_('Please select more than one element (lead or opportunity) from the list view.'))
- # Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it
- # The confidence level increases with the stage sequence
- # An Opportunity always has higher confidence level than a lead
- def opps_key(opportunity):
- return opportunity.type == 'opportunity', opportunity.stage_id.sequence, -opportunity.id
- opportunities = self.sorted(key=opps_key, reverse=True)
+ opportunities = self._sort_by_confidence_level(reverse=True)
# get SORTED recordset of head and tail, and complete list
opportunities_head = opportunities[0]
@@ -803,6 +798,16 @@ class Lead(models.Model):
return opportunities_head
+ def _sort_by_confidence_level(self, reverse=False):
+ """ Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it
+ The confidence level increases with the stage sequence
+ An Opportunity always has higher confidence level than a lead
+ """
+ def opps_key(opportunity):
+ return opportunity.type == 'opportunity', opportunity.stage_id.sequence, -opportunity._origin.id
+
+ return self.sorted(key=opps_key, reverse=reverse)
+
def get_duplicated_leads(self, partner_id, include_lost=False):
""" Search for opportunities that have the same partner and that arent done or cancelled
:param partner_id : partner to search
diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js
index 5a893f2499a..182dd403954 100644
--- a/addons/im_livechat/static/src/js/im_livechat.js
+++ b/addons/im_livechat/static/src/js/im_livechat.js
@@ -125,7 +125,7 @@ var LivechatButton = Widget.extend({
var message = new WebsiteLivechatMessage(this, data, options);
var hasAlreadyMessage = _.some(this._messages, function (msg) {
- return message.id === msg.id;
+ return message.getID() === msg.getID();
});
if (hasAlreadyMessage) {
return;
@@ -240,7 +240,9 @@ var LivechatButton = Widget.extend({
data: livechatData
});
return self._openChatWindow().then(function () {
- self._sendWelcomeMessage();
+ if (!self._history) {
+ self._sendWelcomeMessage();
+ }
self._renderMessages();
self.call('bus_service', 'addChannel', self._livechat.getUUID());
self.call('bus_service', 'startPolling');
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index da99aa35287..ccfc2c531e4 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -12,13 +12,12 @@ import werkzeug.routing
import werkzeug.utils
import odoo
-from odoo import api, models, registry, fields
+from odoo import api, models, registry
from odoo import SUPERUSER_ID
from odoo.http import request
-from odoo.tools import config, ormcache
+from odoo.tools import config
from odoo.tools.safe_eval import safe_eval
from odoo.osv.expression import FALSE_DOMAIN, OR
-from odoo.osv import osv
from odoo.addons.base.models.qweb import QWebException
from odoo.addons.http_routing.models.ir_http import ModelConverter, _guess_mimetype
@@ -100,8 +99,21 @@ class Http(models.AbstractModel):
@classmethod
def _dispatch(cls):
+ """
+ In case of rerouting for translate (e.g. when visiting odoo.com/fr_BE/),
+ _dispatch calls reroute() that returns _dispatch with altered request properties.
+ The second _dispatch will continue until end of process. When second _dispatch is finished, the first _dispatch
+ call receive the new altered request and continue.
+ At the end, 2 calls of _dispatch (and this override) are made with exact same request properties, instead of one.
+ As the response has not been sent back to the client, the visitor cookie does not exist yet when second _dispatch call
+ is treated in _handle_webpage_dispatch, leading to create 2 visitors with exact same properties.
+ To avoid this, we check if, !!! before calling super !!!, we are in a rerouting request. If not, it means that we are
+ handling the original request, in which we should create the visitor. We ignore every other rerouting requests.
+ """
+ is_rerouting = hasattr(request, 'routing_iteration')
response = super(Http, cls)._dispatch()
- cls._register_website_track(response)
+ if not is_rerouting:
+ cls._register_website_track(response)
return response
@classmethod
diff --git a/addons/website/models/website_visitor.py b/addons/website/models/website_visitor.py
index d2d9ca0bf7d..db4a50af825 100644
--- a/addons/website/models/website_visitor.py
+++ b/addons/website/models/website_visitor.py
@@ -2,12 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
-from hashlib import sha256
-import hmac
import uuid
from odoo import fields, models, api, _
-from odoo.tools.misc import _consteq, _format_time_ago
+from odoo.exceptions import UserError
+from odoo.tools.misc import _format_time_ago
from odoo.http import request
from odoo.osv import expression
@@ -42,17 +41,25 @@ class WebsiteVisitor(models.Model):
active = fields.Boolean('Active', default=True)
website_id = fields.Many2one('website', "Website", readonly=True)
user_partner_id = fields.Many2one('res.partner', string="Linked Partner", help="Partner of the last logged in user.")
- create_date = fields.Datetime('First connection date', readonly=True)
- last_connection_datetime = fields.Datetime('Last Connection', compute="_compute_time_statistics", help="Last page view date", search='_search_last_connection', readonly=True)
- last_connections_ids = fields.One2many('website.visitor.lastconnection', 'visitor_id', readonly=True)
+
+ # localisation and info
country_id = fields.Many2one('res.country', 'Country', readonly=True)
country_flag = fields.Binary(related="country_id.image", string="Country Flag")
lang_id = fields.Many2one('res.lang', string='Language', help="Language from the website when visitor has been created")
+ email = fields.Char(string='Email', compute='_compute_email_phone')
+ mobile = fields.Char(string='Mobile Phone', compute='_compute_email_phone')
+
+ # Visit fields
visit_count = fields.Integer('Number of visits', default=1, readonly=True, help="A new visit is considered if last connection was more than 24 hours ago.")
website_track_ids = fields.One2many('website.track', 'visitor_id', string='Visited Pages History', readonly=True)
visitor_page_count = fields.Integer('Page Views', compute="_compute_page_statistics", help="Total number of visits on tracked pages")
page_ids = fields.Many2many('website.page', string="Visited Pages", compute="_compute_page_statistics")
page_count = fields.Integer('# Visited Pages', compute="_compute_page_statistics", help="Total number of tracked page visited")
+
+ # Time fields
+ create_date = fields.Datetime('First connection date', readonly=True)
+ last_connection_datetime = fields.Datetime('Last Connection', compute="_compute_time_statistics", help="Last page view date", search='_search_last_connection', readonly=True)
+ last_connections_ids = fields.One2many('website.visitor.lastconnection', 'visitor_id', readonly=True)
time_since_last_action = fields.Char('Last action', compute="_compute_time_statistics", help='Time since last page view. E.g.: 2 minutes ago')
is_connected = fields.Boolean('Is connected ?', compute='_compute_time_statistics', help='A visitor is considered as connected if his last page view was within the last 5 minutes.')
@@ -77,6 +84,23 @@ class WebsiteVisitor(models.Model):
""" % (operator,)
return [('id', 'inselect', (query, [value]))]
+ @api.depends('user_partner_id.email_normalized', 'user_partner_id.mobile')
+ def _compute_email_phone(self):
+ results = self.env['res.partner'].search_read(
+ [('id', 'in', self.user_partner_id.ids)],
+ ['id', 'email_normalized', 'mobile'],
+ )
+ mapped_data = {
+ result['id']: {
+ 'email_normalized': result['email_normalized'],
+ 'mobile': result['mobile']
+ } for result in results
+ }
+
+ for visitor in self:
+ visitor.email = mapped_data.get(visitor.user_partner_id.id, {}).get('email_normalized')
+ visitor.mobile = mapped_data.get(visitor.user_partner_id.id, {}).get('mobile')
+
@api.depends('website_track_ids')
def _compute_page_statistics(self):
results = self.env['website.track'].read_group(
@@ -107,6 +131,40 @@ class WebsiteVisitor(models.Model):
visitor.time_since_last_action = _format_time_ago(self.env, (datetime.now() - last_connection_datetime))
visitor.is_connected = (datetime.now() - last_connection_datetime) < timedelta(minutes=5)
+ def _prepare_visitor_send_mail_values(self):
+ if self.user_partner_id.email:
+ return {
+ 'res_model': 'res.partner',
+ 'res_id': self.user_partner_id.id,
+ 'partner_ids': [self.user_partner_id.id],
+ }
+ return {}
+
+ def action_send_mail(self):
+ self.ensure_one()
+ visitor_mail_values = self._prepare_visitor_send_mail_values()
+ if not visitor_mail_values:
+ raise UserError(_("There is no email linked this visitor."))
+ compose_form = self.env.ref('mail.email_compose_message_wizard_form', False)
+ ctx = dict(
+ default_model=visitor_mail_values.get('res_model'),
+ default_res_id=visitor_mail_values.get('res_id'),
+ default_use_template=False,
+ default_partner_ids=[(6, 0, visitor_mail_values.get('partner_ids'))],
+ default_composition_mode='comment',
+ default_reply_to=self.env.user.partner_id.email,
+ )
+ return {
+ 'name': _('Compose Email'),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'res_model': 'mail.compose.message',
+ 'views': [(compose_form.id, 'form')],
+ 'view_id': compose_form.id,
+ 'target': 'new',
+ 'context': ctx,
+ }
+
def _get_visitor_from_request(self, with_previous_visitors=False):
if not request:
return None
@@ -147,6 +205,8 @@ class WebsiteVisitor(models.Model):
response.set_cookie('visitor_id', visitor_sudo.access_token, expires=expiration_date)
else:
visitor_sudo._add_tracking(domain, website_track_values)
+ if visitor_sudo.lang_id.id != request.lang.id:
+ visitor_sudo.write({'lang_id': request.lang.id})
def _add_tracking(self, domain, website_track_values):
""" Update the visitor when a website_track is added"""
@@ -155,8 +215,10 @@ class WebsiteVisitor(models.Model):
if not last_view or last_view.visit_datetime < datetime.now() - timedelta(minutes=30):
website_track_values['visitor_id'] = self.id
self.env['website.track'].create(website_track_values)
- self.env['website.visitor.lastconnection'].create({'visitor_id': self.id,
- 'connection_datetime': website_track_values['visit_datetime']})
+ self.env['website.visitor.lastconnection'].create({
+ 'visitor_id': self.id,
+ 'connection_datetime': website_track_values['visit_datetime']
+ })
def _create_visitor(self, website_track_values=None):
""" Create a visitor and add a track to it if website_track_values is set."""
@@ -165,6 +227,7 @@ class WebsiteVisitor(models.Model):
vals = {
'lang_id': request.lang.id,
'country_id': country_id,
+ 'website_id': request.website.id,
'last_connections_ids': [(0, 0, {'connection_datetime': website_track_values['visit_datetime'] or datetime.now()})],
}
if not self.env.user._is_public():
diff --git a/addons/website/views/website_visitor_views.xml b/addons/website/views/website_visitor_views.xml
index c82b66bbe99..a3f97a7ba84 100644
--- a/addons/website/views/website_visitor_views.xml
+++ b/addons/website/views/website_visitor_views.xml
@@ -85,6 +85,7 @@
<field name="is_connected"/>
<field name="display_name"/>
<field name="page_ids"/>
+ <field name="user_partner_id"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
@@ -114,7 +115,10 @@
<field name="model">website.visitor</field>
<field name="arch" type="xml">
<form string="Website Visitor" create="0">
- <header></header>
+ <header>
+ <button name="action_send_mail" type="object" class="btn btn-primary"
+ attrs="{'invisible': [('email', '=', False)]}" string="Send Email"/>
+ </header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button o_stat_button_info" attrs="{'invisible': [('is_connected', '=', False)]}">
@@ -143,9 +147,10 @@
<group string="Visitor Informations">
<field name="is_connected" invisible="1"/>
<field name="user_partner_id" attrs="{'invisible': [('user_partner_id', '=', False)]}"/>
+ <field name="email"/>
+ <field name="mobile"/>
<field name="country_id" attrs="{'invisible': [('country_id', '=', False)]}"/>
<field name="lang_id"/>
-
</group>
<group id="visits" string="Visits">
<field name="create_date"/>
@@ -162,11 +167,12 @@
<field name="name">website.visitor.view.tree</field>
<field name="model">website.visitor</field>
<field name="arch" type="xml">
- <tree string="Web Visitors" create="0">
+ <tree string="Web Visitors" create="0" decoration-success="is_connected" decoration-danger="not is_connected">
<!--TODO DBE : Handle no_label in treeview-->
<field name="country_flag" widget="image" options='{"size": [20, 20]}' string=" "/>
<field name="display_name"/>
- <field name="time_since_last_action"/>
+ <field name="create_date"/>
+ <field name="last_connection_datetime"/>
<field name="lang_id"/>
<field name="visit_count"/>
<field name="page_ids" widget="many2many_tags"/>
@@ -210,6 +216,16 @@
</field>
</record>
+ <record id="website_visitor_view_graph" model="ir.ui.view">
+ <field name="name">website.visitor.view.graph</field>
+ <field name="model">website.visitor</field>
+ <field name="arch" type="xml">
+ <graph string="Visitors">
+ <field name="create_date" type="row"/>
+ </graph>
+ </field>
+ </record>
+
<record id="website_visitors_action" model="ir.actions.act_window">
<field name="name">Visitors</field>
<field name="type">ir.actions.act_window</field>
diff --git a/addons/website_crm/models/crm_lead.py b/addons/website_crm/models/crm_lead.py
index 256a597f25a..dc929284842 100644
--- a/addons/website_crm/models/crm_lead.py
+++ b/addons/website_crm/models/crm_lead.py
@@ -31,7 +31,7 @@ class Lead(models.Model):
action = self.env.ref('website.website_visitor_page_action').read()[0]
action['domain'] = [('visitor_id', 'in', visitors.ids)]
# avoid grouping if only few records
- if len(visitors.visitor_page_ids.ids) > 15 and len(visitors.page_ids.ids) > 1:
+ if len(visitors.website_track_ids.ids) > 15 and len(visitors.page_ids.ids) > 1:
action['context'] = {'search_default_group_by_page': '1'}
return action
diff --git a/addons/website_crm/models/website_visitor.py b/addons/website_crm/models/website_visitor.py
index 7bd3570a17a..82fa1367967 100644
--- a/addons/website_crm/models/website_visitor.py
+++ b/addons/website_crm/models/website_visitor.py
@@ -14,3 +14,47 @@ class WebsiteVisitor(models.Model):
def _compute_lead_count(self):
for visitor in self:
visitor.lead_count = len(visitor.lead_ids)
+
+ @api.depends('user_partner_id.email_normalized', 'user_partner_id.mobile', 'lead_ids.email_normalized', 'lead_ids.mobile')
+ def _compute_email_phone(self):
+ super(WebsiteVisitor, self)._compute_email_phone()
+ self.flush()
+ sql = """ SELECT v.id as visitor_id, l.id as lead_id,
+ CASE WHEN p.email_normalized is not null THEN p.email_normalized ELSE l.email_normalized END as email,
+ CASE WHEN p.mobile is not null THEN p.mobile WHEN l.mobile is not null THEN l.mobile ELSE l.phone END as mobile
+ FROM website_visitor v
+ JOIN crm_lead_website_visitor_rel lv on lv.website_visitor_id = v.id
+ JOIN crm_lead l ON lv.crm_lead_id = l.id
+ LEFT JOIN res_partner p on p.id = v.user_partner_id
+ WHERE v.id in %s
+ ORDER BY l.create_date ASC"""
+ self.env.cr.execute(sql, (tuple(self.ids),))
+ results = self.env.cr.dictfetchall()
+ mapped_data = {}
+ for result in results:
+ visitor_info = mapped_data.get(result['visitor_id'], {'email': '', 'mobile': ''})
+ visitor_info['email'] += result['email'] + ','
+ visitor_info['mobile'] = result['mobile']
+ mapped_data[result['visitor_id']] = visitor_info
+
+ for visitor in self:
+ email = mapped_data.get(visitor.id, {}).get('email')
+ visitor.email = email[:-1] if email else False
+ visitor.mobile = mapped_data.get(visitor.id, {}).get('mobile')
+
+ def _prepare_visitor_send_mail_values(self):
+ visitor_mail_values = super(WebsiteVisitor, self)._prepare_visitor_send_mail_values()
+ if self.lead_ids:
+ lead = self.lead_ids._sort_by_confidence_level(reverse=True)[0]
+ partner_id = self.user_partner_id.id
+ if not self.user_partner_id:
+ partner_id = lead.handle_partner_assignation()[lead.id]
+ if not lead.partner_id:
+ lead.partner_id = partner_id
+ self.user_partner_id = partner_id
+ return {
+ 'res_model': 'crm.lead',
+ 'res_id': lead.id,
+ 'partner_ids': [partner_id],
+ }
+ return visitor_mail_values
diff --git a/addons/website_crm/security/ir.model.access.csv b/addons/website_crm/security/ir.model.access.csv
index c85af9f0e0c..d2dd311b15c 100644
--- a/addons/website_crm/security/ir.model.access.csv
+++ b/addons/website_crm/security/ir.model.access.csv
@@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_website_visitor_salesman,access_website_visitor_salesman,model_website_visitor,sales_team.group_sale_salesman,1,0,0,0
+access_website_visitor_page_salesman,access_website_visitor_page_salesman,model_website_visitor_page,sales_team.group_sale_salesman,1,0,0,0
diff --git a/addons/website_crm_sms/__init__.py b/addons/website_crm_sms/__init__.py
new file mode 100644
index 00000000000..dc5e6b693d1
--- /dev/null
+++ b/addons/website_crm_sms/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/website_crm_sms/__manifest__.py b/addons/website_crm_sms/__manifest__.py
new file mode 100644
index 00000000000..c4169ef7865
--- /dev/null
+++ b/addons/website_crm_sms/__manifest__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': 'Send SMS to Visitor with leads',
+ 'category': 'Website',
+ 'sequence': 54,
+ 'summary': 'Allows to send sms to website visitor that have lead',
+ 'version': '1.0',
+ 'description': """Allows to send sms to website visitor if the visitor is linked to a lead.""",
+ 'depends': ['website_sms', 'crm'],
+ 'data': [],
+ 'installable': True,
+ 'auto_install': True,
+}
diff --git a/addons/website_crm_sms/models/__init__.py b/addons/website_crm_sms/models/__init__.py
new file mode 100644
index 00000000000..8c76c6becd2
--- /dev/null
+++ b/addons/website_crm_sms/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import website_visitor
diff --git a/addons/website_crm_sms/models/website_visitor.py b/addons/website_crm_sms/models/website_visitor.py
new file mode 100644
index 00000000000..57ce4281ff0
--- /dev/null
+++ b/addons/website_crm_sms/models/website_visitor.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class WebsiteVisitor(models.Model):
+ _inherit = 'website.visitor'
+
+ def _prepare_visitor_send_sms_values(self):
+ visitor_sms_values = super(WebsiteVisitor, self)._prepare_visitor_send_sms_values()
+ if not visitor_sms_values:
+ leads_with_number = self.lead_ids.filtered(lambda l: l.mobile == self.mobile or l.phone == self.mobile)._sort_by_confidence_level(reverse=True)
+ if leads_with_number:
+ lead = leads_with_number[0]
+ return {
+ 'res_model': 'crm.lead',
+ 'res_id': lead.id,
+ 'partner_ids': [lead.id],
+ 'number_field_name': 'mobile' if lead.mobile == self.mobile else 'phone',
+ }
+ return visitor_sms_values
diff --git a/addons/website_livechat/controllers/main.py b/addons/website_livechat/controllers/main.py
index 4525191b916..9fb4678d39e 100644
--- a/addons/website_livechat/controllers/main.py
+++ b/addons/website_livechat/controllers/main.py
@@ -64,7 +64,7 @@ class WebsiteLivechat(http.Controller):
in conversation with an operator, it's not possible to send the visitor a chat request."""
mail_channel = request.env['mail.channel'].sudo().search([('uuid', '=', uuid)])
if mail_channel:
- mail_channel.close_livechat_request_session(_('has left the conversation.'))
+ mail_channel.close_livechat_request_session()
@http.route('/im_livechat/close_empty_livechat', type='json', auth="public")
def close_empty_livechat(self, uuid):
diff --git a/addons/website_livechat/models/im_livechat_channel.py b/addons/website_livechat/models/im_livechat_channel.py
index db41f626a83..dd083c6e104 100644
--- a/addons/website_livechat/models/im_livechat_channel.py
+++ b/addons/website_livechat/models/im_livechat_channel.py
@@ -20,6 +20,6 @@ class ImLivechatChannel(models.Model):
# As chat requested by the visitor, delete the chat requested by an operator if any to avoid conflicts between two flows
chat_request_channel = self.env['mail.channel'].sudo().search([('livechat_visitor_id', '=', visitor_sudo.id), ('livechat_active', '=', True)])
for mail_channel in chat_request_channel:
- mail_channel.close_livechat_request_session(_('has started a conversation with %s. The chat request has been canceled.') % operator.name)
+ mail_channel.close_livechat_request_session(type='cancel', speaking_with=operator.name)
return mail_channel_vals
diff --git a/addons/website_livechat/models/mail_channel.py b/addons/website_livechat/models/mail_channel.py
index e6d00133eff..48d6d08a847 100644
--- a/addons/website_livechat/models/mail_channel.py
+++ b/addons/website_livechat/models/mail_channel.py
@@ -58,12 +58,20 @@ class MailChannel(models.Model):
break
return ' → '.join(history)
- def close_livechat_request_session(self, message):
+ def close_livechat_request_session(self, type='leave', **kwargs):
+ """ Set deactivate the livechat channel and notify (the operator) the reason of closing the session."""
self.ensure_one()
if self.livechat_active:
self.livechat_active = False
+ # avoid useless notification if the channel is empty
+ if not self.channel_message_ids:
+ return
# Notify that the visitor has left the conversation
name = _('The visitor') if not self.livechat_visitor_id else self.livechat_visitor_id.name
+ if type == 'cancel':
+ message = _('has started a conversation with %s. The chat request has been canceled.') % kwargs.get('speaking_with', 'an operator')
+ else:
+ message = _('has left the conversation.')
leave_message = '%s %s' % (name, message)
self.message_post(author_id=self.env.ref('base.user_root').sudo().partner_id.id,
body=leave_message, message_type='comment', subtype='mt_comment')
@@ -76,7 +84,8 @@ class MailChannel(models.Model):
message_author_id = message.author_id
visitor = self.livechat_visitor_id
if len(self) == 1 and visitor and message_author_id != self.livechat_operator_id:
- visitor.sudo().write({
- 'last_connection_datetime': fields.datetime.now(),
+ self.env['website.visitor.lastconnection'].create({
+ 'visitor_id': visitor.id,
+ 'connection_datetime': fields.datetime.now()
})
return message
diff --git a/addons/website_livechat/security/ir.model.access.csv b/addons/website_livechat/security/ir.model.access.csv
index 90de53247be..ce1863efbbc 100644
--- a/addons/website_livechat/security/ir.model.access.csv
+++ b/addons/website_livechat/security/ir.model.access.csv
@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_im_livechat_channel_public,im_livechat.channel.public,im_livechat.model_im_livechat_channel,,1,0,0,0
access_website_visitor_livechat_users,website.visitor.livechat.users,model_website_visitor,im_livechat.im_livechat_group_user,1,0,0,0
+access_website_track_livechat_users,website.track.livechat.users,website.model_website_track,im_livechat.im_livechat_group_user,1,0,0,0
diff --git a/addons/website_livechat/static/src/xml/thread.xml b/addons/website_livechat/static/src/xml/thread.xml
index a8872290b2a..b257f5264b3 100644
--- a/addons/website_livechat/static/src/xml/thread.xml
+++ b/addons/website_livechat/static/src/xml/thread.xml
@@ -32,7 +32,7 @@
<div class="o_thread_message_sidebar align-self-center">
<div class="o_thread_message_sidebar_image">
<img alt=""
- t-att-src="visitor.partner_id ? '/web/image/res.partner/' + visitor.partner_id + '/image_small' : '/mail/static/src/img/smiley/avatar.jpg'"
+ t-att-src="visitor.partner_id ? '/web/image/res.partner/' + visitor.partner_id + '/image_64' : '/mail/static/src/img/smiley/avatar.jpg'"
class="o_thread_message_avatar rounded-circle"/>
<span>
<i t-if="visitor.is_connected" aria-label="Visitor is connected" class="o_w_visitor_status o_w_visitor_connected fa fa-circle" role="img" title="Online"></i>
diff --git a/addons/website_sms/__init__.py b/addons/website_sms/__init__.py
new file mode 100644
index 00000000000..dc5e6b693d1
--- /dev/null
+++ b/addons/website_sms/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/addons/website_sms/__manifest__.py b/addons/website_sms/__manifest__.py
new file mode 100644
index 00000000000..2f950750094
--- /dev/null
+++ b/addons/website_sms/__manifest__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': 'Send SMS to Visitor',
+ 'category': 'Website',
+ 'sequence': 54,
+ 'summary': 'Allows to send sms to website visitor',
+ 'version': '1.0',
+ 'description': """Allows to send sms to website visitor if the visitor is linked to a partner.""",
+ 'depends': ['website', 'sms'],
+ 'data': [
+ 'views/website_visitor_views.xml',
+ ],
+ 'installable': True,
+ 'auto_install': True,
+}
diff --git a/addons/website_sms/models/__init__.py b/addons/website_sms/models/__init__.py
new file mode 100644
index 00000000000..8c76c6becd2
--- /dev/null
+++ b/addons/website_sms/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import website_visitor
diff --git a/addons/website_sms/models/website_visitor.py b/addons/website_sms/models/website_visitor.py
new file mode 100644
index 00000000000..7042ed22766
--- /dev/null
+++ b/addons/website_sms/models/website_visitor.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models, _
+from odoo.exceptions import UserError
+
+
+class WebsiteVisitor(models.Model):
+ _inherit = 'website.visitor'
+
+ def _prepare_visitor_send_sms_values(self):
+ if self.user_partner_id.mobile:
+ return {
+ 'res_model': 'res.partner',
+ 'res_id': self.user_partner_id.id,
+ 'partner_ids': [self.user_partner_id.id],
+ 'number_field_name': 'mobile',
+ }
+ return {}
+
+ def action_send_sms(self):
+ self.ensure_one()
+ visitor_sms_values = self._prepare_visitor_send_sms_values()
+ if not visitor_sms_values:
+ raise UserError(_("There is no mobile phone number linked to this visitor."))
+
+ context = dict(self.env.context)
+ context.update({
+ 'default_res_model': visitor_sms_values['res_model'],
+ 'default_res_id': visitor_sms_values['res_id'],
+ 'default_composition_mode': 'comment',
+ 'default_number_field_name': visitor_sms_values['number_field_name'],
+ })
+
+ return {
+ "type": "ir.actions.act_window",
+ "res_model": "sms.composer",
+ "view_mode": 'form',
+ "context": context,
+ "name": "Send SMS Text Message",
+ "target": "new",
+ }
diff --git a/addons/website_sms/views/website_visitor_views.xml b/addons/website_sms/views/website_visitor_views.xml
new file mode 100644
index 00000000000..6e30b773cc0
--- /dev/null
+++ b/addons/website_sms/views/website_visitor_views.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo><data>
+ <record id="website_visitor_view_form" model="ir.ui.view">
+ <field name="name">website.visitor.view.form.inherit.website.mass.mailing.sms</field>
+ <field name="model">website.visitor</field>
+ <field name="inherit_id" ref="website.website_visitor_view_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//header" position="inside">
+ <button name="action_send_sms" type="object" class="btn btn-primary"
+ attrs="{'invisible': [('mobile', '=', False)]}" string="Send SMS"/>
+ </xpath>
+ </field>
+ </record>
+</data></odoo>