[MERGE][ADD] mail, various: add activities

This commit introduces generic activities to use in your addons. Activities are
actions user have to take on a document like making a phonecall or organizing
a meeting. Activities come with the mail module as they are integrated in the
Chatter but are not bundled with mail.thread.

New models are

 * mail.activity.type: used to categorize activities. Each type is a different
   kind of activity e.g. call, mail, meeting. An activity can be generic i.e.
   available for all models using activities; or specific to a model in which
   case res_model_id field should be used.
 * mail.activity: an actual activity to perform. Activities are linked to
   documents using res_id and res_model_id fields. Activities have a deadline
   that can be used in kanban view to display a status. Once done activities
   are unlinked and a message is posted. This message has a new activity_type_id
   field that indicates the activity linked to the message.

This commit introduces a mail.activity mixin to use in various addons that
enables the activities feature. It works like the mail.thread mixin. It defines
a activity_ids one2many field toward activities using res_id and res_model_id.
Various related / computed fields are also added to have a global status of
activities on documents.

Activities come with a new JS widget for the form view. It is integrated in the
Chatter widget although it is a separate widget. It displays activities linked
to the current record and allow to schedule, edit and mark done activities.
Use widget="mail_activity" on activity_ids field in form view to use it.

There is also a kanban widget defined. It defines a small widget to integrate
in kanban vignettes. It allow to manage activities directly from the kanban
view. Use widget="kanban_activity" on activitiy_ids field in kanban view to
use it.

Next commits will aim at integrating activities inside main Odoo addons.

