[IMP] hr_holidays:
authorThibault Delavallée <tde@openerp.com>
Fri, 9 Aug 2013 14:47:52 +0000 (16:47 +0200)
committerThibault Delavallée <tde@openerp.com>
Fri, 9 Aug 2013 14:47:52 +0000 (16:47 +0200)
- added a workflow transition from refuse to draft, to allow resetting
a refused request
- resetting is now based on a can_reset field, taht is based on whether
it is my own request or I am an hr manager
- added an access right on crm_meeting that was preventing hr officers
to validate some requests
- improved form view to show reset to draft button accordingly
- added tests that helped trigerred the various bugs and improvements

bzr revid: tde@openerp.com-20130809144752-o21pjbc56o0t8fym

addons/hr_holidays/hr_holidays.py
addons/hr_holidays/hr_holidays_view.xml
addons/hr_holidays/hr_holidays_workflow.xml
addons/hr_holidays/security/ir.model.access.csv
addons/hr_holidays/tests/__init__.py [new file with mode: 0644]
addons/hr_holidays/tests/common.py [new file with mode: 0644]
addons/hr_holidays/tests/test_holidays_flow.py [new file with mode: 0644]

index b75d253..008c711 100644 (file)
@@ -129,6 +129,19 @@ class hr_holidays(osv.osv):
                 result[hol.id] = hol.number_of_days_temp
         return result
 
+    def _get_can_reset(self, cr, uid, ids, name, arg, context=None):
+        """User can reset a leave request if it is its own leave request or if
+        he is an Hr Manager. """
+        user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+        group_hr_manager_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')[1]
+        if group_hr_manager_id in [g.id for g in user.groups_id]:
+            return dict.fromkeys(ids, True)
+        result = dict.fromkeys(ids, False)
+        for holiday in self.browse(cr, uid, ids, context=context):
+            if holiday.employee_id and holiday.employee_id.user_id and holiday.employee_id.user_id.id == uid:
+                result[holiday.id] = True
+        return result
+
     def _check_date(self, cr, uid, ids):
         for holiday in self.browse(cr, uid, ids):
             holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id)])
@@ -162,6 +175,9 @@ class hr_holidays(osv.osv):
         'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Tag')], 'Allocation Mode', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category', required=True),
         'manager_id2': fields.many2one('hr.employee', 'Second Approval', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)'),
         'double_validation': fields.related('holiday_status_id', 'double_validation', type='boolean', relation='hr.holidays.status', string='Apply Double Validation'),
