[MERGE][IMP] digest, various: improve digest content and tips
PURPOSE
Make digest email and tips more appealing. The goals of these tips are
* to encourage the adoption of other apps (Did you know ?);
* to make Odoo look more fun (Fun tips and tricks, young and dynamic style);
* to show social proof and increase trust (emphasis on already existing
projects / customers to);
SPECIFICATIONS: DIGEST TEMPLATE
Improve template according to FP specifications, aka
Header
Up to 2 tips
KPIs
Want to customize?
Mobile Tip
Footer
SPECIFICATIONS: QWEB TEMPLATE
Digest now uses qweb views instead of a standard mail.template record for
rendering as
* template is very custom;
* probability of breaking it while edition the mail template is high;
it is easier to maintain and extend in qweb;
* only body was really used, other mail-related fields were not used;
We therefore remove the mail template data and the field used for it on
the digest model. Template is now forced to a Qweb view.
We can also remove the template_id field. Indeed we think that having
different templates for digests is a really advanced use case we do not
want to support.
SPECIFICATIONS: DELAY BEFORE SENDING
In this commit we introduce a way to set a delay before sending a tip.
Using a new field, a tip can be send a few days after its module installation.
This require to store the source module on the tip itself. When installed
from data it will be automatically filled using its xml_id if not set
SPECIFICATIONS: TIPS
Improve tips content and display according to FP specifications.
LINKS
Task ID 2197417
Community PR odoo/odoo#51619
Enterprise PR odoo/enterprise#10697
Upgrade PR odoo/upgrade#1256
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
@@ -27,7 +27,7 @@ class Digest(models.Model):
|
||||
query_res = self._cr.fetchone()
|
||||
record.kpi_account_total_revenue_value = query_res and query_res[0] or 0.0
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_account_total_revenue'] = 'account.action_move_out_invoice_type&menu_id=%s' % self.env.ref('account.menu_finance').id
|
||||
return res
|
||||
|
||||
@@ -9,20 +9,49 @@
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_crm_0" model="digest.tip">
|
||||
<field name="name">CRM: try mailgateway</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="name">CRM: Create opportunities from incoming emails</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="group_id" ref="sales_team.group_sale_salesman_all_leads"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
% set email = object.env['crm.team'].search([('alias_name','!=', False)],limit=1).alias_id.display_name
|
||||
% if email
|
||||
<strong style="font-size: 16px;">Try the mail gateway</strong>
|
||||
<div style="font-size: 14px;">Email sent to <strong>${email}</strong> generate opportunities in your pipeline.<br/>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:2px;">
|
||||
<a href="mailto:${email}" style="background-color:#56b3b5;padding:2px;color:#FFFFFF;font-weight:bold;text-decoration:none;">Try Now</a>
|
||||
</div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px;">Create opportunities from incoming emails</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top: 0;">Emails sent to <strong>${email}</strong> generate opportunities in your pipeline.</p>
|
||||
<img src="/crm/static/src/img/tips-crm-pipeline.jpg" style="max-width: 580px" width="100%" />
|
||||
<div style="text-align:center;margin-top:10px;">
|
||||
<a href="mailto:${email}" target="_blank" style="display:inline-block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center; padding:0 10px;">Try it now</a>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
<record id="digest_tip_crm_1" model="digest.tip">
|
||||
<field name="name">CRM: Enrich your contacts</field>
|
||||
<field name="sequence">50</field>
|
||||
<field name="group_id" ref="sales_team.group_sale_salesman_all_leads"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px;">Enrich your contacts</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top: 0;">Activate the "Partner Autocomplete" feature to fill in your Partner's details faster.</p>
|
||||
<img src="/crm/static/src/img/tips-crm-autocomplete.jpg" style="max-width: 580px" width="100%" />
|
||||
<div style="text-align:center;margin-top:10px;">
|
||||
<a href="/web#action=base_setup.action_general_configuration" target="_blank" style="display:inline-block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center; padding:0 10px;">Try it now</a>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
<record id="digest_tip_crm_2" model="digest.tip">
|
||||
<field name="name">CRM: Get more leads</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="group_id" ref="sales_team.group_sale_salesman_all_leads"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px;">Get more leads</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top: 0;">Activate the lead mining feature from your CRM settings and start generating more leads.</p>
|
||||
<div style="text-align:center;margin-top:10px;">
|
||||
<a href="https://www.odoo.com/documentation/user/13.0/crm/acquire_leads/lead_mining.html" target="_blank" style="display:inline-block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center; padding:0 10px;">Find out more</a>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -37,8 +37,8 @@ class Digest(models.Model):
|
||||
('company_id', '=', company.id)
|
||||
])
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_crm_lead_created'] = 'crm.crm_lead_action_pipeline&menu_id=%s' % self.env.ref('crm.crm_menu_root').id
|
||||
res['kpi_crm_opportunities_won'] = 'crm.crm_lead_action_pipeline&menu_id=%s' % self.env.ref('crm.crm_menu_root').id
|
||||
if user.has_group('crm.group_use_lead'):
|
||||
|
||||
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 151 KiB |
@@ -7,7 +7,7 @@
|
||||
Send KPI Digests periodically
|
||||
=============================
|
||||
""",
|
||||
'version': '1.0',
|
||||
'version': '1.1',
|
||||
'depends': [
|
||||
'mail',
|
||||
'portal',
|
||||
@@ -15,8 +15,8 @@ Send KPI Digests periodically
|
||||
],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/digest_template_data.xml',
|
||||
'data/digest_data.xml',
|
||||
'data/digest_tips_data.xml',
|
||||
'data/ir_cron_data.xml',
|
||||
'data/res_config_settings_data.xml',
|
||||
'views/digest_views.xml',
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
from odoo import _
|
||||
from odoo.http import Controller, request, route
|
||||
|
||||
|
||||
class DigestController(Controller):
|
||||
|
||||
@route('/digest/<int:digest_id>/unsubscribe', type='http', website=True, auth='user')
|
||||
def digest_unsubscribe(self, digest_id, **post):
|
||||
digest = request.env['digest.digest'].sudo().browse(digest_id)
|
||||
def digest_unsubscribe(self, digest_id):
|
||||
digest = request.env['digest.digest'].browse(digest_id).exists()
|
||||
digest.action_unsubcribe()
|
||||
return request.render('digest.portal_digest_unsubscribed', {
|
||||
'digest': digest,
|
||||
})
|
||||
|
||||
@route('/digest/<int:digest_id>/set_periodicity', type='http', website=True, auth='user')
|
||||
def digest_set_periodicity(self, digest_id, periodicity='weekly'):
|
||||
if not request.env.user.has_group('base.group_erp_manager'):
|
||||
raise Forbidden()
|
||||
if periodicity not in ('daily', 'weekly', 'monthly', 'quarterly'):
|
||||
raise ValueError(_('Invalid periodicity set on digest'))
|
||||
|
||||
digest = request.env['digest.digest'].browse(digest_id).exists()
|
||||
digest.action_set_periodicity(periodicity)
|
||||
|
||||
url_params = {
|
||||
'model': digest._name,
|
||||
'id': digest.id,
|
||||
'active_id': digest.id,
|
||||
}
|
||||
return request.redirect('/web?#%s' % url_encode(url_params))
|
||||
|
||||
@@ -1,59 +1,245 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="digest_digest_default" model="digest.digest">
|
||||
<field name="name">Weekly Stats in Odoo</field>
|
||||
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||
<field name="next_run_date" eval="(DateTime.now() + timedelta(days=7)).strftime('%Y-%m-%d')"/>
|
||||
<field name="kpi_res_users_connected">True</field>
|
||||
<field name="kpi_mail_message_total">True</field>
|
||||
</record>
|
||||
</data>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data noupdate="1">
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_mail_0" model="digest.tip">
|
||||
<field name="name">Mail: ping colleagues</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
% set users = object.env['res.users'].search([('share', '=' , False)], limit=10, order='id desc')
|
||||
% set channel_id = object.env.ref('mail.channel_all_employees').id
|
||||
<strong style="font-size: 16px;">Did you know...?</strong>
|
||||
<div style="font-size: 14px;">You can ping colleagues by tagging them in your messages using "@". They will be instantly notified.<br/>
|
||||
<div>
|
||||
<center>
|
||||
<img src="/digest/static/src/img/notification.png" width="70%" height="100%"/><br/>
|
||||
</center>
|
||||
${', '.join(users.mapped('name'))} signed up. Say hello in the <a href="/web#action=mail.action_discuss&active_id=${channel_id}" style="color: #006d6b;">company's discussion channel.</a>
|
||||
<!-- Email layout: encapsulation when sending (not used in backend display) -->
|
||||
<template id="digest_mail_layout">
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="format-detection" content="telephone=no"/>
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE" />
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@media only screen and (max-width:650px) {
|
||||
.d-block{
|
||||
display: block !important;
|
||||
}
|
||||
.mobile-hide{
|
||||
display: none !important;
|
||||
}
|
||||
.mobile-w-75{
|
||||
width: 75% !important;
|
||||
}
|
||||
.mobile-w-33{
|
||||
width: 33% !important;
|
||||
}
|
||||
.font-25{
|
||||
font-size: 25px !important;
|
||||
line-height: 25px !important;
|
||||
}
|
||||
.font-18{
|
||||
font-size: 18px !important;
|
||||
line-height: 25px !important;
|
||||
}
|
||||
.font-16{
|
||||
font-size: 16px !important;
|
||||
line-height: 25px !important;
|
||||
}
|
||||
.font-12{
|
||||
font-size: 12px !important;
|
||||
line-height: 20px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<t t-raw="body"/>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
|
||||
<!-- DIGEST MAIN TEMPLATE -->
|
||||
<template id="digest_mail_main">
|
||||
<div style="margin:0; padding:0">
|
||||
<table cellspacing="0" cellpadding="0" align="center" border="0" bgcolor="#eeeeee" style="width:100%; font-family: Arial,Helvetica,Verdana,sans-serif;">
|
||||
<tr class="mobile-hide" bgcolor="#875a7b"><td height="70"> </td></tr>
|
||||
<tr bgcolor="#875a7b">
|
||||
<td align="center" valign="top">
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border: 1px solid #eeeeee;width: 100%; max-width: 650px; padding: 30px 30px">
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;">
|
||||
<tr>
|
||||
<td class="mobile-w-75" style="text-align: left;">
|
||||
<span class="font-16" style="color:#8F8F8F;font-weight:bold;font-size: 20px; line-height: 30px;" t-field="company.name"/>
|
||||
<br/>
|
||||
<span class="font-18" style="color:#282f33;font-weight:bold;font-size: 25px; line-height: 35px; margin-right: 5px;" t-field="object.name"/>
|
||||
<span class="d-block font-12" style="color:#8F8F8F;font-size: 15px;"><t t-esc="datetime.today().strftime('%B %d, %Y')"/></span>
|
||||
</td>
|
||||
<td valign="top" style="text-align:right;">
|
||||
<a href="/web/login" target="_blank" style="display:block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center;">Connect</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table t-if="tips" cellspacing="0" cellpadding="0" align="center" border="0" bgcolor="#eeeeee" style="width:100%; font-family: Arial,Helvetica,Verdana,sans-serif;">
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border: 1px solid #eeeeee; border-bottom: none;width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr>
|
||||
<td>
|
||||
<table t-foreach="tips" t-as="tip" cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<t t-raw="tip"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table t-if="kpi_data" cellspacing="0" cellpadding="0" align="center" border="0" bgcolor="#eeeeee" style="width:100%; font-family: Arial,Helvetica,Verdana,sans-serif;">
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border: 1px solid #eeeeee; border-bottom: none;width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr t-foreach="kpi_data" t-as="kpi_info">
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;">
|
||||
<tr>
|
||||
<td align="left" style="border-bottom: 1px solid #eeeeee;">
|
||||
<span style="color:#282f33;font-size: 15px; font-weight: bold; line-height: 30px" t-esc="kpi_info['kpi_fullname']"/>
|
||||
<a t-if="kpi_info['kpi_action']" t-att-href="'/web#action=%s' % kpi_info['kpi_action']"
|
||||
style="float:right; background-color:#875A7B;margin-left: 5px; padding: 5px 12px 5px 12px; text-decoration: none; color: #ffffff; border-radius: 3px; font-size:13px; font-weight:bold; margin-bottom: 5px;">
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellspacing="0" cellpadding="0" align="center" border="0" style="width:100%; padding: 20px 0;">
|
||||
<tr valign="top">
|
||||
<td class="mobile-w-33" width="196" style=" text-align: center;">
|
||||
<t t-call="digest.digest_tool_kpi">
|
||||
<t t-set="kpi_value" t-value="kpi_info['kpi_col1']['value']"/>
|
||||
<t t-set="kpi_margin" t-value="kpi_info['kpi_col1']['margin']"/>
|
||||
<t t-set="kpi_subtitle" t-value="kpi_info['kpi_col1']['col_subtitle']"/>
|
||||
</t>
|
||||
</td>
|
||||
<td class="mobile-w-33" width="196" style=" text-align: center;">
|
||||
<t t-call="digest.digest_tool_kpi">
|
||||
<t t-set="kpi_value" t-value="kpi_info['kpi_col2']['value']"/>
|
||||
<t t-set="kpi_margin" t-value="kpi_info['kpi_col2']['margin']"/>
|
||||
<t t-set="kpi_subtitle" t-value="kpi_info['kpi_col2']['col_subtitle']"/>
|
||||
</t>
|
||||
</td>
|
||||
<td class="mobile-w-33" width="196" style=" text-align: center;">
|
||||
<t t-call="digest.digest_tool_kpi">
|
||||
<t t-set="kpi_value" t-value="kpi_info['kpi_col3']['value']"/>
|
||||
<t t-set="kpi_margin" t-value="kpi_info['kpi_col3']['margin']"/>
|
||||
<t t-set="kpi_subtitle" t-value="kpi_info['kpi_col3']['col_subtitle']"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table t-if="preferences" bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border-left: 1px solid #eeeeee; border-right: 1px solid #eeeeee; width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr>
|
||||
<td bgcolor="#fafafa" width="100%" style="padding-top: 20px; padding-bottom: 20px;" align="center">
|
||||
<p t-foreach="preferences" t-as="preference" class="font-12" style="font-size: 15px; color: #6b6d70">
|
||||
<t t-raw="preference"/>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table t-if="display_mobile_banner" cellspacing="0" cellpadding="0" align="center" border="0" bgcolor="#eeeeee" style="width:100%; font-family: Arial,Helvetica,Verdana,sans-serif;">
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border: 1px solid #eeeeee; width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<t t-call="digest.digest_section_mobile"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellspacing="0" cellpadding="0" align="center" border="0" bgcolor="#eeeeee" style="width:100%; font-family: Arial,Helvetica,Verdana,sans-serif;">
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<table bgcolor="#eeeeee" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="margin-top:30px;font-size: 15px; text-align:center; width: 100%; max-width: 650px;">
|
||||
<tr>
|
||||
<td style="color:#8F8F8F;font-weight:bold;font-size: 20px; line-height: 30px;" t-esc="company.name"/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color:#8F8F8F;font-size: 12px; line-height: 30px;">Powered by <a href="https://www.odoo.com" target="_blank" style="color:#875A7B">Odoo</a>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="30"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- DIGEST PARTS -->
|
||||
|
||||
<!-- MOBILE BANNER -->
|
||||
<template id="digest_section_mobile">
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;">
|
||||
<tr>
|
||||
<td valign="top" width="168">
|
||||
<img src="https://www.odoo.com/web/image/24717933/odoo-mobile.png" alt="Odoo Mobile" width="168"/>
|
||||
</td>
|
||||
<td valign="top">
|
||||
<p class="font-16" style="color:#2d2a26;font-size: 18px;line-height: 23px; margin-top: 20px">Run your business from anywhere with <br class="mobile-hide" /><b>Odoo Mobile</b>.</p>
|
||||
<a href="https://play.google.com/store/apps/details?id=com.odoo.mobile" target="_blank"><img src="https://www.odoo.com/digest/static/src/img/google_play.png" style="display:inline-block;height: 40px; margin-bottom: 5px"/></a>
|
||||
<span class="mobile-hide"> </span>
|
||||
<a href="https://itunes.apple.com/us/app/odoo/id1272543640" target="_blank"><img src="https://www.odoo.com/digest/static/src/img/app_store.png" style="display:inline-block;height: 40px; margin-bottom: 5px"/></a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- DIGEST TOOLS -->
|
||||
|
||||
<!-- KPI DISPLAY -->
|
||||
<template id="digest_tool_kpi">
|
||||
<span class="font-25" style="color:#282f33;font-size: 35px; font-weight: bold; text-decoration: none; line-height: 36px;">
|
||||
<t t-esc="kpi_value"/>
|
||||
</span><br/>
|
||||
<span style="color:#888888;display: inline-block; font-size: 12px; line-height: 18px; text-transform: uppercase;">
|
||||
<t t-esc="kpi_subtitle"/>
|
||||
</span>
|
||||
<t t-if="kpi_margin != 0.0">
|
||||
<t t-if="kpi_margin > 0.0">
|
||||
<div style="background-color:#d5f1e2; border: 1px solid #c4ecd7; margin: 10px auto;width: 67px; line-height:27px; text-decoration: none; color: #17613a; border-radius: 5px; font-size:13px;">
|
||||
▲ <t t-esc="'%.2f' % kpi_margin"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
<record id="digest_tip_mail_1" model="digest.tip">
|
||||
<field name="name">Mail: get things done with activities</field>
|
||||
<field name="sequence">7</field>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<strong style="font-size: 16px;">Get things done with activities</strong>
|
||||
<div style="font-size: 14px;">You don't have any activity scheduled. Use activities on any business document to schedule meetings, calls and todos.</div>
|
||||
<center>
|
||||
<img src="/digest/static/src/img/activity.png" width="70%" height="100%"/><br/>
|
||||
</center>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</t>
|
||||
<t t-elif="kpi_margin < 0.0">
|
||||
<div style="background-color:#f7dddc; border: 1px solid #f4cfce; margin: 10px auto;width: 67px; line-height:27px; text-decoration: none; color: #712b29; border-radius: 5px; font-size:13px;">
|
||||
▼ <t t-esc="'%.2f' % kpi_margin"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<record id="digest_tip_mail_2" model="digest.tip">
|
||||
<field name="name">Mail: apps videos tour</field>
|
||||
<field name="group_id" ref='base.group_system'/>
|
||||
<field name="sequence">0</field>
|
||||
<field name="tip_description" type="html">
|
||||
<div style="font-size: 14px;">
|
||||
To reach your full potential with Odoo, have a look at our <a href="https://www.odoo.com/page/tour">apps videos</a> and discover new tools to work better and faster.
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
</data></odoo>
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="digest_mail_template" model="mail.template">
|
||||
<field name="name">Digest: Default main template</field>
|
||||
<field name="model_id" ref="digest.model_digest_digest"/>
|
||||
<field name="subject">${'%s: %s' % (ctx.get('user', user).company_id.name, object.name)}</field>
|
||||
<field name="email_from">${('%s' % (object.company_id.partner_id.email_formatted or user.email_formatted))|safe}</field>
|
||||
<field name="body_html" type="html">
|
||||
<table style="width: 100%; border-spacing: 0; font-family: Helvetica,Arial,Verdana,sans-serif;">
|
||||
<tr>
|
||||
<td align="center" valign="top" style="border-collapse: collapse; padding: 0">
|
||||
% set user = ctx.get('user', user)
|
||||
% set company = user.company_id
|
||||
% set data = object.compute_kpis(company, user)
|
||||
% set tips = object.compute_tips(company, user)
|
||||
% set kpi_actions = object.compute_kpis_actions(company, user)
|
||||
% set kpis = data.yesterday.keys()
|
||||
<table style="width: 100%; max-width: 600px; border-spacing: 0; border: 1px solid #e7e7e7; border-bottom: none; color: #6e7172; line-height: 23px; text-align: left;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; padding: 10px 20px; text-align: left;">
|
||||
<strong style="color: #000000; font-size: 22px; line-height: 32px;">${object.name}</strong>
|
||||
<div style="color: #000000; font-size: 15px;">${datetime.date.today().strftime('%B %d, %Y')}</div>
|
||||
</td>
|
||||
<td style="text-align: right; padding: 10px 20px">
|
||||
<img style="padding: 0px; margin: 0px; height: auto; width: 80px;" src="/logo.png?company=${company.id}"/>
|
||||
<div style="padding-top: 5px;">
|
||||
<a href="/web/login" style="background-color: #875A7B; margin-left: 5px; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">Connect</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td colspan="2" style="text-align: center;">
|
||||
<hr width="95%" style="background-color: rgb(204,204,204); border: medium none; clear: both; display: block; font-size: 0px; min-height: 1px; line-height: 0; margin: 16px 0px 16px 14px;"/>
|
||||
</td></tr>
|
||||
</table>
|
||||
% for kpi in kpis:
|
||||
<table style="border-spacing: 0; width: 100%; max-width: 600px;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; background-color: #ffffff; border-left: 1px solid #e7e7e7; border-right: 1px solid #e7e7e7; line-height: 21px; padding: 0 20px 10px 20px; text-align: left;"><br/>
|
||||
<span style="color: #3d466e; font-size: 18px; font-weight: 500; line-height: 23px;">${object.fields_get()[kpi]['string']}</span>
|
||||
%if kpi in kpi_actions:
|
||||
<span style="float: right;">
|
||||
<a href="/web#action=${kpi_actions[kpi]}">View more</a>
|
||||
</span>
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; margin: 0; padding:0;">
|
||||
<table style="width: 100%; border-spacing: 0; background-color: #f9f9f9; border: 1px solid #e7e7e7; border-top: none;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; margin: 0; padding: 0; display: block; border-top: 2px solid #56b3b5;">
|
||||
<table style="width: 100%; max-width: 199px; border-spacing: 0;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; padding: 20px; text-align: center;">
|
||||
<span style="color: #56b3b5; font-size: 35px; font-weight: bold; text-decoration: none; line-height: 36px;">${data['yesterday'][kpi][kpi]}</span><br/>
|
||||
<span style="color: #888888; display: inline-block; font-size: 12px; line-height: 18px; text-transform: uppercase;">Yesterday</span>
|
||||
% if data['yesterday'][kpi]['margin'] != 0.0:
|
||||
<span style="color: #888888; display: block; font-size: 12px; line-height: 18px; text-transform: uppercase;">
|
||||
% if data['yesterday'][kpi]['margin'] is greaterthan(0.0):
|
||||
<span style="color: #0bbc22;">▲</span>${"%.2f" % data['yesterday'][kpi]['margin']} %
|
||||
% endif
|
||||
% if data['yesterday'][kpi]['margin'] is lessthan(0.0):
|
||||
<span style="color: #ff0000;">▼</span>${"%.2f" % data['yesterday'][kpi]['margin']} %
|
||||
% endif
|
||||
</span>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td style="border-collapse: collapse; margin: 0; padding: 0; border-top: 2px solid #9a5b82;">
|
||||
<table style="width: 100%; max-width: 199px; border-spacing: 0; margin: 0; padding: 0;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; padding: 20px; text-align: center;">
|
||||
<span style="color: #9a5b82; font-size: 35px; font-weight: bold; text-decoration: none; line-height: 36px;">${data['lastweek'][kpi][kpi]}</span><br/>
|
||||
<span style="color: #888888; display: inline-block; font-size: 12px; line-height: 18px; text-transform: uppercase;">Last 7 Days</span>
|
||||
% if data['lastweek'][kpi]['margin'] != 0.0:
|
||||
<span style="color: #888888; display: block; font-size: 12px; line-height: 18px; text-transform: uppercase;">
|
||||
% if data['lastweek'][kpi]['margin'] is greaterthan(0.0):
|
||||
<span style="color: #0bbc22;">▲</span>${"%.2f" % data['lastweek'][kpi]['margin']} %
|
||||
% endif
|
||||
% if data['lastweek'][kpi]['margin'] is lessthan(0.0):
|
||||
<span style="color: #ff0000;">▼</span>${"%.2f" % data['lastweek'][kpi]['margin']} %
|
||||
%endif
|
||||
</span>
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td style="border-collapse: collapse; margin: 0; padding: 15px; border-top: 2px solid #56b3b5;">
|
||||
<table style="width: 100%; max-width: 199px; border-spacing: 0; margin: 0; padding: 0;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; margin: 0; text-align: center;">
|
||||
<span style="color: #56b3b5; font-size: 35px; font-weight: bold; text-decoration: none; line-height: 36px">${data['lastmonth'][kpi][kpi]}</span><br/>
|
||||
<span style="color: #888888; display: inline-block; font-size: 12px; line-height: 18px; text-transform: uppercase;">Last 30 Days</span>
|
||||
% if data['lastmonth'][kpi]['margin'] != 0.0:
|
||||
<span style="color: #888888; display: block; font-size: 12px; line-height: 18px; text-transform: uppercase;">
|
||||
% if data['lastmonth'][kpi]['margin'] is greaterthan(0.0):
|
||||
<span style="color: #0bbc22;">▲</span>${"%.2f" % data['lastmonth'][kpi]['margin']} %
|
||||
% endif
|
||||
% if data['lastmonth'][kpi]['margin'] is lessthan(0.0):
|
||||
<span style="color: #ff0000;">▼</span>${"%.2f" % data['lastmonth'][kpi]['margin']} %
|
||||
%endif
|
||||
</span>
|
||||
%endif
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endfor
|
||||
% if tips:
|
||||
<table style="width: 100%; max-width: 600px; margin-top: 5px; border: 1px solid #e7e7e7;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; background-color: #ffffff; line-height: 21px; padding: 0px 20px 20px;"><br/>
|
||||
<div style="color: #3d466e; line-height: 23px;">${tips | safe}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endif
|
||||
<table style="width: 100%; max-width: 600px; margin-top: 5px; border: 1px solid #e7e7e7;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; background-color: #ffffff; line-height: 21px; padding: 0 20px 10px 20px; text-align: center;"><br/>
|
||||
<div style="color: #3d466e; font-size: 16px; font-weight: 600; line-height: 23px;">Run your business from anywhere with Odoo Mobile.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom:20px;">
|
||||
<div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=com.odoo.mobile" target="_blank"><img src="/digest/static/src/img/google_play.png" style="display: inline-block; height: 30px; margin-left: auto; margin-right: 12px;"/></a><a href="https://itunes.apple.com/us/app/odoo/id1272543640" target="_blank"><img src="/digest/static/src/img/app_store.png" style="display: inline-block; height: 30px; margin-left: 12px; margin-right: auto;"/></a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table style="margin-top: 5px; border: 1px solid #e7e7e7; font-size: 15px; width: 100%; max-width: 600px;">
|
||||
<tr>
|
||||
<td style="border-collapse: collapse; margin: 0; padding: 10px 20px;">
|
||||
% if user.has_group('base.group_system'):
|
||||
<div style="margin-top: 20px;">
|
||||
Want to customize this email?
|
||||
<a href="/web#view_type=form&model=digest.digest&id=${object.id}" target="_blank" style="color: #875A7B;">Choose the metrics you care about</a>
|
||||
</div>
|
||||
<br />
|
||||
% endif
|
||||
<p style="font-size: 11px; margin-top: 10px;">
|
||||
<strong>
|
||||
Sent by
|
||||
<a href="https://www.odoo.com" style="text-decoration: none; color: #875A7B;">Odoo</a> - <a href="/web#view_type=form&model=digest.digest&id=${object.id}" target="_blank" style="color: #888888;">Unsubscribe</a>
|
||||
</strong>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table></field>
|
||||
<field name="lang">${user.lang}</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,109 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="digest_digest_default" model="digest.digest">
|
||||
<field name="name">Your Odoo Periodic Report</field>
|
||||
<field name="periodicity">daily</field>
|
||||
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||
<field name="next_run_date" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d')"/>
|
||||
<field name="kpi_res_users_connected">True</field>
|
||||
<field name="kpi_mail_message_total">True</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_digest_0" model="digest.tip">
|
||||
<field name="name">Invite Team Members</field>
|
||||
<field name="sequence">1</field>
|
||||
<field name="group_id" ref="base.group_erp_manager"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Invite Team Members</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">Go to your general settings to invite new users to your database.</p>
|
||||
<img src="/digest/static/src/img/tips-digest-invite-team.jpg" style="max-width: 580px" width="100%"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="digest_tip_digest_1" model="digest.tip">
|
||||
<field name="name"> Want to learn more about Odoo?</field>
|
||||
<field name="sequence">2</field>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px"> Want to learn more about Odoo?</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">Visit our <a href="https://www.odoo.com/slides">eLearning Platform</a></p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="digest_tip_digest_2" model="digest.tip">
|
||||
<field name="name">Odoo Push Notifications</field>
|
||||
<field name="sequence">3</field>
|
||||
<field name="group_id" ref="base.group_erp_manager"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Odoo Push Notifications</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">Change your browser settings to "Allow Push Notifications" and never miss a thing.</p>
|
||||
<img src="/digest/static/src/img/tips-digest-browser-notifications.jpg" style="max-width: 580px" width="100%"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="digest_tip_digest_3" model="digest.tip">
|
||||
<field name="name">Reach your full potential with Odoo</field>
|
||||
<field name="sequence">4</field>
|
||||
<field name="group_id" ref="base.group_erp_manager"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Reach your full potential with Odoo</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">Take a look at our <a href="https://www.odoo.com/page/tour">App videos</a> and discover the next tool that will allow you to work better and faster.</p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="digest_tip_mail_0" model="digest.tip">
|
||||
<field name="name">Did you know?</field>
|
||||
<field name="sequence">10</field>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
% set users = object.env['res.users'].search([('share', '=' , False)], limit=10, order='id desc')
|
||||
% set channel_id = object.env.ref('mail.channel_all_employees').id
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Did you know?</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">You can ping your colleagues by tagging them in your messages using “@” followed by their name. They will be instantly notified.</p>
|
||||
<img src="/digest/static/src/img/tips-mail-notifications.jpg" style="max-width: 580px" width="100%"/>
|
||||
<div style="margin-top: 20px; background-color: #f8f9fa">
|
||||
<p class="font-12" style="text-align:center; color:#888888;font-size: 13px; line-height: 25px; margin:0;"><b>${', '.join(users.mapped('name'))}</b> just signed up. <a href="/web#action=mail.action_discuss&active_id=${channel_id}" style="color: #875a7b; font-weight: bold">Say hello in the company's discussion channel.</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
<record id="digest_tip_mail_1" model="digest.tip">
|
||||
<field name="name">Get things done with activities</field>
|
||||
<field name="sequence">11</field>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Get things done with activities</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">Use activities on any business document to schedule meetings, calls and todos.</p>
|
||||
<img src="/digest/static/src/img/tips-mail-activities.jpg" style="max-width: 580px" width="100%" />
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="digest_tip_mail_2" model="digest.tip">
|
||||
<field name="name">Follow what you want</field>
|
||||
<field name="sequence">12</field>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Follow what you want</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top:0;">Keep track of any object in Odoo (opportunity, sales order, etc.) and choose exactly what you want to follow (discussion, status, status change, etc.).</p>
|
||||
<img src="/digest/static/src/img/tips-mail-following.jpg" style="max-width: 580px" width="100%" />
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -2,13 +2,12 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import math
|
||||
import pytz
|
||||
|
||||
from datetime import datetime, date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tools.float_utils import float_round
|
||||
@@ -23,15 +22,12 @@ class Digest(models.Model):
|
||||
# Digest description
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
user_ids = fields.Many2many('res.users', string='Recipients', domain="[('share', '=', False)]")
|
||||
periodicity = fields.Selection([('weekly', 'Weekly'),
|
||||
periodicity = fields.Selection([('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('quarterly', 'Quarterly')],
|
||||
string='Periodicity', default='weekly', required=True)
|
||||
string='Periodicity', default='daily', required=True)
|
||||
next_run_date = fields.Date(string='Next Send Date')
|
||||
template_id = fields.Many2one('mail.template', string='Email Template',
|
||||
domain="[('model','=','digest.digest')]",
|
||||
default=lambda self: self.env.ref('digest.digest_mail_template'),
|
||||
required=True)
|
||||
currency_id = fields.Many2one(related="company_id.currency_id", string='Currency', readonly=False)
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company.id)
|
||||
available_fields = fields.Char(compute='_compute_available_fields')
|
||||
@@ -77,15 +73,21 @@ class Digest(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
vals['next_run_date'] = date.today() + relativedelta(days=3)
|
||||
return super(Digest, self).create(vals)
|
||||
digest = super(Digest, self).create(vals)
|
||||
if not digest.next_run_date:
|
||||
digest.next_run_date = digest._get_next_run_date()
|
||||
return digest
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def action_subscribe(self):
|
||||
if self.env.user not in self.user_ids:
|
||||
if self.env.user.has_group('base.group_user') and self.env.user not in self.user_ids:
|
||||
self.sudo().user_ids |= self.env.user
|
||||
|
||||
def action_unsubcribe(self):
|
||||
if self.env.user in self.user_ids:
|
||||
if self.env.user.has_group('base.group_user') and self.env.user in self.user_ids:
|
||||
self.sudo().user_ids -= self.env.user
|
||||
|
||||
def action_activate(self):
|
||||
@@ -94,53 +96,145 @@ class Digest(models.Model):
|
||||
def action_deactivate(self):
|
||||
self.state = 'deactivated'
|
||||
|
||||
def action_set_periodicity(self, periodicity):
|
||||
self.periodicity = periodicity
|
||||
|
||||
def action_send(self):
|
||||
to_slowdown = self._check_daily_logs()
|
||||
for digest in self:
|
||||
for user in digest.user_ids:
|
||||
subject = '%s: %s' % (user.company_id.name, digest.name)
|
||||
digest.template_id.with_context(user=user).send_mail(digest.id, force_send=True, raise_exception=True, email_values={'email_to': user.email, 'subject': subject})
|
||||
digest.with_context(
|
||||
digest_slowdown=digest in to_slowdown,
|
||||
lang=user.lang
|
||||
)._action_send_to_user(user, tips_count=1)
|
||||
if digest in to_slowdown:
|
||||
digest.write({'periodicity': 'weekly'})
|
||||
digest.next_run_date = digest._get_next_run_date()
|
||||
|
||||
def _action_send_to_user(self, user, tips_count=1):
|
||||
rendered_body = self.env['mail.render.mixin']._render_template(
|
||||
'digest.digest_mail_main',
|
||||
'digest.digest',
|
||||
self.ids,
|
||||
engine='qweb',
|
||||
add_context={
|
||||
'company': user.company_id,
|
||||
'user': user,
|
||||
'tips_count': tips_count,
|
||||
'datetime': datetime, # TDE TEMP FIXME
|
||||
'display_mobile_banner': True,
|
||||
'kpi_data': self.compute_kpis(user.company_id, user),
|
||||
'tips': self.compute_tips(user.company_id, user, tips_count=tips_count),
|
||||
'preferences': self.compute_preferences(user.company_id, user),
|
||||
},
|
||||
post_process=True
|
||||
)[self.id]
|
||||
full_mail = self.env['mail.render.mixin']._render_encapsulate(
|
||||
'digest.digest_mail_layout',
|
||||
rendered_body,
|
||||
add_context={
|
||||
'company': user.company_id,
|
||||
'user': user,
|
||||
},
|
||||
)
|
||||
# create a mail_mail based on values, without attachments
|
||||
mail_values = {
|
||||
'subject': '%s: %s' % (user.company_id.name, self.name),
|
||||
'email_from': self.company_id.partner_id.email_formatted if self.company_id else self.env.user.email_formatted,
|
||||
'email_to': user.email_formatted,
|
||||
'body_html': full_mail,
|
||||
'auto_delete': True,
|
||||
}
|
||||
mail = self.env['mail.mail'].sudo().create(mail_values)
|
||||
mail.send(raise_exception=False)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _cron_send_digest_email(self):
|
||||
digests = self.search([('next_run_date', '<=', fields.Date.today()), ('state', '=', 'activated')])
|
||||
for digest in digests:
|
||||
try:
|
||||
digest.action_send()
|
||||
except MailDeliveryException as e:
|
||||
_logger.warning('MailDeliveryException while sending digest %d. Digest is now scheduled for next cron update.')
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# KPIS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def compute_kpis(self, company, user):
|
||||
""" Compute KPIs to display in the digest template. It is expected to be
|
||||
a list of KPIs, each containing values for 3 columns display.
|
||||
|
||||
:return list: result [{
|
||||
'kpi_name': 'kpi_mail_message',
|
||||
'kpi_fullname': 'Messages', # translated
|
||||
'kpi_action': 'crm.crm_lead_action_pipeline', # xml id of an action to execute
|
||||
'kpi_col1': {
|
||||
'value': '12.0',
|
||||
'margin': 32.36,
|
||||
'col_subtitle': 'Yesterday', # translated
|
||||
},
|
||||
'kpi_col2': { ... },
|
||||
'kpi_col3': { ... },
|
||||
}, { ... }] """
|
||||
self.ensure_one()
|
||||
res = {}
|
||||
for tf_name, tf in self._compute_timeframes(company).items():
|
||||
digest_fields = self._get_kpi_fields()
|
||||
invalid_fields = []
|
||||
kpis = [
|
||||
dict(kpi_name=field_name,
|
||||
kpi_fullname=self._fields[field_name].string,
|
||||
kpi_action=False,
|
||||
kpi_col1=dict(),
|
||||
kpi_col2=dict(),
|
||||
kpi_col3=dict(),
|
||||
)
|
||||
for field_name in digest_fields
|
||||
]
|
||||
kpis_actions = self._compute_kpis_actions(company, user)
|
||||
|
||||
for col_index, (tf_name, tf) in enumerate(self._compute_timeframes(company)):
|
||||
digest = self.with_context(start_date=tf[0][0], end_date=tf[0][1]).with_user(user).with_company(company)
|
||||
previous_digest = self.with_context(start_date=tf[1][0], end_date=tf[1][1]).with_user(user).with_company(company)
|
||||
kpis = {}
|
||||
for field_name, field in self._fields.items():
|
||||
if field.type == 'boolean' and field_name.startswith(('kpi_', 'x_kpi_', 'x_studio_kpi_')) and self[field_name]:
|
||||
for index, field_name in enumerate(digest_fields):
|
||||
kpi_values = kpis[index]
|
||||
kpi_values['kpi_action'] = kpis_actions.get(field_name)
|
||||
try:
|
||||
compute_value = digest[field_name + '_value']
|
||||
# Context start and end date is different each time so invalidate to recompute.
|
||||
digest.invalidate_cache([field_name + '_value'])
|
||||
previous_value = previous_digest[field_name + '_value']
|
||||
# Context start and end date is different each time so invalidate to recompute.
|
||||
previous_digest.invalidate_cache([field_name + '_value'])
|
||||
except AccessError: # no access rights -> just skip that digest details from that user's digest email
|
||||
invalid_fields.append(field_name)
|
||||
continue
|
||||
margin = self._get_margin_value(compute_value, previous_value)
|
||||
if self._fields['%s_value' % field_name].type == 'monetary':
|
||||
converted_amount = self._format_human_readable_amount(compute_value)
|
||||
compute_value = self._format_currency_amount(converted_amount, company.currency_id)
|
||||
kpi_values['kpi_col%s' % (col_index + 1)].update({
|
||||
'value': compute_value,
|
||||
'margin': margin,
|
||||
'col_subtitle': tf_name,
|
||||
})
|
||||
|
||||
try:
|
||||
compute_value = digest[field_name + '_value']
|
||||
# Context start and end date is different each time so invalidate to recompute.
|
||||
digest.invalidate_cache([field_name + '_value'])
|
||||
previous_value = previous_digest[field_name + '_value']
|
||||
# Context start and end date is different each time so invalidate to recompute.
|
||||
previous_digest.invalidate_cache([field_name + '_value'])
|
||||
except AccessError: # no access rights -> just skip that digest details from that user's digest email
|
||||
continue
|
||||
margin = self._get_margin_value(compute_value, previous_value)
|
||||
if self._fields[field_name+'_value'].type == 'monetary':
|
||||
converted_amount = self._format_human_readable_amount(compute_value)
|
||||
kpis.update({field_name: {field_name: self._format_currency_amount(converted_amount, company.currency_id), 'margin': margin}})
|
||||
else:
|
||||
kpis.update({field_name: {field_name: compute_value, 'margin': margin}})
|
||||
# filter failed KPIs
|
||||
return [kpi for kpi in kpis if kpi['kpi_name'] not in invalid_fields]
|
||||
|
||||
res.update({tf_name: kpis})
|
||||
return res
|
||||
def compute_tips(self, company, user, tips_count=1):
|
||||
tips = self.env['digest.tip'].search([
|
||||
('user_ids', '!=', user.id),
|
||||
'|', ('group_id', 'in', user.groups_id.ids), ('group_id', '=', False)
|
||||
], limit=tips_count)
|
||||
tip_descriptions = [
|
||||
self.env['mail.render.mixin']._render_template(tools.html_sanitize(tip.tip_description), 'digest.tip', tip.ids, post_process=True)[tip.id]
|
||||
for tip in tips
|
||||
]
|
||||
tips.user_ids += user
|
||||
return tip_descriptions
|
||||
|
||||
def compute_tips(self, company, user):
|
||||
tip = self.env['digest.tip'].search([('user_ids', '!=', user.id), '|', ('group_id', 'in', user.groups_id.ids), ('group_id', '=', False)], limit=1)
|
||||
if not tip:
|
||||
return False
|
||||
tip.user_ids += user
|
||||
body = tools.html_sanitize(tip.tip_description)
|
||||
# FIXME: sanitize ? links ?
|
||||
tip_description = self.env['mail.render.mixin']._render_template(body, 'digest.tip', self.ids)[self.id]
|
||||
return tip_description
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
""" Give an optional action to display in digest email linked to some KPIs.
|
||||
|
||||
:return dict: key: kpi name (field name), value: an action that will be
|
||||
@@ -148,8 +242,34 @@ class Digest(models.Model):
|
||||
"""
|
||||
return {}
|
||||
|
||||
def compute_preferences(self, company, user):
|
||||
""" Give an optional text for preferences, like a shortcut for configuration.
|
||||
|
||||
:return string: html to put in template
|
||||
"""
|
||||
preferences = []
|
||||
if self._context.get('digest_slowdown'):
|
||||
preferences.append(_("We have noticed you did not connect these last few days so we've automatically switched your preference to weekly Digests."))
|
||||
elif self.periodicity == 'daily' and user.has_group('base.group_erp_manager'):
|
||||
preferences.append('%s <a href="/digest/%s/set_periodicity?periodicity=weekly" target="_blank" style="color:#875A7B; font-weight: bold;">%s</a>' % (
|
||||
_('Prefer a broader overview ?'),
|
||||
self.id,
|
||||
_('Switch to weekly Digests')
|
||||
))
|
||||
if user.has_group('base.group_erp_manager'):
|
||||
preferences.append('%s <a href="/web#view_type=form&model=%s&id=%s" target="_blank" style="color:#875A7B; font-weight: bold;">%s</a>' % (
|
||||
_('Want to customize this email?'),
|
||||
self._name,
|
||||
self.id,
|
||||
_('Choose the metrics you care about')
|
||||
))
|
||||
|
||||
return preferences
|
||||
|
||||
def _get_next_run_date(self):
|
||||
self.ensure_one()
|
||||
if self.periodicity == 'daily':
|
||||
delta = relativedelta(days=1)
|
||||
if self.periodicity == 'weekly':
|
||||
delta = relativedelta(weeks=1)
|
||||
elif self.periodicity == 'monthly':
|
||||
@@ -164,17 +284,27 @@ class Digest(models.Model):
|
||||
if tz_name:
|
||||
now = pytz.timezone(tz_name).localize(now)
|
||||
start_date = now.date()
|
||||
return {
|
||||
'yesterday': (
|
||||
return [
|
||||
(_('Yesterday'), (
|
||||
(start_date + relativedelta(days=-1), start_date),
|
||||
(start_date + relativedelta(days=-2), start_date + relativedelta(days=-1))),
|
||||
'lastweek': (
|
||||
(start_date + relativedelta(days=-2), start_date + relativedelta(days=-1)))
|
||||
), (_('Last 7 Days'), (
|
||||
(start_date + relativedelta(weeks=-1), start_date),
|
||||
(start_date + relativedelta(weeks=-2), start_date + relativedelta(weeks=-1))),
|
||||
'lastmonth': (
|
||||
(start_date + relativedelta(weeks=-2), start_date + relativedelta(weeks=-1)))
|
||||
), (_('Last 30 Days'), (
|
||||
(start_date + relativedelta(months=-1), start_date),
|
||||
(start_date + relativedelta(months=-2), start_date + relativedelta(months=-1))),
|
||||
}
|
||||
(start_date + relativedelta(months=-2), start_date + relativedelta(months=-1)))
|
||||
)
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# FORMATTING / TOOLS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def _get_kpi_fields(self):
|
||||
return [field_name for field_name, field in self._fields.items()
|
||||
if field.type == 'boolean' and field_name.startswith(('kpi_', 'x_kpi_', 'x_studio_kpi_')) and self[field_name]
|
||||
]
|
||||
|
||||
def _get_margin_value(self, value, previous_value=0.0):
|
||||
margin = 0.0
|
||||
@@ -182,6 +312,18 @@ class Digest(models.Model):
|
||||
margin = float_round((float(value-previous_value) / previous_value or 1) * 100, precision_digits=2)
|
||||
return margin
|
||||
|
||||
def _check_daily_logs(self):
|
||||
three_days_ago = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - relativedelta(days=3)
|
||||
to_slowdown = self.env['digest.digest']
|
||||
for digest in self.filtered(lambda digest: digest.periodicity == 'daily'):
|
||||
users_logs = self.env['res.users.log'].sudo().search_count([
|
||||
('create_uid', 'in', digest.user_ids.ids),
|
||||
('create_date', '>=', three_days_ago)
|
||||
])
|
||||
if not users_logs:
|
||||
to_slowdown += digest
|
||||
return to_slowdown
|
||||
|
||||
def _format_currency_amount(self, amount, currency_id):
|
||||
pre = currency_id.position == 'before'
|
||||
symbol = u'{symbol}'.format(symbol=currency_id.symbol or '')
|
||||
@@ -193,12 +335,3 @@ class Digest(models.Model):
|
||||
return "%3.2f%s%s" % (amount, unit, suffix)
|
||||
amount /= 1000.0
|
||||
return "%.2f%s%s" % (amount, 'T', suffix)
|
||||
|
||||
@api.model
|
||||
def _cron_send_digest_email(self):
|
||||
digests = self.search([('next_run_date', '=', fields.Date.today()), ('state', '=', 'activated')])
|
||||
for digest in digests:
|
||||
try:
|
||||
digest.action_send()
|
||||
except MailDeliveryException as e:
|
||||
_logger.warning('MailDeliveryException while sending digest %d. Digest is now scheduled for next cron update.')
|
||||
|
||||
@@ -2,4 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_digest_digest_system,digest.digest.administration,model_digest_digest,base.group_erp_manager,1,1,1,1
|
||||
access_digest_digest_user,digest.digest.user,model_digest_digest,base.group_user,1,0,0,0
|
||||
access_digest_tip_system,digest.tip.administration,model_digest_tip,base.group_erp_manager,1,1,1,1
|
||||
access_digest_tip_user,digest.tip.user,model_digest_tip,base.group_user,0,0,0,0
|
||||
access_digest_tip_user,digest.tip.user,model_digest_tip,base.group_user,1,0,0,0
|
||||
|
||||
|
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 123 KiB |
@@ -46,10 +46,8 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="periodicity" widget="radio" options="{'horizontal': true}"/>
|
||||
<field name="template_id" groups="base.group_no_one"/>
|
||||
<field name="next_run_date" groups="base.group_system"/>
|
||||
<field name="company_id" options="{'no_create': True}" invisible="1"/>
|
||||
<!-- <field name="digest_history_ids" invisible="1"/> -->
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
|
||||
@@ -8,16 +8,15 @@
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_hr_recruitment_0" model="digest.tip">
|
||||
<field name="name">HR Recruitment: try mailgateway</field>
|
||||
<field name="name">Recruitment: Create new applicants from incoming emails</field>
|
||||
<field name="sequence">4</field>
|
||||
<field name="group_id" ref="hr_recruitment.group_hr_recruitment_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<strong style="font-size: 16px;">Try the mail gateway</strong>
|
||||
<div style="font-size: 14px;">New applicants can be generated from incoming emails. You just need to set email aliases in configuration of job positions.<br/>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:2px;">
|
||||
<a href="/web#action=hr_recruitment.action_hr_job_config" style="background-color:#56b3b5;padding:2px;color:#FFFFFF;font-weight:bold;text-decoration:none;">Set Email Aliases</a>
|
||||
</div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Create new applicants from incoming emails</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top: 0">You just need to set email aliases in your job positions' settings.</p>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:10px;">
|
||||
<a href="/web#action=hr_recruitment.action_hr_job_config" target="_blank" style="display:inline-block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center; padding:0 10px;">Set Email Aliases</a>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
|
||||
@@ -23,7 +23,7 @@ class Digest(models.Model):
|
||||
])
|
||||
record.kpi_hr_recruitment_new_colleagues_value = new_colleagues
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_hr_recruitment_new_colleagues'] = 'hr.open_view_employee_list_my&menu_id=%s' % self.env.ref('hr.menu_hr_root').id
|
||||
return res
|
||||
|
||||
@@ -42,8 +42,8 @@ class Digest(models.Model):
|
||||
('partner_id', '=', self.env.user.partner_id.id)], ['partner_id', 'time_to_answer'], ['partner_id'])
|
||||
record.kpi_livechat_response_value = "%.2f" % sum([response['time_to_answer'] for response in response_time]) or 0
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_livechat_rating'] = 'im_livechat.rating_rating_action_livechat_report'
|
||||
res['kpi_livechat_conversations'] = 'im_livechat.im_livechat_report_operator_action'
|
||||
res['kpi_livechat_response'] = 'im_livechat.im_livechat_report_channel_time_to_answer_action'
|
||||
|
||||
@@ -183,10 +183,74 @@ class MailRenderMixin(models.AbstractModel):
|
||||
|
||||
return html
|
||||
|
||||
@api.model
|
||||
def _render_encapsulate(self, layout_xmlid, html, add_context=None, context_record=None):
|
||||
try:
|
||||
template = self.env.ref(layout_xmlid, raise_if_not_found=True)
|
||||
except ValueError:
|
||||
_logger.warning('QWeb template %s not found when rendering encapsulation template.' % (layout_xmlid))
|
||||
else:
|
||||
record_name = context_record.display_name if context_record else ''
|
||||
model_description = self.env['ir.model']._get(context_record._name).display_name if context_record else False
|
||||
template_ctx = {
|
||||
'body': html,
|
||||
'record_name': record_name,
|
||||
'model_description': model_description,
|
||||
'company': context_record['company_id'] if (context_record and 'company_id' in context_record) else self.env.company,
|
||||
'record': context_record,
|
||||
}
|
||||
if add_context:
|
||||
template_ctx.update(**add_context)
|
||||
|
||||
html = template._render(template_ctx, engine='ir.qweb', minimal_qcontext=True)
|
||||
html = self.env['mail.render.mixin']._replace_local_links(html)
|
||||
return html
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# RENDERING
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@api.model
|
||||
def _render_qweb_eval_context(self):
|
||||
""" Prepare qweb evaluation context, containing for all rendering
|
||||
|
||||
* ``user``: current user browse record;
|
||||
* ``ctx```: current context;
|
||||
* various formatting tools;
|
||||
"""
|
||||
render_context = {
|
||||
'format_date': lambda date, date_format=False, lang_code=False: format_date(self.env, date, date_format, lang_code),
|
||||
'format_datetime': lambda dt, tz=False, dt_format=False, lang_code=False: format_datetime(self.env, dt, tz, dt_format, lang_code),
|
||||
'format_amount': lambda amount, currency, lang_code=False: tools.format_amount(self.env, amount, currency, lang_code),
|
||||
'format_duration': lambda value: tools.format_duration(value),
|
||||
'user': self.env.user,
|
||||
'ctx': self._context,
|
||||
}
|
||||
return render_context
|
||||
|
||||
@api.model
|
||||
def _render_template_qweb(self, template_src, model, res_ids, add_context=None):
|
||||
view = self.env.ref(template_src, raise_if_not_found=False) or self.env['ir.ui.view']
|
||||
results = dict.fromkeys(res_ids, u"")
|
||||
if not view:
|
||||
return results
|
||||
|
||||
# prepare template variables
|
||||
variables = self._render_qweb_eval_context()
|
||||
if add_context:
|
||||
variables.update(**add_context)
|
||||
|
||||
for record in self.env[model].browse(res_ids):
|
||||
variables['object'] = record
|
||||
try:
|
||||
render_result = view._render(variables, engine='ir.qweb', minimal_qcontext=True)
|
||||
except Exception as e:
|
||||
_logger.info("Failed to render template : %s (%d)" % (template_src, view.id), exc_info=True)
|
||||
raise UserError(_("Failed to render template : %s (%d)") % (template_src, view.id))
|
||||
results[record.id] = render_result
|
||||
|
||||
return results
|
||||
|
||||
@api.model
|
||||
def _render_jinja_eval_context(self):
|
||||
""" Prepare jinja evaluation context, containing for all rendering
|
||||
@@ -207,7 +271,7 @@ class MailRenderMixin(models.AbstractModel):
|
||||
return render_context
|
||||
|
||||
@api.model
|
||||
def _render_template_jinja(self, template_txt, model, res_ids):
|
||||
def _render_template_jinja(self, template_txt, model, res_ids, add_context=None):
|
||||
""" Render a string-based template on records given by a model and a list
|
||||
of IDs, using jinja.
|
||||
|
||||
@@ -238,6 +302,9 @@ class MailRenderMixin(models.AbstractModel):
|
||||
|
||||
# prepare template variables
|
||||
variables = self._render_jinja_eval_context()
|
||||
if add_context:
|
||||
variables.update(**add_context)
|
||||
|
||||
# TDE CHECKME
|
||||
# records = self.env[model].browse(it for it in res_ids if it) # filter to avoid browsing [None]
|
||||
if any(r is None for r in res_ids):
|
||||
@@ -271,11 +338,12 @@ class MailRenderMixin(models.AbstractModel):
|
||||
return rendered
|
||||
|
||||
@api.model
|
||||
def _render_template(self, template_txt, model, res_ids, engine='jinja', post_process=False):
|
||||
def _render_template(self, template_src, model, res_ids, engine='jinja', add_context=None, post_process=False):
|
||||
""" Render the given string on records designed by model / res_ids using
|
||||
the given rendering engine. Currently only jinja is supported.
|
||||
|
||||
:param str template_txt: template text to render
|
||||
:param str template_src: template text to render (jinja) or xml id of view (qweb)
|
||||
this could be cleaned but hey, we are in a rush
|
||||
:param str model: model name of records on which we want to perform rendering
|
||||
:param list res_ids: list of ids of records (all belonging to same model)
|
||||
:param string engine: jinja
|
||||
@@ -286,10 +354,13 @@ class MailRenderMixin(models.AbstractModel):
|
||||
"""
|
||||
if not isinstance(res_ids, (list, tuple)):
|
||||
raise ValueError(_('Template rendering should be called only using on a list of IDs.'))
|
||||
if engine != 'jinja':
|
||||
raise ValueError(_('Template rendering supports only jinja.'))
|
||||
if engine not in ('jinja', 'qweb'):
|
||||
raise ValueError(_('Template rendering supports only jinja or qweb.'))
|
||||
|
||||
rendered = self._render_template_jinja(template_txt, model, res_ids)
|
||||
if engine == 'qweb':
|
||||
rendered = self._render_template_qweb(template_src, model, res_ids, add_context=add_context)
|
||||
else:
|
||||
rendered = self._render_template_jinja(template_src, model, res_ids, add_context=add_context)
|
||||
if post_process:
|
||||
rendered = self._render_template_postprocess(rendered)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class Digest(models.Model):
|
||||
('company_id', '=', company.id)
|
||||
]).mapped('amount_total'))
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_pos_total'] = 'point_of_sale.action_pos_sale_graph&menu_id=%s' % self.env.ref('point_of_sale.menu_point_root').id
|
||||
return res
|
||||
|
||||
@@ -8,17 +8,16 @@
|
||||
|
||||
<data>
|
||||
<record id="digest_tip_project_0" model="digest.tip">
|
||||
<field name="name">Project: try mailgateway</field>
|
||||
<field name="sequence">6</field>
|
||||
<field name="name">Project: Create tasks from incoming emails</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="group_id" ref="project.group_project_manager"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<strong style="font-size: 16px;">Try the mail gateway</strong>
|
||||
<div style="font-size: 14px;">
|
||||
New tasks can be generated from incoming emails. You just need to set email aliases on your projects.<br/>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:2px;">
|
||||
<a href="/web#action=project.open_view_project_all_config" style="background-color:#56b3b5;padding:2px;color:#FFFFFF;font-weight:bold;text-decoration:none;">Set Email Aliases</a>
|
||||
</div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Create tasks from incoming emails</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin: 0">You just need to set email aliases to your projects.</p>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:10px;">
|
||||
<a href="/web#action=project.open_view_project_all_config" target="_blank" style="display:inline-block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center; padding:0 10px;">Try it now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
|
||||
@@ -23,7 +23,7 @@ class Digest(models.Model):
|
||||
('company_id', '=', company.id)
|
||||
])
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_project_task_opened'] = 'project.open_view_project_all&menu_id=%s' % self.env.ref('project.menu_main_pm').id
|
||||
return res
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<record id="digest_tip_sale_expense_0" model="digest.tip">
|
||||
<field name="name">Expense: try mailgateway</field>
|
||||
<field name="name">Expense: Submit expenses by email</field>
|
||||
<field name="sequence">5</field>
|
||||
<field name="group_id" ref="sales_team.group_sale_salesman_all_leads"/>
|
||||
<field name="group_id" ref="base.group_user"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<strong style="font-size: 16px;">Submit expenses by email</strong>
|
||||
<div style="font-size: 14px;">Take a snapshot of your expenses and submit your expenses by email.<br/>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:10px;">
|
||||
<a href="/web#action=hr_expense.action_hr_expense_configuration" style="background-color:#56b3b5;padding:10px;text-decoration:none;color:#FFFFFF;border-radius:5px;font-size:12px;">Activate Expense Emails</a>
|
||||
</div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Submit expenses by email</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top: 0">Take a snapshot of your expenses and submit your expenses by email.</p>
|
||||
<div style="text-align:center;margin-top:5px;margin-bottom:10px;">
|
||||
<a href="/web#action=hr_expense.action_hr_expense_configuration" target="_blank"
|
||||
style="display:inline-block;background-color:#875a7b;line-height:40px;text-decoration: none;color: #ffffff;border-radius: 3px;font-size:13px;font-weight:bold;text-align:center;padding:0 10px;">
|
||||
Activate Expense Emails
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
|
||||
@@ -23,7 +23,7 @@ class Digest(models.Model):
|
||||
('company_id', '=', company.id)], ['price_total'], ['price_total'])
|
||||
record.kpi_all_sale_total_value = sum([channel_sale['price_total'] for channel_sale in all_channels_sales])
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_all_sale_total'] = 'sale.report_all_channels_sales_action&menu_id=%s' % self.env.ref('sale.sale_menu_root').id
|
||||
return res
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="digest_tip_website_0" model="digest.tip">
|
||||
<field name="name">Website: Use SEO to get more visitors</field>
|
||||
<field name="sequence">100</field>
|
||||
<field name="group_id" ref="website.group_website_designer"/>
|
||||
<field name="tip_description" type="html">
|
||||
<div>
|
||||
<b class="font-16" style="font-size: 20px;line-height: 30px">Use SEO to get more visitors on your website</b>
|
||||
<p class="font-12" style="color:#888888;font-size: 15px; line-height: 25px; margin-top: 0">Odoo helps you to optimize your website and get a better ranking in search engines like Google.</p>
|
||||
<img src="/digest/static/src/img/tips-website-seo.jpg" style="max-width: 580px" width="100%"/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
After Width: | Height: | Size: 105 KiB |
@@ -25,7 +25,7 @@ class Digest(models.Model):
|
||||
])
|
||||
record.kpi_website_sale_total_value = sum(confirmed_website_sales.mapped('amount_total'))
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self).compute_kpis_actions(company, user)
|
||||
def _compute_kpis_actions(self, company, user):
|
||||
res = super(Digest, self)._compute_kpis_actions(company, user)
|
||||
res['kpi_website_sale_total'] = 'website.backend_dashboard&menu_id=%s' % self.env.ref('website.menu_website_configuration').id
|
||||
return res
|
||||
|
||||