[ADD]: Digest KPI module
This commit is contained in:
parent
35e76be277
commit
44e47f76a1
@ -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',
|
||||
|
6
addons/account/data/digest_data.xml
Normal file
6
addons/account/data/digest_data.xml
Normal 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>
|
@ -14,3 +14,4 @@ from . import company
|
||||
from . import res_config_settings
|
||||
from . import web_planner
|
||||
from . import account_cash_rounding
|
||||
from . import digest
|
||||
|
29
addons/account/models/digest.py
Normal file
29
addons/account/models/digest.py
Normal 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
|
15
addons/account/views/digest_views.xml
Normal file
15
addons/account/views/digest_views.xml
Normal 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>
|
@ -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',
|
||||
|
29
addons/crm/data/digest_data.xml
Normal file
29
addons/crm/data/digest_data.xml
Normal 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>
|
@ -9,3 +9,4 @@ from . import crm_team
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import web_planner
|
||||
from . import digest
|
||||
|
46
addons/crm/models/digest.py
Normal file
46
addons/crm/models/digest.py
Normal 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
|
16
addons/crm/views/digest_views.xml
Normal file
16
addons/crm/views/digest_views.xml
Normal 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>
|
6
addons/digest/__init__.py
Normal file
6
addons/digest/__init__.py
Normal 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
|
27
addons/digest/__manifest__.py
Normal file
27
addons/digest/__manifest__.py
Normal 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,
|
||||
}
|
4
addons/digest/controllers/__init__.py
Normal file
4
addons/digest/controllers/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import portal
|
15
addons/digest/controllers/portal.py
Normal file
15
addons/digest/controllers/portal.py
Normal 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,
|
||||
})
|
45
addons/digest/data/digest_data.xml
Normal file
45
addons/digest/data/digest_data.xml
Normal 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&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>
|
154
addons/digest/data/digest_template_data.xml
Normal file
154
addons/digest/data/digest_template_data.xml
Normal 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&model=digest.digest&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>
|
13
addons/digest/data/ir_cron_data.xml
Normal file
13
addons/digest/data/ir_cron_data.xml
Normal 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>
|
11
addons/digest/data/res_config_settings_data.xml
Normal file
11
addons/digest/data/res_config_settings_data.xml
Normal 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>
|
7
addons/digest/models/__init__.py
Normal file
7
addons/digest/models/__init__.py
Normal 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
|
209
addons/digest/models/digest.py
Normal file
209
addons/digest/models/digest.py
Normal 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.')
|
21
addons/digest/models/digest_tip.py
Normal file
21
addons/digest/models/digest_tip.py
Normal 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'))
|
10
addons/digest/models/res_config_settings.py
Normal file
10
addons/digest/models/res_config_settings.py
Normal 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')
|
18
addons/digest/models/res_users.py
Normal file
18
addons/digest/models/res_users.py
Normal 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
|
5
addons/digest/security/ir.model.access.csv
Normal file
5
addons/digest/security/ir.model.access.csv
Normal 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
|
|
BIN
addons/digest/static/src/img/activity.png
Normal file
BIN
addons/digest/static/src/img/activity.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
addons/digest/static/src/img/app_store.png
Normal file
BIN
addons/digest/static/src/img/app_store.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
addons/digest/static/src/img/google_play.png
Normal file
BIN
addons/digest/static/src/img/google_play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
addons/digest/static/src/img/notification.png
Normal file
BIN
addons/digest/static/src/img/notification.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
16
addons/digest/views/digest_templates.xml
Normal file
16
addons/digest/views/digest_templates.xml
Normal 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>
|
132
addons/digest/views/digest_views.xml
Normal file
132
addons/digest/views/digest_views.xml
Normal 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>
|
32
addons/digest/views/res_config_settings_views.xml
Normal file
32
addons/digest/views/res_config_settings_views.xml
Normal 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>
|
4
addons/digest/wizard/__init__.py
Normal file
4
addons/digest/wizard/__init__.py
Normal 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
|
137
addons/digest/wizard/digest_custom_fields.py
Normal file
137
addons/digest/wizard/digest_custom_fields.py
Normal 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',
|
||||
}
|
||||
|
34
addons/digest/wizard/digest_custom_fields_view.xml
Normal file
34
addons/digest/wizard/digest_custom_fields_view.xml
Normal 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>
|
@ -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',
|
||||
|
25
addons/hr_recruitment/data/digest_data.xml
Normal file
25
addons/hr_recruitment/data/digest_data.xml
Normal 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>
|
@ -4,3 +4,4 @@ from . import hr_employee
|
||||
from . import hr_job
|
||||
from . import res_config_settings
|
||||
from . import calendar
|
||||
from . import digest
|
||||
|
29
addons/hr_recruitment/models/digest.py
Normal file
29
addons/hr_recruitment/models/digest.py
Normal 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
|
15
addons/hr_recruitment/views/digest_views.xml
Normal file
15
addons/hr_recruitment/views/digest_views.xml
Normal 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>
|
@ -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',
|
||||
|
6
addons/point_of_sale/data/digest_data.xml
Normal file
6
addons/point_of_sale/data/digest_data.xml
Normal 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>
|
@ -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
|
||||
|
29
addons/point_of_sale/models/digest.py
Normal file
29
addons/point_of_sale/models/digest.py
Normal 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
|
15
addons/point_of_sale/views/digest_views.xml
Normal file
15
addons/point_of_sale/views/digest_views.xml
Normal 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>
|
@ -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',
|
||||
],
|
||||
|
26
addons/project/data/digest_data.xml
Normal file
26
addons/project/data/digest_data.xml
Normal 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>
|
@ -6,3 +6,4 @@ from . import res_config_settings
|
||||
from . import res_company
|
||||
from . import res_partner
|
||||
from . import web_planner
|
||||
from . import digest
|
||||
|
29
addons/project/models/digest.py
Normal file
29
addons/project/models/digest.py
Normal 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
|
15
addons/project/views/digest_views.xml
Normal file
15
addons/project/views/digest_views.xml
Normal 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>
|
@ -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
|
||||
|
@ -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',
|
||||
|
17
addons/sale_expense/data/digest_data.xml
Normal file
17
addons/sale_expense/data/digest_data.xml
Normal 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>
|
@ -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
|
||||
|
||||
|
||||
|
@ -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',
|
||||
|
6
addons/sale_management/data/digest_data.xml
Normal file
6
addons/sale_management/data/digest_data.xml
Normal 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>
|
4
addons/sale_management/models/__init__.py
Normal file
4
addons/sale_management/models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import digest
|
28
addons/sale_management/models/digest.py
Normal file
28
addons/sale_management/models/digest.py
Normal 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
|
17
addons/sale_management/views/digest_views.xml
Normal file
17
addons/sale_management/views/digest_views.xml
Normal 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>
|
@ -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',
|
||||
|
6
addons/website_sale/data/digest_data.xml
Normal file
6
addons/website_sale/data/digest_data.xml
Normal 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>
|
@ -14,3 +14,4 @@ from . import sale_report
|
||||
from . import ir_model_fields
|
||||
from . import website
|
||||
from . import res_config_settings
|
||||
from . import digest
|
||||
|
31
addons/website_sale/models/digest.py
Normal file
31
addons/website_sale/models/digest.py
Normal 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
|
17
addons/website_sale/views/digest_views.xml
Normal file
17
addons/website_sale/views/digest_views.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user