+        'can_reset': fields.function(
+            _get_can_reset,
+            type='boolean'),
     }
     _defaults = {
         'employee_id': _employee_get,
@@ -297,14 +313,9 @@ class hr_holidays(osv.osv):
         if context is None:
             context = {}
         context = dict(context, mail_create_nolog=True)
-        return super(hr_holidays, self).create(cr, uid, values, context=context)
-
-    def write(self, cr, uid, ids, vals, context=None):
-        check_fnct = self.pool.get('hr.holidays.status').check_access_rights
-        for  holiday in self.browse(cr, uid, ids, context=context):
-            if holiday.state in ('validate','validate1') and not check_fnct(cr, uid, 'write', raise_exception=False):
-                raise osv.except_osv(_('Warning!'),_('You cannot modify a leave request that has been approved. Contact a human resource manager.'))
-        return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
+        hol_id = super(hr_holidays, self).create(cr, uid, values, context=context)
+        self.check_holidays(cr, uid, [hol_id], context=context)
+        return hol_id
 
     def holidays_reset(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids, {
index 9a91a21..e212ba7 100644 (file)
             <field name="priority">1</field>
             <field name="arch" type="xml">
                 <form string="Leave Request" version="7.0">
+                <field name="can_reset" invisible="1"/>
                 <header>
-                    <button string="Confirm" name="confirm" states="draft" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
+                    <button string="Confirm" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
                     <button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
                     <button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
                     <button string="Refuse" name="refuse" states="confirm,validate1,validate" type="workflow" groups="base.group_hr_user"/>
-                    <button string="Reset to Draft" name="reset" states="confirm" type="workflow" groups="base.group_hr_manager"/>
+                    <button string="Reset to Draft" name="reset" type="workflow"
+                            attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
                     <field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
                 </header>
                 <sheet string="Leave Request">
             <field name="model">hr.holidays</field>
             <field name="arch" type="xml">
                 <form string="Allocation Request" version="7.0">
+                <field name="can_reset" invisible="1"/>
                 <header>
+                    <button string="Confirm" name="confirm" states="draft" type="workflow" class="oe_highlight"/>
                     <button string="Approve" name="validate" states="confirm" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
                     <button string="Validate" name="second_validate" states="validate1" type="workflow" groups="base.group_hr_user" class="oe_highlight"/>
                     <button string="Refuse" name="refuse" states="confirm,validate,validate1" type="workflow" groups="base.group_hr_user"/>
-                    <button string="Reset to Draft" name="reset" states="confirm" type="workflow" groups="base.group_hr_manager"/>
+                    <button string="Reset to Draft" name="reset" type="workflow"
+                            attrs="{'invisible': ['|', ('can_reset', '=', False), ('state', 'not in', ['confirm', 'refuse'])]}"/>
                     <field name="state" widget="statusbar" statusbar_visible="draft,confirm,validate" statusbar_colors='{"confirm":"blue","validate1":"blue","refuse":"red"}'/>
                 </header>
                 <sheet>
index 7d626c1..14cbc48 100644 (file)
@@ -3,13 +3,15 @@
 <data>
 
     <!-- Workflow definition
-        1. draft->submitted (confirm signal)
+        1. draft->submitted (confirm signal) if can_reset
+        2. submitted->draft (reset signal) if can_reset
         2. submitted->accepted (validate signal) if not double_validation
         2. submitted->first_accepted (validate signal) if double_validation
         2. submitted->refused (refuse signal)
         3. accepted->refused (refuse signal)
-        4. first_accepted -> accepted (second_validate  signal)
+        4. first_accepted -> accepted (second_validate signal)
         4. first_accepted -> refused (refuse signal)
+        5. refuse -> draft (reset signal) if can_reset
     -->
 
     <record model="workflow" id="wkf_holidays">
         <field name="split_mode">OR</field>
     </record>
 
-
     <record model="workflow.activity" id="act_refuse"> <!-- refused -->
         <field name="wkf_id" ref="wkf_holidays" />
         <field name="name">refuse</field>
-        <field name="flow_stop">True</field>
         <field name="kind">function</field>
         <field name="action">holidays_refuse()</field>
     </record>
         <field name="act_from" ref="act_draft" />
         <field name="act_to" ref="act_confirm" />
         <field name="signal">confirm</field>
-        <field name="condition">True</field>
-        <field name="group_id" ref="base.group_hr_user"/>
+        <field name="condition">can_reset</field>
+        <field name="group_id" ref="base.group_user"/>
     </record>
 
-    <record model="workflow.transition" id="holiday_confirm2draft"> <!-- 1. submitted->draft (reset signal) -->
+    <record model="workflow.transition" id="holiday_confirm2draft"> <!-- 2. submitted->draft (reset signal) -->
         <field name="act_from" ref="act_confirm" />
         <field name="act_to" ref="act_draft" />
         <field name="signal">reset</field>
-        <field name="condition">True</field>
-        <field name="group_id" ref="base.group_hr_user"/>
+        <field name="condition">can_reset</field>
+        <field name="group_id" ref="base.group_user"/>
     </record>
 
     <record model="workflow.transition" id="holiday_confirm2validate"> <!-- 2. submitted->accepted (validate signal) if not double_validation-->
         <field name="group_id" ref="base.group_hr_user"/>
     </record>
 
-
     <record model="workflow.transition" id="holiday_validate1_validate"> <!-- 4. first_accepted -> accepted (second_validate  signal) -->
         <field name="act_from" ref="act_validate1" />
         <field name="act_to" ref="act_validate" />
         <field name="group_id" ref="base.group_hr_user"/>
     </record>
 
+    <record model="workflow.transition" id="holiday_refuse2draft"> <!-- 5. refused->draft (reset signal) -->
+        <field name="act_from" ref="act_refuse" />
+        <field name="act_to" ref="act_draft" />
+        <field name="signal">reset</field>
+        <field name="condition">can_reset</field>
+        <field name="group_id" ref="base.group_hr_manager"/>
+    </record>
+
 </data>
 </openerp>
index 0375a17..8a2594b 100644 (file)
@@ -1,8 +1,9 @@
 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink\r
-access_hr_holydays_status_user,hr.holidays.status user,model_hr_holidays_status,base.group_hr_user,1,1,1,1\r
 access_hr_holidays_user,hr.holidays.user,model_hr_holidays,base.group_hr_user,1,1,1,1\r
 access_hr_holidays_employee,hr.holidays.employee,model_hr_holidays,base.group_user,1,1,1,1\r
 access_hr_holydays_status_employee,hr.holidays.status employee,model_hr_holidays_status,base.group_user,1,0,0,0\r
+access_hr_holydays_status_manager,hr.holidays.status manager,model_hr_holidays_status,base.group_hr_manager,1,1,1,1\r
 access_hr_holidays_remain_user,hr.holidays.ramain.user,model_hr_holidays_remaining_leaves_user,base.group_hr_user,1,1,1,1\r
 access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,base.group_hr_user,1,1,1,1\r
+access_crm_meeting_hr_user,crm.meeting.hr.user,base_calendar.model_crm_meeting,base.group_hr_user,1,1,1,1\r
 access_crm_meeting_type_manager,crm.meeting.type.manager,base_calendar.model_crm_meeting_type,base.group_hr_manager,1,1,1,1\r
diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py
new file mode 100644 (file)
index 0000000..f70a627
--- /dev/null
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.addons.hr_holidays.tests import test_holidays_flow
+
+checks = [
+    test_holidays_flow,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py
new file mode 100644 (file)
index 0000000..2124a25
--- /dev/null
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.tests import common
+
+
+class TestHrHolidaysBase(common.TransactionCase):
+
+    def setUp(self):
+        super(TestHrHolidaysBase, self).setUp()
+        cr, uid = self.cr, self.uid
+
+        # Usefull models
+        self.hr_employee = self.registry('hr.employee')
+        self.hr_holidays = self.registry('hr.holidays')
+        self.hr_holidays_status = self.registry('hr.holidays.status')
+        self.res_users = self.registry('res.users')
+        self.res_partner = self.registry('res.partner')
+
+        # Find Employee group
+        group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
+        self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
+
+        # Find Hr User group
+        group_hr_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_user')
+        self.group_hr_user_ref_id = group_hr_user_ref and group_hr_user_ref[1] or False
+
+        # Find Hr Manager group
+        group_hr_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')
+        self.group_hr_manager_ref_id = group_hr_manager_ref and group_hr_manager_ref[1] or False
+
+        # Test partners to use through the various tests
+        self.hr_partner_id = self.res_partner.create(cr, uid, {
+            'name': 'Gertrude AgrolaitPartner',
+            'email': 'gertrude.partner@agrolait.com',
+        })
+        self.email_partner_id = self.res_partner.create(cr, uid, {
+            'name': 'Patrick Ratatouille',
+            'email': 'patrick.ratatouille@agrolait.com',
+        })
+
+        # Test users to use through the various tests
+        self.user_hruser_id = self.res_users.create(cr, uid, {
+            'name': 'Armande HrUser',
+            'login': 'Armande',
+            'alias_name': 'armande',
+            'email': 'armande.hruser@example.com',
+            'groups_id': [(6, 0, [self.group_employee_id, self.group_hr_user_ref_id])]
+        }, {'no_reset_password': True})
+        self.user_hrmanager_id = self.res_users.create(cr, uid, {
+            'name': 'Bastien HrManager',
+            'login': 'bastien',
+            'alias_name': 'bastien',
+            'email': 'bastien.hrmanager@example.com',
+            'groups_id': [(6, 0, [self.group_employee_id, self.group_hr_manager_ref_id])]
+        }, {'no_reset_password': True})
+        self.user_none_id = self.res_users.create(cr, uid, {
+            'name': 'Charlie Avotbonkeur',
+            'login': 'charlie',
+            'alias_name': 'charlie',
+            'email': 'charlie.noone@example.com',
+            'groups_id': [(6, 0, [])]
+        }, {'no_reset_password': True})
+        self.user_employee_id = self.res_users.create(cr, uid, {
+            'name': 'David Employee',
+            'login': 'david',
+            'alias_name': 'david',
+            'email': 'david.employee@example.com',
+            'groups_id': [(6, 0, [self.group_employee_id])]
+        }, {'no_reset_password': True})
+
+        # Hr Data
+        self.employee_emp_id = self.hr_employee.create(cr, uid, {
+            'name': 'David Employee',
+            'user_id': self.user_employee_id,
+        })
+        self.employee_hruser_id = self.hr_employee.create(cr, uid, {
+            'name': 'Armande HrUser',
+            'user_id': self.user_hruser_id,
+        })
diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py
new file mode 100644 (file)
index 0000000..caa6e14
--- /dev/null
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from openerp.addons.hr_holidays.tests.common import TestHrHolidaysBase
+from openerp.osv.orm import except_orm
+from openerp.tools import mute_logger
+
+
+class TestHolidaysFlow(TestHrHolidaysBase):
+
+    @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
+    def test_00_leave_request_flow(self):
+        """ Testing leave request flow """
+        cr, uid = self.cr, self.uid
+
+        def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
+            self.assertEqual(holiday_status.max_leaves, ml,
+                             'hr_holidays: wrong type days computation')
+            self.assertEqual(holiday_status.leaves_taken, lt,
+                             'hr_holidays: wrong type days computation')
+            self.assertEqual(holiday_status.remaining_leaves, rl,
+                             'hr_holidays: wrong type days computation')
+            self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
+                             'hr_holidays: wrong type days computation')
+
+        # HrUser creates some holiday statuses -> crash because only HrManagers should do this
+        with self.assertRaises(except_orm):
+            self.holidays_status_dummy = self.hr_holidays_status.create(cr, self.user_hruser_id, {
+                'name': 'UserCheats',
+                'limit': True,
+            })
+
+        # HrManager creates some holiday statuses
+        self.holidays_status_0 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
+            'name': 'WithMeetingType',
+            'limit': True,
+            'categ_id': self.registry('crm.meeting.type').create(cr, self.user_hrmanager_id, {'name': 'NotLimitedMeetingType'}),
+        })
+        self.holidays_status_1 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
+            'name': 'NotLimited',
+            'limit': True,
+        })
+        self.holidays_status_2 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
+            'name': 'Limited',
+            'limit': False,
+            'double_validation': True,
+        })
+
+        # --------------------------------------------------
+        # Case1: unlimited type of leave request
+        # --------------------------------------------------
+
+        # Employee creates a leave request for another employee -> should crash
+        with self.assertRaises(except_orm):
+            self.hr_holidays.create(cr, self.user_employee_id, {
+                'name': 'Hol10',
+                'employee_id': self.employee_hruser_id,
+                'holiday_status_id': self.holidays_status_1,
+                'date_from': (datetime.today() - relativedelta(days=1)),
+                'date_to': datetime.today(),
+                'number_of_days_temp': 1,
+            })
+
+        # Employee creates a leave request in a no-limit category
+        hol1_id = self.hr_holidays.create(cr, self.user_employee_id, {
+            'name': 'Hol11',
+            'employee_id': self.employee_emp_id,
+            'holiday_status_id': self.holidays_status_1,
+            'date_from': (datetime.today() - relativedelta(days=1)),
+            'date_to': datetime.today(),
+            'number_of_days_temp': 1,
+        })
+        hol1 = self.hr_holidays.browse(cr, self.user_hruser_id, hol1_id)
+        self.assertEqual(hol1.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
+
+        # Employee validates its leave request -> should not work
+        self.hr_holidays.signal_validate(cr, self.user_employee_id, [hol1_id])
+        hol1.refresh()
+        self.assertEqual(hol1.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request')
+
+        # HrUser validates the employee leave request
+        self.hr_holidays.signal_validate(cr, self.user_hrmanager_id, [hol1_id])
+        hol1.refresh()
+        self.assertEqual(hol1.state, 'validate', 'hr_holidays: validates leave request should be in validate state')
+
+        # --------------------------------------------------
+        # Case2: limited type of leave request
+        # --------------------------------------------------
+
+        # Employee creates a new leave request at the same time -> crash, avoid interlapping
+        with self.assertRaises(except_orm):
+            self.hr_holidays.create(cr, self.user_employee_id, {
+                'name': 'Hol21',
+                'employee_id': self.employee_emp_id,
+                'holiday_status_id': self.holidays_status_1,
+                'date_from': (datetime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
+                'date_to': datetime.today(),
+                'number_of_days_temp': 1,
+            })
+
+        # Employee creates a leave request in a limited category -> crash, not enough days left
+        with self.assertRaises(except_orm):
+            self.hr_holidays.create(cr, self.user_employee_id, {
+                'name': 'Hol22',
+                'employee_id': self.employee_emp_id,
+                'holiday_status_id': self.holidays_status_2,
+                'date_from': (datetime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M'),
+                'date_to': (datetime.today() + relativedelta(days=1)),
+                'number_of_days_temp': 1,
+            })
+
+        # Clean transaction
+        self.hr_holidays.unlink(cr, uid, self.hr_holidays.search(cr, uid, [('name', 'in', ['Hol21', 'Hol22'])]))
+
+        # HrUser allocates some leaves to the employee
+        aloc1_id = self.hr_holidays.create(cr, self.user_hruser_id, {
+            'name': 'Days for limited category',
+            'employee_id': self.employee_emp_id,
+            'holiday_status_id': self.holidays_status_2,
+            'type': 'add',
+            'number_of_days_temp': 2,
+        })
+        # HrUser validates the allocation request
+        self.hr_holidays.signal_validate(cr, self.user_hruser_id, [aloc1_id])
+        self.hr_holidays.signal_second_validate(cr, self.user_hruser_id, [aloc1_id])
+        # Checks Employee has effectively some days left
+        hol_status_2 = self.hr_holidays_status.browse(cr, self.user_employee_id, self.holidays_status_2)
+        _check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 2.0)
+
+        # Employee creates a leave request in the limited category, now that he has some days left
+        hol2_id = self.hr_holidays.create(cr, self.user_employee_id, {
+            'name': 'Hol22',
+            'employee_id': self.employee_emp_id,
+            'holiday_status_id': self.holidays_status_2,
+            'date_from': (datetime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M'),
+            'date_to': (datetime.today() + relativedelta(days=3)),
+            'number_of_days_temp': 1,
+        })
+        hol2 = self.hr_holidays.browse(cr, self.user_hruser_id, hol2_id)
+        # Check left days: - 1 virtual remaining day
+        hol_status_2.refresh()
+        _check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 1.0)
+
+        # HrUser validates the first step
+        self.hr_holidays.signal_validate(cr, self.user_hruser_id, [hol2_id])
+        hol2.refresh()
+        self.assertEqual(hol2.state, 'validate1',
+                         'hr_holidays: first validation should lead to validate1 state')
+
+        # HrUser validates the second step
+        self.hr_holidays.signal_second_validate(cr, self.user_hruser_id, [hol2_id])
+        hol2.refresh()
+        self.assertEqual(hol2.state, 'validate',
+                         'hr_holidays: second validation should lead to validate state')
+        # Check left days: - 1 day taken
+        hol_status_2.refresh()
+        _check_holidays_status(hol_status_2, 2.0, 1.0, 1.0, 1.0)
+
+        # HrManager finds an error: he refuses the leave request
+        self.hr_holidays.signal_refuse(cr, self.user_hrmanager_id, [hol2_id])
+        hol2.refresh()
+        self.assertEqual(hol2.state, 'refuse',
+                         'hr_holidays: refuse should lead to refuse state')
+        # Check left days: 2 days left again
+        hol_status_2.refresh()
+        _check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 2.0)
+
+        # Annoyed, HrUser tries to fix its error and tries to reset the leave request -> does not work, only HrManager
+        self.hr_holidays.signal_reset(cr, self.user_hruser_id, [hol2_id])
+        self.assertEqual(hol2.state, 'refuse',
+                         'hr_holidays: hr_user should not be able to reset a refused leave request')
+
+        # HrManager resets the request
+        self.hr_holidays.signal_reset(cr, self.user_hrmanager_id, [hol2_id])
+        hol2.refresh()
+        self.assertEqual(hol2.state, 'draft',
+                         'hr_holidays: resetting should lead to draft state')
+
+        # HrManager changes the date and put too much days -> crash when confirming
+        self.hr_holidays.write(cr, self.user_hrmanager_id, [hol2_id], {
+            'date_from': (datetime.today() + relativedelta(days=4)).strftime('%Y-%m-%d %H:%M'),
+            'date_to': (datetime.today() + relativedelta(days=7)),
+            'number_of_days_temp': 4,
+        })
+        with self.assertRaises(except_orm):
+            self.hr_holidays.signal_confirm(cr, self.user_hrmanager_id, [hol2_id])