diff --git a/mail_activity_team/__manifest__.py b/mail_activity_team/__manifest__.py index 23e3daf24..f79128675 100644 --- a/mail_activity_team/__manifest__.py +++ b/mail_activity_team/__manifest__.py @@ -14,6 +14,7 @@ 'mail_activity_board', ], 'data': [ + 'views/calendar_event.xml', 'views/assets_backend.xml', 'security/ir.model.access.csv', 'security/mail_activity_team_security.xml', diff --git a/mail_activity_team/migrations/11.0.2.2.0/post-migration.py b/mail_activity_team/migrations/11.0.2.2.0/post-migration.py new file mode 100644 index 000000000..62522074e --- /dev/null +++ b/mail_activity_team/migrations/11.0.2.2.0/post-migration.py @@ -0,0 +1,17 @@ +# Copyright 2019 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl +from openupgradelib import openupgrade + + +@openupgrade.migrate(use_env=True) +def migrate(env, version): + rule = env.ref('calendar.calendar_event_rule_private') + rule.write({ + 'name': 'Private or group events', + 'domain_force': "['|', '|', " + "('privacy', 'not in', ['private', 'team'])," + "'&', ('privacy', '=', 'private')," + "('partner_ids', '=', user.partner_id.id)," + "'&', ('privacy', '=', 'team')," + "('team_id', 'in', user.activity_team_ids.ids)]" + }) diff --git a/mail_activity_team/models/__init__.py b/mail_activity_team/models/__init__.py index 635ee6958..3a4420b37 100644 --- a/mail_activity_team/models/__init__.py +++ b/mail_activity_team/models/__init__.py @@ -2,3 +2,4 @@ from . import mail_activity_team from . import mail_activity from . import res_users from . import mail_activity_mixin +from . import calendar_event diff --git a/mail_activity_team/models/calendar_event.py b/mail_activity_team/models/calendar_event.py new file mode 100644 index 000000000..da2cfe055 --- /dev/null +++ b/mail_activity_team/models/calendar_event.py @@ -0,0 +1,75 @@ +# Copyright 2019 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ + + +class CalendarEvent(models.Model): + _inherit = 'calendar.event' + + def _get_default_team_id(self, user_id=None): + if not user_id: + user_id = self.env.uid + domain = [('member_ids', 'in', [user_id])] + res_model = self.env.context.get('default_res_model') + if res_model: + model = self.env['ir.model'].search( + [('model', '=', res_model)], limit=1) + domain.extend(['|', ('res_model_ids', '=', False), + ('res_model_ids', 'in', model.ids)]) + return self.env['mail.activity.team'].search(domain, limit=1) + + privacy = fields.Selection(selection_add=[ + ('team', 'Only team'), + ]) + team_id = fields.Many2one( + comodel_name='mail.activity.team', + default=lambda s: s._get_default_team_id(), + ) + + def _get_read_fields(self, fields): + expected_fields = ['privacy', 'team_id'] + extra_fields = [] + fixed_fields = [ + 'id', 'allday', 'start', 'stop', 'display_start', 'display_stop', + 'duration', 'user_id', 'state', 'interval', 'count', + 'recurrent_id_date', 'rrule' + ] + recurrent_fields = self._get_recurrent_fields() + public_fields = list(set( + recurrent_fields + fixed_fields)) + if not fields: + fields = list(self._fields) + for field in expected_fields: + if field not in fields: + fields.append(field) + extra_fields.append(field) + return fields, extra_fields, public_fields + + @api.multi + def read(self, fields=None, load='_classic_read'): + # This function manages which fields a user can read based on the + # team it belongs. + + fields, extra_fields, public_fields = self._get_read_fields(fields) + result = super().read(fields, load) + for r in result: + if r['team_id'] and r['privacy'] == 'team': + team_id = r['team_id'] + if isinstance(team_id, tuple): + team_id = team_id[0] + team = self.env['mail.activity.team'].browse(team_id) + users = team.member_ids + if self.env.user not in users: + for f in r: + if f not in public_fields: + if isinstance(r[f], list): + r[f] = [] + else: + r[f] = False + if f == 'name': + r[f] = _('Busy') + for f in extra_fields: + if f in r: + del r[f] + return result diff --git a/mail_activity_team/models/mail_activity.py b/mail_activity_team/models/mail_activity.py index 8eb118a48..2b4eaf36d 100644 --- a/mail_activity_team/models/mail_activity.py +++ b/mail_activity_team/models/mail_activity.py @@ -72,3 +72,9 @@ class MailActivity(models.Model): activity.user_id not in activity.team_id.member_ids: raise ValidationError( _('The assigned user is not member of the team.')) + + @api.multi + def action_create_calendar_event(self): + res = super().action_create_calendar_event() + res['context']['default_team_id'] = self.team_id.id or False + return res diff --git a/mail_activity_team/readme/CONTRIBUTORS.rst b/mail_activity_team/readme/CONTRIBUTORS.rst index 6bb24056e..a01fe5e30 100644 --- a/mail_activity_team/readme/CONTRIBUTORS.rst +++ b/mail_activity_team/readme/CONTRIBUTORS.rst @@ -2,3 +2,5 @@ * Jordi Ballester Alomar (jordi.ballester@eficent.com) * Miquel Raïch (miquel.raich@eficent.com) + +* Enric Tobella diff --git a/mail_activity_team/security/mail_activity_team_security.xml b/mail_activity_team/security/mail_activity_team_security.xml index e19398ab2..f7ef87106 100644 --- a/mail_activity_team/security/mail_activity_team_security.xml +++ b/mail_activity_team/security/mail_activity_team_security.xml @@ -12,4 +12,9 @@ + + Private or group events + ['|', '|', ('privacy', 'not in', ['private', 'team']), '&', ('privacy', '=', 'private'), ('partner_ids', '=', user.partner_id.id), '&', ('privacy', '=', 'team'), ('team_id', 'in', user.activity_team_ids.ids)] + + diff --git a/mail_activity_team/tests/test_mail_activity_team.py b/mail_activity_team/tests/test_mail_activity_team.py index 1dbf8f7ac..6a8aa9b3c 100644 --- a/mail_activity_team/tests/test_mail_activity_team.py +++ b/mail_activity_team/tests/test_mail_activity_team.py @@ -1,80 +1,134 @@ # Copyright 2018 Eficent Business and IT Consulting Services, S.L. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.tests.common import TransactionCase + +from odoo.tests.common import SavepointCase from odoo.exceptions import ValidationError +from odoo.fields import Datetime +from datetime import timedelta -class TestMailActivityTeam(TransactionCase): +class TestMailActivityTeam(SavepointCase): - def setUp(self): - super(TestMailActivityTeam, self).setUp() + @classmethod + def setUpClass(cls): + super(TestMailActivityTeam, cls).setUpClass() + cls.env = cls.env(context=dict( + cls.env.context, tracking_disable=True, no_reset_password=True) + ) + cls.env["mail.activity.team"].search([]).unlink() - self.env["mail.activity.team"].search([]).unlink() - - self.employee = self.env['res.users'].create({ - 'company_id': self.env.ref("base.main_company").id, + cls.employee = cls.env['res.users'].create({ + 'company_id': cls.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, - self.env.ref('base.group_partner_manager').id])] + cls.env.ref('base.group_user').id, + cls.env.ref('base.group_partner_manager').id])] }) - self.employee2 = self.env['res.users'].create({ - 'company_id': self.env.ref("base.main_company").id, + cls.employee2 = cls.env['res.users'].create({ + 'company_id': cls.env.ref("base.main_company").id, 'name': "Employee 2", 'login': "csu2", 'email': "crmuser2@yourcompany.com", - 'groups_id': [(6, 0, [self.env.ref('base.group_user').id])] + 'groups_id': [(6, 0, [cls.env.ref('base.group_user').id])] }) - self.partner_ir_model = self.env['ir.model']._get('res.partner') + cls.partner_ir_model = cls.env['ir.model']._get('res.partner') - activity_type_model = self.env['mail.activity.type'] - self.activity1 = activity_type_model.create({ + activity_type_model = cls.env['mail.activity.type'] + cls.activity1 = activity_type_model.create({ 'name': 'Initial Contact', - 'days': 5, 'summary': 'ACT 1 : Presentation, barbecue, ... ', - 'res_model_id': self.partner_ir_model.id, + 'res_model_id': cls.partner_ir_model.id, }) - self.activity2 = activity_type_model.create({ + cls.activity2 = activity_type_model.create({ 'name': 'Call for Demo', - 'days': 6, 'summary': 'ACT 2 : I want to show you my ERP !', - 'res_model_id': self.partner_ir_model.id, + 'res_model_id': cls.partner_ir_model.id, }) - self.partner_client = self.env.ref("base.res_partner_1") + cls.partner_client = cls.env.ref("base.res_partner_1") - self.act1 = self.env['mail.activity'].sudo(self.employee).create({ - 'activity_type_id': self.activity1.id, + cls.act1 = cls.env['mail.activity'].sudo(cls.employee).create({ + 'activity_type_id': cls.activity1.id, 'note': 'Partner activity 1.', - 'res_id': self.partner_client.id, - 'res_model_id': self.partner_ir_model.id, - 'user_id': self.employee.id, + 'res_id': cls.partner_client.id, + 'res_model_id': cls.partner_ir_model.id, + 'user_id': cls.employee.id, }) - self.team1 = self.env['mail.activity.team'].sudo().create({ + cls.team1 = cls.env['mail.activity.team'].sudo().create({ 'name': 'Team 1', - 'res_model_ids': [(6, 0, [self.partner_ir_model.id])], - 'member_ids': [(6, 0, [self.employee.id])], + 'res_model_ids': [(6, 0, [cls.partner_ir_model.id])], + 'member_ids': [(6, 0, [cls.employee.id])], }) - self.team2 = self.env['mail.activity.team'].sudo().create({ + cls.team2 = cls.env['mail.activity.team'].sudo().create({ 'name': 'Team 2', - 'res_model_ids': [(6, 0, [self.partner_ir_model.id])], - 'member_ids': [(6, 0, [self.employee.id, self.employee2.id])], + 'res_model_ids': [(6, 0, [cls.partner_ir_model.id])], + 'member_ids': [(6, 0, [cls.employee.id, cls.employee2.id])], }) - self.act2 = self.env['mail.activity'].sudo(self.employee).create({ - 'activity_type_id': self.activity2.id, + cls.act2 = cls.env['mail.activity'].sudo(cls.employee).create({ + 'activity_type_id': cls.activity2.id, 'note': 'Partner activity 2.', - 'res_id': self.partner_client.id, - 'res_model_id': self.partner_ir_model.id, - 'user_id': self.employee.id, + 'res_id': cls.partner_client.id, + 'res_model_id': cls.partner_ir_model.id, + 'user_id': cls.employee.id, }) + cls.act3 = cls.env['mail.activity'].sudo(cls.employee).create({ + 'activity_type_id': cls.env.ref( + 'mail.mail_activity_data_meeting').id, + 'note': 'Meeting activity 3.', + 'res_id': cls.partner_client.id, + 'res_model_id': cls.partner_ir_model.id, + 'user_id': cls.employee.id, + 'team_id': cls.team1.id, + 'summary': 'Metting activity' + }) + cls.start = Datetime.now() + cls.stop = Datetime.to_string( + Datetime.from_string(cls.start) + timedelta(hours=1) + ) + + def test_meeting_blank(self): + meeting = self.env['calendar.event'].sudo(self.employee).create({ + 'start': self.start, + 'stop': self.stop, + 'name': 'Test meeting' + }) + self.assertTrue(meeting.team_id) + + def test_meeting_from_activity(self): + action = self.act3.with_context( + default_res_id=self.act3.res_id, + default_res_model=self.act3.res_model, + ).action_create_calendar_event() + + meeting = self.env['calendar.event'].sudo(self.employee).with_context( + **action['context'] + ).create({ + 'start': self.start, + 'stop': self.stop + }) + self.assertTrue(meeting.team_id) + self.assertTrue(meeting.read(['description'])[0]['description']) + self.assertTrue( + meeting.sudo(self.employee2).read( + ['description'])[0]['description'], + 'He should be able to read the record as it is public by default', + ) + meeting.write({'privacy': 'team'}) + self.assertFalse( + meeting.sudo(self.employee2).read( + ['description'])[0]['description'], + 'He shouldn\'t be able to read the record as it is ' + 'public by default', + ) + def test_missing_activities(self): self.assertFalse( self.act1.team_id, 'Error: Activity 1 should not have a team.') diff --git a/mail_activity_team/views/calendar_event.xml b/mail_activity_team/views/calendar_event.xml new file mode 100644 index 000000000..8ed959144 --- /dev/null +++ b/mail_activity_team/views/calendar_event.xml @@ -0,0 +1,35 @@ + + + + + + + calendar.event.form (in mail_activity_team) + calendar.event + + + + + + + + + + calendar.event.search + calendar.event + + + + + + + + + + + + +