[MERGE] [IMP] hr_holidays: days computation and workflow update

Remaining days calculation:
- get_days now also calculate virtual remaining days that take into account the confirmed leave requests.
- virtual remaining days are taken into account when confirming / validating a leave request. This allows to avoid having several pending leave requests that cannot be validated because of a lack of remaining days once all requests are validated.

Workflow update:
- begins in confirm state
- added transition confirm->draft; transition draft->confirm is not automatic anymore (Confirm button)
- added transition refuse->draft, to reset the leave request and allow edition

Misc
- leave summary does not show refused leave requests anymore
- added tests + some fixes for bugs trigerred by the tests (mainly access rights issues)
- added changelog

bzr revid: tde@openerp.com-20130812131723-ioe7sfgvl20z0npc
This commit is contained in:
Thibault Delavallée
2013-08-12 15:17:23 +02:00
11 changed files with 462 additions and 71 deletions
+13
View File
@@ -0,0 +1,13 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- updated ``hr.holidays`` workflow. It now starts in ``confirm`` state. In
``confirm```and ``refuse`` a Reset to Draft has been added in view / workflow,
allowing to edit the request. Added a ``can_reset`` computed field to enable
this transition. A user can edit its own requests, or all requests if he is
an Hr Manager.
+13
View File
@@ -0,0 +1,13 @@
Hr Holidays module documentation
================================
Hr Holidays documentation topics
''''''''''''''''''''''''''''''''
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst
+59 -52
View File
@@ -22,11 +22,10 @@
##############################################################################
import datetime
import time
from itertools import groupby
from operator import attrgetter, itemgetter
import math
import time
from operator import attrgetter
from openerp import tools
from openerp.osv import fields, osv
from openerp.tools.translate import _
@@ -36,40 +35,37 @@ class hr_holidays_status(osv.osv):
_name = "hr.holidays.status"
_description = "Leave Type"
def get_days(self, cr, uid, ids, employee_id, return_false, context=None):
cr.execute("""SELECT id, type, number_of_days, holiday_status_id FROM hr_holidays WHERE employee_id = %s AND state='validate' AND holiday_status_id in %s""",
[employee_id, tuple(ids)])
result = sorted(cr.dictfetchall(), key=lambda x: x['holiday_status_id'])
grouped_lines = dict((k, [v for v in itr]) for k, itr in groupby(result, itemgetter('holiday_status_id')))
res = {}
for record in self.browse(cr, uid, ids, context=context):
res[record.id] = {}
max_leaves = leaves_taken = 0
if not return_false:
if record.id in grouped_lines:
leaves_taken = -sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'remove'])
max_leaves = sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'add'])
res[record.id]['max_leaves'] = max_leaves
res[record.id]['leaves_taken'] = leaves_taken
res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
return res
def get_days(self, cr, uid, ids, employee_id, context=None):
result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0,
virtual_remaining_leaves=0)) for id in ids)
holiday_ids = self.pool['hr.holidays'].search(cr, uid, [('employee_id', '=', employee_id),
('state', 'in', ['confirm', 'validate1', 'validate']),
('holiday_status_id', 'in', ids)
], context=context)
for holiday in self.pool['hr.holidays'].browse(cr, uid, holiday_ids, context=context):
status_dict = result[holiday.holiday_status_id.id]
if holiday.type == 'add':
status_dict['virtual_remaining_leaves'] += holiday.number_of_days
if holiday.state == 'validate':
status_dict['max_leaves'] += holiday.number_of_days
status_dict['remaining_leaves'] += holiday.number_of_days
elif holiday.type == 'remove': # number of days is negative
status_dict['virtual_remaining_leaves'] += holiday.number_of_days
if holiday.state == 'validate':
status_dict['leaves_taken'] -= holiday.number_of_days
status_dict['remaining_leaves'] += holiday.number_of_days
return result
def _user_left_days(self, cr, uid, ids, name, args, context=None):
return_false = False
employee_id = False
res = {}
if context and context.has_key('employee_id'):
if not context['employee_id']:
return_false = True
if context and 'employee_id' in context:
employee_id = context['employee_id']
else:
employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
if employee_ids:
employee_id = employee_ids[0]
else:
return_false = True
if employee_id:
res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
res = self.get_days(cr, uid, ids, employee_id, context=context)
else:
res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
return res
@@ -84,6 +80,7 @@ class hr_holidays_status(osv.osv):
'max_leaves': fields.function(_user_left_days, string='Maximum Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
'leaves_taken': fields.function(_user_left_days, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),
'remaining_leaves': fields.function(_user_left_days, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
'virtual_remaining_leaves': fields.function(_user_left_days, string='Virtual Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval', multi='user_left_days'),
'double_validation': fields.boolean('Apply Double Validation', help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved."),
}
_defaults = {
@@ -92,8 +89,6 @@ class hr_holidays_status(osv.osv):
}
def name_get(self, cr, uid, ids, context=None):
if not ids:
return []
res = []
for record in self.browse(cr, uid, ids, context=context):
name = record.name
@@ -134,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)])
@@ -167,10 +175,13 @@ 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,
'state': 'draft',
'state': 'confirm',
'type': 'remove',
'user_id': lambda obj, cr, uid, context: uid,
'holiday_type': 'employee'
@@ -302,27 +313,20 @@ 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)
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 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)
def set_to_draft(self, cr, uid, ids, context=None):
def holidays_reset(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {
'state': 'draft',
'manager_id': False,
'manager_id2': False,
})
self.delete_workflow(cr, uid, ids)
self.create_workflow(cr, uid, ids)
to_unlink = []
for record in self.browse(cr, uid, ids, context=context):
for record2 in record.linked_request_ids:
self.set_to_draft(cr, uid, [record2.id], context=context)
self.holidays_reset(cr, uid, [record2.id], context=context)
to_unlink.append(record2.id)
if to_unlink:
self.unlink(cr, uid, to_unlink, context=context)
@@ -421,13 +425,16 @@ class hr_holidays(osv.osv):
return True
def check_holidays(self, cr, uid, ids, context=None):
holi_status_obj = self.pool.get('hr.holidays.status')
for record in self.browse(cr, uid, ids):
if record.holiday_type == 'employee' and record.type == 'remove':
if record.employee_id and not record.holiday_status_id.limit:
leaves_rest = holi_status_obj.get_days( cr, uid, [record.holiday_status_id.id], record.employee_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
if leaves_rest < record.number_of_days_temp:
raise osv.except_osv(_('Warning!'), _('There are not enough %s allocated for employee %s; please create an allocation request for this leave type.') % (record.holiday_status_id.name, record.employee_id.name))
for record in self.browse(cr, uid, ids, context=context):
if record.holiday_type != 'employee' or record.type != 'remove' or not record.employee_id or record.holiday_status_id.limit:
continue
leave_days = self.pool.get('hr.holidays.status').get_days(cr, uid, [record.holiday_status_id.id], record.employee_id.id, context=context)[record.holiday_status_id.id]
if leave_days['remaining_leaves'] < record.number_of_days_temp:
raise osv.except_osv(_('Warning!'),
_('There are not enough remaining days available in %s for employee %s.') % (record.holiday_status_id.name, record.employee_id.name))
if leave_days['virtual_remaining_leaves'] < record.number_of_days_temp:
raise osv.except_osv(_('Warning!'),
_('Other pending requests already book too much days in %s for employee %s.') % (record.holiday_status_id.name, record.employee_id.name))
return True
# -----------------------------
+1 -1
View File
@@ -49,7 +49,7 @@
<record id="mt_holidays_confirmed" model="mail.message.subtype">
<field name="name">To Approve</field>
<field name="res_model">hr.holidays</field>
<field name="description">Request created and waiting confirmation</field>
<field name="description">Request confirmed and waiting approval</field>
</record>
<record id="mt_holidays_approved" model="mail.message.subtype">
<field name="name">Approved</field>
+11 -5
View File
@@ -53,11 +53,14 @@
<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" 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 New" name="set_to_draft" states="refuse" type="object" groups="base.group_hr_user"/>
<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">
@@ -98,11 +101,14 @@
<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 New" name="set_to_draft" states="cancel,refuse" type="object" groups="base.group_hr_user"/>
<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>
@@ -163,7 +169,7 @@
<button string="Submit to Manager" name="confirm" states="draft" type="workflow" icon="gtk-yes"/>
<button string="Approve" name="validate" states="confirm" type="workflow" icon="gtk-apply"/>
<button string="Refuse" name="refuse" states="confirm,validate,draft" type="workflow" icon="gtk-no"/>
<button string="Reset to New" name="set_to_draft" states="cancel" type="object" icon="gtk-convert"/>
<button string="Reset to Draft" name="reset" states="confirm" type="workflow" groups="base.group_hr_manager"/>
<field name="state"/>
</header>
<group col="4">
@@ -351,8 +357,8 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="view_holiday_simple"/>
<field name="context">{'search_default_group_type': 1, 'search_default_validated': 1}</field>
<field name="domain">[('holiday_type','=','employee')]</field>
<field name="context">{'search_default_group_type': 1}</field>
<field name="domain">[('holiday_type','=','employee'), ('state', '!=', 'refuse')]</field>
<field name="search_view_id" ref="view_hr_holidays_filter"/>
</record>
+29 -11
View File
@@ -2,15 +2,16 @@
<openerp>
<data>
<!-- Workflow definition
1. draft->submitted (no signal)
<!-- Workflow definition
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->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">
@@ -21,13 +22,15 @@
<record model="workflow.activity" id="act_draft"> <!-- draft -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="flow_start">True</field>
<field name="name">draft</field>
<field name="kind">function</field>
<field name="action">holidays_reset()</field>
</record>
<record model="workflow.activity" id="act_confirm"> <!-- submitted -->
<field name="wkf_id" ref="wkf_holidays" />
<field name="name">confirm</field>
<field name="flow_start">True</field>
<field name="kind">function</field>
<field name="action">holidays_confirm()</field>
<field name="split_mode">OR</field>
@@ -48,11 +51,9 @@
<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>
@@ -61,9 +62,20 @@
workflow transition
-->
<record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (no signal) -->
<record model="workflow.transition" id="holiday_draft2confirm"> <!-- 1. draft->submitted (confirm signal) -->
<field name="act_from" ref="act_draft" />
<field name="act_to" ref="act_confirm" />
<field name="signal">confirm</field>
<field name="condition">can_reset</field>
<field name="group_id" ref="base.group_user"/>
</record>
<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">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-->
@@ -82,7 +94,6 @@
<field name="group_id" ref="base.group_hr_user"/>
</record>
<record model="workflow.transition" id="holiday_confirm2refuse"> <!-- 2. submitted->refused (refuse signal) -->
<field name="act_from" ref="act_confirm" />
<field name="act_to" ref="act_refuse" />
@@ -107,7 +118,6 @@
<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" />
@@ -124,5 +134,13 @@
<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>
@@ -1,8 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_holydays_status_user,hr.holidays.status user,model_hr_holidays_status,base.group_hr_user,1,1,1,1
access_hr_holidays_user,hr.holidays.user,model_hr_holidays,base.group_hr_user,1,1,1,1
access_hr_holidays_employee,hr.holidays.employee,model_hr_holidays,base.group_user,1,1,1,1
access_hr_holydays_status_employee,hr.holidays.status employee,model_hr_holidays_status,base.group_user,1,0,0,0
access_hr_holydays_status_manager,hr.holidays.status manager,model_hr_holidays_status,base.group_hr_manager,1,1,1,1
access_hr_holidays_remain_user,hr.holidays.ramain.user,model_hr_holidays_remaining_leaves_user,base.group_hr_user,1,1,1,1
access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,base.group_hr_user,1,1,1,1
access_crm_meeting_hr_user,crm.meeting.hr.user,base_calendar.model_crm_meeting,base.group_hr_user,1,1,1,1
access_crm_meeting_type_manager,crm.meeting.type.manager,base_calendar.model_crm_meeting_type,base.group_hr_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
access_hr_holydays_status_user hr.holidays.status user model_hr_holidays_status base.group_hr_user 1 1 1 1
2 access_hr_holidays_user hr.holidays.user model_hr_holidays base.group_hr_user 1 1 1 1
3 access_hr_holidays_employee hr.holidays.employee model_hr_holidays base.group_user 1 1 1 1
4 access_hr_holydays_status_employee hr.holidays.status employee model_hr_holidays_status base.group_user 1 0 0 0
5 access_hr_holydays_status_manager hr.holidays.status manager model_hr_holidays_status base.group_hr_manager 1 1 1 1
6 access_hr_holidays_remain_user hr.holidays.ramain.user model_hr_holidays_remaining_leaves_user base.group_hr_user 1 1 1 1
7 access_resource_calendar_leaves_user resource_calendar_leaves_user resource.model_resource_calendar_leaves base.group_hr_user 1 1 1 1
8 access_crm_meeting_hr_user crm.meeting.hr.user base_calendar.model_crm_meeting base.group_hr_user 1 1 1 1
9 access_crm_meeting_type_manager crm.meeting.type.manager base_calendar.model_crm_meeting_type base.group_hr_manager 1 1 1 1
+1 -1
View File
@@ -18,7 +18,7 @@
I again set to draft and then confirm.
-
!python {model: hr.holidays}: |
self.set_to_draft(cr, uid, [ref('hr_holidays_employee1_cl')])
self.holidays_reset(cr, uid, [ref('hr_holidays_employee1_cl')])
self.signal_confirm(cr, uid, [ref('hr_holidays_employee1_cl')])
-
I validate the holiday request by clicking on "To Approve" button.
+28
View File
@@ -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:
+98
View File
@@ -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,
})
@@ -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])