From 1905b8a5d5b071076dfb7d71dc30f5c36b5d601b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 27 Mar 2018 13:42:18 +0200 Subject: [PATCH 01/11] [IMP] mail: take first found reminder activity type as default when creating reminders If people define several reminders, take first found one based on sequence as default activity type when creating reminders. This commit is linked to the recent merge of reminders done at f488dc58c37fdc7fd49df759165de641e213444b. --- addons/mail/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index 9682cbe1125..3445691f601 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -237,7 +237,7 @@ class MailController(http.Controller): if date_deadline: values['date_deadline'] = date_deadline if not activity_type_id: - activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')]).id + activity_type_id = request.env['mail.activity.type'].sudo().search([('category', '=', 'reminder')], limit=1).id if activity_type_id: values['activity_type_id'] = activity_type_id activity = request.env['mail.activity'].create(values) From 093dd9b5122bc8a627d758a86839b42d3fdc0bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 27 Mar 2018 13:55:39 +0200 Subject: [PATCH 02/11] [IMP] test_mail: clean reminder-related activity tests It has not been done completely at f488dc58c37fdc7fd49df759165de641e213444b. --- addons/test_mail/tests/test_mail_activity.py | 81 +++++++++----------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/addons/test_mail/tests/test_mail_activity.py b/addons/test_mail/tests/test_mail_activity.py index cb1b0173d9f..3a86d86ed4b 100644 --- a/addons/test_mail/tests/test_mail_activity.py +++ b/addons/test_mail/tests/test_mail_activity.py @@ -55,8 +55,6 @@ class TestMailActivity(BaseFunctionalTest): with self.sudoAs('chell'): test_record = self.env['mail.test.activity'].browse(self.test_record.id) - # test_record.name - # test_record.read() with self.assertRaises(exceptions.AccessError): self.env['mail.activity'].create({ 'summary': 'Test Activity', @@ -64,52 +62,47 @@ class TestMailActivity(BaseFunctionalTest): 'res_model_id': self.env['ir.model']._get(test_record._name).id, 'res_id': test_record.id, }) - # self.assertEqual(test_record.activity_ids, self.env['mail.activity']) + def test_reminder_security(self): - admin = self.env.ref('base.user_root') - demo = self.env.ref('base.user_demo') - - reminder = self.env['mail.activity'].sudo(admin).create({ - 'note': 'Test Reminder', - 'date_deadline': date.today() + relativedelta(days=1), - }) #try to delete record with demo user - with self.assertRaises(exceptions.AccessError): - #try to delete admin record with demo user - reminder.sudo(demo).unlink() - - with self.assertRaises(exceptions.AccessError): - #try to update admin record with demo user - reminder.sudo(demo).write({'note': 'Give money to demo user'}) - - #but demo should be able to edit and delete its own reminder - demo_reminder = self.env['mail.activity'].sudo(demo).create({ - 'note': 'Test Reminder demo', - 'date_deadline': date.today() + relativedelta(days=2), + reminder = self.env['mail.activity'].sudo(self.user_admin).create({ + 'note': 'Test Reminder', + 'date_deadline': date.today() + relativedelta(days=1), }) - demo_reminder.sudo(demo).write({'note': 'Holidays'}) - self.assertEqual(demo_reminder.note, '

Holidays

') - #but edit or res_id and res_model should be impossible with self.assertRaises(exceptions.AccessError): - demo_reminder.sudo(demo).write({'res_id': 1}) - with self.assertRaises(exceptions.AccessError): - demo_reminder.sudo(demo).write({'res_model_id': 1}) - #user can unlink own reminder - demo_reminder.sudo(demo).unlink() + # try to delete admin record with demo user + reminder.sudo(self.user_employee).unlink() - def test_reminder_summary(self): - reminder = self.env['mail.activity'].create({ + with self.assertRaises(exceptions.AccessError): + # try to update admin record with demo user + reminder.sudo(self.user_employee).write({'note': 'Give money to demo user'}) + + # but demo should be able to edit and delete its own reminder + demo_reminder = self.env['mail.activity'].sudo(self.user_employee).create({ + 'note': 'Test Reminder demo', + 'date_deadline': date.today() + relativedelta(days=2), + }) + demo_reminder.write({'note': '

Holidays

'}) + self.assertEqual(demo_reminder.note, '

Holidays

