[ADD][mail_activity_board] Add new module that insert activities board in boards. (#283)

* [ADD] Module that insert activities board.

* [FIX] Author error in __manifest__ file and style changes.

* [FIX] Fix replace in view, rename files and style changes.

* [FIX] Enumerated list ends without a blank line; unexpected unindent.

* [FIX] Name fail.

* [FIX] Bug in view.

* [FIX]  Add button Activities in mail.thread and readme folder. Others improvements in style of code.

* [FIX] Type 'tree' not found in registry: problem solved.

* [FIX] Dependence change: 'mail' for 'calendar'.

* [FIX] Eliminated unnecessary imports.

* [FIX] Bugs about js and if/else.

* [FIX] Improvements following guide lines and eliminating unnecessary attributes in views.

* [ADD] Added counter in the 'Activities List' button.

* [FIX] Bugs in javascript with 'Activities' button.

* [ADD] Tests folder.

* [FIX] Deleted references to modules not installed.

* [FIX] Formatting javascript.

* [FIX] Bug: added a soft line before a class.

* [FIX] Bug: https://github.com/OCA/social/pull/283#discussion_r204302325

* [FIX] Escaping 'lt' in xml file. Bug: https://github.com/OCA/social/pull/283#discussion_r204302325

* [FIX] The meeting attendees are shown in kanban mode on the meeting board.

* [FIX] Hide in form view of the activity board the assistant field if the activity is not a meeting type or if there are no assistants.

* [FIX] Change to default kanban view for partners.
This commit is contained in:
Dajuayen 2018-11-02 10:38:21 +01:00 committed by Joan Mateu Jordi
parent e757c2a2dd
commit 2dda6e20df
15 changed files with 540 additions and 0 deletions

View File

@ -0,0 +1,21 @@
**This file is going to be generated by oca-gen-addon-readme.**
*Manual changes will be overwritten.*
Please provide content in the ``readme`` directory:
* **DESCRIPTION.rst** (required)
* INSTALL.rst (optional)
* CONFIGURE.rst (optional)
* **USAGE.rst** (optional, highly recommended)
* DEVELOP.rst (optional)
* ROADMAP.rst (optional)
* HISTORY.rst (optional, recommended)
* **CONTRIBUTORS.rst** (optional, highly recommended)
* CREDITS.rst (optional)
Content of this README will also be drawn from the addon manifest,
from keys such as name, authors, maintainers, development_status,
and license.
A good, one sentence summary in the manifest is also highly recommended.

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,24 @@
# Copyright 2018 David Juaneda - <djuaneda@sdi.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Activities board',
'summary': 'Add Activity Boards',
'version': '11.0.1.0.0',
'development_status': 'Beta',
'category': 'Social Network',
'website': 'https://github.com/OCA/social',
'author': 'SDi, David Juaneda, Odoo Community Association (OCA)',
'license': 'AGPL-3',
'installable': True,
'depends': [
'calendar',
'board',
],
'data': [
'views/templates.xml',
'views/mail_activity_view.xml',
],
'qweb': [
'static/src/xml/inherit_chatter.xml',
]
}

View File

@ -0,0 +1,2 @@
from . import mail_activity
from . import mail_activity_mixin

View File

@ -0,0 +1,45 @@
# Copyright 2018 David Juaneda - <djuaneda@sdi.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, models, fields
class MailActivity(models.Model):
_inherit = "mail.activity"
res_model_id_name = fields.Char(
related='res_model_id.name', string="Origin",
readonly=True)
duration = fields.Float(
related='calendar_event_id.duration', readonly=True)
calendar_event_id_start = fields.Datetime(
related='calendar_event_id.start', readonly=True)
calendar_event_id_partner_ids = fields.Many2many(
related='calendar_event_id.partner_ids',
readonly=True)
@api.multi
def open_origin(self):
self.ensure_one()
vid = self.env[self.res_model].browse(self.res_id).get_formview_id()
response = {
'type': 'ir.actions.act_window',
'res_model': self.res_model,
'view_mode': 'form',
'res_id': self.res_id,
'target': 'current',
'flags': {
'form': {
'action_buttons': False
}
},
'views': [
(vid, "form")
]
}
return response
@api.model
def action_activities_board(self):
action = self.env.ref(
'mail_activity_board.open_boards_activities').read()[0]
return action

View File

