[ADD]: Digest KPI module

This commit is contained in:
Haresh Chavda 2018-08-20 14:59:49 +05:30
parent 35e76be277
commit 44e47f76a1
63 changed files with 1390 additions and 5 deletions

View File

@ -12,12 +12,13 @@ Core mechanisms for the accounting modules. To display the menuitems, install th
'category': 'Accounting',
'website': 'https://flectrahq.com/accounting',
'images' : ['images/accounts.jpeg','images/bank_statement.jpeg','images/cash_register.jpeg','images/chart_of_accounts.jpeg','images/customer_invoice.jpeg','images/journal_entries.jpeg'],
'depends' : ['base_setup', 'product', 'analytic', 'web_planner', 'portal'],
'depends' : ['base_setup', 'product', 'analytic', 'web_planner', 'portal', 'digest'],
'data': [
'security/account_security.xml',
'security/ir.model.access.csv',
'data/data_account_type.xml',
'data/account_data.xml',
'data/digest_data.xml',
'views/account_menuitem.xml',
'views/account_payment_view.xml',
'wizard/account_reconcile_view.xml',
@ -67,6 +68,7 @@ Core mechanisms for the accounting modules. To display the menuitems, install th
'views/account_dashboard_setup_bar.xml',
'wizard/account_report_tax_view.xml',
'views/report_tax.xml',
'views/digest_views.xml',
],
'demo': [
'demo/account_demo.xml',

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_account_total_revenue">True</field>
</record>
</flectra>

View File

@ -14,3 +14,4 @@ from . import company
from . import res_config_settings
from . import web_planner
from . import account_cash_rounding
from . import digest

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_account_total_revenue = fields.Boolean('Revenue')
kpi_account_total_revenue_value = fields.Monetary(compute='_compute_kpi_account_total_revenue_value')
def _compute_kpi_account_total_revenue_value(self):
if not self.env.user.has_group('account.group_account_invoice'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
account_moves = self.env['account.move'].read_group([
('journal_id.type', '=', 'sale'),
('company_id', '=', company.id),
('date', '>=', start),
('date', '<', end)], ['journal_id', 'amount'], ['journal_id'])
record.kpi_account_total_revenue_value = sum([account_move['amount'] for account_move in account_moves])
def compute_kpis_actions(self, company, user):
res = super(Digest, self).compute_kpis_actions(company, user)
res['kpi_account_total_revenue'] = 'account.action_invoice_tree1&menu_id=%s' % self.env.ref('account.menu_finance').id
return res

View File

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.account.account</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_general']" position="after">
<group name="kpi_account" string="Invoicing" groups="account.group_account_manager">
<field name="kpi_account_total_revenue"/>
</group>
</xpath>
</field>
</record>
</flectra>

View File

@ -20,7 +20,8 @@
'utm',
'web_planner',
'web_tour',
'contacts'
'contacts',
'digest',
],
'data': [
'security/crm_security.xml',
@ -29,6 +30,7 @@
'data/crm_data.xml',
'data/crm_stage_data.xml',
'data/crm_lead_data.xml',
'data/digest_data.xml',
'data/mail_template_data.xml',
'wizard/base_partner_merge_views.xml',
@ -48,6 +50,7 @@
'report/crm_activity_report_views.xml',
'report/crm_opportunity_report_views.xml',
'views/crm_team_views.xml',
'views/digest_views.xml',
],
'demo': [
'data/crm_demo.xml',

View File

@ -0,0 +1,29 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<data noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_crm_lead_created">True</field>
<field name="kpi_crm_opportunities_won">True</field>
</record>
</data>
<data>
<record id="digest_tip_crm_0" model="digest.tip">
<field name="sequence">2</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>
</div>
% endif
</div>
</field>
</record>
</data>
</flectra>

View File

@ -9,3 +9,4 @@ from . import crm_team
from . import res_config_settings
from . import res_partner
from . import web_planner
from . import digest

View File

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_crm_lead_created = fields.Boolean('New Leads/Opportunities')
kpi_crm_lead_created_value = fields.Integer(compute='_compute_kpi_crm_lead_created_value')
kpi_crm_opportunities_won = fields.Boolean('Opportunities Won')
kpi_crm_opportunities_won_value = fields.Integer(compute='_compute_kpi_crm_opportunities_won_value')
def _compute_kpi_crm_lead_created_value(self):
if not self.env.user.has_group('sales_team.group_sale_salesman'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
record.kpi_crm_lead_created_value = self.env['crm.lead'].search_count([
('create_date', '>=', start),
('create_date', '<', end),
('company_id', '=', company.id)
])
def _compute_kpi_crm_opportunities_won_value(self):
if not self.env.user.has_group('sales_team.group_sale_salesman'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
record.kpi_crm_opportunities_won_value = self.env['crm.lead'].search_count([
('type', '=', 'opportunity'),
('probability', '=', '100'),
('date_closed', '>=', start),
('date_closed', '<', end),
('company_id', '=', company.id)
])
def compute_kpis_actions(self, company, user):
res = super(Digest, self).compute_kpis_actions(company, user)
res['kpi_crm_lead_created'] = 'crm.crm_lead_opportunities_tree_view&menu_id=%s' % self.env.ref('crm.crm_menu_root').id
res['kpi_crm_opportunities_won'] = 'crm.crm_lead_opportunities_tree_view&menu_id=%s' % self.env.ref('crm.crm_menu_root').id
if user.has_group('crm.group_use_lead'):
res['kpi_crm_lead_created'] = 'crm.crm_lead_all_leads&menu_id=%s' % self.env.ref('crm.crm_menu_root').id
return res

View File

@ -0,0 +1,16 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.crm.lead</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_general']" position="after">
<group name="kpi_crm" string="CRM" groups="sales_team.group_sale_salesman_all_leads">
<field name="kpi_crm_lead_created"/>
<field name="kpi_crm_opportunities_won"/>
</group>
</xpath>
</field>
</record>
</flectra>

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import wizard

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
{
'name': 'KPI Digests',
'category': 'Marketing',
'description': """
Send KPI Digests periodically
=============================
""",
'version': '1.0',
'depends': [
'mail',
'portal'
],
'data': [
'security/ir.model.access.csv',
'data/digest_template_data.xml',
'data/digest_data.xml',
'data/ir_cron_data.xml',
'data/res_config_settings_data.xml',
'views/digest_views.xml',
'views/digest_templates.xml',
'views/res_config_settings_views.xml',
'wizard/digest_custom_fields_view.xml',
],
'installable': True,
}

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import portal

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra.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)
digest.action_unsubcribe()
return request.render('digest.portal_digest_unsubscribed', {
'digest': digest,
})

View File

@ -0,0 +1,45 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<data noupdate="1">
<record id="digest_digest_default" model="digest.digest">
<field name="name">Weekly Digest</field>
<field name="user_ids" eval="[(4, ref('base.user_root'))]"/>
<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>
<data>
<record id="digest_tip_mail_0" model="digest.tip">
<field name="sequence">1</field>
<field name="tip_description" type="html">
<div>
% set users = object.env['res.users'].search([], 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.mail_channel_action_client_chat&amp;active_id=${channel_id}" style="color: #006d6b;">company's discussion channel.</a>
</div>
</div>
</div>
</field>
</record>
<record id="digest_tip_mail_1" model="digest.tip">
<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>
</data>
</flectra>

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<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="auto_delete" eval="True" />
<field name="email_from">${user.email}</field>
<field name="lang">${user.lang}</field>
<field name="body_html"><![CDATA[
<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 company, user = ctx['company'], ctx['user']
% 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 40px; text-align: left;">
<strong style="margin-left: -22px; color: #000000; font-size: 22px; line-height: 32px;">${company.name} at a glance</strong>
<div style="color: #000000; font-size: 15px; margin-left:-22px;">${datetime.date.today().strftime('%B %d, %Y')}</div>
</td>
<td style="text-align: right; padding: 10px 40px">
<img style="padding: 0px; margin: 0px; height: auto; width: 80px;" src="/logo.png?company=${company.id}"/>
</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'] > 0.0:
<span style="color: #0bbc22;"></span>${"%.2f" % data['yesterday'][kpi]['margin']} %
% endif
% if data['yesterday'][kpi]['margin'] < 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'] > 0.0:
<span style="color: #0bbc22;"></span>${"%.2f" % data['lastweek'][kpi]['margin']} %
% endif
% if data['lastweek'][kpi]['margin'] < 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: 0; 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; padding: 20; 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'] > 0.0:
<span style="color: #0bbc22;"></span>${"%.2f" % data['lastmonth'][kpi]['margin']} %
% endif
% if data['lastmonth'][kpi]['margin'] < 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;"><br/>
<div style="color: #3d466e; line-height: 23px;">${ctx['tip_description']|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 bussiness from anywhere with Flectra Mobile.</div>
</td>
</tr>
<tr>
<td>
<div style="text-align: center;"><a href="https://play.google.com/store/apps/details?id=com.flectrahq.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/flectrahq/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 ctx['user'].has_group('base.group_system'):
<div style="margin-top: 20px;">
Want to customize the email?
<a href="/web#view_type=form&amp;model=digest.digest&amp;id=${object.id}" target="_blank" style="color: #875A7B;">Choose the metrics you care about</a>
</div>
<br />
% endif
</td>
</tr>
</table>
</td>
</tr>
</table>
]]></field>
</record>
</flectra>

View File

@ -0,0 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<record forcecreate="True" id="ir_cron_digest_scheduler_action" model="ir.cron">
<field name="name">Digest Emails</field>
<field name="model_id" ref="model_digest_digest"/>
<field name="state">code</field>
<field name="code">model._cron_send_digest_email()</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
</record>
</flectra>

View File

@ -0,0 +1,11 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<record id="default_emails_digest" model="ir.config_parameter">
<field name="key">digest.default_digest_emails</field>
<field name="value">True</field>
</record>
<record id="default_digest" model="ir.config_parameter">
<field name="key">digest.default_digest_id</field>
<field name="value" ref="digest.digest_digest_default"/>
</record>
</flectra>

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import digest
from . import digest_tip
from . import res_config_settings
from . import res_users

View File

@ -0,0 +1,209 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. 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 flectra import api, fields, models, tools
from flectra.addons.base.ir.ir_mail_server import MailDeliveryException
from flectra.exceptions import AccessError
from flectra.tools.float_utils import float_round
_logger = logging.getLogger(__name__)
class Digest(models.Model):
_name = 'digest.digest'
_description = 'Digest'
# 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([('daily', 'Daily'),
('weekly', 'Weekly'),
('monthly', 'Monthly'),
('quarterly', 'Quarterly')],
string='Periodicity', default='weekly', 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')
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id.id)
available_fields = fields.Char(compute='_compute_available_fields')
is_subscribed = fields.Boolean('Is user subscribed', compute='_compute_is_subscribed')
state = fields.Selection([('activated', 'Activated'), ('deactivated', 'Deactivated')], string='Status', readonly=True, default='activated')
# First base-related KPIs
kpi_res_users_connected = fields.Boolean('Connected Users')
kpi_res_users_connected_value = fields.Integer(compute='_compute_kpi_res_users_connected_value')
kpi_mail_message_total = fields.Boolean('Messages')
kpi_mail_message_total_value = fields.Integer(compute='_compute_kpi_mail_message_total_value')
def _compute_is_subscribed(self):
for digest in self:
digest.is_subscribed = self.env.user in digest.user_ids
def _compute_available_fields(self):
for digest in self:
kpis_values_fields = []
for field_name, field in digest._fields.items():
if field.type == 'boolean' and (field_name.startswith('kpi_') or field_name.startswith('x_kpi_')) and digest[field_name]:
kpis_values_fields += [field_name + '_value']
digest.available_fields = ', '.join(kpis_values_fields)
def _get_kpi_compute_parameters(self):
return fields.Date.to_string(self._context.get('start_date')), fields.Date.to_string(self._context.get('end_date')), self._context.get('company')
def _compute_kpi_res_users_connected_value(self):
for record in self:
start, end, company = record._get_kpi_compute_parameters()
user_connected = self.env['res.users'].search_count([('company_id', '=', company.id), ('login_date', '>=', start), ('login_date', '<', end)])
record.kpi_res_users_connected_value = user_connected
def _compute_kpi_mail_message_total_value(self):
for record in self:
start, end, company = record._get_kpi_compute_parameters()
total_messages = self.env['mail.message'].search_count([('create_date', '>=',start), ('create_date', '<', end)])
record.kpi_mail_message_total_value = total_messages
@api.onchange('periodicity')
def _onchange_periodicity(self):
self.next_run_date = self._get_next_run_date()
@api.model
def create(self, vals):
vals['next_run_date'] = date.today() + relativedelta(days=3)
return super(Digest, self).create(vals)
@api.multi
def action_subscribe(self):
if self.env.user not in self.user_ids:
self.sudo().user_ids |= self.env.user
@api.multi
def action_unsubcribe(self):
if self.env.user in self.user_ids:
self.sudo().user_ids -= self.env.user
@api.multi
def action_activate(self):
self.state = 'activated'
@api.multi
def action_deactivate(self):
self.state = 'deactivated'
def action_send(self):
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, company=user.company_id).send_mail(digest.id, force_send=True, raise_exception=True, email_values={'email_to': user.email, 'subject': subject})
digest.next_run_date = digest._get_next_run_date()
def compute_kpis(self, company, user):
self.ensure_one()
res = {}
for tf_name, tf in self._compute_timeframes(company).items():
digest = self.with_context(start_date=tf[0][0], end_date=tf[0][1], company=company).sudo(user.id)
previous_digest = self.with_context(start_date=tf[1][0], end_date=tf[1][1], company=company).sudo(user.id)
kpis = {}
for field_name, field in self._fields.items():
if field.type == 'boolean' and (field_name.startswith('kpi_') or field_name.startswith('x_kpi_')) and self[field_name]:
try:
compute_value = digest[field_name + '_value']
previous_value = previous_digest[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
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 = [4, user.id]
body = tools.html_sanitize(tip.tip_description)
tip_description = self.env['mail.template'].render_template(body, 'digest.tip', self.id)
return tip_description
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
concatenated with /web#action={action}
"""
return {}
def _get_next_run_date(self):
self.ensure_one()
if self.periodicity == 'daily':
delta = relativedelta(days=1)
elif self.periodicity == 'weekly':
delta = relativedelta(weeks=1)
elif self.periodicity == 'monthly':
delta = relativedelta(months=1)
elif self.periodicity == 'quarterly':
delta = relativedelta(months=3)
return date.today() + delta
def _compute_timeframes(self, company):
now = datetime.utcnow()
tz_name = company.resource_calendar_id.tz
if tz_name:
now = pytz.timezone(tz_name).localize(now)
start_date = now.date()
return {
'yesterday': (
(start_date + relativedelta(days=-1), start_date),
(start_date + relativedelta(days=-2), start_date + relativedelta(days=-1))),
'lastweek': (
(start_date + relativedelta(weeks=-1), start_date),
(start_date + relativedelta(weeks=-2), start_date + relativedelta(weeks=-1))),
'lastmonth': (
(start_date + relativedelta(months=-1), start_date),
(start_date + relativedelta(months=-2), start_date + relativedelta(months=-1))),
}
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):
margin = float_round((float(value-previous_value) / previous_value or 1) * 100, precision_digits=2)
return margin
def _format_currency_amount(self, amount, currency_id):
pre = post = u''
if currency_id.position == 'before':
pre = u'{symbol}\N{NO-BREAK SPACE}'.format(symbol=currency_id.symbol or '')
else:
post = u'\N{NO-BREAK SPACE}{symbol}'.format(symbol=currency_id.symbol or '')
return u'{pre}{0}{post}'.format(amount, pre=pre, post=post)
def _format_human_readable_amount(self, amount, suffix=''):
for unit in ['', 'K', 'M', 'G']:
if abs(amount) < 1000.0:
return "%3.1f%s%s" % (amount, unit, suffix)
amount /= 1000.0
return "%.1f%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.')

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models
from flectra.tools.translate import html_translate
class DigestTip(models.Model):
_name = 'digest.tip'
_description = 'Digest Tips'
_order = 'sequence'
sequence = fields.Integer(
'Sequence', default=1,
help='Used to display digest tip in email template base on order')
user_ids = fields.Many2many(
'res.users', string='Recipients',
help='Users having already received this tip')
tip_description = fields.Html('Tip description', translate=html_translate)
group_id = fields.Many2one(
'res.groups', string='Authorized Group',
default=lambda self: self.env.ref('base.group_user'))

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
digest_emails = fields.Boolean(string="Digest Emails", config_parameter='digest.default_digest_emails')
digest_id = fields.Many2one('digest.digest', string='Digest Email', config_parameter='digest.default_digest_id')

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, models
class ResUsers(models.Model):
_inherit = "res.users"
@api.model
def create(self, vals):
""" Automatically subscribe employee users to default digest if activated """
user = super(ResUsers, self).create(vals)
default_digest_emails = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_emails')
default_digest_id = self.env['ir.config_parameter'].sudo().get_param('digest.default_digest_id')
if user.has_group('base.group_user') and default_digest_emails and default_digest_id:
digest = self.env['digest.digest'].sudo().browse(int(default_digest_id))
digest.user_ids |= user
return user

View File

@ -0,0 +1,5 @@
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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_digest_digest_system digest.digest.administration model_digest_digest base.group_erp_manager 1 1 1 1
3 access_digest_digest_user digest.digest.user model_digest_digest base.group_user 1 0 0 0
4 access_digest_tip_system digest.tip.administration model_digest_tip base.group_erp_manager 1 1 1 1
5 access_digest_tip_user digest.tip.user model_digest_tip base.group_user 0 0 0 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,16 @@
<flectra>
<template id="portal_digest_unsubscribed" name="Unsubscription">
<t t-call="portal.portal_layout">
<div class="container mt8">
<div class="row">
<div class="col-lg-6 offset-lg-3">
<h3>Digest Subscriptions</h3>
<div class="alert alert-success text-center" role="status">
<p>You have been successfully unsubscribed from <string t-field="digest.name"/></p>
</div>
</div>
</div>
</div>
</t>
</template>
</flectra>

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="digest_digest_view_tree" model="ir.ui.view">
<field name="name">digest.digest.view.tree</field>
<field name="model">digest.digest</field>
<field name="arch" type="xml">
<tree string="KPI Digest">
<field name="name"/>
<field name="periodicity"/>
<field name="next_run_date" groups="base.group_no_one"/>
</tree>
</field>
</record>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form</field>
<field name="model">digest.digest</field>
<field name="arch" type="xml">
<form string="KPI Digest">
<field name="is_subscribed" invisible="1"/>
<header>
<button type="object" name="action_subscribe" string="Subscribe"
class="oe_highlight"
attrs="{'invisible': ['|',('is_subscribed', '=', True), ('state','=','deactivated')]}"/>
<button type="object" name="action_unsubcribe" string="Unsubscribe me"
class="oe_highlight"
attrs="{'invisible': ['|',('is_subscribed', '=', False), ('state','=','deactivated')]}"/>
<button type="object" name="action_deactivate" string="Deactivate for everyone"
class="oe_highlight"
attrs="{'invisible': [('state','=','deactivated')]}" groups="base.group_system"/>
<button type="object" name="action_activate" string="Activate"
class="oe_highlight"
attrs="{'invisible': [('state','=','activated')]}" groups="base.group_system"/>
<button type="object" name="action_send" string="Send Now"
class="oe_highlight"
attrs="{'invisible': [('state','=','deactivated')]}" groups="base.group_system"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<field name="periodicity" widget="radio" options="{'horizontal': true}"/>
<field name="user_ids" widget="many2many_tags" options="{'no_create': True}" groups="base.group_system"/>
<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>
<page name="kpis" string="KPIs">
<group name="kpis">
<group name="kpi_general" string="General" groups="base.group_system">
<field name="kpi_res_users_connected"/>
<field name="kpi_mail_message_total"/>
</group>
<group name="kpi_sales"/>
</group>
</page>
<page name="how_to" string="How to customize your digest?" groups="base.group_no_one">
<div>
<button type="action" name="%(digest.digest_custom_fields_action)d" string="Customized Digest"/>
</div>
<div class="alert alert-info" role="alert">
In order to build your customized digest, follow these steps:
<ol>
<li>
You may want to add new computed fields:
<ul>
<li>
you must create 2 fields on the
<code>digest</code>
object:
</li>
<li>
first create a boolean field called
<code>x_kpi_field_name</code>
and display it in the KPI's tab;
</li>
<li>
then create a computed field called
<code>x_kpi_field_name_value</code>
that will compute your customized KPI.
</li>
</ul>
</li>
<li>Select your KPIs in the KPI's tab.</li>
<li>
Create or edit the mail template: you may get computed KPI's value using these fields:
<code>
<field name="available_fields" class="oe_inline" />
</code>
</li>
</ol>
</div>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="digest_digest_view_search" model="ir.ui.view">
<field name="name">digest.digest.view.search</field>
<field name="model">digest.digest</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="user_ids"/>
<group expand="1" string="Group by">
<filter string="Periodicity" name="periodicity" context="{'group_by': 'periodicity'}"/>
</group>
</search>
</field>
</record>
<record id="digest_digest_action" model="ir.actions.act_window">
<field name="name">Digest Emails</field>
<field name="res_model">digest.digest</field>
<field name="view_type">form</field>
<field name="search_view_id" ref="digest_digest_view_search"/>
</record>
<menuitem id="digest_menu"
action="digest_digest_action"
parent="base.menu_email"
groups="base.group_erp_manager"
sequence="93"/>
</flectra>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.digest</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='access_rights']" position="before">
<div class="col-12 col-lg-6 o_setting_box" title="New users are automatically added as recipient of the following digest email.">
<div class="o_setting_left_pane">
<field name="digest_emails"/>
</div>
<div class="o_setting_right_pane">
<label string="Digest Email" for="digest_emails"/>
<div class="text-muted" id="msg_module_digest">
Add new users as recipient of a periodic email with key metrics
</div>
<div class="content-group" attrs="{'invisible': [('digest_emails','=',False)]}">
<div class="mt16">
<label for="digest_id" class="o_light_label"/>
<field name="digest_id" class="oe_inline"/>
</div>
<div>
<button type="action" name="%(digest.digest_digest_action)d" string="Configure Digest Emails" icon="fa-arrow-right" class="btn-link"/>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</flectra>

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Flectra. See LICENSE file for full copyright and licensing details.
from . import digest_custom_fields

View File

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
# Part of Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models, _
from flectra.exceptions import ValidationError, UserError
from lxml import etree
from flectra.tools.safe_eval import test_python_expr
class DigestCustomFields(models.TransientModel):
_name = 'digest.custom.fields'
DEFAULT_PYTHON_CODE = """# Available variables:
# - env: Flectra Environment on which the action is triggered
# - model: Flectra Model of the record on which the action is triggered; is a void recordset
# - record: record on which the action is triggered; may be be void
# - records: recordset of all records on which the action is triggered in multi-mode; may be void
# - time, datetime, dateutil, timezone: useful Python libraries
# - log: log(message, level='info'): logging function to record debug information in ir.logging table
# - Warning: Warning Exception to use with raise
# To return an action, assign: action = {...}\n\n\n\n"""
# field = fields.Many2one('ir.model.fields', domain="[('model_id', '=', 'digest.digest'), ('name', 'ilike', 'x_kpi'), ('depends', '=', False)]")
# compute_field = fields.Many2one('ir.model.fields', domain="[('model_id', '=', 'digest.digest'), ('name', 'ilike', 'x_kpi'), ('depends', '!=', False)]")
field_name = fields.Char('Field Name', default='x_kpi_', required=True)
label_name = fields.Char('Label Name', required=True)
group_name = fields.Char('Group Name', required=True)
ttype = fields.Selection([('integer', 'Integer'), ('monetary', 'Monetary')], string='Field Type', required=True)
# compute = fields.Text(help="Code to compute the value of the field.\n"
# "Iterate on the recordset 'self' and assign the field's value:\n\n"
# " for record in self:\n"
# " record['size'] = len(record.name)\n\n"
# "Modules time, datetime, dateutil are available.")
compute = fields.Text(string='Python Code', groups='base.group_system',
default=DEFAULT_PYTHON_CODE,
help="Write Python code that the action will execute. Some variables are "
"available for use; help about pyhon expression is given in the help tab.")
compute_field_name = fields.Char(compute='_compute_get_field_name', string='Compute Field Name')
@api.constrains('compute')
def _check_python_code(self):
for record in self.sudo().filtered('compute'):
msg = test_python_expr(expr=record.compute.strip(), mode="exec")
if msg:
raise ValidationError(msg)
@api.depends('field_name')
def _compute_get_field_name(self):
for record in self:
if record.field_name:
record.compute_field_name = record.field_name + '_value'
@api.constrains('field_name')
def _check_name(self):
for field in self:
if not field.field_name.startswith('x_kpi_'):
raise ValidationError(_("Custom fields must have a name that starts with 'x_kpi_' !"))
try:
models.check_pg_name(field.field_name)
except ValidationError:
msg = _("Field names can only contain characters, digits and underscores (up to 63).")
raise ValidationError(msg)
def add_new_fields(self):
model_id = self.env['ir.model'].search([('model', '=', 'digest.digest')])
ir_model_fields_obj = self.env['ir.model.fields']
first_field_name = self.field_name
values = {
'model_id': model_id.id,
'ttype': 'boolean',
'name': first_field_name,
'field_description': self.label_name,
'model': 'digest.digest'
}
ir_model_fields_obj.create(values)
values = {
'model_id': model_id.id,
'ttype': self.ttype,
'name': self.field_name + '_value',
'field_description': self.label_name + ' Value',
'model': 'digest.digest',
'depends': first_field_name,
'compute': self.compute
}
print("====values=======", values)
ir_model_fields_obj.create(values)
def field_arch(self):
xpath = etree.Element('xpath')
xpath_type = "group"
name = "kpi_general"
position = "after"
xpath_field = self.field_name
expr = '//' + xpath_type + '[@name="' + name + '"][not(ancestor::field)]'
xpath.set('expr', expr)
xpath.set('position', position)
if position == 'after' or position == 'before' or position == 'inside':
expr = '//' + xpath_type + '[@name="' + name + '"][not(ancestor::field)]'
group = etree.Element('group')
group.set('string', self.group_name)
field = etree.Element('field')
field.set('name', xpath_field)
xpath.set('expr', expr)
group.append(field)
xpath.append(group)
return etree.tostring(xpath).decode("utf-8")
@api.multi
def action_customize_digest(self):
self.add_new_fields()
arch = '<data>' + str(self.field_arch()) + '</data>'
print("====arch=======", arch)
vals = {
'type': 'form',
'model': 'digest.digest',
'inherit_id': self.env.ref('digest.digest_digest_view_form').id,
'mode': 'extension',
'arch_base': arch,
'name': 'x_kpi_' + self.field_name + "_Customization",
}
ir_model = self.env['ir.model'].search([('model', '=', 'digest.digest')])
if hasattr(ir_model, 'module_id'):
vals.update({'module_id': ir_model.module_id.id})
self.env['ir.ui.view'].sudo().create(vals)
return {
'type': 'ir.actions.client',
'tag': 'reload',
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<flectra>
<record id="digest_custom_fields_view_form" model="ir.ui.view">
<field name="name">digest.custom.fields.form</field>
<field name="model">digest.custom.fields</field>
<field name="arch" type="xml">
<form string="Customized Digest">
<group class="oe_title" col="4">
<field name="field_name"/>
<field name="label_name"/>
<field name="group_name"/>
</group>
<group string="Compute Details">
<field name="compute_field_name"/>
<field name="ttype"/>
<field name="compute" widget="ace" options="{'mode': 'python'}"/>
</group>
<footer>
<button name="action_customize_digest" string="Save" type="object" class="btn btn-sm btn-primary"/>
<button string="Cancel" class="btn btn-sm btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="digest_custom_fields_action" model="ir.actions.act_window">
<field name="name">Customized Digest</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">digest.custom.fields</field>
<field name="view_mode">form</field>
<field name="view_id" ref="digest_custom_fields_view_form"/>
<field name="target">new</field>
</record>
</flectra>

View File

@ -17,16 +17,19 @@
'utm',
'document',
'web_tour',
'digest',
],
'data': [
'security/hr_recruitment_security.xml',
'security/ir.model.access.csv',
'data/hr_recruitment_data.xml',
'data/digest_data.xml',
'views/hr_recruitment_views.xml',
'views/res_config_settings_views.xml',
'views/hr_recruitment_templates.xml',
'views/hr_department_views.xml',
'views/hr_job_views.xml',
'views/digest_views.xml',
],
'demo': [
'data/hr_recruitment_demo.xml',

View File

@ -0,0 +1,25 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<data noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_hr_recruitment_new_colleagues">True</field>
</record>
</data>
<data>
<record id="digest_tip_hr_recruitment_0" model="digest.tip">
<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>
</div>
</div>
</field>
</record>
</data>
</flectra>

View File

@ -4,3 +4,4 @@ from . import hr_employee
from . import hr_job
from . import res_config_settings
from . import calendar
from . import digest

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_hr_recruitment_new_colleagues = fields.Boolean('Employees')
kpi_hr_recruitment_new_colleagues_value = fields.Integer(compute='_compute_kpi_hr_recruitment_new_colleagues_value')
def _compute_kpi_hr_recruitment_new_colleagues_value(self):
if not self.env.user.has_group('hr_recruitment.group_hr_recruitment_user'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
new_colleagues = self.env['hr.employee'].search_count([
('create_date', '>=', start),
('create_date', '<', end),
('company_id', '=', company.id)
])
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)
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

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.hr.recruitment</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form" />
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_general']" position="after">
<group name="kpi_hr" string="Recruitment" groups="hr_recruitment.group_hr_recruitment_user">
<field name="kpi_hr_recruitment_new_colleagues"/>
</group>
</xpath>
</field>
</record>
</flectra>

View File

@ -9,11 +9,12 @@
'sequence': 20,
'summary': 'Touchscreen Interface for Shops',
'description': "",
'depends': ['stock_account', 'barcodes', 'web_editor'],
'depends': ['stock_account', 'barcodes', 'web_editor', 'digest'],
'data': [
'security/point_of_sale_security.xml',
'security/ir.model.access.csv',
'data/default_barcode_patterns.xml',
'data/digest_data.xml',
'wizard/pos_box.xml',
'wizard/pos_details.xml',
'wizard/pos_discount.xml',
@ -36,6 +37,7 @@
'views/account_statement_view.xml',
'views/account_statement_report.xml',
'views/res_users_view.xml',
'views/digest_views.xml',
'views/res_partner_view.xml',
'views/report_statement.xml',
'views/report_userlabel.xml',

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_pos_total">True</field>
</record>
</flectra>

View File

@ -4,6 +4,7 @@
from . import account_bank_statement
from . import account_journal
from . import barcode_rule
from . import digest
from . import pos_category
from . import pos_config
from . import pos_order

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_pos_total = fields.Boolean('POS Sales')
kpi_pos_total_value = fields.Monetary(compute='_compute_kpi_pos_total_value')
def _compute_kpi_pos_total_value(self):
if not self.env.user.has_group('point_of_sale.group_pos_user'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
record.kpi_pos_total_value = sum(self.env['pos.order'].search([
('date_order', '>=', start),
('date_order', '<', end),
('state', 'not in', ['draft', 'cancel', 'invoiced']),
('company_id', '=', company.id)
]).mapped('amount_total'))
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

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.sale.order</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form" />
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_general']" position="after">
<group name="kpi_pos" string="Point of Sale">
<field name="kpi_pos_total"/>
</group>
</xpath>
</field>
</record>
</flectra>

View File

@ -19,6 +19,7 @@
'web',
'web_planner',
'web_tour',
'digest'
],
'description': "",
'data': [
@ -26,12 +27,14 @@
'security/ir.model.access.csv',
'data/project_data.xml',
'report/project_report_views.xml',
'views/digest_views.xml',
'views/project_views.xml',
'views/res_partner_views.xml',
'views/res_config_settings_views.xml',
'views/project_templates.xml',
'views/project_portal_templates.xml',
'data/web_planner_data.xml',
'data/digest_data.xml',
'data/project_mail_template_data.xml',
'wizard/project_task_merge_wizard_views.xml',
],

View File

@ -0,0 +1,26 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<data noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_project_task_opened">True</field>
</record>
</data>
<data>
<record id="digest_tip_project_0" model="digest.tip">
<field name="sequence">6</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>
</div>
</div>
</field>
</record>
</data>
</flectra>

View File

@ -6,3 +6,4 @@ from . import res_config_settings
from . import res_company
from . import res_partner
from . import web_planner
from . import digest

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_project_task_opened = fields.Boolean('Open Tasks')
kpi_project_task_opened_value = fields.Integer(compute='_compute_project_task_opened_value')
def _compute_project_task_opened_value(self):
if not self.env.user.has_group('project.group_project_user'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
record.kpi_project_task_opened_value = self.env['project.task'].search_count([
('stage_id.fold', '=', False),
('create_date', '>=', start),
('create_date', '<', end),
('company_id', '=', company.id)
])
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

View File

@ -0,0 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.project.task</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_general']" position="after">
<group name="kpi_project" string="Project" groups="project.group_project_user">
<field name="kpi_project_task_opened"/>
</group>
</xpath>
</field>
</record>
</flectra>

View File

@ -89,6 +89,10 @@ class ResourceCalendar(models.Model):
'resource.calendar.leaves', 'calendar_id', 'Global Leaves',
domain=[('resource_id', '=', False)]
)
tz = fields.Selection(
_tz_get, string='Timezone', required=True,
default=lambda self: self._context.get('tz') or self.env.user.tz or 'UTC',
help="This field is used in order to define in which timezone the resources will work.")
# --------------------------------------------------
# Utility methods

View File

@ -17,6 +17,7 @@ This module allow to reinvoice employee expense, by setting the SO directly on t
'website': 'https://flectrahq.com/page/warehouse',
'depends': ['sale_management', 'hr_expense'],
'data': [
'data/digest_data.xml',
'security/ir.model.access.csv',
'security/sale_expense_security.xml',
'views/product_view.xml',

View File

@ -0,0 +1,17 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra>
<record id="digest_tip_sale_expense_0" model="digest.tip">
<field name="sequence">5</field>
<field name="group_id" ref="sales_team.group_sale_salesman_all_leads"/>
<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:2px;">
<a href="/web#action=hr_expense.action_hr_expense_configuration" style="background-color:#56b3b5;padding:2px;color:#FFFFFF;font-weight:bold;text-decoration:none;">Activate Expense Emails</a>
</div>
</div>
</div>
</field>
</record>
</flectra>

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import models
from flectra.api import Environment, SUPERUSER_ID

View File

@ -42,10 +42,12 @@ The Dashboard for the Sales Manager will include
* Monthly Turnover (Graph)
""",
'website': 'https://flectrahq.com/page/sales',
'depends': ['sale', 'account_invoicing'],
'depends': ['sale', 'account_invoicing', 'digest'],
'data': [
'data/digest_data.xml',
'views/sale_management_views.xml',
'views/sale_management_templates.xml',
'views/digest_views.xml',
],
'application': True,
'uninstall_hook': 'uninstall_hook',

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_all_sale_total">True</field>
</record>
</flectra>

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import digest

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_all_sale_total = fields.Boolean('All Sales')
kpi_all_sale_total_value = fields.Monetary(compute='_compute_kpi_sale_total_value')
def _compute_kpi_sale_total_value(self):
if not self.env.user.has_group('sales_team.group_sale_salesman_all_leads'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
all_channels_sales = self.env['sale.report'].read_group([
('confirmation_date', '>=', start),
('confirmation_date', '<', end),
('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)
res['kpi_all_sale_total'] = 'sale.report_all_channels_sales_action&menu_id=%s' % self.env.ref('sale.sale_menu_root').id
return res

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.sale.order</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form" />
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_sales']" position="attributes">
<attribute name="string">Sales</attribute>
<attribute name="groups">sales_team.group_sale_salesman_all_leads</attribute>
</xpath>
<xpath expr="//group[@name='kpi_sales']" position="inside">
<field name="kpi_all_sale_total"/>
</xpath>
</field>
</record>
</flectra>

View File

@ -7,7 +7,7 @@
'website': 'https://flectrahq.com/page/e-commerce',
'version': '1.1',
'description': "",
'depends': ['website', 'sale_payment', 'website_payment', 'website_mail', 'website_form', 'website_rating'],
'depends': ['website', 'sale_payment', 'website_payment', 'website_mail', 'website_form', 'website_rating', 'digest'],
'data': [
'security/ir.model.access.csv',
'security/website_sale.xml',
@ -15,6 +15,7 @@
'data/web_planner_data.xml',
'data/mail_template_data.xml',
'data/ir_cron_view.xml',
'data/digest_data.xml',
'views/product_views.xml',
'views/account_views.xml',
'views/sale_report_views.xml',
@ -25,6 +26,7 @@
'views/snippets.xml',
'views/report_shop_saleorder.xml',
'views/res_config_settings_views.xml',
'views/digest_views.xml',
],
'demo': [
'data/demo.xml',

View File

@ -0,0 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<flectra noupdate="1">
<record id="digest.digest_digest_default" model="digest.digest">
<field name="kpi_website_sale_total">True</field>
</record>
</flectra>

View File

@ -14,3 +14,4 @@ from . import sale_report
from . import ir_model_fields
from . import website
from . import res_config_settings
from . import digest

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import fields, models, _
from flectra.exceptions import AccessError
class Digest(models.Model):
_inherit = 'digest.digest'
kpi_website_sale_total = fields.Boolean('eCommerce Sales')
kpi_website_sale_total_value = fields.Monetary(compute='_compute_kpi_website_sale_total_value')
def _compute_kpi_website_sale_total_value(self):
if not self.env.user.has_group('sales_team.group_sale_salesman_all_leads'):
raise AccessError(_("Do not have access, skip this data for user's digest email"))
for record in self:
start, end, company = record._get_kpi_compute_parameters()
confirmed_website_sales = self.env['sale.order'].search([
('confirmation_date', '>=', start),
('confirmation_date', '<', end),
('state', 'not in', ['draft', 'cancel', 'sent']),
('team_id.team_type', '=', 'website'),
('company_id', '=', company.id)
])
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)
res['kpi_website_sale_total'] = 'website.backend_dashboard&menu_id=%s' % self.env.ref('website.menu_website_configuration').id
return res

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<record id="digest_digest_view_form" model="ir.ui.view">
<field name="name">digest.digest.view.form.inherit.website.sale.order</field>
<field name="model">digest.digest</field>
<field name="inherit_id" ref="digest.digest_digest_view_form" />
<field name="arch" type="xml">
<xpath expr="//group[@name='kpi_sales']" position="attributes">
<attribute name="string">Sales</attribute>
<attribute name="groups">sales_team.group_sale_salesman_all_leads</attribute>
</xpath>
<xpath expr="//group[@name='kpi_sales']" position="inside">
<field name="kpi_website_sale_total"/>
</xpath>
</field>
</record>
</flectra>