diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py index 79a0e47b5e4..ed14ca78a0c 100644 --- a/addons/hr_holidays/__manifest__.py +++ b/addons/hr_holidays/__manifest__.py @@ -33,7 +33,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_holidays_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', @@ -41,8 +43,7 @@ A synchronization with an internal agenda (Meetings of the CRM module) is also p 'report/hr_holidays_templates.xml', 'report/hr_holidays_reports.xml', - 'report/hr_holidays_leaves_report_views.xml', - 'report/hr_holidays_views.xml', + 'report/hr_leave_reports.xml', ], 'demo': [ 'data/hr_holidays_demo.xml', diff --git a/addons/hr_holidays/controllers/main.py b/addons/hr_holidays/controllers/main.py index 3b0b5ee1b0d..3c991502681 100644 --- a/addons/hr_holidays/controllers/main.py +++ b/addons/hr_holidays/controllers/main.py @@ -7,9 +7,9 @@ from odoo import http class HrHolidaysController(http.Controller): - @http.route('/hr_holidays/validate', type='http', auth='user', methods=['GET']) - def hr_holidays_validate(self, res_id, token): - comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token) + @http.route('/leave/validate', type='http', auth='user', methods=['GET']) + def hr_holidays_request_validate(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.leave', int(res_id), token) if comparison and record: try: record.action_approve() @@ -17,9 +17,29 @@ class HrHolidaysController(http.Controller): return MailController._redirect_to_messaging() return redirect - @http.route('/hr_holidays/refuse', type='http', auth='user', methods=['GET']) - def hr_holidays_refuse(self, res_id, token): - comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token) + @http.route('/leave/refuse', type='http', auth='user', methods=['GET']) + def hr_holidays_request_refuse(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.leave', int(res_id), token) + if comparison and record: + try: + record.action_refuse() + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/allocation/validate', type='http', auth='user', methods=['GET']) + def hr_holidays_allocation_validate(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.leave.allocation', int(res_id), token) + if comparison and record: + try: + record.action_approve() + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/allocation/refuse', type='http', auth='user', methods=['GET']) + def hr_holidays_allocation_refuse(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.leave.allocation', int(res_id), token) if comparison and record: try: record.action_refuse() diff --git a/addons/hr_holidays/data/hr_holidays_data.xml b/addons/hr_holidays/data/hr_holidays_data.xml index 285afe48442..235bd6e036d 100644 --- a/addons/hr_holidays/data/hr_holidays_data.xml +++ b/addons/hr_holidays/data/hr_holidays_data.xml @@ -3,93 +3,156 @@ - + black - + Sick Leaves red - + Compensatory Days lavender - + Unpaid brown - + Confirmed - hr.holidays + hr.leave Request created and waiting confirmation - + Waiting Second Validation - hr.holidays + hr.leave Request validated, waiting second validation - + Approved - hr.holidays + hr.leave Request approved - + Refused - hr.holidays + hr.leave + + Request refused + + + + + 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 Request refused - - Leaves/Allocations Confirmed + + Leaves Confirmed hr.department - + department_id - - Leaves/Allocations First Approval + + Leaves First Approval hr.department - + department_id - - Leaves/Allocation Approved + + Leaves Approved hr.department - + department_id - - Leaves/Allocation Refused + + Leaves Refused hr.department - + + department_id + + + + + Allocations Confirmed + hr.department + + + department_id + + + + + Allocations First Approval + hr.department + + + department_id + + + + + Allocation Approved + hr.department + + + department_id + + + + + Allocation Refused + hr.department + + department_id diff --git a/addons/hr_holidays/data/hr_holidays_demo.xml b/addons/hr_holidays/data/hr_holidays_demo.xml index fbfc9602c50..fb9b43eead5 100644 --- a/addons/hr_holidays/data/hr_holidays_demo.xml +++ b/addons/hr_holidays/data/hr_holidays_demo.xml @@ -2,62 +2,53 @@ - + Legal Leaves for Peter Parker - add 20 confirm - + International Tour - - - add 7 confirm - + Summer Vacation - - - add 7 - + - + Trip with Family - remove 3 - + Doctor Appointment - remove 3 confirm - + diff --git a/addons/hr_holidays/models/__init__.py b/addons/hr_holidays/models/__init__.py index 334982c4a1d..621aeccd08b 100644 --- a/addons/hr_holidays/models/__init__.py +++ b/addons/hr_holidays/models/__init__.py @@ -3,4 +3,6 @@ from . import resource from . import hr -from . import hr_holidays +from . import hr_leave +from . import hr_leave_allocation +from . import hr_leave_type diff --git a/addons/hr_holidays/models/hr.py b/addons/hr_holidays/models/hr.py index a0e95494857..3f70bec2961 100644 --- a/addons/hr_holidays/models/hr.py +++ b/addons/hr_holidays/models/hr.py @@ -23,22 +23,23 @@ class Department(models.Model): @api.multi def _compute_leave_count(self): - Holiday = self.env['hr.holidays'] + Requests = self.env['hr.leave'] + Allocations = self.env['hr.leave.allocation'] today_date = datetime.datetime.utcnow().date() today_start = fields.Datetime.to_string(today_date) # get the midnight of the current utc day today_end = fields.Datetime.to_string(today_date + relativedelta(hours=23, minutes=59, seconds=59)) - leave_data = Holiday.read_group( + leave_data = Requests.read_group( [('department_id', 'in', self.ids), - ('state', '=', 'confirm'), ('type', '=', 'remove')], + ('state', '=', 'confirm')], ['department_id'], ['department_id']) - allocation_data = Holiday.read_group( + allocation_data = Allocations.read_group( [('department_id', 'in', self.ids), - ('state', '=', 'confirm'), ('type', '=', 'add')], + ('state', '=', 'confirm')], ['department_id'], ['department_id']) - absence_data = Holiday.read_group( + absence_data = Requests.read_group( [('department_id', 'in', self.ids), ('state', 'not in', ['cancel', 'refuse']), - ('date_from', '<=', today_end), ('date_to', '>=', today_start), ('type', '=', 'remove')], + ('date_from', '<=', today_end), ('date_to', '>=', today_start)], ['department_id'], ['department_id']) res_leave = dict((data['department_id'][0], data['department_id_count']) for data in leave_data) @@ -74,7 +75,7 @@ class Employee(models.Model): ('validate', 'Approved'), ('cancel', 'Cancelled') ]) - current_leave_id = fields.Many2one('hr.holidays.status', compute='_compute_leave_status', string="Current Leave Type") + current_leave_id = fields.Many2one('hr.leave.type', compute='_compute_leave_status', string="Current Leave Type") leave_date_from = fields.Date('From Date', compute='_compute_leave_status') leave_date_to = fields.Date('To Date', compute='_compute_leave_status') leaves_count = fields.Float('Number of Leaves', compute='_compute_leaves_count') @@ -90,8 +91,16 @@ class Employee(models.Model): sum(h.number_of_days) AS days, h.employee_id FROM - hr_holidays h - join hr_holidays_status s ON (s.id=h.holiday_status_id) + ( + SELECT holiday_status_id, number_of_days, + state, employee_id + FROM hr_leave_allocation + UNION + SELECT holiday_status_id, number_of_days, + state, employee_id + FROM hr_leave + ) h + join hr_leave_type s ON (s.id=h.holiday_status_id) WHERE h.state='validate' AND s.limit=False AND @@ -107,7 +116,7 @@ class Employee(models.Model): @api.multi def _inverse_remaining_leaves(self): - status_list = self.env['hr.holidays.status'].search([('limit', '=', False)]) + status_list = self.env['hr.leave.type'].search([('limit', '=', False)]) # Create leaves (adding remaining leaves) or raise (reducing remaining leaves) actual_remaining = self._get_remaining_leaves() for employee in self.filtered(lambda employee: employee.remaining_leaves): @@ -125,11 +134,10 @@ class Employee(models.Model): # if a status is found, then compute remaing leave for current employee difference = employee.remaining_leaves - actual_remaining.get(employee.id, 0) if difference > 0: - leave = self.env['hr.holidays'].create({ + leave = self.env['hr.leave.allocation'].create({ 'name': _('Allocation for %s') % employee.name, 'employee_id': employee.id, 'holiday_status_id': status.id, - 'type': 'add', 'holiday_type': 'employee', 'number_of_days_temp': difference }) @@ -142,11 +150,10 @@ class Employee(models.Model): @api.multi def _compute_leave_status(self): # Used SUPERUSER_ID to forcefully get status of other user's leave, to bypass record rule - holidays = self.env['hr.holidays'].sudo().search([ + holidays = self.env['hr.leave'].sudo().search([ ('employee_id', 'in', self.ids), ('date_from', '<=', fields.Datetime.now()), ('date_to', '>=', fields.Datetime.now()), - ('type', '=', 'remove'), ('state', 'not in', ('cancel', 'refuse')) ]) leave_data = {} @@ -165,12 +172,12 @@ class Employee(models.Model): @api.multi def _compute_leaves_count(self): - leaves = self.env['hr.holidays'].read_group([ + all_leaves = self.env['hr.leave.report'].read_group([ ('employee_id', 'in', self.ids), ('holiday_status_id.limit', '=', False), ('state', '=', 'validate') ], fields=['number_of_days', 'employee_id'], groupby=['employee_id']) - mapping = dict([(leave['employee_id'][0], leave['number_of_days']) for leave in leaves]) + mapping = dict([(leave['employee_id'][0], leave['number_of_days']) for leave in all_leaves]) for employee in self: employee.leaves_count = mapping.get(employee.id) @@ -188,12 +195,11 @@ class Employee(models.Model): today_date = datetime.datetime.utcnow().date() today_start = fields.Datetime.to_string(today_date) # get the midnight of the current utc day today_end = fields.Datetime.to_string(today_date + relativedelta(hours=23, minutes=59, seconds=59)) - data = self.env['hr.holidays'].read_group([ + data = self.env['hr.leave'].read_group([ ('employee_id', 'in', self.ids), ('state', 'not in', ['cancel', 'refuse']), ('date_from', '<=', today_end), - ('date_to', '>=', today_start), - ('type', '=', 'remove') + ('date_to', '>=', today_start) ], ['employee_id'], ['employee_id']) result = dict.fromkeys(self.ids, False) for item in data: @@ -207,11 +213,10 @@ class Employee(models.Model): today_date = datetime.datetime.utcnow().date() today_start = fields.Datetime.to_string(today_date) # get the midnight of the current utc day today_end = fields.Datetime.to_string(today_date + relativedelta(hours=23, minutes=59, seconds=59)) - holidays = self.env['hr.holidays'].sudo().search([ + holidays = self.env['hr.leave'].sudo().search([ ('employee_id', '!=', False), ('state', 'not in', ['cancel', 'refuse']), ('date_from', '<=', today_end), - ('date_to', '>=', today_start), - ('type', '=', 'remove') + ('date_to', '>=', today_start) ]) return [('id', 'in', holidays.mapped('employee_id').ids)] diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_leave.py similarity index 64% rename from addons/hr_holidays/models/hr_holidays.py rename to addons/hr_holidays/models/hr_leave.py index fc72a6a9caf..2d72154e76c 100644 --- a/addons/hr_holidays/models/hr_holidays.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -17,139 +17,10 @@ _logger = logging.getLogger(__name__) HOURS_PER_DAY = 8 - -class HolidaysType(models.Model): - _name = "hr.holidays.status" - _description = "Leave Type" - - name = fields.Char('Leave Type', required=True, translate=True) - categ_id = fields.Many2one('calendar.event.type', string='Meeting Type', - help='Once a leave is validated, Odoo will create a corresponding meeting of this type in the calendar.') - color_name = fields.Selection([ - ('red', 'Red'), - ('blue', 'Blue'), - ('lightgreen', 'Light Green'), - ('lightblue', 'Light Blue'), - ('lightyellow', 'Light Yellow'), - ('magenta', 'Magenta'), - ('lightcyan', 'Light Cyan'), - ('black', 'Black'), - ('lightpink', 'Light Pink'), - ('brown', 'Brown'), - ('violet', 'Violet'), - ('lightcoral', 'Light Coral'), - ('lightsalmon', 'Light Salmon'), - ('lavender', 'Lavender'), - ('wheat', 'Wheat'), - ('ivory', 'Ivory')], string='Color in Report', required=True, default='red', - help='This color will be used in the leaves summary located in Reporting > Leaves by Department.') - limit = fields.Boolean('Allow to Override Limit', - help='If you select this check box, the system allows the employees to take more leaves ' - 'than the available ones for this type and will not take them into account for the ' - '"Remaining Legal Leaves" defined on the employee form.') - active = fields.Boolean('Active', default=True, - help="If the active field is set to false, it will allow you to hide the leave type without removing it.") - - max_leaves = fields.Float(compute='_compute_leaves', string='Maximum Allowed', - help='This value is given by the sum of all leaves requests with a positive value.') - leaves_taken = fields.Float(compute='_compute_leaves', string='Leaves Already Taken', - help='This value is given by the sum of all leaves requests with a negative value.') - remaining_leaves = fields.Float(compute='_compute_leaves', string='Remaining Leaves', - help='Maximum Leaves Allowed - Leaves Already Taken') - virtual_remaining_leaves = fields.Float(compute='_compute_leaves', string='Virtual Remaining Leaves', - help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval') - - double_validation = fields.Boolean(string='Apply Double Validation', - help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved.") - company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id) - - @api.multi - def get_days(self, employee_id): - # need to use `dict` constructor to create a dict per id - result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0, virtual_remaining_leaves=0)) for id in self.ids) - - holidays = self.env['hr.holidays'].search([ - ('employee_id', '=', employee_id), - ('state', 'in', ['confirm', 'validate1', 'validate']), - ('holiday_status_id', 'in', self.ids) - ]) - - for holiday in holidays: - status_dict = result[holiday.holiday_status_id.id] - if holiday.type == 'add': - if holiday.state == 'validate': - # note: add only validated allocation even for the virtual - # count; otherwise pending then refused allocation allow - # the employee to create more leaves than possible - status_dict['virtual_remaining_leaves'] += holiday.number_of_days_temp - status_dict['max_leaves'] += holiday.number_of_days_temp - status_dict['remaining_leaves'] += holiday.number_of_days_temp - elif holiday.type == 'remove': # number of days is negative - status_dict['virtual_remaining_leaves'] -= holiday.number_of_days_temp - if holiday.state == 'validate': - status_dict['leaves_taken'] += holiday.number_of_days_temp - status_dict['remaining_leaves'] -= holiday.number_of_days_temp - return result - - @api.multi - def _compute_leaves(self): - data_days = {} - if 'employee_id' in self._context: - employee_id = self._context['employee_id'] - else: - employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.user.id)], limit=1).id - - if employee_id: - data_days = self.get_days(employee_id) - - for holiday_status in self: - result = data_days.get(holiday_status.id, {}) - holiday_status.max_leaves = result.get('max_leaves', 0) - holiday_status.leaves_taken = result.get('leaves_taken', 0) - holiday_status.remaining_leaves = result.get('remaining_leaves', 0) - holiday_status.virtual_remaining_leaves = result.get('virtual_remaining_leaves', 0) - - @api.multi - def name_get(self): - if not self._context.get('employee_id'): - # leave counts is based on employee_id, would be inaccurate if not based on correct employee - return super(HolidaysType, self).name_get() - res = [] - for record in self: - name = record.name - if not record.limit: - name = "%(name)s (%(count)s)" % { - 'name': name, - 'count': _('%g remaining out of %g') % (record.virtual_remaining_leaves or 0.0, record.max_leaves or 0.0) - } - res.append((record.id, name)) - return res - - @api.model - def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): - """ Override _search to order the results, according to some employee. - The order is the following - - - limit (limited leaves first, such as Legal Leaves) - - virtual remaining leaves (higher the better, so using reverse on sorted) - - This override is necessary because those fields are not stored and depends - on an employee_id given in context. This sort will be done when there - is an employee_id in context and that no other order has been given - to the method. - """ - leave_ids = super(HolidaysType, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) - if not count and not order and self._context.get('employee_id'): - leaves = self.browse(leave_ids) - sort_key = lambda l: (not l.limit, l.virtual_remaining_leaves) - return leaves.sorted(key=sort_key, reverse=True).ids - return leave_ids - - -class Holidays(models.Model): - _name = "hr.holidays" +class HolidaysRequest(models.Model): + _name = "hr.leave" _description = "Leave" - _order = "type desc, date_from desc" + _order = "date_from desc" _inherit = ['mail.thread'] def _default_employee(self): @@ -172,11 +43,11 @@ class Holidays(models.Model): help='Green this button when the leave has been taken into account in the payslip.') report_note = fields.Text('HR Comments') user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) - date_from = fields.Datetime('Start Date', readonly=True, index=True, copy=False, + date_from = fields.Datetime('Start Date', readonly=True, index=True, copy=False, required=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange') - date_to = fields.Datetime('End Date', readonly=True, copy=False, + date_to = fields.Datetime('End Date', readonly=True, copy=False, required=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, track_visibility='onchange') - holiday_status_id = fields.Many2one("hr.holidays.status", string="Leave Type", required=True, readonly=True, + holiday_status_id = fields.Many2one("hr.leave.type", string="Leave Type", required=True, readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) employee_id = fields.Many2one('hr.employee', string='Employee', index=True, readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange') @@ -188,15 +59,9 @@ class Holidays(models.Model): help='Number of days of the leave request according to your working schedule.') number_of_days = fields.Float('Number of Days', compute='_compute_number_of_days', store=True, track_visibility='onchange') meeting_id = fields.Many2one('calendar.event', string='Meeting') - type = fields.Selection([ - ('remove', 'Leave Request'), - ('add', 'Allocation Request') - ], string='Request Type', required=True, readonly=True, index=True, track_visibility='always', default='remove', - states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, - help="Choose 'Leave Request' if someone wants to take an off-day. " - "\nChoose 'Allocation Request' if you want to increase the number of leaves available for someone") - parent_id = fields.Many2one('hr.holidays', string='Parent') - linked_request_ids = fields.One2many('hr.holidays', 'parent_id', string='Linked Requests') + + parent_id = fields.Many2one('hr.leave', string='Parent') + linked_request_ids = fields.One2many('hr.leave', 'parent_id', string='Linked Requests') department_id = fields.Many2one('hr.department', string='Department', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee') @@ -214,14 +79,18 @@ class Holidays(models.Model): double_validation = fields.Boolean('Apply Double Validation', related='holiday_status_id.double_validation') can_reset = fields.Boolean('Can reset', compute='_compute_can_reset') + _sql_constraints = [ + ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL) or (holiday_type='department' AND department_id IS NOT NULL) )", + "The employee, department or employee category of this request is missing. Please make sure that your user login is linked to an employee."), + ('date_check2', "CHECK ((date_from <= date_to))", "The start date must be anterior to the end date."), + ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."), + ] + @api.multi - @api.depends('number_of_days_temp', 'type') + @api.depends('number_of_days_temp') def _compute_number_of_days(self): for holiday in self: - if holiday.type == 'remove': - holiday.number_of_days = -holiday.number_of_days_temp - else: - holiday.number_of_days = holiday.number_of_days_temp + holiday.number_of_days = -holiday.number_of_days_temp @api.multi def _compute_can_reset(self): @@ -234,39 +103,6 @@ class Holidays(models.Model): if group_hr_manager in user.groups_id or holiday.employee_id and holiday.employee_id.user_id == user: holiday.can_reset = True - @api.constrains('date_from', 'date_to') - def _check_date(self): - for holiday in self: - domain = [ - ('date_from', '<=', holiday.date_to), - ('date_to', '>=', holiday.date_from), - ('employee_id', '=', holiday.employee_id.id), - ('id', '!=', holiday.id), - ('type', '=', holiday.type), - ('state', 'not in', ['cancel', 'refuse']), - ] - nholidays = self.search_count(domain) - if nholidays: - raise ValidationError(_('You can not have 2 leaves that overlaps on same day!')) - - @api.constrains('state', 'number_of_days_temp') - def _check_holidays(self): - for holiday in self: - if holiday.holiday_type != 'employee' or holiday.type != 'remove' or not holiday.employee_id or holiday.holiday_status_id.limit: - continue - leave_days = holiday.holiday_status_id.get_days(holiday.employee_id.id)[holiday.holiday_status_id.id] - if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or \ - float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1: - raise ValidationError(_('The number of remaining leaves is not sufficient for this leave type.\n' - 'Please verify also the leaves waiting for validation.')) - - _sql_constraints = [ - ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL) or (holiday_type='department' AND department_id IS NOT NULL) )", - "The employee, department or employee category of this request is missing. Please make sure that your user login is linked to an employee."), - ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."), - ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."), - ] - @api.onchange('holiday_type') def _onchange_type(self): if self.holiday_type == 'employee' and not self.employee_id: @@ -285,18 +121,6 @@ class Holidays(models.Model): if self.holiday_type == 'employee': self.department_id = self.employee_id.department_id - def _get_number_of_days(self, date_from, date_to, employee_id): - """ Returns a float equals to the timedelta between two dates given as string.""" - from_dt = fields.Datetime.from_string(date_from) - to_dt = fields.Datetime.from_string(date_to) - - if employee_id: - employee = self.env['hr.employee'].browse(employee_id) - return employee.get_work_days_count(from_dt, to_dt) - - time_delta = to_dt - from_dt - return math.ceil(time_delta.days + float(time_delta.seconds) / 86400) - @api.onchange('date_from') def _onchange_date_from(self): """ If there are no date set for date_to, automatically set one 8 hours later than @@ -328,6 +152,43 @@ class Holidays(models.Model): else: self.number_of_days_temp = 0 + @api.constrains('date_from', 'date_to') + def _check_date(self): + for holiday in self: + domain = [ + ('date_from', '<=', holiday.date_to), + ('date_to', '>=', holiday.date_from), + ('employee_id', '=', holiday.employee_id.id), + ('id', '!=', holiday.id), + ('state', 'not in', ['cancel', 'refuse']), + ] + nholidays = self.search_count(domain) + if nholidays: + raise ValidationError(_('You can not have 2 leaves that overlaps on same day!')) + + @api.constrains('state', 'number_of_days_temp') + def _check_holidays(self): + for holiday in self: + if holiday.holiday_type != 'employee' or not holiday.employee_id or holiday.holiday_status_id.limit: + continue + leave_days = holiday.holiday_status_id.get_days(holiday.employee_id.id)[holiday.holiday_status_id.id] + if float_compare(leave_days['remaining_leaves'], 0, precision_digits=2) == -1 or \ + float_compare(leave_days['virtual_remaining_leaves'], 0, precision_digits=2) == -1: + raise ValidationError(_('The number of remaining leaves is not sufficient for this leave type.\n' + 'Please verify also the leaves waiting for validation.')) + + def _get_number_of_days(self, date_from, date_to, employee_id): + """ Returns a float equals to the timedelta between two dates given as string.""" + from_dt = fields.Datetime.from_string(date_from) + to_dt = fields.Datetime.from_string(date_to) + + if employee_id: + employee = self.env['hr.employee'].browse(employee_id) + return employee.get_work_days_count(from_dt, to_dt) + + time_delta = to_dt - from_dt + return math.ceil(time_delta.days + float(time_delta.seconds) / 86400) + #################################################### # ORM Overrides methods #################################################### @@ -336,20 +197,12 @@ class Holidays(models.Model): def name_get(self): res = [] for leave in self: - if leave.type == 'remove': - if self.env.context.get('short_name'): - res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days_temp))) - else: - res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp))) + if self.env.context.get('short_name'): + res.append((leave.id, _("%s : %.2f day(s)") % (leave.name or leave.holiday_status_id.name, leave.number_of_days_temp))) else: - res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp, leave.employee_id.name))) + res.append((leave.id, _("%s on %s : %.2f day(s)") % (leave.employee_id.name or leave.category_id.name, leave.holiday_status_id.name, leave.number_of_days_temp))) return res - def _check_state_access_right(self, vals): - if vals.get('state') and vals['state'] not in ['draft', 'confirm', 'cancel'] and not self.env['res.users'].has_group('hr_holidays.group_hr_holidays_user'): - return False - return True - @api.multi def add_follower(self, employee_id): employee = self.env['hr.employee'].browse(employee_id) @@ -360,20 +213,16 @@ class Holidays(models.Model): def create(self, values): """ Override to avoid automatic logging of creation """ employee_id = values.get('employee_id', False) - if not self._check_state_access_right(values): - raise AccessError(_('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % values.get('state')) if not values.get('department_id'): values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id}) - holiday = super(Holidays, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) + holiday = super(HolidaysRequest, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values) holiday.add_follower(employee_id) return holiday @api.multi def write(self, values): employee_id = values.get('employee_id', False) - if not self._check_state_access_right(values): - raise AccessError(_('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % values.get('state')) - result = super(Holidays, self).write(values) + result = super(HolidaysRequest, self).write(values) self.add_follower(employee_id) return result @@ -381,7 +230,7 @@ class Holidays(models.Model): def unlink(self): for holiday in self.filtered(lambda holiday: holiday.state not in ['draft', 'cancel', 'confirm']): raise UserError(_('You cannot delete a leave which is in %s state.') % (holiday.state,)) - return super(Holidays, self).unlink() + return super(HolidaysRequest, self).unlink() @api.multi def copy_data(self, default=None): @@ -410,105 +259,10 @@ class Holidays(models.Model): """ This method will create entry in resource calendar leave object at the time of holidays cancel/removed """ return self.env['resource.calendar.leaves'].search([('holiday_id', 'in', self.ids)]).unlink() - @api.multi - def action_draft(self): - for holiday in self: - if not holiday.can_reset: - raise UserError(_('Only an HR Manager or the concerned employee can reset to draft.')) - if holiday.state not in ['confirm', 'refuse']: - raise UserError(_('Leave request state must be "Refused" or "To Approve" in order to reset to Draft.')) - holiday.write({ - 'state': 'draft', - 'first_approver_id': False, - 'second_approver_id': False, - }) - linked_requests = holiday.mapped('linked_request_ids') - for linked_request in linked_requests: - linked_request.action_draft() - linked_requests.unlink() - 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'}) - - @api.multi - def _check_security_action_approve(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) - - @api.multi - def action_approve(self): - # if double_validation: this method is the first approval approval - # if not double_validation: this method calls action_validate() below - self._check_security_action_approve() - - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) - if any(holiday.state != 'confirm' for holiday in self): - raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.')) - - self.filtered(lambda hol: hol.double_validation).write({'state': 'validate1', 'first_approver_id': current_employee.id}) - self.filtered(lambda hol: not hol.double_validation).action_validate() - return True - - @api.multi - def _prepare_holiday_values(self, employee): - self.ensure_one() - values = { - 'name': self.name, - 'type': self.type, - 'holiday_type': 'employee', - 'holiday_status_id': self.holiday_status_id.id, - 'date_from': self.date_from, - 'date_to': self.date_to, - 'notes': self.notes, - 'number_of_days_temp': self.number_of_days_temp, - 'parent_id': self.id, - 'employee_id': employee.id - } - return values - - @api.multi - def _check_security_action_validate(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) - - @api.multi - def action_validate(self): - self._check_security_action_validate() - - current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) - for holiday in self: - if holiday.state not in ['confirm', 'validate1']: - raise UserError(_('Leave request must be confirmed in order to approve it.')) - if holiday.state == 'validate1' and not holiday.env.user.has_group('hr_holidays.group_hr_holidays_manager'): - raise UserError(_('Only an HR Manager can apply the second approval on leave requests.')) - - holiday.write({'state': 'validate'}) - if holiday.double_validation: - holiday.write({'second_approver_id': current_employee.id}) - else: - holiday.write({'first_approver_id': current_employee.id}) - if holiday.holiday_type == 'employee' and holiday.type == 'remove': - holiday._validate_leave_request() - elif holiday.holiday_type in ['category', 'department']: - leaves = self.env['hr.holidays'] - employees = holiday.category_id.employee_ids if holiday.holiday_type == 'category' else holiday.department_id.member_ids - for employee in employees: - values = holiday._prepare_holiday_values(employee) - leaves += self.with_context(mail_notify_force_send=False).create(values) - # TODO is it necessary to interleave the calls? - leaves.action_approve() - if leaves and leaves[0].double_validation: - leaves.action_validate() - return True - def _validate_leave_request(self): - """ Validate leave requests (holiday_type='employee' and holiday.type='remove') + """ Validate leave requests (holiday_type='employee') by creating a calendar event and a resource leaves. """ - for holiday in self.filtered(lambda request: request.type == 'remove' and request.holiday_type == 'employee'): + for holiday in self.filtered(lambda request: request.holiday_type == 'employee'): meeting_values = holiday._prepare_holidays_meeting_values() meeting = self.env['calendar.event'].with_context(no_mail_to_attendees=True).create(meeting_values) holiday.write({'meeting_id': meeting.id}) @@ -536,9 +290,96 @@ class Holidays(models.Model): (4, self.user_id.partner_id.id)] return meeting_values + @api.multi + def _prepare_holiday_values(self, employee): + self.ensure_one() + values = { + 'name': self.name, + 'holiday_type': 'employee', + 'holiday_status_id': self.holiday_status_id.id, + 'date_from': self.date_from, + 'date_to': self.date_to, + 'notes': self.notes, + 'number_of_days_temp': self.number_of_days_temp, + 'parent_id': self.id, + 'employee_id': employee.id + } + return values + + @api.multi + def action_draft(self): + for holiday in self: + if not holiday.can_reset: + raise UserError(_('Only an HR Manager or the concerned employee can reset to draft.')) + if holiday.state not in ['confirm', 'refuse']: + raise UserError(_('Leave request state must be "Refused" or "To Approve" in order to reset to Draft.')) + holiday.write({ + 'state': 'draft', + 'first_approver_id': False, + 'second_approver_id': False, + }) + linked_requests = holiday.mapped('linked_request_ids') + for linked_request in linked_requests: + linked_request.action_draft() + linked_requests.unlink() + 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'}) + + @api.multi + def action_approve(self): + # if double_validation: this method is the first approval approval + # if not double_validation: this method calls action_validate() below + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) + + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + if any(holiday.state != 'confirm' for holiday in self): + raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.')) + + self.filtered(lambda hol: hol.double_validation).write({'state': 'validate1', 'first_approver_id': current_employee.id}) + self.filtered(lambda hol: not hol.double_validation).action_validate() + return True + + @api.multi + def action_validate(self): + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) + + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + for holiday in self: + if holiday.state not in ['confirm', 'validate1']: + raise UserError(_('Leave request must be confirmed in order to approve it.')) + if holiday.state == 'validate1' and not holiday.env.user.has_group('hr_holidays.group_hr_holidays_manager'): + raise UserError(_('Only an HR Manager can apply the second approval on leave requests.')) + + holiday.write({'state': 'validate'}) + if holiday.double_validation: + holiday.write({'second_approver_id': current_employee.id}) + else: + holiday.write({'first_approver_id': current_employee.id}) + if holiday.holiday_type == 'employee': + holiday._validate_leave_request() + elif holiday.holiday_type in ['category', 'department']: + leaves = self.env['hr.leave'] + employees = holiday.category_id.employee_ids if holiday.holiday_type == 'category' else holiday.department_id.member_ids + for employee in employees: + values = holiday._prepare_holiday_values(employee) + leaves += self.with_context(mail_notify_force_send=False).create(values) + # TODO is it necessary to interleave the calls? + leaves.action_approve() + if leaves and leaves[0].double_validation: + leaves.action_validate() + return True + @api.multi def action_refuse(self): - self._check_security_action_refuse() + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can refuse leave requests.')) current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) for holiday in self: @@ -557,11 +398,6 @@ class Holidays(models.Model): self._remove_resource_leave() return True - @api.multi - def _check_security_action_refuse(self): - if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): - raise UserError(_('Only an HR Officer or Manager can refuse leave requests.')) - #################################################### # Messaging methods #################################################### @@ -569,28 +405,28 @@ class Holidays(models.Model): @api.multi def _track_subtype(self, init_values): if 'state' in init_values and self.state == 'validate': - return 'hr_holidays.mt_holidays_approved' + return 'hr_holidays.mt_leave_approved' elif 'state' in init_values and self.state == 'validate1': - return 'hr_holidays.mt_holidays_first_validated' + return 'hr_holidays.mt_leave_first_validated' elif 'state' in init_values and self.state == 'confirm': - return 'hr_holidays.mt_holidays_confirmed' + return 'hr_holidays.mt_leave_confirmed' elif 'state' in init_values and self.state == 'refuse': - return 'hr_holidays.mt_holidays_refused' - return super(Holidays, self)._track_subtype(init_values) + return 'hr_holidays.mt_leave_refused' + return super(HolidaysRequest, self)._track_subtype(init_values) @api.multi def _notification_recipients(self, message, groups): """ Handle HR users and officers recipients that can validate or refuse holidays directly from email. """ - groups = super(Holidays, self)._notification_recipients(message, groups) + groups = super(HolidaysRequest, self)._notification_recipients(message, groups) self.ensure_one() hr_actions = [] if self.state == 'confirm': - app_action = self._notification_link_helper('controller', controller='/hr_holidays/validate') + app_action = self._notification_link_helper('controller', controller='/leave/validate') hr_actions += [{'url': app_action, 'title': _('Approve')}] if self.state in ['confirm', 'validate', 'validate1']: - ref_action = self._notification_link_helper('controller', controller='/hr_holidays/refuse') + ref_action = self._notification_link_helper('controller', controller='/leave/refuse') hr_actions += [{'url': ref_action, 'title': _('Refuse')}] new_group = ( @@ -602,9 +438,8 @@ class Holidays(models.Model): @api.multi def _message_notification_recipients(self, message, recipients): - result = super(Holidays, self)._message_notification_recipients(message, recipients) - leave_type = self.env[message.model].browse(message.res_id).type - title = _("See Leave") if leave_type == 'remove' else _("See Allocation") + result = super(HolidaysRequest, self)._message_notification_recipients(message, recipients) + title = _("See Leave") for res in result: if result[res].get('button_access'): result[res]['button_access']['title'] = title diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py new file mode 100644 index 00000000000..a1fa77e0730 --- /dev/null +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -0,0 +1,299 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com) + +import logging + +from odoo import api, fields, models +from odoo.exceptions import UserError, AccessError +from odoo.tools.translate import _ + +_logger = logging.getLogger(__name__) + + +class HolidaysAllocation(models.Model): + _name = "hr.leave.allocation" + _description = "Allocation" + _inherit = ['mail.thread'] + + 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) + + name = fields.Char('Description') + state = fields.Selection([ + ('draft', 'To Submit'), + ('cancel', 'Cancelled'), + ('confirm', 'To Approve'), + ('refuse', 'Refused'), + ('validate1', 'Second Approval'), + ('validate', 'Approved') + ], string='Status', readonly=True, track_visibility='onchange', copy=False, default='confirm', + help="The status is set to 'To Submit', when a leave request is created." + + "\nThe status is 'To Approve', when leave request is confirmed by user." + + "\nThe status is 'Refused', when leave request is refused by manager." + + "\nThe status is 'Approved', when leave request is approved by manager.") + holiday_status_id = fields.Many2one("hr.leave.type", string="Leave Type", required=True, readonly=True, + states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) + employee_id = fields.Many2one('hr.employee', string='Employee', index=True, readonly=True, + states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, default=_default_employee, track_visibility='onchange') + notes = fields.Text('Reasons', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) + number_of_days_temp = fields.Float( + 'Allocation', copy=False, readonly=True, + states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, + help='Number of days of the leave request according to your working schedule.') + number_of_days = fields.Float('Number of Days', compute='_compute_number_of_days', store=True, track_visibility='onchange') + parent_id = fields.Many2one('hr.leave.allocation', string='Parent') + linked_request_ids = fields.One2many('hr.leave.allocation', 'parent_id', string='Linked Requests') + department_id = fields.Many2one('hr.department', string='Department', readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}) + category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True, + states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, help='Category of Employee') + holiday_type = fields.Selection([ + ('employee', 'By Employee'), + ('department', 'By Department'), + ('category', 'By Employee Tag') + ], string='Allocation Mode', readonly=True, required=True, default='employee', + states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, + help='By Employee: Allocation for individual Employee, By Employee Tag: Allocation for group of employees in category') + first_approver_id = fields.Many2one('hr.employee', string='First Approval', readonly=True, copy=False, + help='This area is automatically filled by the user who validate the leave', oldname='manager_id') + second_approver_id = fields.Many2one('hr.employee', string='Second Approval', readonly=True, copy=False, oldname='manager_id2', + 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.Boolean('Apply Double Validation', related='holiday_status_id.double_validation') + can_reset = fields.Boolean('Can reset', compute='_compute_can_reset') + + _sql_constraints = [ + ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL) or (holiday_type='department' AND department_id IS NOT NULL) )", + "The employee, department or employee category of this request is missing. Please make sure that your user login is linked to an employee."), + ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."), + ] + + @api.multi + @api.depends('number_of_days_temp') + def _compute_number_of_days(self): + for holiday in self: + holiday.number_of_days = holiday.number_of_days_temp + + @api.multi + def _compute_can_reset(self): + """ User can reset a leave request if it is its own leave request + or if he is an Hr Manager. + """ + user = self.env.user + group_hr_manager = self.env.ref('hr_holidays.group_hr_holidays_manager') + for holiday in self: + if group_hr_manager in user.groups_id or holiday.employee_id and holiday.employee_id.user_id == user: + holiday.can_reset = True + + @api.onchange('holiday_type') + def _onchange_type(self): + if self.holiday_type == 'employee' and not self.employee_id: + if self.env.user.employee_ids: + self.employee_id = self.env.user.employee_ids[0] + elif self.holiday_type == 'department': + if self.env.user.employee_ids: + self.department_id = self.department_id or self.env.user.employee_ids[0].department_id + self.employee_id = None + elif self.holiday_type == 'category': + self.employee_id = None + self.department_id = None + + @api.onchange('employee_id') + def _onchange_employee(self): + if self.holiday_type == 'employee': + self.department_id = self.employee_id.department_id + + #################################################### + # ORM Overrides methods + #################################################### + + @api.multi + def name_get(self): + res = [] + for leave in self: + res.append((leave.id, _("Allocation of %s : %.2f day(s) To %s") % (leave.holiday_status_id.name, leave.number_of_days_temp, leave.employee_id.name))) + return res + + @api.multi + def add_follower(self, employee_id): + employee = self.env['hr.employee'].browse(employee_id) + if employee.user_id: + self.message_subscribe_users(user_ids=employee.user_id.ids) + + @api.model + def create(self, values): + """ Override to avoid automatic logging of creation """ + employee_id = values.get('employee_id', False) + if not values.get('department_id'): + 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) + return holiday + + @api.multi + def write(self, values): + employee_id = values.get('employee_id', False) + result = super(HolidaysAllocation, self).write(values) + self.add_follower(employee_id) + return result + + @api.multi + def unlink(self): + for holiday in self.filtered(lambda holiday: holiday.state not in ['draft', 'cancel', 'confirm']): + raise UserError(_('You cannot delete a leave which is in %s state.') % (holiday.state,)) + return super(HolidaysAllocation, self).unlink() + + @api.multi + def copy_data(self, default=None): + raise UserError(_('A leave cannot be duplicated.')) + + #################################################### + # Business methods + #################################################### + + @api.multi + def _prepare_holiday_values(self, employee): + self.ensure_one() + values = { + 'name': self.name, + 'holiday_type': 'employee', + 'holiday_status_id': self.holiday_status_id.id, + 'notes': self.notes, + 'number_of_days_temp': self.number_of_days_temp, + 'parent_id': self.id, + 'employee_id': employee.id + } + return values + + @api.multi + def action_draft(self): + for holiday in self: + if not holiday.can_reset: + raise UserError(_('Only an HR Manager or the concerned employee can reset to draft.')) + if holiday.state not in ['confirm', 'refuse']: + raise UserError(_('Leave request state must be "Refused" or "To Approve" in order to reset to Draft.')) + holiday.write({ + 'state': 'draft', + 'first_approver_id': False, + 'second_approver_id': False, + }) + linked_requests = holiday.mapped('linked_request_ids') + for linked_request in linked_requests: + linked_request.action_draft() + linked_requests.unlink() + 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'}) + + @api.multi + def action_approve(self): + # if double_validation: this method is the first approval approval + # if not double_validation: this method calls action_validate() below + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) + + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + if any(holiday.state != 'confirm' for holiday in self): + raise UserError(_('Leave request must be confirmed ("To Approve") in order to approve it.')) + + self.filtered(lambda hol: hol.double_validation).write({'state': 'validate1', 'first_approver_id': current_employee.id}) + self.filtered(lambda hol: not hol.double_validation).action_validate() + return True + + @api.multi + def action_validate(self): + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can approve leave requests.')) + + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + for holiday in self: + if holiday.state not in ['confirm', 'validate1']: + raise UserError(_('Leave request must be confirmed in order to approve it.')) + if holiday.state == 'validate1' and not holiday.env.user.has_group('hr_holidays.group_hr_holidays_manager'): + raise UserError(_('Only an HR Manager can apply the second approval on leave requests.')) + + holiday.write({'state': 'validate'}) + if holiday.double_validation: + holiday.write({'second_approver_id': current_employee.id}) + else: + holiday.write({'first_approver_id': current_employee.id}) + if holiday.holiday_type in ['category', 'department']: + leaves = self.env['hr.leave.allocation'] + employees = holiday.category_id.employee_ids if holiday.holiday_type == 'category' else holiday.department_id.member_ids + for employee in employees: + values = holiday._prepare_holiday_values(employee) + leaves += self.with_context(mail_notify_force_send=False).create(values) + # TODO is it necessary to interleave the calls? + leaves.action_approve() + if leaves and leaves[0].double_validation: + leaves.action_validate() + return True + + @api.multi + def action_refuse(self): + if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): + raise UserError(_('Only an HR Officer or Manager can refuse leave requests.')) + + current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1) + for holiday in self: + if holiday.state not in ['confirm', 'validate', 'validate1']: + raise UserError(_('Leave request must be confirmed or validated in order to refuse it.')) + + if holiday.state == 'validate1': + holiday.write({'state': 'refuse', 'first_approver_id': current_employee.id}) + else: + 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() + return True + + #################################################### + # Messaging methods + #################################################### + + @api.multi + 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) + + @api.multi + def _notification_recipients(self, message, groups): + """ Handle HR users and officers recipients that can validate or refuse holidays + directly from email. """ + groups = super(HolidaysAllocation, self)._notification_recipients(message, groups) + + self.ensure_one() + hr_actions = [] + if self.state == 'confirm': + app_action = self._notification_link_helper('controller', controller='/allocation/validate') + hr_actions += [{'url': app_action, 'title': _('Approve')}] + if self.state in ['confirm', 'validate', 'validate1']: + ref_action = self._notification_link_helper('controller', controller='/allocation/refuse') + hr_actions += [{'url': ref_action, 'title': _('Refuse')}] + + new_group = ( + 'group_hr_holidays_user', lambda partner: bool(partner.user_ids) and any(user.has_group('hr_holidays.group_hr_holidays_user') for user in partner.user_ids), { + 'actions': hr_actions, + }) + + return [new_group] + groups + + @api.multi + def _message_notification_recipients(self, message, recipients): + result = super(HolidaysAllocation, self)._message_notification_recipients(message, recipients) + title = _("See Allocation") + for res in result: + if result[res].get('button_access'): + result[res]['button_access']['title'] = title + return result diff --git a/addons/hr_holidays/models/hr_leave_type.py b/addons/hr_holidays/models/hr_leave_type.py new file mode 100644 index 00000000000..f916000b10d --- /dev/null +++ b/addons/hr_holidays/models/hr_leave_type.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +# Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com) + +import logging + +from odoo import api, fields, models +from odoo.tools.translate import _ + +_logger = logging.getLogger(__name__) + + +class HolidaysType(models.Model): + _name = "hr.leave.type" + _description = "Leave Type" + + name = fields.Char('Leave Type', required=True, translate=True) + categ_id = fields.Many2one('calendar.event.type', string='Meeting Type', + help='Once a leave is validated, Odoo will create a corresponding meeting of this type in the calendar.') + color_name = fields.Selection([ + ('red', 'Red'), + ('blue', 'Blue'), + ('lightgreen', 'Light Green'), + ('lightblue', 'Light Blue'), + ('lightyellow', 'Light Yellow'), + ('magenta', 'Magenta'), + ('lightcyan', 'Light Cyan'), + ('black', 'Black'), + ('lightpink', 'Light Pink'), + ('brown', 'Brown'), + ('violet', 'Violet'), + ('lightcoral', 'Light Coral'), + ('lightsalmon', 'Light Salmon'), + ('lavender', 'Lavender'), + ('wheat', 'Wheat'), + ('ivory', 'Ivory')], string='Color in Report', required=True, default='red', + help='This color will be used in the leaves summary located in Reporting > Leaves by Department.') + limit = fields.Boolean('Allow to Override Limit', + help='If you select this check box, the system allows the employees to take more leaves ' + 'than the available ones for this type and will not take them into account for the ' + '"Remaining Legal Leaves" defined on the employee form.') + active = fields.Boolean('Active', default=True, + help="If the active field is set to false, it will allow you to hide the leave type without removing it.") + + max_leaves = fields.Float(compute='_compute_leaves', string='Maximum Allowed', + help='This value is given by the sum of all leaves requests with a positive value.') + leaves_taken = fields.Float(compute='_compute_leaves', string='Leaves Already Taken', + help='This value is given by the sum of all leaves requests with a negative value.') + remaining_leaves = fields.Float(compute='_compute_leaves', string='Remaining Leaves', + help='Maximum Leaves Allowed - Leaves Already Taken') + virtual_remaining_leaves = fields.Float(compute='_compute_leaves', string='Virtual Remaining Leaves', + help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval') + + double_validation = fields.Boolean(string='Apply Double Validation', + help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved.") + company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id) + + @api.multi + def get_days(self, employee_id): + # need to use `dict` constructor to create a dict per id + result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0, virtual_remaining_leaves=0)) for id in self.ids) + + requests = self.env['hr.leave'].search([ + ('employee_id', '=', employee_id), + ('state', 'in', ['confirm', 'validate1', 'validate']), + ('holiday_status_id', 'in', self.ids) + ]) + + allocations = self.env['hr.leave.allocation'].search([ + ('employee_id', '=', employee_id), + ('state', 'in', ['confirm', 'validate1', 'validate']), + ('holiday_status_id', 'in', self.ids) + ]) + + for request in requests: + status_dict = result[request.holiday_status_id.id] + status_dict['virtual_remaining_leaves'] -= request.number_of_days_temp + if request.state == 'validate': + status_dict['leaves_taken'] += request.number_of_days_temp + status_dict['remaining_leaves'] -= request.number_of_days_temp + + for allocation in allocations: + status_dict = result[allocation.holiday_status_id.id] + if allocation.state == 'validate': + # note: add only validated allocation even for the virtual + # count; otherwise pending then refused allocation allow + # the employee to create more leaves than possible + status_dict['virtual_remaining_leaves'] += allocation.number_of_days_temp + status_dict['max_leaves'] += allocation.number_of_days_temp + status_dict['remaining_leaves'] += allocation.number_of_days_temp + + return result + + @api.multi + def _compute_leaves(self): + data_days = {} + if 'employee_id' in self._context: + employee_id = self._context['employee_id'] + else: + employee_id = self.env['hr.employee'].search([('user_id', '=', self.env.user.id)], limit=1).id + + if employee_id: + data_days = self.get_days(employee_id) + + for holiday_status in self: + result = data_days.get(holiday_status.id, {}) + holiday_status.max_leaves = result.get('max_leaves', 0) + holiday_status.leaves_taken = result.get('leaves_taken', 0) + holiday_status.remaining_leaves = result.get('remaining_leaves', 0) + holiday_status.virtual_remaining_leaves = result.get('virtual_remaining_leaves', 0) + + @api.multi + def name_get(self): + if not self._context.get('employee_id'): + # leave counts is based on employee_id, would be inaccurate if not based on correct employee + return super(HolidaysType, self).name_get() + res = [] + for record in self: + name = record.name + if not record.limit: + name = "%(name)s (%(count)s)" % { + 'name': name, + 'count': _('%g remaining out of %g') % (record.virtual_remaining_leaves or 0.0, record.max_leaves or 0.0) + } + res.append((record.id, name)) + return res + + @api.model + def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): + """ Override _search to order the results, according to some employee. + The order is the following + + - limit (limited leaves first, such as Legal Leaves) + - virtual remaining leaves (higher the better, so using reverse on sorted) + + This override is necessary because those fields are not stored and depends + on an employee_id given in context. This sort will be done when there + is an employee_id in context and that no other order has been given + to the method. + """ + leave_ids = super(HolidaysType, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid) + if not count and not order and self._context.get('employee_id'): + leaves = self.browse(leave_ids) + sort_key = lambda l: (not l.limit, l.virtual_remaining_leaves) + return leaves.sorted(key=sort_key, reverse=True).ids + return leave_ids diff --git a/addons/hr_holidays/models/resource.py b/addons/hr_holidays/models/resource.py index 54e27c74cca..7a761e5650d 100644 --- a/addons/hr_holidays/models/resource.py +++ b/addons/hr_holidays/models/resource.py @@ -9,4 +9,4 @@ class CalendarLeaves(models.Model): _inherit = "resource.calendar.leaves" _description = "Leave Detail" - holiday_id = fields.Many2one("hr.holidays", string='Leave Request') + holiday_id = fields.Many2one("hr.leave", string='Leave Request') diff --git a/addons/hr_holidays/report/__init__.py b/addons/hr_holidays/report/__init__.py index 46fb8bf59f5..4dcb9db9075 100644 --- a/addons/hr_holidays/report/__init__.py +++ b/addons/hr_holidays/report/__init__.py @@ -2,4 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import holidays_summary_report -from . import hr_holidays_leaves_report +from . import hr_leave_report diff --git a/addons/hr_holidays/report/holidays_summary_report.py b/addons/hr_holidays/report/holidays_summary_report.py index 3b5df55d916..1532f49e0f3 100644 --- a/addons/hr_holidays/report/holidays_summary_report.py +++ b/addons/hr_holidays/report/holidays_summary_report.py @@ -19,7 +19,7 @@ class HrHolidaySummaryReport(models.AbstractModel): 'end_date': fields.Date.to_string(st_date + relativedelta(days=59)), 'holiday_type': 'Confirmed and Approved' if holiday_type == 'both' else holiday_type } - + def _date_is_day_off(self, date): return date.weekday() in (calendar.SATURDAY, calendar.SUNDAY,) @@ -58,9 +58,9 @@ class HrHolidaySummaryReport(models.AbstractModel): res[index]['color'] = '#ababab' # count and get leave summary details. holiday_type = ['confirm','validate'] if holiday_type == 'both' else ['confirm'] if holiday_type == 'Confirmed' else ['validate'] - holidays = self.env['hr.holidays'].search([ + holidays = self.env['hr.leave'].search([ ('employee_id', '=', empid), ('state', 'in', holiday_type), - ('type', '=', 'remove'), ('date_from', '<=', str(end_date)), + ('date_from', '<=', str(end_date)), ('date_to', '>=', str(start_date)) ]) for holiday in holidays: @@ -102,7 +102,7 @@ class HrHolidaySummaryReport(models.AbstractModel): def _get_holidays_status(self): res = [] - for holiday in self.env['hr.holidays.status'].search([]): + for holiday in self.env['hr.leave.type'].search([]): res.append({'color': holiday.color_name, 'name': holiday.name}) return res @@ -112,7 +112,7 @@ class HrHolidaySummaryReport(models.AbstractModel): raise UserError(_("Form content is missing, this report cannot be printed.")) holidays_report = self.env['ir.actions.report']._get_report_from_name('hr_holidays.report_holidayssummary') - holidays = self.env['hr.holidays'].browse(self.ids) + holidays = self.env['hr.leave'].browse(self.ids) return { 'doc_ids': self.ids, 'doc_model': holidays_report.model, diff --git a/addons/hr_holidays/report/hr_holidays_leaves_report.py b/addons/hr_holidays/report/hr_holidays_leaves_report.py deleted file mode 100644 index b971023f9cf..00000000000 --- a/addons/hr_holidays/report/hr_holidays_leaves_report.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import fields, models, tools - - -class HrHolidaysRemainingLeavesUser(models.Model): - - _name = "hr.holidays.remaining.leaves.user" - _description = "Total holidays by type" - _auto = False - - name = fields.Char('Employee', readonly=True) - no_of_leaves = fields.Integer('Remaining leaves', readonly=True) - user_id = fields.Many2one('res.users', string='User', readonly=True) - leave_type = fields.Char('Leave Type', readonly=True) - - def init(self): - tools.drop_view_if_exists(self._cr, 'hr_holidays_remaining_leaves_user') - self._cr.execute(""" - CREATE or REPLACE view hr_holidays_remaining_leaves_user as ( - SELECT - min(hrs.id) as id, - rr.name as name, - sum(hrs.number_of_days) as no_of_leaves, - rr.user_id as user_id, - hhs.name as leave_type - FROM - hr_holidays as hrs, hr_employee as hre, - resource_resource as rr,hr_holidays_status as hhs - WHERE - hrs.employee_id = hre.id and - hre.resource_id = rr.id and - hhs.id = hrs.holiday_status_id - GROUP BY - rr.name,rr.user_id,hhs.name - ) - """) diff --git a/addons/hr_holidays/report/hr_holidays_leaves_report_views.xml b/addons/hr_holidays/report/hr_holidays_leaves_report_views.xml deleted file mode 100644 index 5626ee5435d..00000000000 --- a/addons/hr_holidays/report/hr_holidays_leaves_report_views.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - hr.holidays.remaining.leaves.user.tree - hr.holidays.remaining.leaves.user - - - - - - - - - - - hr.holidays.remaining.leaves.user.graph - hr.holidays.remaining.leaves.user - - - - - - - - - - diff --git a/addons/hr_holidays/report/hr_holidays_reports.xml b/addons/hr_holidays/report/hr_holidays_reports.xml index 907112eb7e0..7c3a35eee6a 100644 --- a/addons/hr_holidays/report/hr_holidays_reports.xml +++ b/addons/hr_holidays/report/hr_holidays_reports.xml @@ -4,7 +4,20 @@ + + + + + + - - - hr.holidays.report_pivot - hr.holidays - 20 - - - - - - - - - - - - hr.holidays.report_graph - hr.holidays - 20 - - - - - - - - - - - Leaves Analysis - hr.holidays - form - graph,pivot,calendar,form - - {'search_default_leaves':1} - - - - - - pivot - - - - - - - graph - - - - - - - diff --git a/addons/hr_holidays/report/hr_leave_report.py b/addons/hr_holidays/report/hr_leave_report.py new file mode 100644 index 00000000000..acca821f58a --- /dev/null +++ b/addons/hr_holidays/report/hr_leave_report.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models, tools + + +class LeaveReport(models.Model): + + _name = "hr.leave.report" + _auto = False + + employee_id = fields.Many2one('hr.employee', string="Employee", readonly=True) + name = fields.Char('Description', readonly=True) + number_of_days = fields.Float('Number of Days', readonly=True) + type = fields.Selection([ + ('allocation', 'Allocation Request'), + ('request', 'Leave Request') + ], string='Request Type', readonly=True) + department_id = fields.Many2one('hr.department', string='Department', readonly=True) + category_id = fields.Many2one('hr.employee.category', string='Employee Tag', readonly=True) + holiday_status_id = fields.Many2one("hr.leave.type", string="Leave Type", readonly=True) + state = fields.Selection([ + ('draft', 'To Submit'), + ('cancel', 'Cancelled'), + ('confirm', 'To Approve'), + ('refuse', 'Refused'), + ('validate1', 'Second Approval'), + ('validate', 'Approved') + ], string='Status', readonly=True) + holiday_type = fields.Selection([ + ('employee', 'By Employee'), + ('category', 'By Employee Tag') + ], string='Allocation Mode', readonly=True) + date_from = fields.Datetime('Start Date', readonly=True) + date_to = fields.Datetime('End Date', readonly=True) + payslip_status = fields.Boolean('Reported in last payslips', readonly=True) + + def init(self): + tools.drop_view_if_exists(self._cr, 'hr_leave_report') + self._cr.execute(""" + CREATE or REPLACE view hr_leave_report as ( + SELECT row_number() over(ORDER BY leaves.employee_id) as id, + leaves.employee_id as employee_id, leaves.name as name, + leaves.number_of_days as number_of_days, leaves.type as type, + leaves.category_id as category_id, leaves.department_id as department_id, + leaves.holiday_status_id as holiday_status_id, leaves.state as state, + leaves.holiday_type as holiday_type, leaves.date_from as date_from, + leaves.date_to as date_to, leaves.payslip_status as payslip_status + from (select + allocation.employee_id as employee_id, + allocation.name as name, + allocation.number_of_days as number_of_days, + allocation.category_id as category_id, + allocation.department_id as department_id, + allocation.holiday_status_id as holiday_status_id, + allocation.state as state, + allocation.holiday_type, + null as date_from, + null as date_to, + FALSE as payslip_status, + 'allocation' as type + from hr_leave_allocation as allocation + union select + request.employee_id as employee_id, + request.name as name, + request.number_of_days as number_of_days, + request.category_id as category_id, + request.department_id as department_id, + request.holiday_status_id as holiday_status_id, + request.state as state, + request.holiday_type, + request.date_from as date_from, + request.date_to as date_to, + request.payslip_status as payslip_status, + 'request' as type + from hr_leave as request) leaves + ); + """) diff --git a/addons/hr_holidays/report/hr_leave_reports.xml b/addons/hr_holidays/report/hr_leave_reports.xml new file mode 100644 index 00000000000..8439ad12e7a --- /dev/null +++ b/addons/hr_holidays/report/hr_leave_reports.xml @@ -0,0 +1,164 @@ + + + + + hr.holidays.filter + hr.leave.report + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + report.hr.holidays.report.leave_all.tree + hr.leave.report + + + + + + + + + + + + + + + report.hr.holidays.report.leave_all.kanban + hr.leave.report + + + + + + + + + +
+
+ + + + +
+ + + + +
+
+ +
+
+
+
+
+
+ + + + + + + + + +
from
to
+
+
+ days +
+
+
+
+
+
+
+
+ + + Leaves Summary + hr.leave.report + hr.employee + form + tree,kanban + + { + 'search_default_group_type': 1, + 'search_default_year': 1 , + 'search_default_my_leaves': 1, + 'needaction_menu_ref': + [ + 'hr_holidays.menu_open_ask_holidays_new', + 'hr_holidays.menu_open_allocation_holidays', + 'hr_holidays.menu_request_approve_allocation', + 'hr_holidays.menu_open_employee_leave', + ] + } + +

+ Click here to create a new leave request. +

+
+ [('holiday_type','=','employee')] + +
+ + + Leaves + ir.actions.act_window + hr.leave.report + hr.employee + form + tree,form + {'search_default_employee_id': [active_id], 'default_employee_id': active_id, 'search_default_group_type': 1, + 'search_default_year': 1} + [('holiday_type','=','employee'), ('holiday_status_id.limit', '=', False), ('state','!=', 'refuse')] + + + + + +
diff --git a/addons/hr_holidays/security/hr_holidays_security.xml b/addons/hr_holidays/security/hr_holidays_security.xml index 16a2f22c01b..18550930f3a 100644 --- a/addons/hr_holidays/security/hr_holidays_security.xml +++ b/addons/hr_holidays/security/hr_holidays_security.xml @@ -17,9 +17,9 @@
- + Employee Leaves - + [('employee_id.user_id','=',user.id)] @@ -27,17 +27,42 @@ - + Employee Leaves Create, Write, Unlink - + [('employee_id.user_id','=',user.id), ('state', 'in', ['draft', 'confirm', 'cancel', 'refuse'])] - + Leaves Officer - + + [(1,'=',1)] + + + + + Employee Allocations + + [('employee_id.user_id','=',user.id)] + + + + + + + + Employee Allocations Create, Write, Unlink + + [('employee_id.user_id','=',user.id), ('state', 'in', ['draft', 'confirm', 'cancel', 'refuse'])] + + + + + + Leaves Officer + [(1,'=',1)] @@ -51,7 +76,7 @@ Leaves multi company rule - + ['|',('company_id','=',False),('company_id','in',[user.company_id.id])] diff --git a/addons/hr_holidays/security/ir.model.access.csv b/addons/hr_holidays/security/ir.model.access.csv index 56b3305aa91..6238cdf051a 100644 --- a/addons/hr_holidays/security/ir.model.access.csv +++ b/addons/hr_holidays/security/ir.model.access.csv @@ -1,10 +1,12 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_hr_holidays_user,hr.holidays.user,model_hr_holidays,hr_holidays.group_hr_holidays_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,hr_holidays.group_hr_holidays_manager,1,1,1,1 -access_hr_holidays_remain_user,hr.holidays.ramain.user,model_hr_holidays_remaining_leaves_user,hr_holidays.group_hr_holidays_user,1,1,1,1 -access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,hr_holidays.group_hr_holidays_user,1,1,1,1 -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 +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_hr_holidays_user_request,hr.holidays.user.request,model_hr_leave,hr_holidays.group_hr_holidays_user,1,1,1,1 +access_hr_holidays_employee_request,hr.holidays.employee.request,model_hr_leave,base.group_user,1,1,1,1 +access_hr_holidays_user_allocation,hr.holidays.user.allocation,model_hr_leave_allocation,hr_holidays.group_hr_holidays_user,1,1,1,1 +access_hr_holidays_employee_allocation,hr.holidays.employee.allocation,model_hr_leave_allocation,base.group_user,1,1,1,1 +access_hr_holidays_status_employee,hr.holidays.status employee,model_hr_leave_type,base.group_user,1,0,0,0 +access_hr_holidays_status_manager,hr.holidays.status manager,model_hr_leave_type,hr_holidays.group_hr_holidays_manager,1,1,1,1 +access_hr_leave_report,access_hr_leave_report,model_hr_leave_report,,1,0,0,0 +access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,hr_holidays.group_hr_holidays_user,1,1,1,1 +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 diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py index 21c98693f05..dfa1d3dea45 100644 --- a/addons/hr_holidays/tests/test_holidays_flow.py +++ b/addons/hr_holidays/tests/test_holidays_flow.py @@ -14,8 +14,9 @@ class TestHolidaysFlow(TestHrHolidaysBase): @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models') def test_00_leave_request_flow(self): """ Testing leave request flow """ - Holidays = self.env['hr.holidays'] - HolidaysStatus = self.env['hr.holidays.status'] + Requests = self.env['hr.leave'] + Allocations = self.env['hr.leave.allocation'] + HolidaysStatus = self.env['hr.leave.type'] def _check_holidays_status(holiday_status, ml, lt, rl, vrl): self.assertEqual(holiday_status.max_leaves, ml, @@ -56,7 +57,7 @@ class TestHolidaysFlow(TestHrHolidaysBase): # -------------------------------------------------- # Employee creates a leave request for another employee -> should crash - HolidaysEmployeeGroup = Holidays.sudo(self.user_employee_id) + HolidaysEmployeeGroup = Requests.sudo(self.user_employee_id) with self.assertRaises(ValidationError): HolidaysEmployeeGroup.create({ 'name': 'Hol10', @@ -66,7 +67,7 @@ class TestHolidaysFlow(TestHrHolidaysBase): 'date_to': datetime.today(), 'number_of_days_temp': 1, }) - Holidays.search([('name', '=', 'Hol10')]).unlink() + Requests.search([('name', '=', 'Hol10')]).unlink() # Employee creates a leave request in a no-limit category hol1_employee_group = HolidaysEmployeeGroup.create({ @@ -110,20 +111,19 @@ class TestHolidaysFlow(TestHrHolidaysBase): 'name': 'Hol22', 'employee_id': self.employee_emp_id, 'holiday_status_id': self.holidays_status_2.id, - 'date_from': (datetime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M'), - 'date_to': (datetime.today() + relativedelta(days=1)), + 'date_from': (datetime.today() + relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'), + 'date_to': (datetime.today() + relativedelta(days=2)), 'number_of_days_temp': 1, }) # Clean transaction - Holidays.search([('name', 'in', ['Hol21', 'Hol22'])]).unlink() + Requests.search([('name', 'in', ['Hol21', 'Hol22'])]).unlink() # HrUser allocates some leaves to the employee - aloc1_user_group = Holidays.sudo(self.user_hruser_id).create({ + aloc1_user_group = Allocations.sudo(self.user_hruser_id).create({ 'name': 'Days for limited category', 'employee_id': self.employee_emp_id, 'holiday_status_id': self.holidays_status_2.id, - 'type': 'add', 'number_of_days_temp': 2, }) # HrUser validates the first step @@ -191,13 +191,12 @@ class TestHolidaysFlow(TestHrHolidaysBase): # cl can be of maximum 20 days for employee_root hol3_status = self.env.ref('hr_holidays.holiday_status_cl').with_context(employee_id=employee_id) # I assign the dates in the holiday request for 1 day - hol3 = Holidays.create({ + hol3 = Requests.create({ 'name': 'Sick Leave', 'holiday_status_id': hol3_status.id, 'date_from': datetime.today().strftime('%Y-%m-10 10:00:00'), 'date_to': datetime.today().strftime('%Y-%m-11 19:00:00'), 'employee_id': employee_id, - 'type': 'remove', 'number_of_days_temp': 1 }) # I find a small mistake on my leave request to I click on "Refuse" button to correct a mistake. diff --git a/addons/hr_holidays/views/hr_holidays_views.xml b/addons/hr_holidays/views/hr_holidays_views.xml deleted file mode 100644 index 3d08d494823..00000000000 --- a/addons/hr_holidays/views/hr_holidays_views.xml +++ /dev/null @@ -1,798 +0,0 @@ - - - - - - hr.holidays.graph - hr.holidays - - - - - - - - - - - - hr.holidays.filter - hr.holidays - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Leave Request - hr.holidays - - - - - - - - - - Leave Request - hr.holidays - -
- - -
-
- -
-

-
- - - - - - - - - - - - - -
- - -
-
-
-
- - -
- -
-
- - - hr.holidays.allocation.tree - hr.holidays - - - - - - - - - - - - - - - - hr.holidays.my.allocation.tree - hr.holidays - - - - - - - - - - - - - - hr.holidays.allocation.tree.customize - hr.holidays - - - - - - - - - - - - - - - - - - hr.holidays.report_tree - hr.holidays - 20 - - - - - - - - - - - - - - - hr.holidays.kanban - hr.holidays - - - - - - - - - -
-
- - - - -
- - - - -
-
- -
-
-
-
-
-
- - - - - - - - - -
from
to
-
-
- days -
-
-
-
-
-
-
-
- - - hr.holidays.tree - hr.holidays - - - - - - - - - - - - - - - - - - - - - - All Leaves - hr.holidays - form - calendar - {'search_default_year': 1, 'search_default_group_employee': 1} - [('holiday_type','=','employee'), ('type', '=', 'remove'), ('state', '!=', 'refuse')] - - - - - - - - - - - - - - - Leaves Request - hr.holidays - form - - { - 'default_type': 'remove', - } - [('type','=','remove'), ('user_id', '=', uid)] - - -

- Click to create a new leave request. -

- Once you have recorded your leave request, it will be sent - to a manager for validation. Be sure to set the right leave - type (recuperation, legal leaves, sickness) and the exact - number of open days related to your leave. -

-
-
- - - - tree - - - - - - - form - - - - - - - calendar - - - - - - - - Requests to Approve - hr.holidays - form - { - 'default_type': 'remove', - 'search_default_approve':1, - } - [('type','=','remove')] - - - - - - - tree - - - - - - - form - - - - - - Allocation Request - hr.holidays - form - tree,kanban,form - { - 'default_type':'add', - } - -

- Click here to create a new leave allocation request. -

-
- [('type','=','add'), ('user_id', '=', uid)] - - -
- - - - tree - - - - - - - form - - - - - - - - Allocation Request - hr.holidays - form - { - 'default_type': 'add', - 'search_default_approve':1, - } - [('type','=','add')] - - - - - - - tree - - - - - - - form - - - - - - Leaves Summary - hr.holidays - form - tree,kanban,form - - { - 'search_default_group_type': 1, - 'search_default_year': 1 , - } - -

- Click here to create a new leave request. -

-
- [('holiday_type','=','employee'), ('user_id', '=', uid)] - -
- - - - - Leaves Overview - hr.holidays - form - tree,kanban,form,calendar - - {'default_type': 'remove',} - -

- Click here to create a new leave request. -

-
- [('type','=','remove')] - -
- - - Department Leaves - hr.holidays - form - tree,form,calendar - {'search_default_department':1, 'search_default_approve':1} - -

- Click here to create a new leave request. -

-
- [('type','=','remove')] - -
- - - - - Department Leaves Allocation - hr.holidays - form - tree,form,calendar - {'default_type':'add', 'search_default_department':1, 'search_default_approve':1} - -

- Click here to create a new leave allocation. -

-
- [('type','=','add')] - -
- - - - - - - - hr.holidays.status.filter - hr.holidays.status - - - - - - - - - - hr.holidays.status.form - hr.holidays.status - -
- -
- -
- - - - - - - - - - - - -
-
-
-
- - - hr.holidays.status.tree - hr.holidays.status - - - - - - - - - - - - hr.holidays.status.kanban - hr.holidays.status - - - - -
-
- -
-
- Max Leaves: - Leaves Taken: -
-
-
-
-
-
-
- - - hr.holidays.status.normal.tree - hr.holidays.status - 35 - - - - - - - - - - hr.holidays.status.search - hr.holidays.status - - - - - - - - - - Leave Types - ir.actions.act_window - hr.holidays.status - form - tree,kanban,form - - - - - - - - - Leaves - ir.actions.act_window - hr.holidays - hr.employee - form - tree,form - {'search_default_employee_id': [active_id], 'default_employee_id': active_id, 'search_default_group_type': 1, - 'search_default_year': 1} - [('holiday_type','=','employee'), ('holiday_status_id.limit', '=', False), ('state','!=', 'refuse')] - - - - - Leaves to be reported in Payslip - ir.actions.act_window - hr.holidays - hr.employee - form - tree,form - {'search_default_employee_id': [active_id], 'search_default_validated': True, 'search_default_gray': True} - [('date_from','>=', context_today().strftime("%Y-%m-1")), ('date_from','<', ((context_today() + relativedelta(months=1)).strftime('%Y-%m-1')) )] - - - - - - hr.employee.leave.tree - hr.employee - - - - - - - - - - - Allocate Leaves for Employees - ir.actions.act_window - hr.employee - form - tree,form - - -

- You can assign remaining Legal Leaves for each employee, Odoo - will automatically create and validate allocation requests. -

-
-
- - - - Leave Request to Approve - hr.holidays - form - tree,form - { - 'default_type': 'remove', - 'search_default_department_id': [active_id], - 'default_department_id': active_id, - 'search_default_approve':1} - - [('type','=','remove')] - - - - - - Allocation Requests to Approve - hr.holidays - form - tree,form - { - 'default_type': 'add', - 'search_default_department_id': [active_id], - 'default_department_id': active_id, - 'search_default_approve':1} - - [('type','=','add')] - - - - - Absent Employees - hr.employee - form - kanban,tree,form - { - 'search_default_is_absent_totay': 1, - 'search_default_department_id': [active_id], - 'default_department_id': active_id} - - - - - - Leaves Analysis - hr.holidays - form - graph,pivot - [('holiday_type','=','employee')] - - - - - Leaves Analysis - hr.holidays - form - graph,pivot - [('holiday_type','=','employee')] - { - 'search_default_department_id': [active_id], - 'default_department_id': active_id} - - - - -
diff --git a/addons/hr_holidays/views/hr_leave_allocation_views.xml b/addons/hr_holidays/views/hr_leave_allocation_views.xml new file mode 100644 index 00000000000..606c0328136 --- /dev/null +++ b/addons/hr_holidays/views/hr_leave_allocation_views.xml @@ -0,0 +1,215 @@ + + + + + hr.holidays.filter_allocations + hr.leave.allocation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Allocation Request + hr.leave.allocation + +
+ +
+
+ +
+

+
+ + + + + + + + + + + + + +
+
+ + +
+ +
+
+ + + hr.leave.allocation.tree + hr.leave.allocation + + + + + + + + + + + + + + + hr.holidays.my.allocation.tree + hr.leave.allocation + + + + + + + + + + + + + + hr.leave.allocation.tree.customize + hr.leave.allocation + + + + + + + + + + + + + + Allocation Request + hr.leave.allocation + form + tree,kanban,form + +

+ Click here to create a new leave allocation request. +

+
+ [('employee_id.user_id', '=', uid)] + + +
+ + + Allocation Request + hr.leave.allocation + form + { + 'search_default_approve':1, + } + + + + + + Department Leaves Allocation + hr.leave.allocation + form + tree,form + {'search_default_department':1, 'search_default_approve':1} + +

+ Click here to create a new leave allocation. +

+
+ +
+ + + Allocation Requests to Approve + hr.leave.allocation + form + tree,form + { + 'search_default_department_id': [active_id], + 'default_department_id': active_id, + 'search_default_approve':1} + + + + + + + tree + + + + + + + form + + + + + + + tree + + + + + + + form + + + + +
diff --git a/addons/hr_holidays/views/hr_leave_type_views.xml b/addons/hr_holidays/views/hr_leave_type_views.xml new file mode 100644 index 00000000000..da4e973f70a --- /dev/null +++ b/addons/hr_holidays/views/hr_leave_type_views.xml @@ -0,0 +1,113 @@ + + + + + + hr.leave.type.filter + hr.leave.type + + + + + + + + + + hr.leave.type.form + hr.leave.type + +
+ +
+ +
+ + + + + + + + + + + + +
+
+
+
+ + + hr.leave.type.tree + hr.leave.type + + + + + + + + + + + + hr.leave.type.kanban + hr.leave.type + + + + +
+
+ +
+
+ Max Leaves: + Leaves Taken: +
+
+
+
+
+
+
+ + + hr.leave.type.normal.tree + hr.leave.type + 35 + + + + + + + + + + hr.leave.type.search + hr.leave.type + + + + + + + + + + Leave Types + ir.actions.act_window + hr.leave.type + form + tree,kanban,form + + + + +
diff --git a/addons/hr_holidays/views/hr_leave_views.xml b/addons/hr_holidays/views/hr_leave_views.xml new file mode 100644 index 00000000000..e11c257b9d7 --- /dev/null +++ b/addons/hr_holidays/views/hr_leave_views.xml @@ -0,0 +1,437 @@ + + + + + + hr.holidays.graph + hr.leave + + + + + + + + + + + + hr.holidays.filter + hr.leave + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Leave Request + hr.leave + + + + + + + + + Leave Request + hr.leave + +
+ +
+
+ +
+

+
+ + + + + + + + + + + + +
+ + +
+
+
+
+ + +
+ +
+
+ + + hr.holidays.tree + hr.leave + + + + + + + + + + + + + + + + + + + + + + All Leaves + hr.leave + form + calendar + {'search_default_year': 1, 'search_default_group_employee': 1} + [('holiday_type','=','employee'), ('state', '!=', 'refuse')] + + + + + + + + + + + + + + + Leaves Request + hr.leave + form + + [('user_id', '=', uid)] + + +

+ Click to create a new leave request. +

+ Once you have recorded your leave request, it will be sent + to a manager for validation. Be sure to set the right leave + type (recuperation, legal leaves, sickness) and the exact + number of open days related to your leave. +

+
+
+ + + + tree + + + + + + + form + + + + + + + calendar + + + + + + + + Requests to Approve + hr.leave + form + { + 'search_default_approve':1, + } + + + + + + + tree + + + + + + + form + + + + + + Leaves Overview + hr.leave + form + tree,kanban,form,calendar + + +

+ Click here to create a new leave request. +

+
+ +
+ + + Department Leaves + hr.leave + form + tree,form,calendar + {'search_default_department':1, 'search_default_approve':1} + +

+ Click here to create a new leave request. +

+
+ +
+ + + + + + + + + + + + Leaves to be reported in Payslip + ir.actions.act_window + hr.leave + hr.employee + form + tree,form + {'search_default_employee_id': [active_id], 'search_default_validated': True, 'search_default_gray': True} + [('date_from','>=', context_today().strftime("%Y-%m-1")), ('date_from','<', ((context_today() + relativedelta(months=1)).strftime('%Y-%m-1')) )] + + + + + + Leave Request to Approve + hr.leave + form + tree,form + { + 'search_default_department_id': [active_id], + 'default_department_id': active_id, + 'search_default_approve':1} + + + + + + + Leaves Analysis + hr.leave + form + graph,pivot + [('holiday_type','=','employee')] + + + + + Leaves Analysis + hr.leave + form + graph,pivot + [('holiday_type','=','employee')] + { + 'search_default_department_id': [active_id], + 'default_department_id': active_id} + + + + + + hr.holidays.report_pivot + hr.leave + 20 + + + + + + + + + + + hr.holidays.report_graph + hr.leave + 20 + + + + + + + + + + Leaves Analysis + hr.leave + form + graph,pivot,calendar,form + + {'search_default_leaves':1} + + + + + + pivot + + + + + + + graph + + + + + + + + +
diff --git a/addons/hr_holidays/views/hr_views.xml b/addons/hr_holidays/views/hr_views.xml index 90b54965535..159681154d3 100644 --- a/addons/hr_holidays/views/hr_views.xml +++ b/addons/hr_holidays/views/hr_views.xml @@ -1,5 +1,59 @@ + + + Leaves + ir.actions.act_window + hr.leave.report + hr.employee + form + tree,form + {'search_default_employee_id': [active_id], 'default_employee_id': active_id, 'search_default_group_type': 1, + 'search_default_year': 1} + [('holiday_type','=','employee'), ('holiday_status_id.limit', '=', False), ('state','!=', 'refuse')] + + + + + hr.employee.leave.tree + hr.employee + + + + + + + + + + + Allocate Leaves for Employees + ir.actions.act_window + hr.employee + form + tree,form + + +

+ You can assign remaining Legal Leaves for each employee, Odoo + will automatically create and validate allocation requests. +

+
+
+ + + Absent Employees + hr.employee + form + kanban,tree,form + { + 'search_default_is_absent_totay': 1, + 'search_default_department_id': [active_id], + 'default_department_id': active_id} + + + + hr.department.kanban.inherit diff --git a/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml b/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml index 932c5b808d9..81ed5cbe0d8 100644 --- a/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml +++ b/addons/l10n_fr_hr_payroll/data/l10n_fr_hr_payroll_data.xml @@ -6,7 +6,7 @@ - + Heures Supplementaires True lightblue diff --git a/addons/project_timesheet_holidays/__init__.py b/addons/project_timesheet_holidays/__init__.py index cb09489a1e7..d553cb8e270 100644 --- a/addons/project_timesheet_holidays/__init__.py +++ b/addons/project_timesheet_holidays/__init__.py @@ -12,9 +12,9 @@ def post_init(cr, registry): from odoo import api, SUPERUSER_ID env = api.Environment(cr, SUPERUSER_ID, {}) - for leave_type in env['hr.holidays.status'].search([('timesheet_generate', '=', True), ('timesheet_project_id', '=', False)]): - company = leave_type.company_id or env.user.company_id - leave_type.write({ + for hr_leave_type in env['hr.leave.type'].search([('timesheet_generate', '=', True), ('timesheet_project_id', '=', False)]): + company = hr_leave_type.company_id or env.user.company_id + hr_leave_type.write({ 'timesheet_project_id': company.leave_timesheet_project_id.id, 'timesheet_task_id': company.leave_timesheet_task_id.id, }) diff --git a/addons/project_timesheet_holidays/models/account_analytic.py b/addons/project_timesheet_holidays/models/account_analytic.py index b0472a515be..d9e8896aa5c 100644 --- a/addons/project_timesheet_holidays/models/account_analytic.py +++ b/addons/project_timesheet_holidays/models/account_analytic.py @@ -8,7 +8,7 @@ from odoo.exceptions import UserError class AccountAnalyticLine(models.Model): _inherit = 'account.analytic.line' - holiday_id = fields.Many2one("hr.holidays", string='Leave Request') + holiday_id = fields.Many2one("hr.leave", string='Leave Request') @api.multi def unlink(self): diff --git a/addons/project_timesheet_holidays/models/hr_holidays.py b/addons/project_timesheet_holidays/models/hr_holidays.py index 2d245e834d9..96ac9446313 100644 --- a/addons/project_timesheet_holidays/models/hr_holidays.py +++ b/addons/project_timesheet_holidays/models/hr_holidays.py @@ -6,7 +6,7 @@ from odoo.exceptions import ValidationError class HolidaysType(models.Model): - _inherit = "hr.holidays.status" + _inherit = "hr.leave.type" def _default_project_id(self): company = self.company_id if self.company_id else self.env.user.company_id @@ -39,7 +39,7 @@ class HolidaysType(models.Model): class Holidays(models.Model): - _inherit = "hr.holidays" + _inherit = "hr.leave" timesheet_ids = fields.One2many('account.analytic.line', 'holiday_id', string="Analytic Lines") @@ -50,8 +50,7 @@ class Holidays(models.Model): """ # create the timesheet on the vacation project for holiday in self.filtered( - lambda request: request.type == 'remove' and - request.holiday_type == 'employee' and + lambda request: request.holiday_type == 'employee' and request.holiday_status_id.timesheet_project_id and request.holiday_status_id.timesheet_task_id): holiday_project = holiday.holiday_status_id.timesheet_project_id diff --git a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py index 9d358c14114..b7b7b6fc3fb 100644 --- a/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py +++ b/addons/project_timesheet_holidays/tests/test_timesheet_holidays.py @@ -14,7 +14,7 @@ class TestTimesheetHolidaysCreate(common.TransactionCase): def test_status_create(self): """Ensure that when a status is created, it fullfills the project and task constrains""" - status = self.env['hr.holidays.status'].create({ + status = self.env['hr.leave.type'].create({ 'name': 'A nice Leave Type', 'limit': True }) @@ -39,14 +39,14 @@ class TestTimesheetHolidays(TestTimesheet): self.internal_project = self.env.user.company_id.leave_timesheet_project_id self.internal_task_leaves = self.env.user.company_id.leave_timesheet_task_id - self.leave_type_with_ts = self.env['hr.holidays.status'].create({ + self.hr_leave_type_with_ts = self.env['hr.leave.type'].create({ 'name': 'Leave Type with timesheet generation', 'limit': True, 'timesheet_generate': True, 'timesheet_project_id': self.internal_project.id, 'timesheet_task_id': self.internal_task_leaves.id, }) - self.leave_type_no_ts = self.env['hr.holidays.status'].create({ + self.hr_leave_type_no_ts = self.env['hr.leave.type'].create({ 'name': 'Leave Type without timesheet generation', 'limit': True, 'timesheet_generate': False, @@ -55,31 +55,30 @@ class TestTimesheetHolidays(TestTimesheet): }) # HR Officer allocates some leaves to the employee 1 - self.Holidays = self.env['hr.holidays'].with_context(mail_create_nolog=True, mail_notrack=True) - self.leave_allocation_with_ts = self.Holidays.sudo().create({ + self.Requests = self.env['hr.leave'].with_context(mail_create_nolog=True, mail_notrack=True) + self.Allocations = self.env['hr.leave.allocation'].with_context(mail_create_nolog=True, mail_notrack=True) + self.hr_leave_allocation_with_ts = self.Allocations.sudo().create({ 'name': 'Days for limited category with timesheet', 'employee_id': self.empl_employee.id, - 'holiday_status_id': self.leave_type_with_ts.id, - 'type': 'add', + 'holiday_status_id': self.hr_leave_type_with_ts.id, 'number_of_days_temp': 10, }) - self.leave_allocation_with_ts.action_approve() - self.leave_allocation_no_ts = self.Holidays.sudo().create({ + self.hr_leave_allocation_with_ts.action_approve() + self.hr_leave_allocation_no_ts = self.Allocations.sudo().create({ 'name': 'Days for limited category without timesheet', 'employee_id': self.empl_employee.id, - 'holiday_status_id': self.leave_type_no_ts.id, - 'type': 'add', + 'holiday_status_id': self.hr_leave_type_no_ts.id, 'number_of_days_temp': 10, }) - self.leave_allocation_no_ts.action_approve() + self.hr_leave_allocation_no_ts.action_approve() def test_validate_with_timesheet(self): # employee creates a leave request number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days+1 - holiday = self.Holidays.sudo(self.user_employee.id).create({ + holiday = self.Requests.sudo(self.user_employee.id).create({ 'name': 'Leave 1', 'employee_id': self.empl_employee.id, - 'holiday_status_id': self.leave_type_with_ts.id, + 'holiday_status_id': self.hr_leave_type_with_ts.id, 'date_from': self.leave_start_datetime, 'date_to': self.leave_end_datetime, 'number_of_days_temp': number_of_days, @@ -94,10 +93,10 @@ class TestTimesheetHolidays(TestTimesheet): def test_validate_without_timesheet(self): # employee creates a leave request number_of_days = (self.leave_end_datetime - self.leave_start_datetime).days - holiday = self.Holidays.sudo(self.user_employee.id).create({ + holiday = self.Requests.sudo(self.user_employee.id).create({ 'name': 'Leave 1', 'employee_id': self.empl_employee.id, - 'holiday_status_id': self.leave_type_no_ts.id, + 'holiday_status_id': self.hr_leave_type_no_ts.id, 'date_from': self.leave_start_datetime, 'date_to': self.leave_end_datetime, 'number_of_days_temp': number_of_days, diff --git a/addons/project_timesheet_holidays/views/hr_holidays_views.xml b/addons/project_timesheet_holidays/views/hr_holidays_views.xml index 27af36b85f8..1e010067a57 100644 --- a/addons/project_timesheet_holidays/views/hr_holidays_views.xml +++ b/addons/project_timesheet_holidays/views/hr_holidays_views.xml @@ -2,8 +2,8 @@ - hr.holidays.status.form - hr.holidays.status + hr.leave.type.form + hr.leave.type