@ -0,0 +1,32 @@
# Copyright 2018 David Juaneda - <djuaneda@sdi.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models
class MailActivityMixin(models.AbstractModel):
_inherit = 'mail.activity.mixin'
def redirect_to_activities(self, **kwargs):
"""Redirects to the list of activities of the object shown.
Redirects to the activity board and configures the domain so that
only those activities that are related to the object shown are
displayed.
Add to the title of the view the name the class of the object from
which the activities will be displayed.
:param kwargs: contains the id of the object and the model it's about.
:return: action.
"""
id = kwargs.get("id")
action = self.env['mail.activity'].action_activities_board()
views = []
for v in action['views']:
if v[1] == 'tree':
v = (v[0], 'list')
views.append(v)
action['views'] = views
action['domain'] = [('res_id', '=', id)]
return action

View File

@ -0,0 +1,3 @@
* `SDI <https://www.sdi.es>`_:
* David Juaneda

View File

@ -0,0 +1,2 @@
This module adds an activity board with form, tree, kanban, calendar, pivot, graph and search views.

View File

@ -0,0 +1,9 @@
To use this module, you need to:
#. Access to the views from menu Boards.
A smartButton of activities is added in the mail thread from form view.
From this smartButton is linked to the activity board, to the view tree,
which shows the activities related to the opportunity.
From the form view of the activity you can navigate to the origin of the activity.

View File

@ -0,0 +1,33 @@
/* Copyright 2018 David Juaneda
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define('mail.Chatter.activity', function (require) {
"use strict";
var chatter = require('mail.Chatter');
chatter.include({
events: _.extend({}, chatter.prototype.events, {
'click .o_chatter_button_list_activity': '_onListActivity',
}),
/**
* Performs the action to redirect to the activities of the object.
*
* @private
*/
_onListActivity: function () {
this._rpc({
model: this.record.model,
method: 'redirect_to_activities',
args: [[]],
kwargs: {
'id':this.record.res_id,
'model':this.record.model,
},
context: this.record.getContext(),
}).then($.proxy(this, "do_action"));
},
});
});

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-extend="mail.Chatter.Buttons">
<t t-jquery="button.o_chatter_button_schedule_activity" t-operation="after">
<button t-if="schedule_activity_btn" class="btn btn-sm btn-link o_chatter_button_list_activity"
title="See activities list" type="button">
<i class="fa fa-list"/> Activities
</button>
</t>
</t>
</templates>

View File

@ -0,0 +1 @@
from . import test_mail_activity_board

View File

