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)])
'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,
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, {
<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>
<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>
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
--- /dev/null
+# -*- 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:
--- /dev/null
+# -*- 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,
+ })
--- /dev/null
+# -*- 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])