') + + # edit of res_id and res_model should be impossible + with self.assertRaises(exceptions.AccessError): + demo_reminder.write({'res_id': 1}) + with self.assertRaises(exceptions.AccessError): + demo_reminder.write({'res_model_id': 1}) + + demo_reminder.unlink() + + def test_reminder_flow(self): + with self.sudoAs('ernest'): + reminder = self.env['mail.activity'].create({ 'note': 'Test Reminder', 'date_deadline': date.today(), - }) - self.assertEqual(reminder.summary, 'Test Reminder', "Summary should be first line of note by default") - reminder.write({'note': 'Holidays\nDestination: Pescara'}) - self.assertEqual(reminder.summary, 'Holidays', "Summary should be first line of note by default") - reminder.write({'note': ''}) - self.assertEqual(reminder.summary, 'Reminder', "If note is empty, summary should be Reminder") - reminder.write({'note': 'Holidays', 'summary': 'Summary'}) - self.assertEqual(reminder.summary, 'Summary', "If summary is set, shouldn't be changed") - - - - + }) + self.assertEqual(reminder.summary, 'Test Reminder') + reminder.write({'note': 'Holidays\nDestination: Pescara'}) + self.assertEqual(reminder.summary, 'Holidays') + reminder.write({'note': ''}) + self.assertEqual(reminder.summary, 'Reminder') + reminder.write({'note': 'Holidays', 'summary': 'Summary'}) + self.assertEqual(reminder.summary, 'Summary') From 60b81e00b27edd95d31d668687561e0b2dd7c714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 17 Nov 2017 12:59:00 +0100 Subject: [PATCH 03/11] [IMP] mail: add tool methods to manage activities directly from the mixin This commit adds tools methods related to activities in the mail.activity mixin. It gives to models inheriting from the activity mixin an easy-to-use API to schedule, unlink or mark activities as done. Purpose of those methods is to avoid having people manually managing activities in the code to hide the technical details of the activities model, notably access rights or activity types. A field is added on activity model to indicate they have been generated automatically. This way when rescheduling or unlinking based on some specific activity types we do not change user-created activities. Those methods include scheduling activities, changing their dates, marking them as done or unlinking them. Future commits will use those methods in various addons to automatically generate activities based on workflow we want to implement. This commit also adds tests for the newly added code. Future commits should probably have a look at activity security and add some tests cases to check it is correctly taken into account. It is considered a bit out of scope for this task. Thanks to @jem-odoo for its in-depth review of this commit. Well thanks for other commits also. --- addons/mail/models/mail_activity.py | 113 ++++++++++++++++++- addons/test_mail/__manifest__.py | 1 + addons/test_mail/data/data.xml | 23 ++++ addons/test_mail/models/test_mail_models.py | 9 ++ addons/test_mail/tests/test_mail_activity.py | 72 +++++++++++- addons/test_mail/tests/test_performance.py | 20 +++- 6 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 addons/test_mail/data/data.xml diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index 1259e7395f8..0f849526b90 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -93,6 +93,9 @@ class MailActivity(models.Model): active = fields.Boolean( 'Open', default=True, help='Done reminders should be archived instead of marked as done.') + automated = fields.Boolean( + 'Automated activity', readonly=True, + help='Indicates this activity has been created automatically and not by any user.') # description user_id = fields.Many2one( 'res.users', 'Assigned to', @@ -345,7 +348,15 @@ class MailActivityMixin(models.AbstractModel): 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.""" + use it. + + Some context keys allow to control the mixin behavior. Use those in some + specific cases like import + + * ``mail_activity_automation_skip``: skip activities automation; it means + no automated activities will be generated, updated or unlinked, allowing + to save computation and avoid generating unwanted activities; + """ _name = 'mail.activity.mixin' _description = 'Activity Mixin' @@ -432,3 +443,103 @@ class MailActivityMixin(models.AbstractModel): [('res_model', '=', self._name), ('res_id', 'in', record_ids)] ).unlink() return result + + def activity_schedule(self, act_type_xmlid='', date_deadline=None, summary='', note='', **act_values): + """ Schedule an activity on each record of the current record set. + This method allow to provide as parameter act_type_xmlid. This is an + xml_id of activity type instead of directly giving an activity_type_id. + It is useful to avoid having various "env.ref" in the code and allow + to let the mixin handle access rights. + """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + if not date_deadline: + date_deadline = fields.Date.today() + if act_type_xmlid: + activity_type = self.sudo().env.ref(act_type_xmlid) + else: + activity_type = self.env['mail.activity.type'].sudo().browse(act_values['activity_type_id']) + + model_id = self.env['ir.model']._get(self._name).id + activities = self.env['mail.activity'] + for record in self: + create_vals = { + 'activity_type_id': activity_type.id, + 'summary': summary or activity_type.summary, + 'automated': True, + 'note': note, + 'date_deadline': date_deadline, + 'res_model_id': model_id, + 'res_id': record.id, + } + create_vals.update(act_values) + activities |= self.env['mail.activity'].create(create_vals) + return activities + + def activity_reschedule(self, act_type_xmlids, user_id=None, date_deadline=None): + """ Reschedule some automated activities. Activities to reschedule are + selected based on type xml ids and optionally by user. Purpose is to be + able to change the deadline, not anything else currently. """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + Data = self.env['ir.model.data'].sudo() + activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + domain = [ + '&', '&', '&', + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('automated', '=', True), + ('activity_type_id', 'in', activity_types_ids) + ] + if user_id: + domain = ['&'] + domain + [('user_id', '=', user_id)] + activities = self.env['mail.activity'].search(domain) + if activities: + activities.write({ + 'date_deadline': date_deadline, + }) + return activities + + def activity_feedback(self, act_type_xmlids, user_id=None, feedback=None): + """ Set activities as done, limiting to some activity types and + optionally to a given user. """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + Data = self.env['ir.model.data'].sudo() + activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + domain = [ + '&', '&', '&', + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('automated', '=', True), + ('activity_type_id', 'in', activity_types_ids) + ] + if user_id: + domain = ['&'] + domain + [('user_id', '=', user_id)] + activities = self.env['mail.activity'].search(domain) + if activities: + activities.action_feedback(feedback=feedback) + return True + + def activity_unlink(self, act_type_xmlids, user_id=None): + """ Unlink activities, limiting to some activity types and optionally + to a given user. """ + if self.env.context.get('mail_activity_automation_skip'): + return False + + Data = self.env['ir.model.data'].sudo() + activity_types_ids = [Data.xmlid_to_res_id(xmlid) for xmlid in act_type_xmlids] + domain = [ + '&', '&', '&', + ('res_model', '=', self._name), + ('res_id', 'in', self.ids), + ('automated', '=', True), + ('activity_type_id', 'in', activity_types_ids) + ] + if user_id: + domain = ['&'] + domain + [('user_id', '=', user_id)] + self.env['mail.activity'].search(domain).unlink() + return True diff --git a/addons/test_mail/__manifest__.py b/addons/test_mail/__manifest__.py index 51d9cc7fe42..40d43883d4b 100644 --- a/addons/test_mail/__manifest__.py +++ b/addons/test_mail/__manifest__.py @@ -12,6 +12,7 @@ tests independently to functional aspects of other models. """, 'depends': ['test_performance', 'mail'], 'data': [ 'security/ir.model.access.csv', + 'data/data.xml', ], 'demo': [ 'data/demo.xml', diff --git a/addons/test_mail/data/data.xml b/addons/test_mail/data/data.xml new file mode 100644 index 00000000000..19be18eb2d6 --- /dev/null +++ b/addons/test_mail/data/data.xml @@ -0,0 +1,23 @@ + + + + + Do Stuff + Really?! Wow! A superpowers drug you can just rub onto your skin? + default + + + + Meet People + You'd think it would be something you'd have to freebase. Noooooo! + default + + + + Call People + Then throw her in the laundry room, which will hereafter be referred to as "the brig". + default + + + + diff --git a/addons/test_mail/models/test_mail_models.py b/addons/test_mail/models/test_mail_models.py index 4c1fd408ddf..06d84986fe8 100644 --- a/addons/test_mail/models/test_mail_models.py +++ b/addons/test_mail/models/test_mail_models.py @@ -38,6 +38,15 @@ class MailTestActivity(models.Model): name = fields.Char() email_from = fields.Char() + def action_start(self, action_summary): + self.activity_schedule( + 'test_mail.mail_act_test_todo', + summary=action_summary + ) + + def action_close(self, action_feedback): + self.activity_feedback(['test_mail.mail_act_test_todo'], feedback=action_feedback) + class MailTestFull(models.Model): """ This model can be used in tests when complex chatter features are diff --git a/addons/test_mail/tests/test_mail_activity.py b/addons/test_mail/tests/test_mail_activity.py index 3a86d86ed4b..e340358a5ee 100644 --- a/addons/test_mail/tests/test_mail_activity.py +++ b/addons/test_mail/tests/test_mail_activity.py @@ -14,7 +14,13 @@ class TestMailActivity(BaseFunctionalTest): @classmethod def setUpClass(cls): super(TestMailActivity, cls).setUpClass() - cls.test_record = cls.env['mail.test.activity'].create({'name': 'Test'}) + cls.test_record = cls.env['mail.test.activity'].with_context(cls._quick_create_ctx).create({'name': 'Test'}) + # reset ctx + cls.test_record = cls.test_record.with_context( + mail_create_nolog=False, + mail_create_nosubscribe=False, + mail_notrack=False + ) def test_activity_flow_employee(self): with self.sudoAs('ernest'): @@ -106,3 +112,67 @@ class TestMailActivity(BaseFunctionalTest): self.assertEqual(reminder.summary, 'Reminder') reminder.write({'note': 'Holidays', 'summary': 'Summary'}) self.assertEqual(reminder.summary, 'Summary') + + def test_activity_mixin(self): + today = date.today() + with self.sudoAs('ernest'): + self.assertEqual(self.test_record.env.user, self.user_employee) + + # Test various scheduling of activities + act1 = self.test_record.activity_schedule( + 'test_mail.mail_act_test_todo', + today + relativedelta(days=1), + user_id=self.user_admin.id) + self.assertEqual(act1.automated, True) + + act_type = self.env.ref('test_mail.mail_act_test_todo') + self.assertEqual(self.test_record.activity_summary, act_type.summary) + self.assertEqual(self.test_record.activity_state, 'planned') + self.assertEqual(self.test_record.activity_user_id, self.user_admin) + + act2 = self.test_record.activity_schedule( + 'test_mail.mail_act_test_meeting', + today + relativedelta(days=-1)) + self.assertEqual(self.test_record.activity_state, 'overdue') + self.assertEqual(self.test_record.activity_user_id, self.user_employee) + + act3 = self.test_record.activity_schedule( + 'test_mail.mail_act_test_todo', + today + relativedelta(days=3), + user_id=self.user_employee.id) + self.assertEqual(self.test_record.activity_state, 'overdue') + self.assertEqual(self.test_record.activity_user_id, self.user_employee) + + self.assertEqual(self.test_record.activity_ids, act1 | act2 | act3) + + # Perform todo activities for admin + self.test_record.activity_feedback( + ['test_mail.mail_act_test_todo'], + user_id=self.user_admin.id, + feedback='Test feedback',) + self.assertEqual(self.test_record.activity_ids, act2 | act3) + + # Reschedule all activities, should update the record state + self.assertEqual(self.test_record.activity_state, 'overdue') + self.test_record.activity_reschedule( + ['test_mail.mail_act_test_meeting', 'test_mail.mail_act_test_todo'], + date_deadline=today + relativedelta(days=3) + ) + self.assertEqual(self.test_record.activity_state, 'planned') + + # Perform todo activities for remaining people + self.test_record.activity_feedback( + ['test_mail.mail_act_test_todo'], + feedback='Test feedback') + + # Setting activities as done should delete them and post messages + self.assertEqual(self.test_record.activity_ids, act2) + self.assertEqual(len(self.test_record.message_ids), 2) + self.assertEqual(self.test_record.message_ids.mapped('subtype_id'), self.env.ref('mail.mt_activities')) + + # Perform meeting activities + self.test_record.activity_unlink(['test_mail.mail_act_test_meeting']) + + # Canceling activities should simply remove them + self.assertEqual(self.test_record.activity_ids, self.env['mail.activity']) + self.assertEqual(len(self.test_record.message_ids), 2) diff --git a/addons/test_mail/tests/test_performance.py b/addons/test_mail/tests/test_performance.py index 183165f5e41..be887fc0173 100644 --- a/addons/test_mail/tests/test_performance.py +++ b/addons/test_mail/tests/test_performance.py @@ -160,16 +160,32 @@ class TestAdvMailPerformance(TransactionCase): 'default_res_model': 'mail.test.activity', }) - with self.assertQueryCount(admin=26, emp=32): # test_mail only: 26 - 32 + with self.assertQueryCount(admin=26, emp=33): # test_mail only: 26 - 32 activity = MailActivity.create({ 'summary': 'Test Activity', 'res_id': record.id, 'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id, }) - with self.assertQueryCount(admin=59, emp=88): # test_mail only: 58 - 86 + with self.assertQueryCount(admin=59, emp=88): # test_mail only: 58 - 87 activity.action_feedback(feedback='Zizisse Done !') + @users('admin', 'emp') + @warmup + @mute_logger('odoo.models.unlink') + def test_adv_activity_mixin(self): + record = self.env['mail.test.activity'].create({'name': 'Test'}) + + with self.assertQueryCount(admin=26, emp=32): # test_mail only: 26 - 32 + record.action_start('Test Start') + + record.write({'name': 'Dupe write'}) + + with self.assertQueryCount(admin=58, emp=88): # test_mail only: 58 - 88 + record.action_close('Dupe feedback') + + self.assertEqual(record.activity_ids, self.env['mail.activity']) + @mute_logger('odoo.addons.mail.models.mail_mail', 'odoo.models.unlink') @users('admin', 'emp') @warmup From 78c4d936e56608fe76d271aa3eca0f3a82888777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 17 Jan 2018 11:27:17 +0100 Subject: [PATCH 04/11] [MOV] hr_holidays: reorder a bit views and menus to avoid circular issues Commit a0f4f6548bdb9a26151fc8a292d152fc67d1a13c splitted hr_holidays models and moved them to different files according to guidelines. However some bits of code related to views were not correctly put in separate files. This commit finishes to order views and moves some of them in the right file. Other views or actions are moved because of dependency between them. --- addons/hr_holidays/__manifest__.py | 2 +- .../views/hr_leave_allocation_views.xml | 53 +++++++++++----- .../hr_holidays/views/hr_leave_type_views.xml | 8 +++ addons/hr_holidays/views/hr_leave_views.xml | 62 +++++-------------- 4 files changed, 62 insertions(+), 63 deletions(-) diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py index a1908e6aaf8..e476035f36f 100644 --- a/addons/hr_holidays/__manifest__.py +++ b/addons/hr_holidays/__manifest__.py @@ -34,9 +34,9 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p 'security/ir.model.access.csv', 'views/resource_views.xml', + 'views/hr_leave_views.xml', 'views/hr_leave_type_views.xml', 'views/hr_leave_allocation_views.xml', - 'views/hr_leave_views.xml', 'views/hr_views.xml', 'wizard/hr_holidays_summary_department_views.xml', diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml index 933a4cbebba..1146dbe399a 100644 --- a/addons/hr_holidays/views/hr_leave_allocation_views.xml +++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml @@ -116,21 +116,6 @@ - - hr.leave.allocation.tree.customize - hr.leave.allocation - - - - - - - - - - - - Allocation Request hr.leave.allocation @@ -212,4 +197,42 @@ + + Requests to Approve + hr.leave + form + { + 'search_default_approve':1, + } + + + + + + + tree + + + + + + + form + + + + + + + + diff --git a/addons/hr_holidays/views/hr_leave_type_views.xml b/addons/hr_holidays/views/hr_leave_type_views.xml index c100347b534..ba988da2a99 100644 --- a/addons/hr_holidays/views/hr_leave_type_views.xml +++ b/addons/hr_holidays/views/hr_leave_type_views.xml @@ -128,4 +128,12 @@ + + diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml index c985ee037e0..4a6fda9b322 100644 --- a/addons/hr_holidays/views/hr_leave_views.xml +++ b/addons/hr_holidays/views/hr_leave_views.xml @@ -240,31 +240,6 @@ action="open_ask_holidays" sequence="22"/> - - Requests to Approve - hr.leave - form - { - 'search_default_approve':1, - } - - - - - - - tree - - - - - - - form - - - - Leaves Overview hr.leave @@ -306,13 +281,6 @@ action="open_department_holidays_approve" sequence="1"/> - - - - + + hr.leave.allocation.tree.customize + hr.leave.allocation + + + + + + + + + + + + Leaves to be reported in Payslip ir.actions.act_window @@ -432,11 +407,4 @@ id="menu_hr_available_holidays_report_tree" action="action_hr_available_holidays_report" parent="menu_hr_holidays_report" sequence="5"/> - - - From 1d71c0623334706abde82b45fd6fcef2b35ba9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 17 Nov 2017 13:54:02 +0100 Subject: [PATCH 05/11] [IMP] hr_holidays: improve activity management on leave model This commit improves leave request and allocation management through a better integration of activities and addition of automated activities. Several things are done in this commit : * leave and allocation models do not inherit from mail.activity.mixin. This commit adds the inherit so that HR users and managers can now schedule and manage activities on leave and allocation requests. This will help them in their daily job; * automatic activities generation is added for leave and allocation. Activities are generated for approval and second approval. They are also automatically set as done when validating or unlinked when refusing or resetting to avoid bloating users with unnecessary activities; * a menu to configure activity types is added. Indeed HR managers should be able to see and configure activity types related to their job; * filters are added to be able to use the systray and to filter the kanban view based on activities Having automated activities allow to replace some messages and tracking that were implemented to warn people of leave and allocation to approve or validate. This commit therefore * simplifies the tracking as only approved or refused state now trigger a subtype; * removes to approve and to validate subtypes on leave and allocation model as well as their parent subtypes on the department. This allows to simplify chatter a lot in hr_holidays app; --- addons/hr_holidays/__manifest__.py | 1 + addons/hr_holidays/data/mail_data.xml | 92 +++++-------------- addons/hr_holidays/models/hr_leave.py | 51 ++++++++-- .../hr_holidays/models/hr_leave_allocation.py | 49 ++++++++-- .../hr_holidays/security/ir.model.access.csv | 1 + .../views/hr_leave_allocation_views.xml | 10 ++ .../hr_holidays/views/hr_leave_type_views.xml | 11 ++- addons/hr_holidays/views/hr_leave_views.xml | 10 ++ .../hr_holidays/views/mail_activity_views.xml | 16 ++++ 9 files changed, 158 insertions(+), 83 deletions(-) create mode 100644 addons/hr_holidays/views/mail_activity_views.xml diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py index e476035f36f..da9238ad82a 100644 --- a/addons/hr_holidays/__manifest__.py +++ b/addons/hr_holidays/__manifest__.py @@ -38,6 +38,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p 'views/hr_leave_type_views.xml', 'views/hr_leave_allocation_views.xml', 'views/hr_views.xml', + 'views/mail_activity_views.xml', 'wizard/hr_holidays_summary_department_views.xml', 'wizard/hr_holidays_summary_employees_views.xml', diff --git a/addons/hr_holidays/data/mail_data.xml b/addons/hr_holidays/data/mail_data.xml index 57c92f06581..2dc75bcb8c8 100644 --- a/addons/hr_holidays/data/mail_data.xml +++ b/addons/hr_holidays/data/mail_data.xml @@ -1,26 +1,36 @@ + + + Leaves Approval + fa-sun-o + + + + Leaves Second Approve + fa-sun-o + + + + + + Allocation Approval + fa-sun-o + + + + Allocation Second Approve + fa-sun-o + + + - - Confirmed - hr.leave - Request created and waiting confirmation - - - - Waiting Second Validation - hr.leave - - Request validated, waiting second validation - - Approved hr.leave Request approved - Refused hr.leave @@ -29,25 +39,11 @@ - - Confirmed - hr.leave.allocation - Request created and waiting confirmation - - - - Waiting Second Validation - hr.leave.allocation - - Request validated, waiting second validation - - Approved hr.leave.allocation Request approved - Refused hr.leave.allocation @@ -56,24 +52,6 @@ - - Leaves Confirmed - hr.department - - - department_id - - - - - Leaves First Approval - hr.department - - - department_id - - - Leaves Approved hr.department @@ -82,7 +60,6 @@ department_id - Leaves Refused hr.department @@ -91,25 +68,6 @@ department_id - - - Allocations Confirmed - hr.department - - - department_id - - - - - Allocations First Approval - hr.department - - - department_id - - - Allocation Approved hr.department @@ -118,7 +76,6 @@ department_id - Allocation Refused hr.department @@ -127,6 +84,5 @@ department_id - \ No newline at end of file diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 4a58d151f55..03c90176363 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -20,7 +20,7 @@ class HolidaysRequest(models.Model): _name = "hr.leave" _description = "Leave" _order = "date_from desc" - _inherit = ['mail.thread'] + _inherit = ['mail.thread', 'mail.activity.mixin'] @api.model def default_get(self, fields_list): @@ -249,6 +249,7 @@ class HolidaysRequest(models.Model): holiday.add_follower(employee_id) if 'employee_id' in values: holiday._onchange_employee_id() + holiday.activity_update() return holiday @api.multi @@ -357,13 +358,16 @@ class HolidaysRequest(models.Model): for linked_request in linked_requests: linked_request.action_draft() linked_requests.unlink() + self.activity_update() return True @api.multi def action_confirm(self): if self.filtered(lambda holiday: holiday.state != 'draft'): raise UserError(_('Leave request must be in Draft state ("To Submit") in order to confirm it.')) - return self.write({'state': 'confirm'}) + self.write({'state': 'confirm'}) + self.activity_update() + return True @api.multi def action_approve(self): @@ -387,6 +391,7 @@ class HolidaysRequest(models.Model): self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id}) self.filtered(lambda hol: not hol.validation_type == 'both').action_validate() + self.activity_update() return True @api.multi @@ -418,6 +423,7 @@ class HolidaysRequest(models.Model): leaves.action_approve() if leaves and leaves[0].validation_type == 'both': leaves.action_validate() + self.activity_update() return True @api.multi @@ -440,8 +446,45 @@ class HolidaysRequest(models.Model): # If a category that created several holidays, cancel all related holiday.linked_request_ids.action_refuse() self._remove_resource_leave() + self.activity_update() return True + # ------------------------------------------------------------ + # Activity methods + # ------------------------------------------------------------ + + def _get_responsible_for_approval(self): + if self.state == 'confirm' and self.manager_id.user_id: + return self.manager_id.user_id + elif self.state == 'confirm' and self.employee_id.parent_id.user_id: + return self.employee_id.parent_id.user_id + elif self.department_id.manager_id.user_id: + return self.department_id.manager_id.user_id + return self.env.user + + def activity_update(self): + to_clean, to_do = self.env['hr.leave'], self.env['hr.leave'] + for holiday in self: + if holiday.state == 'draft': + to_clean |= holiday + elif holiday.state == 'confirm': + holiday.activity_schedule( + 'hr_holidays.mail_act_leave_approval', fields.Date.today(), + user_id=holiday._get_responsible_for_approval().id) + elif holiday.state == 'validate1': + holiday.activity_feedback(['hr_holidays.mail_act_leave_approval']) + holiday.activity_schedule( + 'hr_holidays.mail_act_leave_second_approval', fields.Date.today(), + user_id=holiday._get_responsible_for_approval().id) + elif holiday.state == 'validate': + to_do |= holiday + elif holiday.state == 'refuse': + to_clean |= holiday + if to_clean: + to_clean.activity_unlink(['hr_holidays.mail_act_leave_approval', 'hr_holidays.mail_act_leave_second_approval']) + if to_do: + to_do.activity_feedback(['hr_holidays.mail_act_leave_approval', 'hr_holidays.mail_act_leave_second_approval']) + #################################################### # Messaging methods #################################################### @@ -450,10 +493,6 @@ class HolidaysRequest(models.Model): def _track_subtype(self, init_values): if 'state' in init_values and self.state == 'validate': return 'hr_holidays.mt_leave_approved' - elif 'state' in init_values and self.state == 'validate1': - return 'hr_holidays.mt_leave_first_validated' - elif 'state' in init_values and self.state == 'confirm': - return 'hr_holidays.mt_leave_confirmed' elif 'state' in init_values and self.state == 'refuse': return 'hr_holidays.mt_leave_refused' return super(HolidaysRequest, self)._track_subtype(init_values) diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index d36d89dd2e4..aca17981c3b 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -15,7 +15,7 @@ _logger = logging.getLogger(__name__) class HolidaysAllocation(models.Model): _name = "hr.leave.allocation" _description = "Allocation" - _inherit = ['mail.thread'] + _inherit = ['mail.thread', 'mail.activity.mixin'] def _default_employee(self): return self.env.context.get('default_employee_id') or self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) @@ -147,6 +147,7 @@ class HolidaysAllocation(models.Model): values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id}) holiday = super(HolidaysAllocation, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) holiday.add_follower(employee_id) + holiday.activity_update() return holiday @api.multi @@ -200,13 +201,16 @@ class HolidaysAllocation(models.Model): for linked_request in linked_requests: linked_request.action_draft() linked_requests.unlink() + self.activity_update() return True @api.multi def action_confirm(self): if self.filtered(lambda holiday: holiday.state != 'draft'): raise UserError(_('Leave request must be in Draft state ("To Submit") in order to confirm it.')) - return self.write({'state': 'confirm'}) + res = self.write({'state': 'confirm'}) + self.activity_update() + return res @api.multi def action_approve(self): @@ -230,6 +234,7 @@ class HolidaysAllocation(models.Model): self.filtered(lambda hol: hol.validation_type == 'both').write({'state': 'validate1', 'first_approver_id': current_employee.id}) self.filtered(lambda hol: not hol.validation_type == 'both').action_validate() + self.activity_update() return True @api.multi @@ -259,6 +264,7 @@ class HolidaysAllocation(models.Model): leaves.action_approve() if leaves and leaves[0].validation_type == 'both': leaves.action_validate() + self.activity_update() return True @api.multi @@ -277,8 +283,43 @@ class HolidaysAllocation(models.Model): holiday.write({'state': 'refuse', 'second_approver_id': current_employee.id}) # If a category that created several holidays, cancel all related holiday.linked_request_ids.action_refuse() + self.activity_update() return True + # ------------------------------------------------------------ + # Activity methods + # ------------------------------------------------------------ + + def _get_responsible_for_approval(self): + if self.state == 'confirm' and self.employee_id.parent_id.user_id: + return self.employee_id.parent_id.user_id + elif self.department_id.manager_id.user_id: + return self.department_id.manager_id.user_id + return self.env.user + + def activity_update(self): + to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation'] + for allocation in self: + if allocation.state == 'draft': + to_clean |= allocation + elif allocation.state == 'confirm': + allocation.activity_schedule( + 'hr_holidays.mail_act_leave_allocation_approval', fields.Date.today(), + user_id=allocation._get_responsible_for_approval().id) + elif allocation.state == 'validate1': + allocation.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval']) + allocation.activity_schedule( + 'hr_holidays.mail_act_leave_allocation_second_approval', fields.Date.today(), + user_id=allocation._get_responsible_for_approval().id) + elif allocation.state == 'validate': + to_do |= allocation + elif allocation.state == 'refuse': + to_clean |= allocation + if to_clean: + to_clean.activity_unlink(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval']) + if to_do: + to_do.activity_feedback(['hr_holidays.mail_act_leave_allocation_approval', 'hr_holidays.mail_act_leave_allocation_second_approval']) + #################################################### # Messaging methods #################################################### @@ -287,10 +328,6 @@ class HolidaysAllocation(models.Model): def _track_subtype(self, init_values): if 'state' in init_values and self.state == 'validate': return 'hr_holidays.mt_leave_allocation_approved' - elif 'state' in init_values and self.state == 'validate1': - return 'hr_holidays.mt_leave_allocation_first_validated' - elif 'state' in init_values and self.state == 'confirm': - return 'hr_holidays.mt_leave_allocation_confirmed' elif 'state' in init_values and self.state == 'refuse': return 'hr_holidays.mt_leave_allocation_refused' return super(HolidaysAllocation, self)._track_subtype(init_values) diff --git a/addons/hr_holidays/security/ir.model.access.csv b/addons/hr_holidays/security/ir.model.access.csv index 6238cdf051a..f06aa9a96f1 100644 --- a/addons/hr_holidays/security/ir.model.access.csv +++ b/addons/hr_holidays/security/ir.model.access.csv @@ -10,3 +10,4 @@ access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.mode access_calendar_event_hr_user,calendar.event.hr.user,calendar.model_calendar_event,hr_holidays.group_hr_holidays_user,1,1,1,1 access_calendar_event_type_manager,calendar.event.type.manager,calendar.model_calendar_event_type,hr_holidays.group_hr_holidays_manager,1,1,1,1 access_calendar_attendee_hr_user,calendar.attendee.hr.user,calendar.model_calendar_attendee,hr_holidays.group_hr_holidays_user,1,1,1,1 +access_mail_activity_type_holidays_manager,mail.activity.type.holidays.manager,mail.model_mail_activity_type,hr_holidays.group_hr_holidays_manager,1,1,1,1 diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml index 1146dbe399a..45887ea0843 100644 --- a/addons/hr_holidays/views/hr_leave_allocation_views.xml +++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml @@ -25,6 +25,15 @@ + + + + @@ -77,6 +86,7 @@
+
diff --git a/addons/hr_holidays/views/hr_leave_type_views.xml b/addons/hr_holidays/views/hr_leave_type_views.xml index ba988da2a99..d9b556106d6 100644 --- a/addons/hr_holidays/views/hr_leave_type_views.xml +++ b/addons/hr_holidays/views/hr_leave_type_views.xml @@ -132,8 +132,13 @@ id="menu_hr_holidays_configuration" name="Configuration" parent="menu_hr_holidays_root" - sequence="100" - groups="hr_holidays.group_hr_holidays_manager" - action="open_view_holiday_status"/> + sequence="100"/> + + diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml index 4a6fda9b322..9ed26df82d0 100644 --- a/addons/hr_holidays/views/hr_leave_views.xml +++ b/addons/hr_holidays/views/hr_leave_views.xml @@ -43,6 +43,15 @@ + + + + @@ -119,6 +128,7 @@
+
diff --git a/addons/hr_holidays/views/mail_activity_views.xml b/addons/hr_holidays/views/mail_activity_views.xml new file mode 100644 index 00000000000..952a81e7b26 --- /dev/null +++ b/addons/hr_holidays/views/mail_activity_views.xml @@ -0,0 +1,16 @@ + + + + + Activity Types + mail.activity.type + form + tree,form + ['|', ('res_model_id', '=', False), ('res_model_id.model', 'in', ['hr.leave', 'hr.leave.allocation'])] + {'default_res_model': 'hr.leave'} + + + \ No newline at end of file From 238c20ffff464ca395ce27cacd1ce0da81b498b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 6 Mar 2018 12:14:17 +0100 Subject: [PATCH 06/11] [IMP] hr_holidays: correctly set description of allocation to leaves allocation Seeing allocation word notably in the activity widget is not easy to understand. Leaves allocation is a better naming. --- addons/hr_holidays/models/hr_leave_allocation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index aca17981c3b..fd98213e091 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -14,7 +14,7 @@ _logger = logging.getLogger(__name__) class HolidaysAllocation(models.Model): _name = "hr.leave.allocation" - _description = "Allocation" + _description = "Leaves Allocation" _inherit = ['mail.thread', 'mail.activity.mixin'] def _default_employee(self): From 133fd0a21e7de2f5f64e3d337c6a427e249576ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 29 Nov 2017 15:09:28 +0100 Subject: [PATCH 07/11] [IMP] hr_expense: improve activity management on expense sheet model This commit improves expense flow management through a better integration of activities and addition of automated activities. Several things are done in this commit : * expense sheet (report) model do not inherit from mail.activity.mixin. This commit adds the inherit so that expense users and managers can now schedule and manage activities on expense reports. This will help them in their daily job; * automatic activities generation is added for expense reports. Activities are generated for approval. They are also automatically set as done when validating or unlinked when refusing or resetting to avoid bloating users with unnecessary activities; * a menu to configure activity types is added. Indeed HR managers should be able to see and configure activity types related to their job; * filters are added to be able to use the systray and to filter the kanban view based on activities Having automated activities allow to replace some messages and tracking that were implemented to warn people of expense reports to approve. This commit therefore * simplifies the tracking as tracking to confirm reports is not necessary anymore; * removes to approve subtype on report model as well as its parent subtype on the department. This allows to simplify chatter in hr_expense app; --- addons/hr_expense/__manifest__.py | 1 + addons/hr_expense/data/mail_data.xml | 23 +++++---------- addons/hr_expense/models/hr_expense.py | 29 ++++++++++++++++--- .../hr_expense/security/ir.model.access.csv | 2 +- addons/hr_expense/views/hr_expense_views.xml | 19 ++++++++++++ .../hr_expense/views/mail_activity_views.xml | 16 ++++++++++ 6 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 addons/hr_expense/views/mail_activity_views.xml diff --git a/addons/hr_expense/__manifest__.py b/addons/hr_expense/__manifest__.py index 09096c0ec21..11ae6223152 100644 --- a/addons/hr_expense/__manifest__.py +++ b/addons/hr_expense/__manifest__.py @@ -36,6 +36,7 @@ This module also uses analytic accounting and is compatible with the invoice on 'wizard/hr_expense_refuse_reason_views.xml', 'wizard/hr_expense_sheet_register_payment.xml', 'views/hr_expense_views.xml', + 'views/mail_activity_views.xml', 'security/ir_rule.xml', 'report/hr_expense_report.xml', 'views/hr_department_views.xml', diff --git a/addons/hr_expense/data/mail_data.xml b/addons/hr_expense/data/mail_data.xml index be825bac3db..02d662ecff5 100644 --- a/addons/hr_expense/data/mail_data.xml +++ b/addons/hr_expense/data/mail_data.xml @@ -2,7 +2,13 @@ - + + Expense Approval + fa-dollar + + + + expense @@ -23,27 +29,12 @@ Expense report refused - - Submitted - hr.expense.sheet - Expense report submitted, waiting approval - - Paid hr.expense.sheet Expense report paid - - - Expenses To Approve - hr.department - - - department_id - -