@ -0,0 +1,136 @@
# Copyright 2018 David Juaneda - <djuaneda@sdi.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests.common import TransactionCase
class TestMailActivityBoardMethods(TransactionCase):
def setUp(self):
super(TestMailActivityBoardMethods, self).setUp()
# Set up activities
# Create a user as 'Crm Salesman' and added few groups
self.employee = self.env['res.users'].create({
'company_id': self.env.ref("base.main_company").id,
'name': "Employee",
'login': "csu",
'email': "crmuser@yourcompany.com",
'groups_id': [(6, 0, [self.env.ref('base.group_user').id])]
})
# lead_model_id = self.env['ir.model']._get('crm.lead').id
partner_model_id = self.env['ir.model']._get('res.partner').id
ActivityType = self.env['mail.activity.type']
self.activity1 = ActivityType.create({
'name': 'Initial Contact',
'days': 5,
'summary': 'ACT 1 : Presentation, barbecue, ... ',
'res_model_id': partner_model_id,
})
self.activity2 = ActivityType.create({
'name': 'Call for Demo',
'days': 6,
'summary': 'ACT 2 : I want to show you my ERP !',
'res_model_id': partner_model_id,
})
self.activity3 = ActivityType.create({
'name': 'Celebrate the sale',
'days': 3,
'summary': 'ACT 3 : '
'Beers for everyone because I am a good salesman !',
'res_model_id': partner_model_id,
})
# I create an opportunity, as employee
self.partner_client = self.env.ref("base.res_partner_1")
self.act1 = self.env['mail.activity'].sudo().create({
'activity_type_id': self.activity3.id,
'note': 'Partner activity 1.',
'res_id': self.partner_client.id,
'res_model_id': partner_model_id,
'user_id': self.employee.id
})
self.act2 = self.env['mail.activity'].sudo().create({
'activity_type_id': self.activity2.id,
'note': 'Partner activity 2.',
'res_id': self.partner_client.id,
'res_model_id': partner_model_id,
'user_id': self.employee.id
})
self.act3 = self.env['mail.activity'].sudo().create({
'activity_type_id': self.activity3.id,
'note': 'Partner activity 3.',
'res_id': self.partner_client.id,
'res_model_id': partner_model_id,
'user_id': self.employee.id
})
def get_view(self, activity):
action = activity.open_origin()
result = self.env[action.get('res_model')]\
.load_views(action.get('views'))
return result.get('fields_views').get(action.get('view_mode'))
def test_open_origin_res_partner(self):
""" This test case checks
- If the method redirects to the form view of the correct one
of an object of the 'res.partner' class to which the activity
belongs.
"""
# Id of the form view for the class 'crm.lead', type 'lead'
form_view_partner_id = self.env.ref('base.view_partner_form').id
# Id of the form view return open_origin()
view = self.get_view(self.act1)
# Check the next view is correct
self.assertEqual(form_view_partner_id, view.get('view_id'))
# Id of the form view return open_origin()
view = self.get_view(self.act2)
# Check the next view is correct
self.assertEqual(form_view_partner_id, view.get('view_id'))
# Id of the form view return open_origin()
view = self.get_view(self.act3)
# Check the next view is correct
self.assertEqual(form_view_partner_id, view.get('view_id'))
def test_redirect_to_activities(self):
""" This test case checks
- if the method returns the correct action,
- if the correct activities are shown.
"""
action_id = self.env.ref(
'mail_activity_board.open_boards_activities').id
action = self.partner_client\
.redirect_to_activities(**{'id': self.partner_client.id})
self.assertEqual(action.get('id'), action_id)
kwargs = {
'groupby': [
"activity_type_id"
]
}
kwargs['domain'] = action.get('domain')
result = self.env[action.get('res_model')]\
.load_views(action.get('views'))
fields = result.get('fields_views').get('kanban').get('fields')
kwargs['fields'] = list(fields.keys())
result = self.env['mail.activity'].read_group(**kwargs)
acts = []
for group in result:
records = self.env['mail.activity'].search_read(
domain=group.get('__domain'), fields=kwargs['fields']
)
acts += [id.get('id') for id in records]
for act in acts:
self.assertIn(act, self.partner_client.activity_ids.ids)

View File