Thanks to R&D India for their work and testing on this task. Thanks to belgian
Usabiliteam for reviewing and testing it. Thanks to @jem-odoo for the final
review. May his soul lie in peace with the trumpets of paradise.
This commit is contained in:
Thibault Delavallée
2016-12-21 11:01:13 +01:00
80 changed files with 1736 additions and 707 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ MAGIC_COLUMNS = ('id', 'create_uid', 'create_date', 'write_uid', 'write_date')
class AccountInvoice(models.Model):
_name = "account.invoice"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Invoice"
_order = "date_invoice desc, number desc, id desc"
@@ -335,6 +335,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -523,6 +524,18 @@
<field name="date" string="Period"/>
<separator/>
<filter domain="[('user_id','=',uid)]" help="My Invoices"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter name="group_by_partner_id" string="Partner" context="{'group_by':'commercial_partner_id'}"/>
<filter string="Salesperson" context="{'group_by':'user_id'}"/>
+1 -4
View File
@@ -41,7 +41,6 @@ Dashboard for CRM will include:
'web_tour',
],
'data': [
'data/crm_activity_data.xml',
'data/crm_data.xml',
'data/crm_stage_data.xml',
'data/sales_config_settings_data.xml',
@@ -53,7 +52,6 @@ Dashboard for CRM will include:
'security/ir.model.access.csv',
'wizard/base_partner_merge_views.xml',
'wizard/crm_activity_log_views.xml',
'wizard/crm_lead_lost_views.xml',
'wizard/crm_lead_to_opportunity_views.xml',
'wizard/crm_merge_opportunities_views.xml',
@@ -64,7 +62,6 @@ Dashboard for CRM will include:
'views/crm_templates.xml',
'views/crm_views.xml',
'views/crm_activity_views.xml',
'views/crm_stage_views.xml',
'views/crm_lead_views.xml',
'views/calendar_views.xml',
@@ -74,8 +71,8 @@ Dashboard for CRM will include:
],
'demo': [
'data/crm_demo.xml',
'data/mail_activity_demo.xml',
'data/crm_lead_demo.xml',
'data/crm_activity_demo.xml',
'data/base_action_rule_demo.xml',
],
'css': ['static/src/css/crm.css'],
-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="crm_activity_data_email" model="crm.activity">
<field name="name">Email</field>
</record>
<record id="crm_activity_data_call" model="crm.activity">
<field name="name">Call</field>
</record>
<record id="crm_activity_data_meeting" model="crm.activity">
<field name="name">Task</field>
</record>
</data>
</odoo>
-37
View File
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="crm_activity_demo_followup_quote" model="crm.activity">
<field name="name">Follow-up Quote</field>
<field name="description">Follow-up quote</field>
<field name="days">30</field>
</record>
<record id="crm_activity_demo_make_quote" model="crm.activity">
<field name="name">Make Quote</field>
<field name="description">Send a quotation</field>
<field name="days">15</field>
<field name="recommended_activity_ids" eval="[(6,0, [ref('crm.crm_activity_demo_followup_quote')])]"/>
</record>
<record id="crm_activity_demo_call_demo" model="crm.activity">
<field name="name">Call for Demo</field>
<field name="days">10</field>
<field name="description">Call for a demo</field>
<field name="recommended_activity_ids" eval="[(6, 0, [ref('crm.crm_activity_demo_make_quote')])]"/>
</record>
<record id="crm_activity_data_call" model="crm.activity">
<field name="days">5</field>
<field name="recommended_activity_ids" eval="[(6, 0, [ref('crm.crm_activity_data_email')])]"/>
</record>
<record id="crm_activity_data_meeting" model="crm.activity">
<field name="recommended_activity_ids" eval="[(6, 0, [ref('crm_activity_data_call'), ref('crm_activity_data_email')])]"/>
<field name="days">15</field>
</record>
</data>
</odoo>
+95 -37
View File
@@ -374,9 +374,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="priority">1</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(weeks=2)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_meeting"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=3)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Meeting to go over pricing information.</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead1"/>
@@ -402,9 +399,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor2])]"/>
<field name="priority">2</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(weeks=2)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=3)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Send Catalog by Email</field>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead1"/>
@@ -427,9 +421,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="priority">1</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(weeks=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_call"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Call to get system requirements</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead2"/>
@@ -516,9 +507,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor5])]"/>
<field name="priority">2</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(weeks=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_meeting"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Convert to quote</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead2"/>
@@ -542,9 +530,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor3,categ_oppor4])]"/>
<field name="priority">1</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=3)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=-1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Send our service pricelist</field>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead2"/>
@@ -577,9 +562,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor4,categ_oppor6])]"/>
<field name="priority">1</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(weeks=3)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_call"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Call to get training needs</field>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead2"/>
@@ -628,9 +610,6 @@ Andrew</p>]]></field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="priority">1</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(weeks=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_call"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=-2)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Followup on the proposal</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -653,7 +632,6 @@ Andrew</p>]]></field>
<field name="phone">+1 312 349 2324</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor4,categ_oppor8])]"/>
<field name="priority">2</field>
<field name="next_activity_id" ref="crm_activity_data_call"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -672,7 +650,6 @@ Andrew</p>]]></field>
<field name="street">Cannon Hill Park</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="priority">2</field>
<field name="next_activity_id" ref="crm_activity_data_meeting"/>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -691,7 +668,6 @@ Andrew</p>]]></field>
<field name="partner_id" ref="base.res_partner_12"/>
<field name="tag_ids" eval="[(6, 0, [categ_oppor3])]"/>
<field name="priority">1</field>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -708,8 +684,6 @@ Andrew</p>]]></field>
<field name="partner_id" ref="base.res_partner_18"/>
<field name="priority">0</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor3])]"/>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -728,8 +702,6 @@ Andrew</p>]]></field>
<field name="city">Lima</field>
<field name="priority">0</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M')"/>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -753,9 +725,6 @@ Andrew</p>]]></field>
<field name="street2">C1414CMS Capital Federal</field>
<field name="priority">2</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="next_activity_id" ref="crm_activity_data_call"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Conf call with technical service</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -778,9 +747,6 @@ Andrew</p>]]></field>
<field name="priority">2</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor2])]"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=6)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Send Catalog by Email</field>
<field name="team_id" ref="sales_team.crm_team_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -804,9 +770,6 @@ Andrew</p>]]></field>
<field name="priority">2</field>
<field name="tag_ids" eval="[(6, 0, [categ_oppor2])]"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=6)).strftime('%Y-%m-%d %H:%M')"/>
<field name="next_activity_id" ref="crm_activity_data_email"/>
<field name="date_action" eval="(DateTime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M')"/>
<field name="title_action">Send Catalog by Email</field>
<field name="team_id" ref="sales_team.team_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
@@ -817,5 +780,100 @@ Andrew</p>]]></field>
eval="[ref('crm_case_20'), ref('crm_case_23'), ref('crm_case_27')],
{'install_mode': True}"/>
<!-- Activities -->
<record id="activity_1" model="mail.activity">
<field name="res_id" ref="crm.crm_case_13" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
<field name="summary">Meeting to go over pricing information.</field>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=3)).strftime('%Y-%m-%d %H:%M')"/>
</record>
<record id="activity_2" model="mail.activity">
<field name="res_id" ref="crm.crm_case_14" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=3)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Send Catalog by Email</field>
</record>
<record id="activity_3" model="mail.activity">
<field name="res_id" ref="crm.crm_case_15" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Call to get system requirements</field>
</record>
<record id="activity_4" model="mail.activity">
<field name="res_id" ref="crm.crm_case_16" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Convert to quote</field>
</record>
<record id="activity_5" model="mail.activity">
<field name="res_id" ref="crm.crm_case_17" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=-1)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Send our service pricelist</field>
</record>
<record id="activity_6" model="mail.activity">
<field name="res_id" ref="crm.crm_case_18" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Call to get training needs</field>
</record>
<record id="activity_7" model="mail.activity">
<field name="res_id" ref="crm.crm_case_19" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=-2)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Followup on the proposal</field>
</record>
<record id="activity_8" model="mail.activity">
<field name="res_id" ref="crm.crm_case_20" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')" />
</record>
<record id="activity_9" model="mail.activity">
<field name="res_id" ref="crm.crm_case_21" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=-1)).strftime('%Y-%m-%d %H:%M')" />
</record>
<record id="activity_10" model="mail.activity">
<field name="res_id" ref="crm.crm_case_22" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=-2)).strftime('%Y-%m-%d %H:%M')" />
</record>
<record id="activity_11" model="mail.activity">
<field name="res_id" ref="crm.crm_case_23" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M')" />
</record>
<record id="activity_12" model="mail.activity">
<field name="res_id" ref="crm.crm_case_24" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M')" />
</record>
<record id="activity_13" model="mail.activity">
<field name="res_id" ref="crm.crm_case_25" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Conf call with technical service</field>
</record>
<record id="activity_14" model="mail.activity">
<field name="res_id" ref="crm.crm_case_26" />
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email"/>
<field name="date_deadline" eval="(DateTime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M')" />
<field name="summary">Send Catalog by Email</field>
</record>
</data>
</odoo>
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<odoo>
<record id="mail_activity_demo_followup_quote" model="mail.activity.type">
<field name="name">Follow-up Quote</field>
<field name="icon">fa-file-text-o</field>
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="summary">Follow-up quote</field>
<field name="days">30</field>
</record>
<record id="mail_activity_demo_make_quote" model="mail.activity.type">
<field name="name">Make Quote</field>
<field name="icon">fa-file-text-o</field>
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="summary">Send a quotation</field>
<field name="days">15</field>
</record>
<record id="mail_activity_demo_call_demo" model="mail.activity.type">
<field name="name">Call for Demo</field>
<field name="icon">fa-phone</field>
<field name="res_model_id" ref="crm.model_crm_lead"/>
<field name="days">10</field>
<field name="summary">Call for a demo</field>
</record>
</odoo>
-1
View File
@@ -3,7 +3,6 @@
import res_users
import calendar
import crm_activity
import crm_lead
import crm_stage
import crm_team
-50
View File
@@ -1,50 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class CrmActivity(models.Model):
''' CrmActivity is a model introduced in Odoo v9 that models activities
performed in CRM, like phone calls, sending emails, making demonstrations,
... Users are able to configure their custom activities.
Each activity can configure recommended next activities. This allows to model
light custom workflows. This way sales manager can configure their crm
workflow that salepersons will use in their daily job.
CrmActivity inherits from mail.message.subtype. This allows users to follow
some activities through subtypes. Each activity will generate messages with
the matching subtypes, allowing reporting and statistics computation based
on mail.message.subtype model. '''
_name = 'crm.activity'
_description = 'CRM Activity'
_inherits = {'mail.message.subtype': 'subtype_id'}
_rec_name = 'name'
_order = "sequence"
days = fields.Integer('Number of days', default=0,
help='Number of days before executing the action, allowing you to plan the date of the action.')
sequence = fields.Integer('Sequence', default=0)
team_id = fields.Many2one('crm.team', string='Sales Team')
subtype_id = fields.Many2one('mail.message.subtype', string='Message Subtype', required=True, ondelete='cascade')
recommended_activity_ids = fields.Many2many(
'crm.activity', 'crm_activity_rel', 'activity_id', 'recommended_id',
string='Recommended Next Activities')
preceding_activity_ids = fields.Many2many(
'crm.activity', 'crm_activity_rel', 'recommended_id', 'activity_id',
string='Preceding Activities')
# setting a default value on inherited fields is a bit involved
res_model = fields.Char('Model', related='subtype_id.res_model', inherited=True, default='crm.lead')
internal = fields.Boolean('Internal Only', related='subtype_id.internal', inherited=True, default=True)
default = fields.Boolean('Default', related='subtype_id.default', inherited=True, default=False)
@api.multi
def unlink(self):
activities = self.search([('subtype_id', '=', self.subtype_id.id)])
# to ensure that the subtype is only linked the current activity
if len(activities) == 1:
self.subtype_id.unlink()
return super(CrmActivity, self).unlink()
+19 -38
View File
@@ -52,11 +52,10 @@ CRM_LEAD_FIELDS_TO_MERGE = [
class Lead(FormatAddress, models.Model):
_name = "crm.lead"
_description = "Lead/Opportunity"
_order = "priority desc,date_action,id desc"
_inherit = ['mail.thread', 'utm.mixin']
_order = "priority desc,activity_date_deadline,id desc"
_inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin']
_mail_mass_mailing = _('Leads / Opportunities')
def _default_probability(self):
@@ -116,12 +115,6 @@ class Lead(FormatAddress, models.Model):
probability = fields.Float('Probability', group_operator="avg", default=lambda self: self._default_probability())
planned_revenue = fields.Float('Expected Revenue', track_visibility='always')
date_deadline = fields.Date('Expected Closing', help="Estimate of the date on which the opportunity will be won.")
# CRM Actions
next_activity_id = fields.Many2one("crm.activity", string="Next Activity", index=True)
date_action = fields.Date('Next Activity Date', index=True)
title_action = fields.Char('Next Activity Summary')
color = fields.Integer('Color Index', default=0)
partner_address_name = fields.Char('Partner Contact Name', related='partner_id.name', readonly=True)
partner_address_email = fields.Char('Partner Contact Email', related='partner_id.email', readonly=True)
@@ -170,8 +163,8 @@ class Lead(FormatAddress, models.Model):
today = date.today()
for lead in self:
kanban_state = 'grey'
if lead.date_action:
lead_date = fields.Date.from_string(lead.date_action)
if lead.activity_date_deadline:
lead_date = fields.Date.from_string(lead.activity_date_deadline)
if lead_date >= today:
kanban_state = 'green'
else:
@@ -270,18 +263,6 @@ class Lead(FormatAddress, models.Model):
if self.state_id:
self.country_id = self.state_id.country_id.id
@api.onchange('next_activity_id')
def _onchange_next_activity_id(self):
values = {
'title_action': False,
'date_action': False,
}
if self.next_activity_id:
values['title_action'] = self.next_activity_id.description
if self.next_activity_id.days:
values['date_action'] = fields.Datetime.to_string(datetime.now() + timedelta(days=self.next_activity_id.days))
self.update(values)
# ----------------------------------------
# ORM override (CRUD, fields_view_get, ...)
# ----------------------------------------
@@ -934,13 +915,13 @@ class Lead(FormatAddress, models.Model):
if date_deadline < date.today():
result['closing']['overdue'] += 1
# Next activities
if opp.next_activity_id and opp.date_action:
date_action = fields.Date.from_string(opp.date_action)
if date_action == date.today():
for activity in opp.activity_ids:
date_deadline = fields.Date.from_string(activity.date_deadline)
if date_deadline == date.today():
result['activity']['today'] += 1
if date.today() <= date_action <= date.today() + timedelta(days=7):
if date.today() <= date_deadline <= date.today() + timedelta(days=7):
result['activity']['next_7_days'] += 1
if date_action < date.today():
if date_deadline < date.today():
result['activity']['overdue'] += 1
# Won in Opportunities
if opp.date_closed:
@@ -957,19 +938,19 @@ class Lead(FormatAddress, models.Model):
# crm.activity is a very messy model so we need to do that in order to retrieve the actions done.
self._cr.execute("""
SELECT
m.id,
m.subtype_id,
m.date,
l.user_id,
l.type
FROM mail_message M
LEFT JOIN crm_lead L ON (M.res_id = L.id)
INNER JOIN crm_activity A ON (M.subtype_id = A.subtype_id)
mail_message.id,
mail_message.subtype_id,
mail_message.mail_activity_type_id,
mail_message.date,
crm_lead.user_id,
crm_lead.type
FROM mail_message
LEFT JOIN crm_lead ON (mail_message.res_id = crm_lead.id)
INNER JOIN mail_activity_type activity_type ON (mail_message.mail_activity_type_id = activity_type.id)
WHERE
(M.model = 'crm.lead') AND (L.user_id = %s) AND (L.type = 'opportunity')
(mail_message.model = 'crm.lead') AND (crm_lead.user_id = %s) AND (crm_lead.type = 'opportunity')
""", (self._uid,))
activites_done = self._cr.dictfetchall()
for activity in activites_done:
if activity['date']:
date_act = fields.Date.from_string(activity['date'])
+4 -2
View File
@@ -18,7 +18,8 @@ class ActivityReport(models.Model):
team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True)
lead_id = fields.Many2one('crm.lead', "Lead", readonly=True)
subject = fields.Char('Summary', readonly=True)
subtype_id = fields.Many2one('mail.message.subtype', 'Activity', readonly=True)
subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', readonly=True)
mail_activity_type_id = fields.Many2one('mail.activity.type', 'Activity Type', readonly=True)
country_id = fields.Many2one('res.country', 'Country', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
stage_id = fields.Many2one('crm.stage', 'Stage', readonly=True)
@@ -37,6 +38,7 @@ class ActivityReport(models.Model):
select
m.id,
m.subtype_id,
m.mail_activity_type_id,
m.author_id,
m.date,
m.subject,
@@ -57,5 +59,5 @@ class ActivityReport(models.Model):
on
(m.res_id = l.id)
WHERE
(m.model = 'crm.lead')
(m.model = 'crm.lead' AND m.mail_activity_type_id IS NOT NULL)
)""")
@@ -6,7 +6,7 @@
<field name="model">crm.activity.report</field>
<field name="arch" type="xml">
<graph string="Activities Analysis" stacked="True">
<field name="subtype_id" type="col"/>
<field name="mail_activity_type_id" type="col"/>
<field name="date" interval="month" type="row"/>
</graph>
</field>
@@ -17,7 +17,7 @@
<field name="model">crm.activity.report</field>
<field name="arch" type="xml">
<pivot string="Activities Analysis" disable_linking="True">
<field name="subtype_id" type="col"/>
<field name="mail_activity_type_id" type="col"/>
<field name="date" interval="month" type="row"/>
</pivot>
</field>
@@ -28,7 +28,7 @@
<field name="model">crm.activity.report</field>
<field name="arch" type="xml">
<search string="Activities Analysis">
<filter string="My Activities" name="my_activity" domain="[('user_id', '=', uid)]"/>
<filter string="My Activities" name="activities_my" domain="[('user_id', '=', uid)]"/>
<separator/>
<filter name="lead" string="Leads" domain="[('lead_type','=', 'lead')]" help="Show only lead"/>
<filter name="opportunity" string="Opportunities" domain="[('lead_type','=','opportunity')]" help="Show only opportunity"/>
@@ -58,7 +58,7 @@
<field name="res_model">crm.activity.report</field>
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="context">{'search_default_my_activity': True}</field>
<field name="context">{'search_default_activities_my': True}</field>
<field name="domain">[]</field>
</record>
-3
View File
@@ -18,9 +18,6 @@ access_crm_lead_tag_manager,crm_lead_tag manager,model_crm_lead_tag,sales_team.g
access_crm_lost_reason_manager,crm.lost.reason.manager,model_crm_lost_reason,sales_team.group_sale_manager,1,1,1,1
access_crm_lost_reason_salesman,crm.lost.reason.salesman,model_crm_lost_reason,sales_team.group_sale_salesman,1,0,0,0
access_crm_lost_reason_user,crm.lost.reason.user,model_crm_lost_reason,base.group_user,1,0,0,0
access_crm_activity_user,crm.activity.user,model_crm_activity,base.group_user,1,1,1,0
access_crm_activity_manager,crm.activity.manager,model_crm_activity,sales_team.group_sale_manager,1,1,1,1
access_crm_activity_salesman,crm.activity.salesman,model_crm_activity,sales_team.group_sale_salesman,1,1,1,0
access_crm_activity_report_user,crm.activity.report.user,model_crm_activity_report,base.group_user,1,0,0,0
access_calendar_event_manager,calendar.event.manager,calendar.model_calendar_event,sales_team.group_sale_manager,1,1,1,1
access_calendar_event,calendar.event,calendar.model_calendar_event,sales_team.group_sale_salesman,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
18 access_crm_lost_reason_manager crm.lost.reason.manager model_crm_lost_reason sales_team.group_sale_manager 1 1 1 1
19 access_crm_lost_reason_salesman crm.lost.reason.salesman model_crm_lost_reason sales_team.group_sale_salesman 1 0 0 0
20 access_crm_lost_reason_user crm.lost.reason.user model_crm_lost_reason base.group_user 1 0 0 0
access_crm_activity_user crm.activity.user model_crm_activity base.group_user 1 1 1 0
access_crm_activity_manager crm.activity.manager model_crm_activity sales_team.group_sale_manager 1 1 1 1
access_crm_activity_salesman crm.activity.salesman model_crm_activity sales_team.group_sale_salesman 1 1 1 0
21 access_crm_activity_report_user crm.activity.report.user model_crm_activity_report base.group_user 1 0 0 0
22 access_calendar_event_manager calendar.event.manager calendar.model_calendar_event sales_team.group_sale_manager 1 1 1 1
23 access_calendar_event calendar.event calendar.model_calendar_event sales_team.group_sale_salesman 1 1 1 0
+10 -6
View File
@@ -35,18 +35,22 @@ tour.register('crm_tour', {
position: "right",
run: "drag_and_drop .o_opportunity_kanban .o_kanban_group:eq(2) ",
}, {
trigger: ".o_kanban_record:not(.o_updating) .oe_kanban_status_grey",
trigger: ".o_kanban_record:not(.o_updating) .o_activity_color_default",
extra_trigger: ".o_opportunity_kanban",
content: _t("This opportunity has <b>no next activity scheduled</b>. <i>Click to set one.</i>"),
content: _t("This opportunity has <b>no activity planned</b>. <i>Click to check them.</i>"),
position: "bottom"
}, {
trigger: ".o_recommended_activity .o_radio_item",
trigger: ".o_schedule_activity",
extra_trigger: ".o_opportunity_kanban",
content: _t("Schedule an activity by clicking here"),
position: "bottom"
}, {
trigger: ".modal-body .o_form_field_many2one",
extra_trigger: ".o_opportunity_kanban",
content: _t("<p>You will be able to customize your followup activities. Examples:</p><ol><li>introductory email</li><li>call 10 days after</li><li>second call 3 days after, ...</li></ol><p class='mb0'><i>Select a standard activity for now on.</i></p>"),
position: "left",
position: "bottom",
run: function (actions) {
actions.auto(this.$anchor.children("input").first());
actions.auto(".modal-footer .btn-primary");
actions.auto(".modal-footer .btn-default");
},
}, {
trigger: ".o_kanban_record",
+54 -72
View File
@@ -3,52 +3,47 @@
from .common import TestCrmCases
from odoo import fields
from datetime import date
from datetime import datetime, timedelta
class TestCrmActivity(TestCrmCases):
class TestCrmMailActivity(TestCrmCases):
def setUp(self):
super(TestCrmActivity, self).setUp()
super(TestCrmMailActivity, self).setUp()
# Set up activities
Activity = self.env['crm.activity']
self.activity3 = Activity.create({
lead_model_id = self.env['ir.model'].search([('model', '=', 'crm.lead')]).id
ActivityType = self.env['mail.activity.type']
self.activity3 = ActivityType.create({
'name': 'Celebrate the sale',
'days': 3,
'description': 'ACT 3 : Beers for everyone because I am a good salesman !',
'internal': True,
'res_model': 'crm.lead',
'summary': 'ACT 3 : Beers for everyone because I am a good salesman !',
'res_model_id': lead_model_id,
})
self.activity2 = Activity.create({
self.activity2 = ActivityType.create({
'name': 'Call for Demo',
'days': 6,
'description': 'ACT 2 : I want to show you my ERP !',
'internal': True,
'res_model': 'crm.lead',
'recommended_activity_ids': [(6, 0, [self.activity3.id])],
'summary': 'ACT 2 : I want to show you my ERP !',
'res_model_id': lead_model_id,
})
self.activity1 = Activity.create({
self.activity1 = ActivityType.create({
'name': 'Initial Contact',
'days': 5,
'description': 'ACT 1 : Presentation, barbecue, ... ',
'internal': True,
'res_model': 'crm.lead',
'recommended_activity_ids': [(6, 0, [self.activity2.id])],
'summary': 'ACT 1 : Presentation, barbecue, ... ',
'res_model_id': lead_model_id,
})
# I create an opportunity, as salesman
self.partner_client = self.env.ref("base.res_partner_1")
Lead = self.env['crm.lead'].sudo(self.crm_salesman.id)
self.lead = Lead.create({
self.lead = self.env['crm.lead'].sudo(self.crm_salesman.id).create({
'name': 'Test Opp',
'type': 'opportunity',
'name': 'Test Opportunity Activity Log',
'partner_id': self.partner_client.id,
'team_id': self.env.ref("sales_team.team_sales_department").id,
'user_id': self.crm_salesman.id,
})
def test_crm_activity_recipients(self):
""" This test case check :
""" This test case checks
- no internal subtype followed by client
- activity subtype are not default ones
- only activity followers are recipients when this kind of activity is logged
@@ -60,73 +55,60 @@ class TestCrmActivity(TestCrmCases):
self.lead.message_subscribe([self.partner_client.id])
# Check the client is not follower of any internal subtype
is_internal_subtype_for_client = self.lead.message_follower_ids.filtered(lambda fol: fol.partner_id.id == self.partner_client.id).mapped('subtype_ids.internal')
self.assertFalse(any(is_internal_subtype_for_client), 'Partner client is following an internal subtype')
internal_subtypes = self.lead.message_follower_ids.filtered(lambda fol: fol.partner_id == self.partner_client).mapped('subtype_ids').filtered(lambda subtype: subtype.internal)
self.assertFalse(internal_subtypes)
# Add sale manager as follower of default subtypes
self.lead.message_subscribe([self.crm_salemanager.partner_id.id])
# Make the sale manager follower of the activity subtype
manager_follower = self.env['mail.followers'].sudo().search([('res_model', '=', 'crm.lead'), ('res_id', '=', self.lead.id), ('partner_id', '=', self.crm_salemanager.partner_id.id)])
manager_follower.write({
'subtype_ids': [(4, activity.subtype_id.id)]
})
# Instanciate wizard, trigger onchange lead and set the next activity
ActivityLogWizard = self.env['crm.activity.log'].sudo(self.crm_salesman.id)
wizard = ActivityLogWizard.create({
activity = self.env['mail.activity'].sudo(self.crm_salesman.id).create({
'activity_type_id': self.activity1.id,
'note': 'Content of the activity to log',
'lead_id': self.lead.id,
'res_id': self.lead.id,
'res_model_id': self.env.ref('crm.model_crm_lead').id,
})
wizard.onchange_lead_id()
wizard.write({
'next_activity_id': activity.id,
})
wizard.action_log()
activity._onchange_activity_type_id()
self.assertEqual(self.lead.activity_type_id, self.activity1)
self.assertEqual(self.lead.activity_summary, self.activity1.summary)
# self.assertEqual(self.lead.activity_date_deadline, self.activity1.summary)
# Check message recipients
# mark as done, check lead and posted message
activity.action_done()
self.assertFalse(self.lead.activity_type_id.id)
self.assertFalse(self.lead.activity_ids)
activity_message = self.lead.message_ids[0]
self.assertEqual(activity_message.needaction_partner_ids, self.crm_salemanager.partner_id, 'Only the crm manager should be notified by the activity')
self.assertEqual(self.lead.next_activity_id.id, False, 'When logging activity, the next activity planned is erased')
self.assertEqual(activity_message.needaction_partner_ids, self.crm_salemanager.partner_id)
self.assertEqual(activity_message.subtype_id, self.env.ref('mail.mt_activities'))
def test_crm_activity_next_action(self):
""" This test case set the next activity on a lead, log another, and schedule a third. """
# Add the next activity (like we set it from a form view)
self.lead.write({
'next_activity_id': self.activity1.id
lead_model_id = self.env['ir.model'].search([('model', '=', 'crm.lead')]).id
activity = self.env['mail.activity'].sudo(self.crm_salesman.id).create({
'activity_type_id': self.activity1.id,
'summary': 'My Own Summary',
'res_id': self.lead.id,
'res_model_id': lead_model_id,
})
self.lead._onchange_next_activity_id()
activity._onchange_activity_type_id()
# Check the next activity is correct
self.assertEqual(self.lead.title_action, self.activity1.description, 'Activity title should be the same on the lead and on the chosen activity')
self.assertEqual(self.lead.activity_summary, activity.summary)
self.assertEqual(self.lead.activity_type_id, activity.activity_type_id)
# self.assertEqual(fields.Datetime.from_string(self.lead.activity_date_deadline), datetime.now() + timedelta(days=activity.activity_type_id.days))
# Instanciate the wizard to schedule the next activity
wizard = self.env['crm.activity.log'].sudo(self.crm_salesman.id).create({
activity.write({
'activity_type_id': self.activity2.id,
'summary': '',
'note': 'Content of the activity to log',
'lead_id': self.lead.id,
})
wizard.onchange_lead_id()
wizard.write({
'next_activity_id': self.activity2.id,
})
wizard.action_log()
activity._onchange_activity_type_id()
self.assertEqual(self.lead.activity_summary, activity.activity_type_id.summary)
self.assertEqual(self.lead.activity_type_id, activity.activity_type_id)
# self.assertEqual(fields.Datetime.from_string(self.lead.activity_date_deadline), datetime.now() + timedelta(days=activity.activity_type_id.days))
activity.action_done()
# Check the next activity on the lead has been removed
self.assertFalse(self.lead.next_activity_id.id, 'No next activity should be set on lead, since we jsut log another activity')
# Instanciate the wizard to schedule the next activity
self.env['crm.activity.log'].sudo(self.crm_salesman.id).create({
'next_activity_id': self.activity3.id,
'note': 'Content of the activity to log',
'lead_id': self.lead.id,
})
wizard.onchange_lead_id()
wizard.write({
'next_activity_id': self.activity3.id,
})
wizard.onchange_next_activity_id()
wizard.action_schedule()
# Check the activity is well scheldule on lead
delta_days = (fields.Date.from_string(self.lead.date_action) - date.today()).days
self.assertEqual(self.activity3.days, delta_days, 'The action date should be in the number of days set up on the activity 3')
self.assertEqual(self.lead.title_action, self.activity3.description, 'Activity title should be the same on the lead and on the activity 3')
self.assertFalse(self.lead.activity_type_id)
-67
View File
@@ -1,67 +0,0 @@
<?xml version="1.0"?>
<odoo>
<record id="crm_activity_view_search" model="ir.ui.view">
<field name="name">crm.activity.search</field>
<field name="model">crm.activity</field>
<field name="arch" type="xml">
<search string="Action Search">
<field name="name"/>
<field name="team_id"/>
</search>
</field>
</record>
<record id="crm_activity_view_form" model="ir.ui.view">
<field name="name">crm.activity.form</field>
<field name="model">crm.activity</field>
<field name="arch" type="xml">
<form string="Activities">
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
</div>
<group>
<group>
<field name="team_id"/>
<field name="description"/>
<field name="days"/>
<field name="default"/>
</group>
<group>
<field name="recommended_activity_ids" widget="many2many_tags"/>
</group>
</group>
</form>
</field>
</record>
<record id="crm_activity_view_tree" model="ir.ui.view">
<field name="name">crm.activity.tree</field>
<field name="model">crm.activity</field>
<field name="arch" type="xml">
<tree string="Activities">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="days"/>
<field name="team_id"/>
</tree>
</field>
</record>
<record id="crm_activity_action" model="ir.actions.act_window">
<field name="name">Activities</field>
<field name="res_model">crm.activity</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">Click to create a new activity.</p>
</field>
</record>
<menuitem
id="crm_activity_menu"
name="Activities"
action="crm_activity_action"
parent="menu_crm_config_lead"/>
</odoo>
+41 -47
View File
@@ -186,7 +186,7 @@
<field name="model">crm.lead</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="Leads Generation" date_start="date_action" color="user_id">
<calendar string="Leads Generation" date_start="activity_date_deadline" color="user_id">
<field name="name"/>
<field name="partner_name"/>
</calendar>
@@ -240,16 +240,18 @@
<field name="priority"/>
<field name="planned_revenue"/>
<field name="kanban_state"/>
<field name="date_action"/>
<field name="activity_date_deadline"/>
<field name="user_email"/>
<field name="user_id"/>
<field name="partner_address_email"/>
<field name="message_needaction_counter"/>
<field name="tag_ids"/>
<field name="partner_id"/>
<field name="title_action"/>
<field name="activity_summary"/>
<field name="active"/>
<field name="company_currency"/>
<field name="activity_state" />
<field name="activity_ids" />
<templates>
<field name="date_deadline"/>
<t t-name="kanban-box">
@@ -284,16 +286,10 @@
<span class='oe_kanban_mail_new' title='Unread Messages'><i class='fa fa-comments'/><t t-raw="record.message_needaction_counter.raw_value"/></span>
</t>
<div>
<a name="%(crm.crm_activity_log_action)d" type="action" t-if="record.kanban_state.raw_value!='grey'">
<span
t-att-title="record.date_action.value + ': '+ record.title_action.value"
t-attf-class="oe_kanban_status oe_kanban_status_#{record.kanban_state.raw_value}"/>
</a>
<a name="%(crm.crm_activity_schedule_action)d" type="action" t-if="record.kanban_state.raw_value=='grey'">
<span
class="oe_kanban_status oe_kanban_status_grey"/>
</a>
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div class="o_kanban_inline_block">
<field name="activity_ids" widget="kanban_activity"/>
</div>
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar"/>
</div>
</div>
</div>
@@ -332,6 +328,18 @@
<filter string="Exclude Opt Out"
name='not_opt_out' domain="[('opt_out', '=', False)]"
help="Leads that did not ask not to be included in mass mailing campaigns"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter string="Salesperson" name="groupby_salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Sales Team" name="groupby_team" domain="[]" context="{'group_by':'team_id'}"/>
@@ -362,8 +370,6 @@
type="action" class="oe_highlight"
context="{'default_lead_id': active_id}"
attrs="{'invisible': [('active', '=', False),('probability', '&lt;', 100)]}"/>
<button name="%(crm.crm_activity_log_action)d" string="Log Activity" type="action" class="btn-link"
attrs="{'invisible': [('active', '=', False)]}"/>
<field name="stage_id" widget="statusbar" clickable="True"
options="{'fold_field': 'fold'}"
domain="['|', ('team_id', '=', team_id), ('team_id', '=', False)]"
@@ -425,21 +431,6 @@
</group>
<group>
<label for="next_activity_id"/>
<div>
<div class="o_row mr16">
<field name="next_activity_id"
options="{'no_create': True, 'no_open': True}"
domain="['|',('team_id','=',False),('team_id','=',team_id)]"/>
<span class="oe_form_field"
attrs="{'invisible': ['|', ('next_activity_id', '=', False), ('date_action', '=', False)]}">&#160;on&#160;</span>
<field name="date_action" attrs="{'invisible': [('next_activity_id', '=', False)]}" placeholder="Choose a date..."/>
</div>
<div>
<field name="title_action" placeholder="e.g. Discuss proposal"
attrs="{'invisible': [('next_activity_id', '=', False)]}"/>
</div>
</div>
<field name="date_deadline"/>
</group>
@@ -503,6 +494,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -513,14 +505,14 @@
<field name="name">crm.lead.tree.opportunity</field>
<field name="model">crm.lead</field>
<field name="arch" type="xml">
<tree string="Opportunities" decoration-bf="message_needaction==True" decoration-muted="probability == 100" decoration-danger="date_action and (date_action &lt; current_date)">
<tree string="Opportunities" decoration-bf="message_needaction==True" decoration-muted="probability == 100" decoration-danger="activity_date_deadline and (activity_date_deadline &lt; current_date)">
<field name="date_deadline" invisible="1"/>
<field name="create_date"/>
<field name="name" string="Opportunity"/>
<field name="partner_id" string="Customer"/>
<field name="country_id"/>
<field name="date_action"/>
<field name="title_action"/>
<field name="activity_date_deadline"/>
<field name="activity_summary"/>
<field name="medium_id" invisible="1"/>
<field name="campaign_id" invisible="1"/>
<field name="source_id" invisible="1"/>
@@ -542,12 +534,12 @@
<field name="name">crm.lead.tree.opportunity.next.activity</field>
<field name="model">crm.lead</field>
<field name="arch" type="xml">
<tree string="Next Activity" decoration-danger="date_action and (date_action &lt; current_date)" default_order="date_action">
<tree string="Next Activities" decoration-danger="activity_date_deadline and (activity_date_deadline &lt; current_date)" default_order="activity_date_deadline">
<field name="name"/>
<field name="partner_id"/>
<field name="date_action"/>
<field name="next_activity_id"/>
<field name="title_action"/>
<field name="activity_date_deadline"/>
<field name="activity_type_id"/>
<field name="activity_summary"/>
<field name="stage_id"/>
<field name="planned_revenue"/>
<field name="date_deadline"/>
@@ -566,8 +558,8 @@
<field name="user_id"/>
<field name="partner_id" operator="child_of" string="Customer"/>
<field name="stage_id" domain="[]"/>
<field name="next_activity_id"/>
<field name="title_action"/>
<field name="activity_type_id"/>
<field name="activity_summary"/>
<field name="probability"/>
<field name="lost_reason"/>
<separator/>
@@ -580,14 +572,16 @@
<filter string="Unassigned" name="unassigned"
domain="[('user_id','=', False)]" help="No salesperson"/>
<separator/>
<filter string="Today Activities" name="today"
domain="[('date_action', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="This Week Activities" name="this_week"
domain="['&amp;', ('date_action', '&gt;=', context_today().strftime('%Y-%m-%d')),
('date_action', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="overdue"
domain="[('date_action', '&lt;', context_today().strftime('%Y-%m-%d'))]"
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all opportunities for which the next action date is before today"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
@@ -831,7 +825,7 @@ if record:
eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('crm_lead_view_tree_activity')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('crm_case_form_view_oppor')}),
(0, 0, {'view_mode': 'kanban'}), (0, 0, {'view_mode': 'calendar'}), (0, 0, {'view_mode': 'pivot'}), (0, 0, {'view_mode': 'graph'})]"/>
<field name="domain">[('type','=','opportunity'), ('date_action', '!=', False)]</field>
<field name="domain">[('type','=','opportunity'), ('activity_date_deadline', '!=', False)]</field>
<field name="context">{
'default_type': 'opportunity',
'default_user_id': uid,
-1
View File
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base_partner_merge
import crm_activity_log
import crm_lead_lost
import crm_partner_binding
import crm_lead_to_opportunity
-103
View File
@@ -1,103 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo import api, fields, models, tools, _
class ActivityLog(models.TransientModel):
_name = "crm.activity.log"
_description = "Log an Activity"
_rec_name = 'title_action'
@api.model
def _default_lead_id(self):
if 'default_lead_id' in self._context:
return self._context['default_lead_id']
if self._context.get('active_model') == 'crm.lead':
return self._context.get('active_id')
return False
next_activity_id = fields.Many2one('crm.activity', 'Activity')
last_activity_id = fields.Many2one('crm.activity', 'Previous Activity', default=False)
recommended_activity_id = fields.Many2one("crm.activity", "Recommended Activities")
title_action = fields.Char('Summary')
note = fields.Html('Note')
date_action = fields.Date('Next Activity Date')
lead_id = fields.Many2one('crm.lead', 'Lead', required=True, default=_default_lead_id)
team_id = fields.Many2one('crm.team', 'Sales Team')
date_deadline = fields.Date('Expected Closing')
planned_revenue = fields.Float('Expected Revenue')
@api.onchange('lead_id')
def onchange_lead_id(self):
self.next_activity_id = self.lead_id.next_activity_id
self.date_deadline = self.lead_id.date_deadline
self.team_id = self.lead_id.team_id
self.planned_revenue = self.lead_id.planned_revenue
self.title_action = self.lead_id.title_action
@api.onchange('next_activity_id')
def onchange_next_activity_id(self):
if not self.title_action:
self.title_action = self.next_activity_id.description
date_action = False
if self.next_activity_id and self.next_activity_id.days:
date_action = (datetime.now() + timedelta(days=self.next_activity_id.days)).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
self.date_action = date_action
@api.onchange('recommended_activity_id')
def onchange_recommended_activity_id(self):
self.next_activity_id = self.recommended_activity_id
@api.multi
def action_log_and_schedule(self):
self.ensure_one()
self.action_log()
view_id = self.env.ref('crm.crm_activity_log_view_form_schedule')
return {
'name': _('Next activity'),
'res_model': 'crm.activity.log',
'context': {
'default_last_activity_id': self.next_activity_id.id,
'default_lead_id': self.lead_id.id
},
'type': 'ir.actions.act_window',
'view_id': False,
'views': [(view_id.id, 'form')],
'view_mode': 'form',
'target': 'new',
'view_type': 'form',
'res_id': False
}
@api.multi
def action_log(self):
for log in self:
body_html = "<div><b>%(title)s</b>: %(next_activity)s</div>%(description)s%(note)s" % {
'title': _('Activity Done'),
'next_activity': log.next_activity_id.name,
'description': log.title_action and '<p><em>%s</em></p>' % log.title_action or '',
'note': log.note or '',
}
log.lead_id.message_post(body_html, subject=log.title_action, subtype_id=log.next_activity_id.subtype_id.id)
log.lead_id.write({
'date_deadline': log.date_deadline,
'planned_revenue': log.planned_revenue,
'title_action': False,
'date_action': False,
'next_activity_id': False,
})
return True
@api.multi
def action_schedule(self):
for log in self:
log.lead_id.write({
'title_action': log.title_action,
'date_action': log.date_action,
'next_activity_id': log.next_activity_id.id,
})
return True
@@ -1,85 +0,0 @@
<?xml version="1.0"?>
<odoo>
<record id="crm_activity_log_view_form" model="ir.ui.view">
<field name="name">Log an Activity</field>
<field name="model">crm.activity.log</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form string="Log an Activity">
<group>
<field name="team_id" invisible="1"/>
<field name="lead_id" invisible="1"/>
<group>
<field name="next_activity_id" required="1"
domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]"
options="{'no_create': True, 'no_open': True}"/>
<field name="title_action" placeholder="e.g. Discuss proposal"/>
</group>
<group>
<field name="planned_revenue"/>
<field name="date_deadline"/>
</group>
</group>
<field name="note" placeholder="Log a note..."/>
<footer>
<button name="action_log_and_schedule" string="Log &amp; Schedule Next" type="object" class="btn btn-primary"/>
<button name="action_log" string="Log Only" type="object" class="btn btn-primary"/>
<button string="Cancel" class="btn btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="crm_activity_log_view_form_schedule" model="ir.ui.view">
<field name="name">Schedule next Activity</field>
<field name="model">crm.activity.log</field>
<field name="priority">40</field>
<field name="arch" type="xml">
<form string="Schedule Next Activity">
<group class="mt0">
<field name="team_id" invisible="1"/>
<field name="lead_id" invisible="1"/>
<field name="last_activity_id" invisible="1"/>
<group>
<field name="next_activity_id"
domain="['|', ('team_id', '=', False), ('team_id', '=', team_id)]"
options="{'no_create': True, 'no_open': True}"/>
<field name="date_action"/>
<field name="title_action" placeholder="e.g. Discuss proposal"/>
</group>
<group>
<div>
<p><strong>Recommended activities:</strong></p>
<field name="recommended_activity_id" widget="radio" class="o_recommended_activity"
domain="[('preceding_activity_ids', '=', last_activity_id)]"/>
</div>
</group>
</group>
<footer>
<button name="action_schedule" string="Schedule Activity" type="object" class="btn btn-primary"/>
<button string="No activity" class="btn btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="crm_activity_log_action" model="ir.actions.act_window">
<field name="name">Log an Activity</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">crm.activity.log</field>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_activity_log_view_form"/>
<field name="target">new</field>
</record>
<record id="crm_activity_schedule_action" model="ir.actions.act_window">
<field name="name">Schedule an Activity</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">crm.activity.log</field>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_activity_log_view_form_schedule"/>
<field name="target">new</field>
</record>
</odoo>
+1 -1
View File
@@ -124,7 +124,7 @@ class Lead2OpportunityPartner(models.TransientModel):
if self.name == 'merge':
leads = self.with_context(active_test=False).opportunity_ids.merge_opportunity()
if not leads.active:
leads.write({'active': True, 'next_activity_id': False, 'lost_reason': False})
leads.write({'active': True, 'activity_type_id': False, 'lost_reason': False})
if leads.type == "lead":
values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]})
self.with_context(active_ids=leads.ids)._convert_opportunity(values)
+1 -1
View File
@@ -4,7 +4,7 @@
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">res.partner.carrier.property.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="mail.view_emails_partner_info_form" />
<field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail" />
<field name="arch" type="xml">
<field name="opt_out" position="after">
<field name="property_delivery_carrier_id"/>
+3 -1
View File
@@ -39,7 +39,9 @@ You can define the different phases of interviews and easily rate the applicant
'views/hr_job_views.xml',
'views/tour_views.xml',
],
'demo': ['data/hr_recruitment_demo.xml'],
'demo': [
'data/hr_recruitment_demo.xml',
],
'test': ['test/recruitment_process.yml'],
'installable': True,
'auto_install': False,
@@ -58,8 +58,6 @@
<field name="partner_name">Enrique Jones</field>
<field name="partner_mobile">9963214587</field>
<field name="stage_id" ref="stage_job1"/>
<field eval="time.strftime('%Y-%m-15 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_traineemca0" model="hr.applicant">
<field name="name">Trainee - MCA</field>
@@ -73,8 +71,6 @@
<field name="partner_mobile">9988774455</field>
<field name="stage_id" ref="stage_job4"/>
<field name="partner_phone">6633225</field>
<field eval="time.strftime('%Y-%m-10 18:15:00')" name="date_action"/>
<field name="title_action">Call to define real needs about training</field>
</record>
<record id="hr_case_fresher0" model="hr.applicant">
<field name="name">Fresher</field>
@@ -87,8 +83,6 @@
<field name="partner_name">Jose</field>
<field name="stage_id" ref="stage_job3"/>
<field name="partner_phone">999666735</field>
<field eval="time.strftime('%Y-%m-5 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_yrsexperienceinphp0" model="hr.applicant">
<field name="name">Marketing Job</field>
@@ -99,8 +93,6 @@
<field name="user_id" ref="base.user_root"/>
<field name="partner_name">John Bruno</field>
<field name="stage_id" ref="stage_job5"/>
<field eval="time.strftime('%Y-%m-24 18:15:00')" name="date_action"/>
<field name="title_action">Call to define real needs about training</field>
</record>
<record id="hr_case_marketingjob0" model="hr.applicant">
<field name="name">More than 5 yrs Experience in PHP</field>
@@ -111,8 +103,6 @@
<field name="user_id" ref="base.user_demo"/>
<field name="partner_name">Sandra Elvis</field>
<field name="stage_id" ref="stage_job5"/>
<field eval="time.strftime('%Y-%m-20 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_financejob0" model="hr.applicant">
<field name="name">Finance Manager</field>
@@ -125,8 +115,6 @@
<field name="partner_name">David Armstrong</field>
<field name="stage_id" ref="stage_job2"/>
<field name="partner_phone">33968745</field>
<field eval="time.strftime('%Y-%m-18 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_traineemca1" model="hr.applicant">
<field name="name">Trainee - MCA</field>
@@ -138,8 +126,6 @@
<field name="partner_mobile">9898745745</field>
<field name="stage_id" ref="stage_job4"/>
<field name="partner_phone">6630125</field>
<field eval="time.strftime('%Y-%m-15 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_programmer" model="hr.applicant">
<field name="name">Programmer</field>
@@ -152,8 +138,6 @@
<field name="stage_id" ref="stage_job4"/>
<field name="partner_phone">6630125</field>
<field name="salary_expected">11000.0</field>
<field eval="time.strftime('%Y-%m-24 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_advertisement" model="hr.applicant">
<field name="name">Advertisement</field>
@@ -165,8 +149,6 @@
<field name="partner_mobile">9988774455</field>
<field name="stage_id" ref="stage_job2"/>
<field name="salary_expected">11000.0</field>
<field eval="time.strftime('%Y-%m-15 18:15:00')" name="date_action"/>
<field name="title_action">Send mail regarding our interview</field>
</record>
<record id="hr_case_salesman0_cv" model="ir.attachment">
@@ -235,5 +217,27 @@
<field name="author_id" ref="base.partner_demo"/>
</record>
<record id="mail_activity_0" model="mail.activity">
<field name="res_id" ref="hr_recruitment.hr_case_salesman0" />
<field name="res_model_id" ref="model_hr_applicant"/>
<field name="activity_type_id" ref="mail.mail_activity_data_email" />
<field name="date_deadline" eval="time.strftime('%Y-%m-15 18:15:00')"/>
<field name="summary">Send mail regarding our interview</field>
</record>
<record id="mail_activity_1" model="mail.activity">
<field name="res_id" ref="hr_recruitment.hr_case_traineemca0" />
<field name="res_model_id" ref="model_hr_applicant"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
<field name="date_deadline" eval="time.strftime('%Y-%m-10 18:15:00')"/>
<field name="summary">Call to define real needs about training</field>
</record>
<record id="mail_activity_2" model="mail.activity">
<field name="res_id" ref="hr_recruitment.hr_case_yrsexperienceinphp0" />
<field name="res_model_id" ref="model_hr_applicant"/>
<field name="activity_type_id" ref="mail.mail_activity_data_call" />
<field name="date_deadline" eval="time.strftime('%Y-%m-24 18:15:00')"/>
<field name="summary">Call to define real needs about training</field>
</record>
</data>
</odoo>
@@ -88,7 +88,7 @@ class Applicant(models.Model):
_name = "hr.applicant"
_description = "Applicant"
_order = "priority desc, id desc"
_inherit = ['mail.thread', 'utm.mixin']
_inherit = ['mail.thread', 'mail.activity.mixin', 'utm.mixin']
_mail_mass_mailing = _('Applicants')
def _default_stage_id(self):
@@ -135,8 +135,6 @@ class Applicant(models.Model):
date_closed = fields.Datetime("Closed", readonly=True, index=True)
date_open = fields.Datetime("Assigned", readonly=True, index=True)
date_last_stage_update = fields.Datetime("Last Stage Update", index=True, default=fields.Datetime.now)
date_action = fields.Date("Next Action Date")
title_action = fields.Char("Next Action", size=64)
priority = fields.Selection(AVAILABLE_PRIORITIES, "Appreciation", default='0')
job_id = fields.Many2one('hr.job', "Applied Job")
salary_proposed_extra = fields.Char("Proposed Salary Extra", help="Salary Proposed by the Organisation, extra advantages")
@@ -36,8 +36,6 @@
<field name="partner_phone"/>
<field name="job_id"/>
<field name="stage_id"/>
<field name="title_action" invisible="context.get('invisible_next_action', True)"/>
<field name="date_action" invisible="context.get('invisible_next_date', True)"/>
<field name="medium_id"/>
<field name="source_id"/>
<field name="priority"/>
@@ -49,6 +47,21 @@
</field>
</record>
<record id="hr_applicant_view_tree_activity" model="ir.ui.view">
<field name="name">hr.applicant.view.tree.activity</field>
<field name="model">hr.applicant</field>
<field name="arch" type="xml">
<tree string="Next Activities" decoration-danger="activity_date_deadline &lt; current_date" default_order="activity_date_deadline">
<field name="name"/>
<field name="partner_id"/>
<field name="activity_date_deadline"/>
<field name="activity_type_id"/>
<field name="activity_summary"/>
<field name="stage_id"/>
</tree>
</field>
</record>
<record model="ir.ui.view" id="crm_case_form_view_job">
<field name="name">Jobs - Recruitment Form</field>
<field name="model">hr.applicant</field>
@@ -113,11 +126,6 @@
</group>
<group>
<field name="user_id"/>
<label for="title_action"/>
<div>
<field name="date_action"/>
<field name="title_action" class="oe_inline" placeholder="e.g. Call for interview"/>
</div>
<field name="priority" widget="priority"/>
<field name="medium_id" groups="base.group_no_one" />
<field name="source_id"/>
@@ -150,6 +158,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -178,17 +187,13 @@
</record>
<record id="view_crm_case_jobs_filter" model="ir.ui.view">
<field name="name">Jobs - Recruitment Search</field>
<field name="name">hr.applicant.view.search</field>
<field name="model">hr.applicant</field>
<field name="arch" type="xml">
<search string="Search Applicants">
<field name="partner_name" filter_domain="['|','|',('name','ilike',self),('partner_name','ilike',self),('email_from','ilike',self)]" string="Subject / Applicant"/>
<filter string="My" domain="[('user_id', '=', uid)]"/>
<filter string="My Applications" domain="[('user_id', '=', uid)]"/>
<filter string="Unassigned" domain="[('user_id', '=', False)]"/>
<separator/>
<filter string="Next Actions" context="{'invisible_next_action':False, 'invisible_next_date':False}"
domain="[('date_action','&lt;&gt;',False)]" help="Filter and view on next actions and date"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<field name="job_id"/>
@@ -196,6 +201,17 @@
<field name="user_id"/>
<field name="stage_id" domain="[]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<field name="categ_ids"/>
<field name="attachment_ids" filter_domain="[('attachment_ids.index_content', 'ilike', self)]" string="Attachments"/>
<separator/>
@@ -211,17 +227,15 @@
</field>
</record>
<!-- CRM Lead Calendar View -->
<record model="ir.ui.view" id="hr_applicant_calendar_view">
<field name="name">Hr Applicants Calendar</field>
<field name="model">hr.applicant</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="Applicants"
date_start="date_action" color="user_id">
<calendar string="Applicants" date_start="activity_date_deadline" color="user_id">
<field name="name"/>
<field name="partner_name"/>
<field name="title_action"/>
<field name="activity_summary"/>
</calendar>
</field>
</record>
@@ -240,12 +254,13 @@
<field name="partner_name"/>
<field name="type_id"/>
<field name="job_id"/>
<field name="title_action"/>
<field name="department_id"/>
<field name="categ_ids"/>
<field name="message_needaction_counter"/>
<field name="attachment_number"/>
<field name="active"/>
<field name="activity_ids" />
<field name="activity_state" />
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@@ -273,13 +288,12 @@
<i><field name="name"/></i><br/>
<field name="job_id"/><br/>
<t t-if="record.partner_mobile.raw_value">Mobile: <field name="partner_mobile"/><br/></t>
<t t-if="record.date_action.raw_value and record.date_action.raw_value lt (new Date())" t-set="red">oe_kanban_text_red</t>
<span t-attf-class="#{red || ''}"><field name="date_action"/></span>
<t t-if="record.date_action.raw_value"> : </t>
<field name="title_action"/>
<div class="oe_kanban_bottom_right">
<div class="o_kanban_inline_block ml4">
<field name="activity_ids" widget="kanban_activity"/>
</div>
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div class="pull-left" groups="base.group_user">
<div class="pull-left mt4" groups="base.group_user">
<field name="priority" widget="priority"/>
</div>
</div>
@@ -316,6 +330,22 @@
</p>
</field>
</record>
<record id="hr_applicant_action_activity" model="ir.actions.act_window">
<field name="name">My Next Activities</field>
<field name="res_model">hr.applicant</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban,calendar</field>
<field name="view_id" ref="hr_applicant_view_tree_activity"/>
<field name="context">{'search_default_activities_my': 1}</field>
<field name="domain">[('activity_date_deadline', '!=', False)]</field>
<field name="help" type="html">
<p>
Currently there are not any activity scheduled.
</p>
</field>
</record>
<record model="ir.actions.act_window" id="action_hr_job_sources">
<field name="name">Jobs Sources</field>
<field name="res_model">hr.recruitment.source</field>
@@ -499,7 +529,7 @@
process and follow up all operations: meetings, interviews, etc.
</p><p>
Applicants and their attached CV are created automatically when an email is sent.
If you install the document management modules, all resumes are indexed automatically,
If you install the document management modules, all resumes are indexed automatically,
so that you can easily search through their content.
</p>
</field>
@@ -526,7 +556,7 @@
<field name="act_window_id" ref="crm_case_categ0_act_job"/>
</record>
<record model="ir.actions.act_window.view" id="action_hr_sec_graph_view_act_job">
<record id="action_hr_sec_graph_view_act_job" model="ir.actions.act_window.view">
<field name="sequence" eval="4"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm_case_graph_view_job"/>
@@ -549,7 +579,18 @@
<menuitem
name="Applications"
parent="menu_hr_recruitment_root"
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="2" groups="base.group_no_one"/>
id="menu_crm_case_categ0_act_job" sequence="2"/>
<menuitem
name="All Applications"
parent="menu_crm_case_categ0_act_job"
id="menu_crm_case_categ_all_app" action="crm_case_categ0_act_job" sequence="2"/>
<menuitem id="hr_applicant_nmenu_activity"
name="Next Activities"
parent="hr_recruitment.menu_crm_case_categ0_act_job"
action="hr_applicant_action_activity"
sequence="20"/>
<!-- Resume and Letters -->
<record id="ir_attachment_view_search_inherit_hr_recruitment" model="ir.ui.view">
+4
View File
@@ -36,10 +36,12 @@ Main Features
'views/mail_followers_views.xml',
'views/mail_channel_views.xml',
'views/mail_shortcode_views.xml',
'views/mail_activity_views.xml',
'views/res_config_views.xml',
'data/mail_data.xml',
'data/mail_channel_data.xml',
'data/mail_shortcode_data.xml',
'data/mail_activity_data.xml',
'security/mail_security.xml',
'security/ir.model.access.csv',
'views/mail_alias_views.xml',
@@ -57,6 +59,7 @@ Main Features
'installable': True,
'application': True,
'qweb': [
'static/src/xml/activity.xml',
'static/src/xml/client_action.xml',
'static/src/xml/composer.xml',
'static/src/xml/chatter.xml',
@@ -66,5 +69,6 @@ Main Features
'static/src/xml/chat_window.xml',
'static/src/xml/extended_chat_window.xml',
'static/src/xml/announcement.xml',
'static/src/xml/web_kanban_activity.xml',
],
}
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="mail_activity_data_email" model="mail.activity.type">
<field name="name">Email</field>
<field name="icon">fa-envelope</field>
<field name="summary">Send an Email</field>
</record>
<record id="mail_activity_data_call" model="mail.activity.type">
<field name="name">Call</field>
<field name="icon">fa-phone</field>
<field name="summary">Make a Call</field>
<field name="days">2</field>
</record>
<record id="mail_activity_data_meeting" model="mail.activity.type">
<field name="name">Meeting</field>
<field name="icon">fa-users</field>
<field name="summary">Reminder for meeting</field>
</record>
<record id="mail_activity_data_todo" model="mail.activity.type">
<field name="name">Todo</field>
<field name="icon">fa-tasks</field>
<field name="summary">Task to perform</field>
<field name="days">5</field>
</record>
</data>
</odoo>
+6
View File
@@ -56,6 +56,12 @@
<field name="internal" eval="True"/>
<field name="sequence" eval="100"/>
</record>
<record id="mt_activities" model="mail.message.subtype">
<field name="name">Activities</field>
<field name="default" eval="True"/>
<field name="internal" eval="True"/>
<field name="sequence" eval="90"/>
</record>
</data>
<data>
+1
View File
@@ -6,6 +6,7 @@ import mail_alias
import mail_followers
import mail_notification
import mail_message
import mail_activity
import mail_mail
import mail_thread
import mail_channel
+226
View File
@@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime, timedelta
from odoo import api, fields, models
class MailActivityType(models.Model):
""" Activity Types are used to categorize activities. Each type is a different
kind of activity e.g. call, mail, meeting. An activity can be generic i.e.
available for all models using activities; or specific to a model in which
case res_model_id field should be used. """
_name = 'mail.activity.type'
_description = 'Activity Type'
_rec_name = 'name'
_order = 'sequence'
name = fields.Char('Name', required=True, translate=True)
summary = fields.Char('Summary', translate=True)
sequence = fields.Integer('Sequence', default=10)
days = fields.Integer(
'# Days', default=0,
help='Number of days before executing the action. It allows to plan the action deadline.')
icon = fields.Char('Icon', help="Font awesome icon e.g. fa-tasks")
res_model_id = fields.Many2one(
'ir.model', 'Model', index=True,
help='Specify a model if the activity should be specific to a model'
'and not available when managing activities for other models.')
class MailActivity(models.Model):
""" An actual activity to perform. Activities are linked to
documents using res_id and res_model_id fields. Activities have a deadline
that can be used in kanban view to display a status. Once done activities
are unlinked and a message is posted. This message has a new activity_type_id
field that indicates the activity linked to the message. """
_name = 'mail.activity'
_description = 'Activity'
_order = 'date_deadline ASC'
_rec_name = 'summary'
@api.model
def default_get(self, fields):
res = super(MailActivity, self).default_get(fields)
if not fields or 'res_model_id' in fields and res.get('res_model'):
res['res_model_id'] = self.env['ir.model'].sudo().search([('model', '=', res['res_model'])]).id
return res
# owner
res_id = fields.Integer('Related Document ID', index=True, required=True)
res_model_id = fields.Many2one(
'ir.model', 'Related Document Model',
index=True, ondelete='cascade', required=True)
res_model = fields.Char(
'Related Document Model',
index=True, related='res_model_id.model', store=True, readonly=True)
res_name = fields.Char(
'Document Name', compute='_compute_res_name', store=True,
help="Display name of the related document.", readonly=True)
# activity
activity_type_id = fields.Many2one(
'mail.activity.type', 'Activity',
domain="['|', ('res_model_id', '=', False), ('res_model_id', '=', res_model_id)]")
icon = fields.Char('Icon', related='activity_type_id.icon')
summary = fields.Char('Summary')
note = fields.Html('Note')
date_deadline = fields.Date('Due Date', index=True, required=True, default=fields.Date.today)
# description
user_id = fields.Many2one(
'res.users', 'Assigned to',
default=lambda self: self.env.user,
index=True, required=True)
state = fields.Selection([
('overdue', 'Overdue'),
('today', 'Today'),
('planned', 'Planned')], 'State',
compute='_compute_state')
@api.depends('res_model', 'res_id')
def _compute_res_name(self):
for activity in self:
activity.res_name = self.env[activity.res_model].browse(activity.res_id).name_get()[0][1]
@api.depends('date_deadline')
def _compute_state(self):
today = date.today()
for record in self.filtered(lambda activity: activity.date_deadline):
date_deadline = fields.Date.from_string(record.date_deadline)
diff = (date_deadline - today)
if diff.days == 0:
record.state = 'today'
elif diff.days < 0:
record.state = 'overdue'
else:
record.state = 'planned'
@api.onchange('activity_type_id')
def _onchange_activity_type_id(self):
if self.activity_type_id:
if not self.summary:
self.summary = self.activity_type_id.summary
self.date_deadline = (datetime.now() + timedelta(days=self.activity_type_id.days))
@api.model
def create(self, values):
activity = super(MailActivity, self).create(values)
activity.post_assignation_message()
return activity
def write(self, values):
res = super(MailActivity, self).write(values)
if values.get('user_id'):
self.post_assignation_message()
return res
def post_assignation_message(self):
for activity in self:
record = self.env[activity.res_model].browse(activity.res_id)
record.with_context(
mail_post_autofollow=True
).message_post_with_view(
'mail.message_activity_assigned',
values={'activity': activity},
partner_ids=[activity.user_id.partner_id.id],
subtype_id=self.env.ref('mail.mt_note').id,
)
@api.multi
def action_done(self):
message = self.env['mail.message']
for activity in self:
record = self.env[activity.res_model].browse(activity.res_id)
record.message_post_with_view(
'mail.message_activity_done',
values={'activity': activity},
subtype_id=self.env.ref('mail.mt_activities').id,
mail_activity_type_id=activity.activity_type_id.id,
)
message |= record.message_ids[0]
self.unlink()
return message.ids and message.ids[0] or False
@api.multi
def action_close_dialog(self):
return {'type': 'ir.actions.act_window_close'}
class MailActivityMixin(models.AbstractModel):
""" Mail Activity Mixin is a mixin class to use if you want to add activities
management on a model. It works like the mail.thread mixin. It defines
an activity_ids one2many field toward activities using res_id and res_model_id.
Various related / computed fields are also added to have a global status of
activities on documents.
Activities come with a new JS widget for the form view. It is integrated in the
Chatter widget although it is a separate widget. It displays activities linked
to the current record and allow to schedule, edit and mark done activities.
Use widget="mail_activity" on activity_ids field in form view to use it.
There is also a kanban widget defined. It defines a small widget to integrate
in kanban vignettes. It allow to manage activities directly from the kanban
view. Use widget="kanban_activity" on activitiy_ids field in kanban view to
use it."""
_name = 'mail.activity.mixin'
_description = 'Activity Mixin'
activity_ids = fields.One2many(
'mail.activity', 'res_id', 'Activities',
auto_join=True,
domain=lambda self: [('res_model', '=', self._name)])
activity_state = fields.Selection([
('overdue', 'Overdue'),
('today', 'Today'),
('planned', 'Planned')], string='State',
compute='_compute_activity_state',
help='Status based on activities\nOverdue: Due date is already passed\n'
'Today: Activity date is today\nPlanned: Future activities.')
activity_user_id = fields.Many2one(
'res.users', 'Responsible',
related='activity_ids.user_id',
search='_search_activity_user_id')
activity_type_id = fields.Many2one(
'mail.activity.type', 'Next Activity Type',
related='activity_ids.activity_type_id',
search='_search_activity_type_id')
activity_date_deadline = fields.Date(
'Next Activity Deadline', related='activity_ids.date_deadline',
readonly=True, store=True) # store to enable ordering + search
activity_summary = fields.Char(
'Next Activity Summary', related='activity_ids.summary',
search='_search_activity_summary')
@api.depends('activity_ids.state')
def _compute_activity_state(self):
for record in self:
states = record.activity_ids.mapped('state')
if 'overdue' in states:
record.activity_state = 'overdue'
elif 'today' in states:
record.activity_state = 'today'
elif 'planned' in states:
record.activity_state = 'planned'
@api.model
def _search_activity_user_id(self, operator, operand):
return [('activity_ids.user_id', operator, operand)]
@api.model
def _search_activity_type_id(self, operator, operand):
return [('activity_ids.activity_type_id', operator, operand)]
@api.model
def _search_activity_summary(self, operator, operand):
return [('activity_ids.summary', operator, operand)]
@api.multi
def unlink(self):
""" Override unlink to delete records activities through (res_model, res_id). """
record_ids = self.ids
result = super(MailActivityMixin, self).unlink()
self.env['mail.activity'].sudo().search(
[('res_model', '=', self._name), ('res_id', 'in', record_ids)]
).unlink()
return result
+6 -1
View File
@@ -66,11 +66,16 @@ class Followers(models.Model):
default_subtypes = self.env['mail.message.subtype'].search([
('default', '=', True),
'|', ('res_model', '=', res_model), ('res_model', '=', False)])
external_default_subtypes = default_subtypes.filtered(lambda subtype: not subtype.internal)
if force_mode:
employee_pids = self.env['res.users'].sudo().search([('partner_id', 'in', partner_data.keys()), ('share', '=', False)]).mapped('partner_id').ids
for pid, data in partner_data.iteritems():
if not data:
partner_data[pid] = default_subtypes.ids
if pid not in employee_pids:
partner_data[pid] = external_default_subtypes.ids
else:
partner_data[pid] = default_subtypes.ids
for cid, data in channel_data.iteritems():
if not data:
channel_data[cid] = default_subtypes.ids
+3
View File
@@ -61,6 +61,9 @@ class Message(models.Model):
"message, comment for other messages such as user replies",
oldname='type')
subtype_id = fields.Many2one('mail.message.subtype', 'Subtype', ondelete='set null', index=True)
mail_activity_type_id = fields.Many2one(
'mail.activity.type', 'Mail Activity Type',
index=True, ondelete='set null')
# origin
email_from = fields.Char(
'From', default=_get_default_from,
+1 -1
View File
@@ -14,7 +14,7 @@ class Partner(models.Model):
""" Update partner to add a field about notification preferences. Add a generic opt-out field that can be used
to restrict usage of automatic email templates. """
_name = "res.partner"
_inherit = ['res.partner', 'mail.thread']
_inherit = ['res.partner', 'mail.thread', 'mail.activity.mixin']
_mail_flat_thread = False
_mail_mass_mailing = _('Customers')
+5 -1
View File
@@ -33,4 +33,8 @@ access_mail_template,mail.template,model_mail_template,base.group_user,1,1,1,0
access_mail_template_system,mail.template_system,model_mail_template,base.group_system,1,1,1,1
access_mail_shortcode,mail.shortcode,model_mail_shortcode,base.group_user,1,1,1,1
access_mail_shortcode_portal,mail.shortcode.portal,model_mail_shortcode,base.group_portal,1,0,0,0
access_mail_test_all,mail.test.all,model_mail_test,base.group_user,1,1,1,1
access_mail_test_all,mail.test.all,model_mail_test,base.group_user,1,1,1,1
access_mail_activity_all,mail.activity.all,model_mail_activity,,1,0,0,0
access_mail_activity_user,mail.activity.user,model_mail_activity,base.group_user,1,1,1,1
access_mail_activity_type_all,mail.activity.type.all,model_mail_activity_type,,1,0,0,0
access_mail_activity_type_user,mail.activity.type.user,model_mail_activity_type,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
33 access_mail_template_system mail.template_system model_mail_template base.group_system 1 1 1 1
34 access_mail_shortcode mail.shortcode model_mail_shortcode base.group_user 1 1 1 1
35 access_mail_shortcode_portal mail.shortcode.portal model_mail_shortcode base.group_portal 1 0 0 0
36 access_mail_test_all mail.test.all model_mail_test base.group_user 1 1 1 1
37 access_mail_activity_all mail.activity.all model_mail_activity 1 0 0 0
38 access_mail_activity_user mail.activity.user model_mail_activity base.group_user 1 1 1 1
39 access_mail_activity_type_all mail.activity.type.all model_mail_activity_type 1 0 0 0
40 access_mail_activity_type_user mail.activity.type.user model_mail_activity_type base.group_user 1 1 1 1
+218
View File
@@ -0,0 +1,218 @@
odoo.define('mail.activity', function (require) {
"use strict";
var core = require('web.core');
var form_common = require('web.form_common');
var Model = require("web.Model");
var Widget = require('web.Widget');
var ChatManager = require('mail.chat_manager');
var QWeb = core.qweb;
var _t = core._t;
/**
* Set the 'label_delay' entry in activity data according to the deadline date
* @param {Array} list of activity Object
* @return {Array} : list of modified activity Object
*/
var set_delay_label = function(activities){
var today = moment().startOf('day');
var today_str = today.format('YYYY-MM-DD');
_.each(activities, function(activity){
var to_display = '';
var deadline = moment(activity.date_deadline + ' 00:00:00');
var diff = deadline.diff(today, 'days', true); // true means no rounding
if(diff === 0){
to_display = _t('Today');
}else{
if(diff < 0){ // overdue
if(diff === -1){
to_display = _t('Yesterday');
}else{
to_display = _.str.sprintf(_t('%d days overdue'), Math.abs(diff));
}
}else{ // due
if(diff === 1){
to_display = _t('Tomorrow');
}else{
to_display = _.str.sprintf(_t('Due in %d days'), Math.abs(diff));
}
}
}
activity['label_delay'] = to_display;
});
return activities;
};
// -----------------------------------------------------------------------------
// Activities Widget ('mail_activity' widget)
//
// Since it is displayed on a form view, it extends 'AbstractField' widget.
//
// Note: the activity widget is moved inside the chatter by the chatter itself
// for layout purposes.
// -----------------------------------------------------------------------------
var Activity = form_common.AbstractField.extend({
className: 'o_mail_activity',
events: {
"click .o_activity_edit": "on_activity_edit",
"click .o_activity_unlink": "on_activity_unlink",
"click .o_activity_done": "on_activity_done",
},
init: function () {
this._super.apply(this, arguments);
this.context = this.options.context || {};
this.activities = [];
this.Activity = new Model('mail.activity');
},
start: function () {
var self = this;
// Hide the chatter in 'create' mode
this.view.on("change:actual_mode", this, this.check_visibility);
// find chatter if any
this.chatter = this.field_manager.fields.message_ids;
return this._super();
},
get_res_id: function () {
return this.view.datarecord.id;
},
check_visibility: function () {
this.set({"force_invisible": this.view.get("actual_mode") === "create"});
},
set_value: function (_value) {
this.value = _value;
this._super(_value);
},
fetch_and_render_value: function () {
var self = this;
return new Model(this.view.dataset.model)
.call("read", [[this.get_res_id()], ['activity_ids']])
.then(function (results) {
self.value = results[0]['activity_ids'];
return self.render_value();
});
},
render_value: function () {
var self = this;
return this.Activity
.call("read", [this.value])
.then(function (results) {
this.activities = set_delay_label(results);
self.$el.html(QWeb.render('mail.activity_items', {
activities: this.activities,
}));
});
},
on_activity_schedule: function (event) {
event.preventDefault();
var self = this;
var action = {
type: 'ir.actions.act_window',
res_model: 'mail.activity',
view_mode: 'form',
view_type: 'form',
views: [
[false, 'form']
],
target: 'new',
context: _.extend({
default_res_id: this.get_res_id(),
default_res_model: this.view.dataset.model,
}, {'mark_done': true}, this.context),
res_id: false,
};
return this.do_action(action, {
on_close: function() {
self.fetch_and_render_value();
self.chatter.refresh_followers();
ChatManager.get_messages({model: self.res_model, res_id: self.get_res_id()});
},
});
},
on_activity_edit: function (event) {
event.preventDefault();
var self = this;
var activity_id = this.$(event.currentTarget).data('activity-id');
var action = {
type: 'ir.actions.act_window',
res_model: 'mail.activity',
view_mode: 'form',
view_type: 'form',
views: [
[false, 'form']
],
target: 'new',
context: _.extend({
default_res_id: this.get_res_id(),
default_res_model: this.model,
}, {'mark_done': true}, this.context),
res_id: activity_id,
}
this.do_action(action, {
on_close: function () {
self.fetch_and_render_value();
self.chatter.refresh_followers();
ChatManager.get_messages({model: self.res_model, res_id: self.get_res_id()});
},
});
},
on_activity_unlink: function (event) {
event.preventDefault();
var self = this;
var activity_id = this.$(event.currentTarget).data('activity-id');
this.Activity
.call("unlink", [[activity_id]])
.then(function (res) {
self.fetch_and_render_value();
});
},
on_activity_done: function (event) {
event.preventDefault();
var self = this;
var activity_id = this.$(event.currentTarget).data('activity-id');
this.Activity
.call("action_done", [[activity_id]])
.then(function (msg_id) {
self.fetch_and_render_value();
self.chatter.msg_ids.unshift(msg_id);
// to stop scrollbar flickering add min height of the thread and remove after
// render. on render thread it will remove and add all it's element which cause flickering
var thread = self.chatter.thread;
thread.$el.css('min-height', thread.$el.height());
self.chatter.fetch_and_render_thread(self.chatter.msg_ids).then(function () {
thread.$el.css('min-height', '');
});
});
},
});
core.form_widget_registry.add('mail_activity', Activity);
return {
Activity: Activity,
set_delay_label: set_delay_label,
};
});
+23 -3
View File
@@ -10,7 +10,9 @@ var config = require('web.config');
var core = require('web.core');
var form_common = require('web.form_common');
var framework = require('web.framework');
var Model = require("web.Model");
var web_utils = require('web.utils');
var Widget = require('web.Widget');
var _t = core._t;
var QWeb = core.qweb;
@@ -278,8 +280,9 @@ var Chatter = form_common.AbstractField.extend({
template: 'mail.Chatter',
events: {
"click .o_chatter_button_new_message": "on_open_composer_new_message",
"click .o_chatter_button_log_note": "on_open_composer_log_note",
'click .o_chatter_button_new_message': 'on_open_composer_new_message',
'click .o_chatter_button_log_note': 'on_open_composer_log_note',
'click .o_activity_schedule': 'on_activity_schedule',
},
init: function () {
@@ -288,6 +291,10 @@ var Chatter = form_common.AbstractField.extend({
this.res_id = undefined;
this.context = this.options.context || {};
this.dp = new web_utils.DropPrevious();
// extends display options
this.options = _.extend(this.options || {}, {
'display_activity_button': this.field_manager.fields.activity_ids
});
},
willStart: function () {
@@ -315,6 +322,12 @@ var Chatter = form_common.AbstractField.extend({
this.followers.on('followers_update', this, this.on_followers_update);
}
// Move the activities widget (if any) inside the chatter
this.activities = this.field_manager.fields.activity_ids
if (this.activities) {
this.$('.o_chatter_topbar').after(this.activities.$el);
}
this.thread = new ChatThread(this, {
display_order: ChatThread.ORDER.DESC,
display_document_link: false,
@@ -476,6 +489,13 @@ var Chatter = form_common.AbstractField.extend({
this.followers.read_value();
}
},
on_activity_schedule: function (event) {
if (this.activities) {
this.activities.on_activity_schedule(event);
}
},
// composer toggle
on_open_composer_new_message: function () {
this.open_composer();
@@ -501,7 +521,7 @@ var Chatter = form_common.AbstractField.extend({
this.composer.on('input_focused', this, function () {
this.composer.mention_set_prefetched_partners(this.mention_suggestions || []);
});
this.composer.insertBefore(this.$('.o_mail_thread')).then(function () {
this.composer.insertAfter(this.$('.o_chatter_topbar')).then(function () {
// destroy existing composer
if (old_composer) {
old_composer.destroy();
@@ -0,0 +1,106 @@
odoo.define('web.kanban.activity', function (require) {
"use strict";
var core = require('web.core');
var Model = require("web.Model");
var Widget = require('web.Widget');
var kanban_widgets = require('web_kanban.widgets');
var AbstractField = kanban_widgets.AbstractField;
var fields_registry = kanban_widgets.registry;
var set_delay_label = require('mail.activity').set_delay_label;
var QWeb = core.qweb;
var KanbanActivity = AbstractField.extend({
template: 'mail.KanbanActivity',
events: {
"click .o_activity_btn": "get_activity_logs",
"click .o_schedule_activity": "on_activity_schedule",
"click .o_mark_as_done": "on_activity_done",
},
init: function(parent, field, $node) {
this._super.apply(this, arguments);
this.parent = parent;
this.activity_state = parent.record.activity_state.raw_value;
this.selection = _.object(parent.fields.activity_state.selection);
this.Activity = new Model("mail.activity");
},
get_activity_logs: function(event) {
event.preventDefault();
var self = this;
this.$('.o_activity').html(QWeb.render("mail.KanbanActivityLoading"));
this.Activity
.call("read", [this.field.raw_value])
.then(function (records) {
records = set_delay_label(records);
self.$('.o_activity').html($(QWeb.render("mail.KanbanActivityDropdown", {
selection: self.selection,
records: _.groupBy(records, 'state'),
uid: self.session.uid,
})));
});
},
reset_state: function () {
var self = this;
new Model(self.parent.model).query(['activity_ids', 'activity_state'])
.filter([['id', '=', self.parent.id]])
.first()
.then(function (record) {
var state_color_class = 'o_activity_color_' + (record.activity_state || 'default');
var current_state_class = 'o_activity_color_' + (self.activity_state || 'default');
self.$(".o_activity_btn > span").removeClass(current_state_class).addClass(state_color_class);
self.activity_state = record.activity_state;
self.parent.record.activity_ids.raw_value = record.activity_ids;
});
},
on_activity_schedule: function(event) {
var self = this;
var activity_id = this.$(event.currentTarget).data('activity-id') || false;
var action = {
type: 'ir.actions.act_window',
res_model: 'mail.activity',
view_mode: 'form',
view_type: 'form',
views: [
[false, 'form']
],
target: 'new',
context: _.extend({
default_res_id: this.parent.id,
default_res_model: this.parent.model,
}, {'mark_done': true}, this.context),
res_id: activity_id,
};
this.do_action(action, {
on_close: function() {
self.reset_state();
self.get_activity_logs(event);
}
});
},
on_activity_done: function (event) {
event.stopPropagation();
var self = this;
var activity_id = this.$(event.currentTarget).data('activity-id');
this.Activity
.call("action_done", [[activity_id]])
.then(function (result) {
self.get_activity_logs(event);
self.reset_state();
});
},
});
fields_registry.add('kanban_activity', KanbanActivity);
return {
KanbanActivity: KanbanActivity
};
});
@@ -0,0 +1,105 @@
/* Common */
.o_mail_activity {
&.o_form_field {
display: block;
}
.o_thread_date_separator.o_border_dashed {
border-bottom: dashed #eeeeee 2px;
}
.o_thread_message_core.o_mail_note {
padding: 0px 3px;
}
.o_activity_color_default {
color: @gray;
}
.o_activity_color_planned {
color: @brand-primary;
}
.o_activity_color_overdue {
color: @brand-danger;
}
.o_activity_color_today {
color: @brand-success;
}
.o_activity_link {
color: @gray;
&:hover {
color: @brand-success;
}
}
}
/* Kanban View */
.o_kanban_record {
.o_kanban_inline_block {
display: inline-block;
}
.o_mail_activity {
.o_activity_btn {
span.fa {
vertical-align: -35%;
}
}
.o_activity_link_kanban {
visibility: hidden;
}
ul.o_activity {
width: 290px;
& > li:last-child {
background-color: @odoo-brand-lightsecondary;
color: @gray-dark;
line-height: 3em;
}
li > ul.o_activity_log {
max-height: 300px;
overflow-y: auto;
color: @text-color;
li {
width: 100%;
display: inline-block;
&.o_activity_label {
line-height: 2.5em;
padding: 0px 5px;
background-color: @odoo-brand-lightsecondary;
}
&.o_schedule_activity {
padding: 8px 10px 8px 0px;
vertical-align: middle;
&:hover {
background-color: @gray-lighter;
.o_activity_link_kanban {
visibility: visible;
}
}
.o_activity_title {
width: 85%;
padding: 0px 5px 0px 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.o_no_activity {
padding: 10px;
cursor: initial;
}
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
@mail-thread-side-date-opacity: 0.6;
@mail-thread-note: @gray-lighter;
.o_mail_thread {
.o_mail_thread, .o_mail_activity {
.o_thread_show_more {
text-align: center;
}
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.activity_items">
<div t-if="activities.length" class="o_thread_date_separator o_border_dashed">
<span class="o_thread_date">Planned</span>
</div>
<t t-foreach="activities" t-as="activity">
<div class="o_thread_message">
<div class="o_thread_message_sidebar">
<img t-attf-src="/web/image/res.users/#{activity.user_id[0]}/image_small"
class="o_thread_message_avatar mb8"/>
<a href="#" class="o_activity_done o_activity_link" t-att-data-activity-id="activity.id"><span class="fa fa-check-circle fa-2x" /></a>
</div>
<div class="o_thread_message_core o_mail_note">
<div class="o_mail_info">
<strong>
<span t-attf-class="fa #{activity.icon}" />
<t t-esc="activity.activity_type_id[1]" />
</strong> planned by <strong><t t-esc="activity.user_id[1]" /></strong>
<span class="fa fa-clock-o ml8" />
<span t-attf-class="o_activity_date o_activity_color_#{activity.state}"><t t-esc="activity.label_delay" /></span>
<div class="pull-right">
<a href="#" class="o_activity_edit o_activity_link" t-att-data-activity-id="activity.id">Edit</a> |
<a href="#" class="o_activity_unlink o_activity_link" t-att-data-activity-id="activity.id">Remove</a>
</div>
</div>
<div t-if="activity.summary">
<strong><i><t t-esc="activity.summary"/></i></strong>
</div>
<div t-if="activity.note">
<t t-raw="activity.note"/>
</div>
</div>
</div>
</t>
</t>
</templates>
+3
View File
@@ -59,6 +59,9 @@
<button t-if="widget.options.display_log_button" class="btn btn-sm btn-link o_chatter_button_log_note" title="Log a note. Followers will not be notified.">
Log an internal note
</button>
<button t-if="widget.options.display_activity_button" class="btn btn-sm btn-link o_activity_schedule" title="Schedule an activity">
Schedule an Activity
</button>
</div>
</div>
</t>
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.KanbanActivity">
<div class="o_kanban_inline_block dropdown o_kanban_selection o_mail_activity">
<a class="dropdown-toggle o_activity_btn" data-toggle="dropdown">
<span t-attf-class="fa o_activity_color_#{widget.activity_state or 'default'} fa-chevron-circle-right fa-lg"/>
</a>
<ul class="dropdown-menu o_activity" role="menu">
</ul>
</div>
</t>
<t t-name="mail.KanbanActivityLoading">
<li class="text-center o_no_activity">
<span class="fa fa-spinner fa-spin fa-2x" />
</li>
</t>
<t t-name="mail.KanbanActivityDropdown">
<li t-if="_.isEmpty(records)" class="text-center o_no_activity">
<span>No activities planned.</span>
</li>
<li t-if="!_.isEmpty(records)">
<ul class="o_activity_log">
<t t-foreach="_.keys(records)" t-as="key">
<t t-set="logs" t-value="records[key]" />
<li class="o_activity_label">
<strong t-attf-class="o_activity_color_#{key}">
<t t-esc="selection[key]" /> (<span t-esc="logs.length" />)
</strong>
</li>
<li t-foreach="logs" t-as="log" t-att-data-activity-id="log.id" class="o_schedule_activity">
<div class="o_activity_title pull-left">
<span t-attf-class="fa #{log.icon} fa-fw" />
<strong>
<t t-esc="log.title_action or log.activity_type_id[1]" />
</strong>
<div>
<span class="fa fa-clock-o fa-fw" />
<span t-att-title="log.date_deadline"><t t-esc="log.label_delay" /></span>
<t t-if="log.user_id[0] != uid">
<span class="ml4 fa fa-user" />
<span><t t-esc="log.user_id[1]" /></span>
</t>
</div>
</div>
<div class="pull-right">
<span class="o_mark_as_done o_activity_link o_activity_link_kanban fa fa-check-circle fa-2x mt4" t-att-data-activity-id="log.id" title="Mark as done" />
</div>
</li>
</t>
</ul>
</li>
<li class="o_schedule_activity text-center">
<strong>Schedule an activity</strong>
</li>
</t>
</templates>
+16
View File
@@ -79,6 +79,22 @@ class TestMailFollowers(TestMail):
self.assertEqual(len(follower), 1)
self.assertEqual(follower.subtype_ids, self.default_group_subtypes)
def test_followers_subtypes_default_internal(self):
mt_mg_def_int = self.env['mail.message.subtype'].create({'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.channel', 'internal': True})
self.group_pigs.message_subscribe_users(user_ids=[self.user_employee.id])
follower = self.env['mail.followers'].search([
('res_model', '=', 'mail.channel'),
('res_id', '=', self.group_pigs.id),
('partner_id', '=', self.user_employee.partner_id.id)])
self.assertEqual(follower.subtype_ids, self.default_group_subtypes | mt_mg_def_int)
self.group_pigs.message_subscribe_users(user_ids=[self.user_portal.id])
follower = self.env['mail.followers'].search([
('res_model', '=', 'mail.channel'),
('res_id', '=', self.group_pigs.id),
('partner_id', '=', self.user_portal.partner_id.id)])
self.assertEqual(follower.subtype_ids, self.default_group_subtypes.filtered(lambda subtype: not subtype.internal))
def test_followers_subtypes_specified(self):
self.group_pigs.sudo(self.user_employee).message_subscribe_users(subtype_ids=[self.mt_mg_nodef.id])
self.assertEqual(self.group_pigs.message_follower_ids.mapped('partner_id'), self.user_employee.partner_id)
+141
View File
@@ -0,0 +1,141 @@
<?xml version="1.0"?>
<odoo>
<record id="mail_activity_type_view_form" model="ir.ui.view">
<field name="name">mail.activity.type.view.form</field>
<field name="model">mail.activity.type</field>
<field name="arch" type="xml">
<form string="Activities">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name"/></h1>
</div>
<group>
<group>
<field name="res_model_id"/>
<field name="icon" groups="base.group_no_one"/>
<field name="days"/>
</group>
<group>
<field name="summary"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="mail_activity_type_view_tree" model="ir.ui.view">
<field name="name">mail.activity.type.view.tree</field>
<field name="model">mail.activity.type</field>
<field name="arch" type="xml">
<tree string="Activities">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="summary"/>
<field name="days"/>
<field name="res_model_id" groups="base.group_no_one"/>
<field name="icon" groups="base.group_no_one"/>
</tree>
</field>
</record>
<record id="mail_activity_type_action" model="ir.actions.act_window">
<field name="name">Activities</field>
<field name="res_model">mail.activity.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record id="mail_activity_view_form_popup" model="ir.ui.view">
<field name="name">mail.activity.view.form.popup</field>
<field name="model">mail.activity</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form string="Log an Activity" create="false">
<sheet string="Activity">
<group>
<field name="res_model" invisible="1"/>
<field name="res_model_id" invisible="1"/>
<field name="res_id" invisible="1"/>
<group>
<field name="activity_type_id" required="1" options="{'no_create': True, 'no_open': True}"/>
<field name="summary" placeholder="e.g. Discuss proposal"/>
</group>
<group>
<field name="date_deadline"/>
<field name="user_id"/>
</group>
</group>
<field name="note" placeholder="Log a note..."/>
<footer>
<button string="Schedule" name="action_close_dialog"
type="object" class="btn-primary"/>
<button string="Mark as Done" name="action_done"
type="object" class="btn-primary"/>
<button string="Discard" class="btn-default" special="cancel" />
</footer>
</sheet>
</form>
</field>
</record>
<record id="mail_activity_view_search" model="ir.ui.view">
<field name="name">mail.activity.view.search</field>
<field name="model">mail.activity</field>
<field name="arch" type="xml">
<search string="Activity">
<field name="res_model_id"/>
<field name="summary"/>
<field name="activity_type_id"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('create_uid', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<separator/>
<group expand="0" string="Group By">
<filter string="Due Date" context="{'group_by': 'date_deadline'}"/>
<filter string="Created By" context="{'group_by': 'create_uid'}"/>
<filter string="Activity Type" context="{'group_by': 'activity_type_id'}"/>
</group>
</search>
</field>
</record>
<record id="mail_activity_view_tree" model="ir.ui.view">
<field name="name">mail.activity.view.tree</field>
<field name="model">mail.activity</field>
<field name="arch" type="xml">
<tree string="Next Activities"
decoration-danger="date_deadline &lt; current_date"
decoration-success="date_deadline == current_date"
default_order="date_deadline" create="false">
<field name="res_name"/>
<field name="activity_type_id"/>
<field name="summary"/>
<field name="date_deadline"/>
</tree>
</field>
</record>
<record id="mail_activity_view_calendar" model="ir.ui.view">
<field name="name">mail.activity.view.calendar</field>
<field name="model">mail.activity</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="Activity" date_start="date_deadline" color="activity_type_id">
<field name="res_name"/>
<field name="create_uid"/>
<field name="summary"/>
</calendar>
</field>
</record>
</odoo>
+28
View File
@@ -17,6 +17,8 @@
<script type="text/javascript" src="/mail/static/src/js/tour.js"></script>
<script type="text/javascript" src="/mail/static/src/js/utils.js"></script>
<script type="text/javascript" src="/mail/static/src/js/window_manager.js"></script>
<script type="text/javascript" src="/mail/static/src/js/activity.js"></script>
<script type="text/javascript" src="/mail/static/src/js/web_kanban_activity.js"></script>
<link rel="stylesheet" href="/mail/static/src/less/announcement.less"/>
<link rel="stylesheet" href="/mail/static/src/less/client_action.less" type="text/less"/>
@@ -27,6 +29,7 @@
<link rel="stylesheet" href="/mail/static/src/less/followers.less" type="text/less"/>
<link rel="stylesheet" href="/mail/static/src/less/thread.less" type="text/less"/>
<link rel="stylesheet" href="/mail/static/src/less/systray.less" type="text/less"/>
<link rel="stylesheet" href="/mail/static/src/less/mail_activity.less"/>
</xpath>
</template>
@@ -50,5 +53,30 @@
<p style="color:#9E588B;">Powered by <a href="https://www.odoo.com">Odoo</a>.</p>
</template>
<template id="message_activity_done">
<div>
<p>
<strong><span t-attf-class="fa #{activity.activity_type_id.icon}"/></strong> <span t-field="activity.activity_type_id.name"/> done
by <span t-field="activity.user_id.name"/>:
<span t-if="activity.summary" t-field="activity.summary"/>
</p>
<div t-field="activity.note"/>
</div>
</template>
<template id="message_activity_assigned">
<div>
<p>
Planned <span t-field="activity.activity_type_id.name"/> related to <t t-esc="object.name_get()[0][1]"/>:
<soan t-if="activity.summary" t-field="activity.summary"/>
</p>
<ul>
<li>Scheduled for: <span t-field="activity.date_deadline"/></li>
<li>Assigned to: <span t-field="activity.user_id.name"/></li>
</ul>
<div t-if="activity.note" t-field="activity.note"/>
</div>
</template>
</data>
</odoo>
+12
View File
@@ -21,6 +21,18 @@
<field name="alias_domain" placeholder="mycompany.odoo.com" class="oe_inline"/>
</div>
</xpath>
<xpath expr="//group[@name='authentication']" position='before'>
<group name="activity">
<label for="id" string="Activities"/>
<div>
<div>
<button class="oe_link" name="%(mail_activity_type_action)d" type="action">
<span>Configure the chatter activities</span>
</button>
</div>
</div>
</group>
</xpath>
</field>
</record>
</data>
+19 -5
View File
@@ -2,8 +2,8 @@
<odoo>
<data>
<record id="view_emails_partner_info_form" model="ir.ui.view">
<field name="name">res.partner.emails.info.inherit</field>
<record id="res_partner_view_form_inherit_mail" model="ir.ui.view">
<field name="name">res.partner.view.form.inherit.mail</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
@@ -15,14 +15,15 @@
<xpath expr="//sheet" position="after">
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="res_partner_opt_out_search">
<field name="name">res.partner.opt_out.search</field>
<record id="res_partner_view_search_inherit_mail" model="ir.ui.view">
<field name="name">res.partner.view.search.inherit.mail</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
@@ -31,6 +32,19 @@
<filter string="Available for mass mailing"
name='not_opt_out' domain="[('opt_out', '=', False)]"
help="Partners that did not ask not to be included in mass mailing campaigns" />
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<separator/>
</filter>
</field>
</record>
@@ -293,6 +293,7 @@ class MailComposer(models.TransientModel):
'record_name': self.record_name,
'no_auto_thread': self.no_auto_thread,
'mail_server_id': self.mail_server_id.id,
'mail_activity_type_id': self.mail_activity_type_id.id,
}
# mass mailing: rendering override wizard static values
if mass_mail_mode and self.model:
+2 -2
View File
@@ -89,7 +89,7 @@ class MaintenanceEquipmentCategory(models.Model):
class MaintenanceEquipment(models.Model):
_name = 'maintenance.equipment'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Equipment'
@api.multi
@@ -205,7 +205,7 @@ class MaintenanceEquipment(models.Model):
class MaintenanceRequest(models.Model):
_name = 'maintenance.request'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Maintenance Requests'
_order = "id desc"
+38 -2
View File
@@ -25,6 +25,18 @@
<separator/>
<filter string="My Requests" name="my_requests" domain="[('owner_user_id', '=', uid)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
@@ -87,6 +99,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -107,6 +120,8 @@
<field name="category_id"/>
<field name="kanban_state"/>
<field name="message_needaction_counter"/>
<field name="activity_ids" />
<field name="activity_state" />
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@@ -140,11 +155,15 @@
<span t-if="record.category_id.raw_value"><field name="category_id"/></span>
</div>
<div class="oe_kanban_bottom_right">
<div class="o_kanban_inline_block ml4 mr4">
<field name="activity_ids" widget="kanban_activity" />
</div>
<field name="kanban_state" widget="kanban_state_selection"/>
<img t-att-src="kanban_image('res.users', 'image_small', record.owner_user_id.raw_value)" t-att-title="record.owner_user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div class="pull-left" groups="base.group_user">
<field name="kanban_state" widget="kanban_state_selection"/>
<div class="pull-left mr4 mt4">
<field name="priority" widget="priority"/>
</div>
</div>
</div>
<div class="oe_clear"></div>
@@ -336,6 +355,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -358,6 +378,8 @@
<field name="maintenance_open_count"/>
<field name="next_action_date"/>
<field name="technician_user_id"/>
<field name="activity_ids" />
<field name="activity_state" />
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@@ -386,6 +408,9 @@
<div t-if="record.owner_user_id.raw_value"><field name="owner_user_id"/></div>
</div>
<div class="oe_kanban_bottom_right">
<div class="o_kanban_inline_block">
<field name="activity_ids" widget="kanban_activity" />
</div>
<img t-att-src="kanban_image('res.users', 'image_small', record.owner_user_id.raw_value)" t-att-title="record.owner_user_id.value" width="27" height="27" class="oe_kanban_avatar"/>
</div>
<div class="oe_kanban_footer_left mt4 label label-danger" t-if="record.maintenance_open_count.raw_value" >
@@ -438,6 +463,17 @@
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<!-- <filter string="Archived" name="inactive" domain="[('active','=',False)]"/> -->
<group expand='0' string='Group by...'>
<filter string='Technicians' domain="[]" context="{'group_by': 'technician_user_id'}"/>
+1 -1
View File
@@ -14,7 +14,7 @@ class MrpProduction(models.Model):
_name = 'mrp.production'
_description = 'Manufacturing Order'
_date_name = 'date_planned_start'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'date_planned_start asc,id'
@api.model
+1 -1
View File
@@ -8,7 +8,7 @@ from odoo.exceptions import UserError
class MrpUnbuild(models.Model):
_name = "mrp.unbuild"
_description = "Unbuild Order"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'id desc'
def _get_default_location_id(self):
+13
View File
@@ -162,6 +162,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -266,6 +267,18 @@
<field name="product_id" string="Raw Material" filter_domain="[('move_raw_ids.product_id','ilike',self)]"/>
<field name="name" string="Work Center" filter_domain="[('routing_id.operation_ids.workcenter_id','ilike',self)]"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By...">
<filter string="Product" domain="[]" context="{'group_by':'product_id'}"/>
<filter string="Routing" domain="[]" context="{'group_by':'routing_id'}"/>
+13
View File
@@ -22,6 +22,18 @@
<group expand="0" string="Filters">
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
<filter name="done" string="Done" domain="[('state', '=', 'done')]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
</group>
<group expand='0' string='Group by...'>
<filter string='Product' name="productgroup" context="{'group_by': 'product_id'}"/>
@@ -83,6 +95,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
+1 -1
View File
@@ -11,7 +11,7 @@ from odoo.exceptions import UserError
class Repair(models.Model):
_name = 'mrp.repair'
_description = 'Repair Order'
_inherit = 'mail.thread'
_inherit = ['mail.thread', 'mail.activity.mixin']
@api.model
def _default_stock_location(self):
@@ -192,6 +192,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -214,6 +215,18 @@
<filter string="Invoiced" domain="[('invoiced','=',True)]"/>
<field name="product_id"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter string="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Product" domain="[]" context="{'group_by':'product_id'}"/>
+1 -1
View File
@@ -33,7 +33,7 @@ class Tag(models.Model):
class Note(models.Model):
_name = 'note.note'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Note"
_order = 'sequence'
+13
View File
@@ -144,6 +144,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -160,6 +161,18 @@
<field name="tag_ids"/>
<filter name="open_true" string="Active" domain="[('open', '=', True)]"/>
<filter name="open_false" string="Archive" domain="[('open', '=', False)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter string="Stage" help="By sticky note Category" context="{'group_by':'stage_id'}"/>
</group>
+1 -1
View File
@@ -288,7 +288,7 @@ class Task(models.Model):
_name = "project.task"
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_mail_post_access = 'read'
_order = "priority desc, sequence, date_start, name, id"
+54 -2
View File
@@ -34,6 +34,18 @@
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter string="Project" name="project" context="{'group_by':'project_id'}"/>
<filter string="Task" context="{'group_by':'name'}"/>
@@ -399,6 +411,7 @@ action = model.activate_sample_project()
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -428,6 +441,8 @@ action = model.activate_sample_project()
<field name="legend_blocked"/>
<field name="legend_normal"/>
<field name="legend_done"/>
<field name="activity_ids"/>
<field name="activity_state"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
@@ -463,8 +478,9 @@ action = model.activate_sample_project()
</div>
<div class="oe_kanban_bottom_right">
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div class="pull-left" groups="base.group_user">
<field name="kanban_state" widget="kanban_state_selection"/>
<div class="pull-left mr4">
<field name="activity_ids" widget="kanban_activity"/>
<field name="kanban_state" widget="kanban_state_selection" groups="base.group_user"/>
</div>
</div>
</div>
@@ -496,6 +512,21 @@ action = model.activate_sample_project()
</field>
</record>
<record id="project_task_view_tree_activity" model="ir.ui.view">
<field name="name">project.task.tree.activity</field>
<field name="model">project.task</field>
<field name="arch" type="xml">
<tree string="Next Activities" decoration-danger="activity_date_deadline &lt; current_date" default_order="activity_date_deadline">
<field name="name"/>
<field name="project_id"/>
<field name="activity_date_deadline"/>
<field name="activity_type_id"/>
<field name="activity_summary"/>
<field name="stage_id"/>
</tree>
</field>
</record>
<record id="view_task_calendar" model="ir.ui.view">
<field name="name">project.task.calendar</field>
<field name="model">project.task</field>
@@ -572,6 +603,21 @@ action = model.activate_sample_project()
<field name="act_window_id" ref="action_view_task"/>
</record>
<record id="project_task_action_activity" model="ir.actions.act_window">
<field name="name">My Next Task Activities</field>
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban,calendar</field>
<field name="view_id" ref="project_task_view_tree_activity"/>
<field name="context">{'search_default_activities_my':1}</field>
<field name="domain">[('activity_date_deadline', '!=', False)]</field>
<field name="help" type="html">
<p>
Currently there are not any activity scheduled.
</p>
</field>
</record>
<menuitem action="action_view_task" id="menu_action_view_task" parent="project.menu_project_management" sequence="5"/>
<record id="project_task_action_from_partner" model="ir.actions.act_window">
@@ -581,6 +627,12 @@ action = model.activate_sample_project()
<field name="search_view_id" ref="view_task_search_form"/>
</record>
<menuitem id="project_task_menu_activity"
name="Next Task Activities"
parent="project.menu_project_management"
action="project_task_action_activity"
sequence="25"/>
<record id="action_view_task_overpassed_draft" model="ir.actions.act_window">
<field name="name">Overpassed Tasks</field>
<field name="res_model">project.task</field>
+1 -1
View File
@@ -9,7 +9,7 @@ from odoo.tools.safe_eval import safe_eval
class ProjectIssue(models.Model):
_name = "project.issue"
_description = "Project Issue"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = "priority desc, create_date desc"
_mail_post_access = 'read'
@@ -60,6 +60,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -86,6 +87,21 @@
</field>
</record>
<record id="project_issue_view_tree_activity" model="ir.ui.view">
<field name="name">project.issue.view.tree.activity</field>
<field name="model">project.issue</field>
<field name="arch" type="xml">
<tree string="Next Activities" decoration-danger="activity_date_deadline &lt; current_date" default_order="activity_date_deadline">
<field name="name"/>
<field name="project_id"/>
<field name="activity_date_deadline"/>
<field name="activity_type_id"/>
<field name="activity_summary"/>
<field name="stage_id"/>
</tree>
</field>
</record>
<record id="view_project_issue_filter" model="ir.ui.view">
<field name="name">project.issue.search.view</field>
<field name="model">project.issue</field>
@@ -105,6 +121,18 @@
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By" >
<filter string="Assigned to" name="Responsible" context="{'group_by':'user_id'}" />
<filter string="Project" name="project" context="{'group_by':'project_id'}" />
@@ -150,6 +178,8 @@
<field name="legend_blocked"/>
<field name="legend_done"/>
<field name="legend_normal"/>
<field name="activity_ids" />
<field name="activity_state" />
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@@ -182,6 +212,9 @@
</t>
</div>
<div class="oe_kanban_bottom_right">
<div class="o_kanban_inline_block ml4 mr4">
<field name="activity_ids" widget="kanban_activity" />
</div>
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar pull-right"/>
<div class="pull-left" groups="base.group_user">
<field name="kanban_state" widget="kanban_state_selection"/>
@@ -315,6 +348,28 @@
</field>
</record>
<record id="project_issue_action_activity" model="ir.actions.act_window">
<field name="name">My Next Issue Activities</field>
<field name="res_model">project.issue</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,kanban,calendar</field>
<field name="view_id" ref="project_issue_view_tree_activity"/>
<field name="context">{'search_default_activities_my':1}</field>
<field name="domain">[('activity_date_deadline', '!=', False)]</field>
<field name="help" type="html">
<p>
Currently there are not any activity scheduled.
</p>
</field>
</record>
<menuitem id="menu_project_issue_track" name="Issues" parent="project.menu_project_management"
action="project_issue_categ_act0" sequence="15"/>
<menuitem id="project_issue_menu_activity"
name="Next Issue Activities"
parent="project.menu_project_management"
action="project_issue_action_activity"
sequence="25"/>
</odoo>
+1 -1
View File
@@ -15,7 +15,7 @@ import odoo.addons.decimal_precision as dp
class PurchaseOrder(models.Model):
_name = "purchase.order"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Purchase Order"
_order = 'date_order desc, id desc'
+13
View File
@@ -298,6 +298,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -320,6 +321,18 @@
<filter name="not_invoiced" string="Waiting Bills" domain="[('invoice_status','=', 'to invoice')]" help="Purchase orders that include lines not invoiced."/>
<filter name="invoiced" string="Bills Received" domain="[('invoice_status','=', 'invoiced')]" help="Purchase orders that have been invoiced."/>
<filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter string="Vendor" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Order Date" domain="[]" context="{'group_by':'date_order'}"/>
+1 -1
View File
@@ -14,7 +14,7 @@ import odoo.addons.decimal_precision as dp
class SaleOrder(models.Model):
_name = "sale.order"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Sales Order"
_order = 'date_order desc, id desc'
+13
View File
@@ -355,6 +355,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -389,6 +390,18 @@
<filter string="My Orders" domain="[('user_id','=',uid)]" name="my_sale_orders_filter"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<group expand="0" string="Group By">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter name="customer" string="Customer" domain="[]" context="{'group_by':'partner_id'}"/>
@@ -27,7 +27,7 @@
</a>
</td>
<td class="o_main" title="To Activities">
<a href="#" class="o_dashboard_action" name="crm.crm_lead_action_activities" data-context="{'search_default_today':'1'}">
<a href="#" class="o_dashboard_action" name="crm.crm_lead_action_activities" data-context="{'search_default_activities_today':'1'}">
<span class="o_highlight">
<t t-if="!show_demo">
<t t-esc="values['activity']['today']"/>
@@ -59,7 +59,7 @@
</a>
</td>
<td class="o_main" title="To Activities">
<a href="#" class="o_dashboard_action" name="crm.crm_lead_action_activities" data-context="{'search_default_this_week':'1'}">
<a href="#" class="o_dashboard_action" name="crm.crm_lead_action_activities" data-context="{'search_default_activities_week':'1'}">
<t t-if="!show_demo">
<t t-esc="values['activity']['next_7_days']"/>
</t>
@@ -74,7 +74,7 @@
<td></td>
<!-- Hide overdue when there is none -->
<td t-if="show_demo or (values and values['activity']['overdue'] != 0)" class="o_warning" title="To Activities">
<a href="#" class="o_dashboard_action" name="crm.crm_lead_action_activities" data-context="{'search_default_overdue':'1'}">
<a href="#" class="o_dashboard_action" name="crm.crm_lead_action_activities" data-context="{'search_default_activities_overdue':'1'}">
<t t-if="!show_demo">
<t t-esc="values['activity']['overdue']"/> overdue
</t>
@@ -84,7 +84,7 @@
</a>
</td>
<td t-if="show_demo or (values and values['closing']['overdue'] != 0)" class="o_warning" title="To Opportunities">
<a href="#" class="o_dashboard_action" name="crm.action_your_pipeline" data-context="{'search_default_overdue':'1'}">
<a href="#" class="o_dashboard_action" name="crm.action_your_pipeline" data-context="{'search_default_activities_overdue':'1'}">
<t t-if="!show_demo">
<t t-esc="values['closing']['overdue']"/> exp. closing
</t>
+1 -1
View File
@@ -157,7 +157,7 @@ class PickingType(models.Model):
class Picking(models.Model):
_name = "stock.picking"
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Transfer"
_order = "priority desc, date asc, id desc"
+1 -1
View File
@@ -8,7 +8,7 @@
<record id="view_partner_stock_form" model="ir.ui.view">
<field name="name">res.partner.stock.property.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="mail.view_emails_partner_info_form"/>
<field name="inherit_id" ref="mail.res_partner_view_form_inherit_mail"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='container_row_2']" position="after">
<group name="container_row_stock" groups="base.group_no_one">
@@ -382,6 +382,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -403,6 +404,18 @@
<filter name="late" string="Late" domain="[('min_date','&lt;', time.strftime('%%Y-%%m-%%d %%H:%%M:%%S'))]" help="Pickings that are late on scheduled time"/>
<separator/>
<filter name="backorder" string="Backorders" domain="[('backorder_id','&lt;&gt;', False)]" help="Remaining parts of picking partially processed"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
<field name="product_id"/>
<field name="picking_type_id"/>
@@ -6,7 +6,7 @@ from odoo.exceptions import UserError
class StockPickingWave(models.Model):
_inherit = "mail.thread"
_inherit = ['mail.thread']
_name = "stock.picking.wave"
_description = "Picking Wave"
_order = "name desc"
+1 -1
View File
@@ -53,7 +53,7 @@ class Survey(models.Model):
_name = 'survey.survey'
_description = 'Survey'
_rec_name = 'title'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'mail.activity.mixin']
def _default_stage(self):
return self.env['survey.stage'].search([], limit=1).id
+18
View File
@@ -161,6 +161,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -194,6 +195,8 @@
<field name="color" />
<field name="auth_required" />
<field name="public_url" />
<field name="activity_ids" />
<field name="activity_state" />
<templates>
<div t-name="kanban-box" t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click">
<div class="o_dropdown_kanban dropdown" t-if="widget.editable">
@@ -226,6 +229,9 @@
<t t-if="record.tot_start_survey.raw_value &lt;= 0"><a style="color: #aaaaaa;">Analyze Answers</a></t>
</li>
</ul></div>
<div class="oe_kanban_bottom_right">
<field name="activity_ids" widget="kanban_activity" />
</div>
</div>
</div>
</templates>
@@ -239,6 +245,18 @@
<search string="Survey">
<field string="Title" name="title"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
<separator/>
<filter string="My Activities" name="activities_my"
domain="[('activity_ids.user_id', '=', uid)]"/>
<filter string="Today Activities" name="activities_today"
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
<filter string="Upcoming Activities (7 days)" name="activities_week"
domain="['&amp;', ('activity_ids.date_deadline', '&gt;=', context_today().strftime('%Y-%m-%d')),
('activity_ids.date_deadline', '&lt;=', (context_today()+datetime.timedelta(days=7)).strftime('%Y-%m-%d'))
]"/>
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all records which has next action date is before today"/>
</search>
</field>
</record>
@@ -550,6 +550,7 @@
height: 12px;
width: 12px;
border-radius: 6px;
vertical-align: middle;
background-color: @odoo-brand-secondary;
@@ -94,10 +94,10 @@ class WebsiteAccount(website_account):
searchbar_filters = {
'all': {'label': _('All'), 'domain': []},
'today': {'label': _('Today Activities'), 'domain': [('date_action', '=', today)]},
'today': {'label': _('Today Activities'), 'domain': [('activity_date_deadline', '=', today)]},
'week': {'label': _('This Week Activities'),
'domain': [('date_action', '>=', today), ('date_action', '<=', this_week_end_date)]},
'overdue': {'label': _('Overdue Activities'), 'domain': [('date_action', '<', today)]},
'domain': [('activity_date_deadline', '>=', today), ('activity_date_deadline', '<=', this_week_end_date)]},
'overdue': {'label': _('Overdue Activities'), 'domain': [('activity_date_deadline', '<', today)]},
'won': {'label': _('Won'), 'domain': [('stage_id.probability', '=', 100), ('stage_id.fold', '=', True)]},
'lost': {'label': _('Lost'), 'domain': [('active', '=', False)]},
}
@@ -158,8 +158,9 @@ class WebsiteAccount(website_account):
return request.render(
"website_crm_partner_assign.portal_my_opportunity", {
'opportunity': lead,
'user_activity': lead.activity_ids.filtered(lambda activity: activity.user_id == request.env.user)[:1],
'stages': request.env['crm.stage'].search([('probability', '!=', '100')], order='sequence desc'),
'activities': request.env['crm.activity'].sudo().search([], order='sequence desc'),
'activity_types': request.env['mail.activity.type'].sudo().search([]),
'states': request.env['res.country.state'].sudo().search([]),
'countries': request.env['res.country'].sudo().search([]),
})
@@ -226,30 +226,33 @@ class CrmLead(models.Model):
@api.multi
def update_lead_portal(self, values):
self.check_access_rights('write')
for lead in self:
if values['date_action'] == '':
values['date_action'] = False
if lead.next_activity_id.id != values['activity_id'] or lead.title_action != values['title_action']\
or lead.date_action != values['date_action']:
activity = lead.sudo().next_activity_id
body_html = "<div><b>%(title)s</b>: %(next_activity)s</div>%(description)s" % {
'title': _('Activity Done'),
'next_activity': activity.name,
'description': lead.title_action and '<p><em>%s</em></p>' % lead.title_action or '',
}
lead.message_post(
body=body_html,
subject=lead.title_action,
subtype="mail.mt_note")
lead.write({
lead_values = {
'planned_revenue': values['planned_revenue'],
'probability': values['probability'],
'next_activity_id': values['activity_id'],
'title_action': values['title_action'],
'date_action': values['date_action'] if values['date_action'] else False,
'priority': values['priority'],
'date_deadline': values['date_deadline'] if values['date_deadline'] else False,
})
}
# As activities may belong to several users, only the current portal user activity
# will be modified by the portal form. If no activity exist we create a new one instead
# that we assign to the portal user.
user_activity = lead.activity_ids.filtered(lambda activity: activity.user_id == self.env.user)[:1]
if user_activity:
user_activity.sudo().write({
'activity_type_id': values['activity_type_id'],
'summary': values['activity_summary'],
'date_deadline': values['activity_date_deadline'],
})
else:
self.env['mail.activity'].sudo().create({
'res_model_id': self.env.ref('crm.model_crm_lead').id,
'res_id': lead.id,
'user_id': self.env.user.id,
'activity_type_id': values['activity_type_id'],
'summary': values['activity_summary'],
'date_deadline': values['activity_date_deadline'],
})
lead.write(lead_values)
@api.model
def create_opp_portal(self, values):
@@ -276,4 +279,4 @@ class CrmLead(models.Model):
lead.convert_opportunity(lead.partner_id.id)
return {
'id': lead.id
}
}
@@ -13,5 +13,4 @@ partner_access_crm_lead,crm.lead,crm.model_crm_lead,base.group_portal,1,1,0,0
access_res_partner_grade_invoicing_payment,res.partner.grade,model_res_partner_grade,account.group_account_invoice,1,0,0,0
access_res_partner_grade_portal,res.partner.grade,model_res_partner_grade,base.group_portal,1,0,0,0
crm_lead_portal_access,crm.lead.portal,crm.model_crm_lead,base.group_portal,1,1,0,0
crm_activity_portal_access,crm.activity.portal,crm.model_crm_activity,base.group_portal,1,1,1,0
mail_message_subtype_portal_access,mail.message.subtype.portal,mail.model_mail_message_subtype,base.group_portal,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
13 access_res_partner_grade_invoicing_payment res.partner.grade model_res_partner_grade account.group_account_invoice 1 0 0 0
14 access_res_partner_grade_portal res.partner.grade model_res_partner_grade base.group_portal 1 0 0 0
15 crm_lead_portal_access crm.lead.portal crm.model_crm_lead base.group_portal 1 1 0 0
crm_activity_portal_access crm.activity.portal crm.model_crm_activity base.group_portal 1 1 1 0
16 mail_message_subtype_portal_access mail.message.subtype.portal mail.model_mail_message_subtype base.group_portal 1 1 1 0
@@ -37,7 +37,7 @@ $('.interested_partner_assign_confirm').on('click',function(e){
$('.desinterested_partner_assign_confirm').on('click',function(){
var $btn = this;
var $btn = $(this);
$btn.prop('disabled', true);
new Model('crm.lead')
.call("partner_desinterested",
@@ -55,7 +55,7 @@ $('.desinterested_partner_assign_confirm').on('click',function(){
});
opp_stage_buttons.on('click',function(e){
var $btn = this;
var $btn = $(this);
$btn.prop('disabled', true);
new Model('crm.lead')
.call("write", [[parseInt(e.currentTarget.getAttribute('opp'))],{
@@ -76,7 +76,7 @@ $('.edit_contact_form .country_id').on('change', function(){
});
$('.edit_contact_confirm').on('click',function(){
var $btn = this;
var $btn = $(this);
$btn.prop('disabled', true);
new Model('crm.lead')
.call("write", [[parseInt($('.edit_contact_form .opportunity_id').val())],{
@@ -120,16 +120,15 @@ $('.new_opp_confirm').on('click',function(e){
});
$('.edit_opp_confirm').on('click',function(){
var $btn = this;
var $btn = $(this);
$btn.prop('disabled', true);
new Model('crm.lead')
.call("update_lead_portal", [[parseInt($('.edit_opp_form .opportunity_id').val())],{
activity_id: parseInt($('.edit_opp_form .next_activity').find(":selected").attr('data')),
planned_revenue: parseFloat($('.edit_opp_form .planned_revenue').val()),
probability: parseFloat($('.edit_opp_form .probability').val()),
date_deadline: $('.edit_opp_form .date_deadline').val(),
title_action: $('.edit_opp_form .title_action').val(),
date_action: $('.edit_opp_form .date_action').val(),
activity_type_id: parseInt($('.edit_opp_form .next_activity').find(":selected").attr('data')),
activity_summary: $('.edit_opp_form .activity_summary').val(),
activity_date_deadline: $('.edit_opp_form .activity_date_deadline').val(),
priority: $('input[name="PriorityRadioOptions"]:checked').val(),
}])
.fail(function() {
@@ -143,14 +142,14 @@ $('.edit_opp_confirm').on('click',function(){
$('.edit_opp_form .next_activity').on('change', function(){
var selected = $('.edit_opp_form .next_activity').find(":selected");
if(selected.attr('description')){
$('.edit_opp_form .title_action').val(selected.attr('description'));
if(selected.attr('activity_summary')){
$('.edit_opp_form .activity_summary').val(selected.attr('activity_summary'));
}
if(selected.attr('days')){
var date_now = moment();
var days = parseInt(selected.attr('days'));
var date = date_now.add(days, 'days');
$('.edit_opp_form .date_action').val(date.format('YYYY-MM-DD'));
$('.edit_opp_form .activity_date_deadline').val(date.format('YYYY-MM-DD'));
}
});
@@ -757,19 +757,19 @@
<div class="form-group">
<label class="control-label" for="next_activity">Next Activity</label>
<select class="form-control next_activity" name="next_activity">
<t t-foreach="activities" t-as="activity">
<option t-att-data="activity.id" t-att-selected="activity.id == opportunity.next_activity_id.id" t-att-name="activity.name" t-att-days="activity.days" t-att-description="activity.description"><t t-esc="activity.name"/></option>
<t t-foreach="activity_types" t-as="activity_type">
<option t-att-data="activity_type.id" t-att-selected="activity_type.id == user_activity.activity_type_id.id" t-att-name="activity_type.name" t-att-days="activity_type.days" t-att-summary="activity_type.summary"><t t-esc="activity_type.name"/></option>
</t>
</select>
</div>
<div class="form-group">
<label class="control-label" for="title_action">Details Next Activity</label>
<textarea row="3" name="title_action" class="form-control title_action"><t t-esc="opportunity.title_action"/></textarea>
<label class="control-label" for="activity_summary">Details Next Activity</label>
<textarea row="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea>
</div>
<div class="form-group">
<label class="control-label" for="date_action">Next Activity Date</label>
<label class="control-label" for="activity_date_deadline">Next Activity Date</label>
<div class='input-group date'>
<input type="text" name="date_action" t-att-value="opportunity.date_action" class="form-control date_action" data-date-format="YYYY-MM-DD" t-att-name="prefix" placeholder="YYYY-MM-DD" />
<input type="text" name="activity_date_deadline" t-att-value="user_activity.date_deadline" class="form-control activity_date_deadline" data-date-format="YYYY-MM-DD" t-att-name="prefix" placeholder="YYYY-MM-DD" />
<span class="input-group-addon"><span class="fa fa-calendar"></span></span>
</div>
</div>
@@ -782,16 +782,16 @@
</div>
</div>
</div>
<div class="row">
<div class="row" t-if="user_activity">
<label class="col-xs-4">Next Activity</label>
<span class="col-xs-8">
<span t-field="opportunity.next_activity_id"/>
<span t-if="opportunity.date_action">&#160;on&#160;</span>
<span t-field="opportunity.date_action"/>
<span t-field="user_activity.activity_type_id"/>
<span t-if="user_activity.date_deadline">&#160;on&#160;</span>
<span t-field="user_activity.date_deadline"/>
</span>
</div>
<div class="row">
<span class="col-xs-8 col-xs-offset-4" t-field="opportunity.title_action"/>
<div class="row" t-if="user_activity">
<span class="col-xs-8 col-xs-offset-4" t-field="user_activity.summary"/>
</div>
</div>
</div>