diff --git a/addons/account/models/digest.py b/addons/account/models/digest.py index da7d673053b37a0cc2f41638a67eefd549f85d20..d3a798ebb2512147eb3640026adca639bcfe3a54 100644 --- a/addons/account/models/digest.py +++ b/addons/account/models/digest.py @@ -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 diff --git a/addons/crm/models/digest.py b/addons/crm/models/digest.py index 5ec2bd07add18e79f8d68f034cebf7574aea99fa..acb529f1a32d76637583bfa8552303ce64b44dbe 100644 --- a/addons/crm/models/digest.py +++ b/addons/crm/models/digest.py @@ -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'): diff --git a/addons/digest/__manifest__.py b/addons/digest/__manifest__.py index b27fbd29ed74a924ed6a78143d893c0358171ea3..6f4cc562e161fa12510bf98737e16d67cbba9dd2 100644 --- a/addons/digest/__manifest__.py +++ b/addons/digest/__manifest__.py @@ -16,7 +16,6 @@ Send KPI Digests periodically 'data': [ 'security/ir.model.access.csv', 'data/digest_data.xml', - 'data/digest_template_data.xml', 'data/digest_tips_data.xml', 'data/ir_cron_data.xml', 'data/res_config_settings_data.xml', diff --git a/addons/digest/data/digest_data.xml b/addons/digest/data/digest_data.xml index 80678308c7950aecee85598a475c9894fc9f2609..d4f2928841840f15259e4020e017b9d230faa4f5 100644 --- a/addons/digest/data/digest_data.xml +++ b/addons/digest/data/digest_data.xml @@ -49,20 +49,197 @@ </style> </head> <body> - <t t-raw="message.body"/> + <t t-raw="body"/> </body> </html> </template> - <!-- DIGEST PART: PREFERENCES --> - <template id="digest_section_preferences"> -<p t-if="object._context.get('digest_slowdown')" class="font-12" style="font-size: 15px; color: #6b6d70"> - We've noticed you did not connect these last few days so we've automatically switched your preference to weekly Digests. -</p> -<p t-elif="user.has_group('base.group_erp_manager') and object.periodicity == 'daily'" class="font-12" style="font-size: 15px; color: #6b6d70"> - Prefer a broader overview ? - <a t-att-href="'/digest/%s/set_periodicity?periodicity=weekly' % object.id" target="_blank" style="color:#875A7B; font-weight: bold;">Switch to weekly Digests.</a> -</p> -<p t-else=""/> + <!-- 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> + </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> + </data></odoo> diff --git a/addons/digest/data/digest_template_data.xml b/addons/digest/data/digest_template_data.xml deleted file mode 100644 index 448fa8b20339e721f67153cad3a2b50627f06da3..0000000000000000000000000000000000000000 --- a/addons/digest/data/digest_template_data.xml +++ /dev/null @@ -1,209 +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"> -<div style="margin:0; 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, tips_count=ctx.get('tips_count', 1)) - % set kpi_actions = object.compute_kpis_actions(company, user) - % set preferences = object.compute_preferences(company, user) - % set kpis = data.yesterday.keys() - <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;">${company.name}</span> - <br/> - <span class="font-18" style="color:#282f33;font-weight:bold;font-size: 25px; line-height: 35px; margin-right: 5px;">${object.name}</span> - <span class="d-block font-12" style="color:#8F8F8F;font-size: 15px;">${datetime.date.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> - % if tips: - <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="#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> - % for tip in tips: - <table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;"> - <tr> - <td valign="top"> - ${tip | safe} - </td> - </tr> - </table> - % endfor - </td> - </tr> - </table> - </td> - </tr> - </table> - % endif - <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"> - % for kpi in kpis: - <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 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">${object.fields_get()[kpi]['string']}</span> - %if kpi in kpi_actions: - <a href="/web#action=${kpi_actions[kpi]}" 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> - %endif - </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;"> - <span class="font-25" style="color:#282f33;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: - % if data['yesterday'][kpi]['margin'] is greaterthan(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;"> - â–² ${"%.2f" % data['yesterday'][kpi]['margin']} - </div> - %endif - % if data['yesterday'][kpi]['margin'] is lessthan(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;"> - â–¼ ${"%.2f" % data['yesterday'][kpi]['margin']} - </div> - %endif - %endif - </td> - <td class="mobile-w-33" width="196" style=" text-align: center;"> - <span class="font-25" style="color:#282f33;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: - % if data['lastweek'][kpi]['margin'] is greaterthan(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;"> - â–² ${"%.2f" % data['lastweek'][kpi]['margin']} - </div> - %endif - % if data['lastweek'][kpi]['margin'] is lessthan(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;"> - â–¼ ${"%.2f" % data['lastweek'][kpi]['margin']} - </div> - %endif - %endif - </td> - <td class="mobile-w-33" width="196" style=" text-align: center;"> - <span class="font-25" style="color:#282f33;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: - % if data['lastmonth'][kpi]['margin'] is greaterthan(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;"> - â–² ${"%.2f" % data['lastmonth'][kpi]['margin']} - </div> - %endif - % if data['lastmonth'][kpi]['margin'] is lessthan(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;"> - â–¼ ${"%.2f" % data['lastmonth'][kpi]['margin']} - </div> - %endif - %endif - </td> - </tr> - </table> - </td> - </tr> - </table> - </td> - </tr> - </table> - % endfor - % if preferences: - <table 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%" height="50" align="center"> - ${preferences | safe} - </td> - </tr> - </table> - % endif - % if user.has_group('base.group_system'): - <table 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%" height="50" align="center"> - <p class="font-12" style="font-size: 15px; color: #6b6d70">Want to customize this email? - <a href="/web#view_type=form&model=digest.digest&id=${object.id}" target="_blank" style="color:#875A7B; font-weight: bold;">Choose the metrics you care about</a></p> - </td> - </tr> - </table> - % endif - </td> - </tr> - <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> - <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> - </td> - </tr> - </table> - </td> - </tr> - <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;">${company.name}</td> - </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> - </field> - <field name="lang">${user.lang}</field> - <field name="auto_delete" eval="True" /> - </record> -</odoo> diff --git a/addons/digest/data/digest_tips_data.xml b/addons/digest/data/digest_tips_data.xml index ae7ed018ed8418ec6184cec539ea0012ba520f49..25242ef2bfc47d1fc6246fc0466fed73e5a63ae5 100644 --- a/addons/digest/data/digest_tips_data.xml +++ b/addons/digest/data/digest_tips_data.xml @@ -2,7 +2,7 @@ <odoo> <data noupdate="1"> <record id="digest_digest_default" model="digest.digest"> - <field name="name">Awesome Stats with Odoo</field> + <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')"/> diff --git a/addons/digest/models/digest.py b/addons/digest/models/digest.py index 3c39f49e244aaa87205aa9d408b83af3d8d190ce..c6e51a4fb9a3d120a46408418fd5143736737491 100644 --- a/addons/digest/models/digest.py +++ b/addons/digest/models/digest.py @@ -7,7 +7,7 @@ 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 @@ -28,10 +28,6 @@ class Digest(models.Model): ('quarterly', 'Quarterly')], 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') @@ -107,22 +103,51 @@ class Digest(models.Model): to_slowdown = self._check_daily_logs() for digest in self: for user in digest.user_ids: - digest.with_context(digest_slowdown=digest in to_slowdown)._action_send_to_user(user, tips_count=1) + 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): - subject = '%s: %s' % (user.company_id.name, self.name) - self.template_id.with_context(user=user, tips_count=tips_count).send_mail( - self.id, - force_send=True, - notif_layout="digest.digest_mail_layout", - raise_exception=True, - email_values={ - 'email_to': user.email, 'subject': subject - } + 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): @@ -138,33 +163,64 @@ class Digest(models.Model): # ------------------------------------------------------------ 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]: - - 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}}) - - res.update({tf_name: kpis}) - return res + 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, + }) + + # filter failed KPIs + return [kpi for kpi in kpis if kpi['kpi_name'] not in invalid_fields] def compute_tips(self, company, user, tips_count=1): tips = self.env['digest.tip'].search([ @@ -175,10 +231,10 @@ class Digest(models.Model): 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 ] - tip.user_ids += user + tips.user_ids += user return tip_descriptions - 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 @@ -191,16 +247,23 @@ class Digest(models.Model): :return string: html to put in template """ - preferences = self.env['mail.render.mixin']._render_template( - 'digest.digest_section_preferences', - 'digest.digest', - self.ids, - engine='qweb', - add_context={ - 'company': company, - 'user': user, - }, - post_process=True)[self.id] + 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): @@ -221,22 +284,28 @@ 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 if (value != previous_value) and (value != 0.0 and previous_value != 0.0): diff --git a/addons/digest/views/digest_views.xml b/addons/digest/views/digest_views.xml index a84df14a2d333dae3115972be48f4d240006a718..9df32772bee24f4d74c84920200c28e6bea3e8e8 100644 --- a/addons/digest/views/digest_views.xml +++ b/addons/digest/views/digest_views.xml @@ -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> diff --git a/addons/hr_recruitment/models/digest.py b/addons/hr_recruitment/models/digest.py index c24cc80e5d59f9b2d7f749d4d5b94bb2612f3894..c8d17019beac7488a19a1dc7a0ed7df8ec059f3a 100644 --- a/addons/hr_recruitment/models/digest.py +++ b/addons/hr_recruitment/models/digest.py @@ -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 diff --git a/addons/im_livechat/models/digest.py b/addons/im_livechat/models/digest.py index 6c0fefd7ae448d5d8c87e08e302a9e56111db083..5b23ab65b9788bdb18e631ddbd1d0c9843a570cb 100644 --- a/addons/im_livechat/models/digest.py +++ b/addons/im_livechat/models/digest.py @@ -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' diff --git a/addons/point_of_sale/models/digest.py b/addons/point_of_sale/models/digest.py index ea115f9b1949c10d933584e96d38d48386ebc652..21a65cc4fea9cd33b4d1d3ea03f012d073142d43 100644 --- a/addons/point_of_sale/models/digest.py +++ b/addons/point_of_sale/models/digest.py @@ -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 diff --git a/addons/project/models/digest.py b/addons/project/models/digest.py index 9034e647125316dc287c3f243fc5ad8854cb71dc..b584ca956d6b6b319cc13cf16dcbb0263603f777 100644 --- a/addons/project/models/digest.py +++ b/addons/project/models/digest.py @@ -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 diff --git a/addons/sale_management/models/digest.py b/addons/sale_management/models/digest.py index c2bea94c98077cb9419a2bad169f53babdbad212..8828ad98a773e529127a4a7543201fe14b7ce4d8 100644 --- a/addons/sale_management/models/digest.py +++ b/addons/sale_management/models/digest.py @@ -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 diff --git a/addons/website_sale/models/digest.py b/addons/website_sale/models/digest.py index 276a5b5461876c25c195859df8f559efa99be87a..4d03289c978ba249af790d1e4f3bd906812accea 100644 --- a/addons/website_sale/models/digest.py +++ b/addons/website_sale/models/digest.py @@ -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