@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!--
VIEWS
-->
<!-- FORM VIEW -->
<record id="mail_activity_view_form_board" model="ir.ui.view">
<field name="name">mail.activity.boards.view.form</field>
<field name="model">mail.activity</field>
<field name="priority">30</field>
<field name="arch" type="xml">
<form string="Activity Form" create="false" edit="false" delete="false">
<sheet string="Activity">
<button name="open_origin" type="object" class="centre oe_link" nolabel="1">
<h1><field name="res_name"/></h1>
</button>
<field name="activity_category" invisible="1" />
<field name="res_model" invisible="1"/>
<field name="res_model_id" invisible="1"/>
<field name="res_id" invisible="1"/>
<group>
<group>
<field name="activity_type_id" required="1" options="{'no_create': True, 'no_open': True}"/>
<field name="res_model_id_name"/>
<field name="calendar_event_id" invisible="1"/>
<field name="create_date" invisible="1"/>
</group>
<group>
<field name="date_deadline"
attrs="{'invisible': [('activity_category', '=', 'meeting')]}"/>
<field name="calendar_event_id_start" string="Start meeting"
attrs="{'invisible': [('calendar_event_id','=', False)]}"/>
<field name="duration" widget="float_time"
attrs="{'invisible': ['|',('duration', '=', False),
('calendar_event_id','=', False)]}"/>
<field name="user_id" options="{'no_open': True}"/>
</group>
</group>
<group attrs="{'invisible': ['|',('calendar_event_id','=', False),('calendar_event_id_partner_ids','=', False)]}">
<field name="calendar_event_id_partner_ids" mode="kanban"/>
</group>
<group>
<field name="summary" placeholder="e.g. Discuss proposal"/>
<field name="note" placeholder="Log a note..."/>
</group>
</sheet>
</form>
</field>
</record>
<!-- TREE VIEW -->
<record id="mail_activity_view_tree" model="ir.ui.view">
<field name="name">mail.activity.boards.view.tree</field>
<field name="model">mail.activity</field>
<field name="inherit_id" ref="mail.mail_activity_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="default_order"></attribute>
<attribute name="decoration-danger">(date_deadline &lt; current_date)</attribute>
<attribute name="decoration-warning">(date_deadline == current_date)</attribute>
<attribute name="decoration-success">(date_deadline &gt; current_date)</attribute>
</xpath>
</field>
</record>
<!-- KANBAN VIEW -->
<record id="mail_activity_view_kanban" model="ir.ui.view">
<field name="name">mail.activity.boards.view.kanban</field>
<field name="model">mail.activity</field>
<field name="priority" eval="10"/>
<field name="arch" type="xml">
<kanban default_group_by="activity_type_id" class="_kanban_small_column o_opportunity_kanban" create="0" _order="date_deadline"
group_create="false" group_delete="false" group_edit="false">
<field name="user_id"/>
<field name="res_id"/>
<field name="res_name"/>
<field name="res_model"/>
<field name="summary"/>
<field name="date_deadline"/>
<field name="state"/>
<field name="icon"/>
<field name="activity_type_id"/>
<field name="activity_category"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_content oe_kanban_global_click">
<div class="oe_kanban_content">
<div>
<strong class="o_kanban_record_subtitle">
<span t-attf-class="fa #{record.icon.raw_value}" />
<field name="summary"/>
</strong>
</div>
<div>
<strong class="o_kanban_record_title"><field name="res_name"/></strong>
</div>
<div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left">
<t t-set="act_date" t-value="new Date(record.date_deadline.raw_value)"/>
<t t-if="act_date &lt; (new Date())">
<span t-attf-class="text-danger"><i class="fa fa-clock-o"/></span>
<t t-if="record.activity_category.raw_value!='meeting'">
<span t-attf-class="text-danger">
<field name="date_deadline" t-options='{"widget": "date"}'/>
</span>
</t>
<t t-else="">
<span t-attf-class="text-danger">
<field name="calendar_event_id_start" t-options='{"widget": "date"}'/>
</span>
</t>
</t>
<t t-else="">
<span><i class="fa fa-clock-o"/></span>
<t t-if="record.activity_category.raw_value!='meeting'">
<span>
<field name="date_deadline" t-options='{"widget": "date"}'/>
</span>
</t>
<t t-else="">
<field name="calendar_event_id_start" t-options='{"widget": "date"}'/>
</t>
</t>
</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"/>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- SEARCH VIEW -->
<record id="mail_activity_view_search" model="ir.ui.view">
<field name="name">mail.activity.boards.view.search</field>
<field name="model">mail.activity</field>
<field name="inherit_id" ref="mail.mail_activity_view_search"/>
<field name="priority" eval="2"/>
<field name="mode">primary</field>
<field name="arch" type="xml">
<xpath expr='//field[@name="res_model_id"]' position='before'>
<field name="user_id"/>
<field name="res_name" string="Origin"/>
</xpath>
<xpath expr='//filter[@name="activities_my"]' position='after'>
<filter string="Act. next month" name="activities_month"
domain="[('date_deadline', '&lt;', (context_today()+datetime.timedelta(days=30)).strftime('%Y-%m-%d'))]"
help="Show activities scheduled for next month."/>
<filter string="Act. next 6 months" name="activities_6_month"
domain="[('date_deadline', '&lt;', (context_today()+datetime.timedelta(days=180)).strftime('%Y-%m-%d'))]"
help="Show activities scheduled for next 6 months."/>
<separator/>
</xpath>
<xpath expr='//search/group' position='inside'>
<filter string="User" context="{'group_by':'user_id'}"/>
<filter string="Origin" context="{'group_by': 'res_model_id'}"/>
</xpath>
</field>
</record>
<!--
ACTION
-->
<record model="ir.actions.act_window" id="open_boards_activities">
<field name="name">Activities</field>
<field name="res_model">mail.activity</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,form</field>
<field name="domain">[]</field>
<field name="context">{}</field>
<field name="view_ids"
eval="[(5, 0, 0),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('mail_activity_view_kanban')}),
(0, 0, {'view_mode': 'tree', 'view_id': ref('mail_activity_view_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('mail_activity_view_form_board')}),
(0, 0, {'view_mode': 'calendar'}),
(0, 0, {'view_mode': 'pivot'}),
(0, 0, {'view_mode': 'graph'})]"/>
<field name="search_view_id" ref="mail_activity_view_search"/>
</record>
<!--
Menus
-->
<menuitem
id="board_menu_activities"
name="Activities"
parent="base.menu_board_root"
action="open_boards_activities"
sequence="1"/>
</odoo>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_backend" name="mail_activity_board assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/mail_activity_board/static/src/js/override_chatter.js"></script>
</xpath>
</template>
</odoo>