From f61687a4cdb0e22df19568c9b4ac3dbb579ca062 Mon Sep 17 00:00:00 2001 From: RomainLibert Date: Thu, 28 Dec 2017 11:30:57 +0100 Subject: [PATCH] [IMP] hr_holidays : split hr.holidays into hr.leave and hr.leave.allocation Purpose ======= Have a clear distinction between leave and allocation requests instead of using the same model to mix 2 different concepts. Splitting the model will allow different business code to be run on each model as allocations and leaves are not exactly the same thing; this will add some code but simplify future improvements Specification ============= 1/ Completely separate the models Model hr.holidays has been split into leave.request and leave.allocation. 2/ Make reporting working again by using an SQL view aggregating data from leaves and allocations A new report has been added in order to aggregate datas from both allocations and requests. The views have been modified accordingly. --- addons/hr_holidays/__manifest__.py | 1 - addons/hr_holidays/controllers/main.py | 32 +- addons/hr_holidays/data/hr_holidays_data.xml | 16 +- addons/hr_holidays/data/hr_holidays_demo.xml | 23 +- addons/hr_holidays/models/hr.py | 51 ++- addons/hr_holidays/models/hr_holidays.py | 433 +++++++++++++++--- addons/hr_holidays/models/resource.py | 2 +- addons/hr_holidays/report/__init__.py | 2 +- .../report/holidays_summary_report.py | 11 +- .../report/hr_holidays_reports.xml | 15 +- .../hr_holidays/report/hr_holidays_views.xml | 166 ++++++- .../hr_holidays/report/report_all_leaves.py | 78 ++++ .../security/hr_holidays_security.xml | 39 +- .../hr_holidays/security/ir.model.access.csv | 22 +- .../hr_holidays/tests/test_holidays_flow.py | 21 +- .../hr_holidays/views/hr_holidays_views.xml | 301 ++++++------ .../data/l10n_fr_hr_payroll_data.xml | 2 +- addons/project_timesheet_holidays/__init__.py | 6 +- .../models/account_analytic.py | 3 +- .../models/hr_holidays.py | 7 +- .../tests/test_timesheet_holidays.py | 31 +- .../views/hr_holidays_views.xml | 4 +- 22 files changed, 932 insertions(+), 334 deletions(-) create mode 100644 addons/hr_holidays/report/report_all_leaves.py diff --git a/addons/hr_holidays/__manifest__.py b/addons/hr_holidays/__manifest__.py index 79a0e47b5e4..7361860c627 100644 --- a/addons/hr_holidays/__manifest__.py +++ b/addons/hr_holidays/__manifest__.py @@ -41,7 +41,6 @@ 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', ], 'demo': [ 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..c6c155a385f 100644 --- a/addons/hr_holidays/data/hr_holidays_data.xml +++ b/addons/hr_holidays/data/hr_holidays_data.xml @@ -3,28 +3,28 @@ - + black - + Sick Leaves red - + Compensatory Days lavender - + Unpaid brown @@ -33,26 +33,26 @@ 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 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/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_holidays.py index fc72a6a9caf..a21be760c68 100644 --- a/addons/hr_holidays/models/hr_holidays.py +++ b/addons/hr_holidays/models/hr_holidays.py @@ -19,7 +19,7 @@ HOURS_PER_DAY = 8 class HolidaysType(models.Model): - _name = "hr.holidays.status" + _name = "hr.leave.type" _description = "Leave Type" name = fields.Char('Leave Type', required=True, translate=True) @@ -68,27 +68,35 @@ class HolidaysType(models.Model): # 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([ + requests = self.env['hr.leave'].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 + 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 @@ -146,10 +154,10 @@ class HolidaysType(models.Model): 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 +180,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 +196,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') @@ -215,13 +217,10 @@ class Holidays(models.Model): can_reset = fields.Boolean('Can reset', compute='_compute_can_reset') @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): @@ -242,7 +241,6 @@ class Holidays(models.Model): ('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) @@ -252,7 +250,7 @@ class Holidays(models.Model): @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: + 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 \ @@ -263,7 +261,7 @@ class Holidays(models.Model): _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_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."), ] @@ -336,20 +334,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 +350,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 +367,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): @@ -434,6 +420,7 @@ class Holidays(models.Model): raise UserError(_('Leave request must be in Draft state ("To Submit") in order to confirm it.')) return self.write({'state': 'confirm'}) + # TODO: Do we have to remove this method? @api.multi def _check_security_action_approve(self): if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'): @@ -458,7 +445,6 @@ class Holidays(models.Model): 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, @@ -491,10 +477,10 @@ class Holidays(models.Model): 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': + if holiday.holiday_type == 'employee': holiday._validate_leave_request() elif holiday.holiday_type in ['category', 'department']: - leaves = self.env['hr.holidays'] + 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) @@ -506,9 +492,9 @@ class Holidays(models.Model): 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}) @@ -576,21 +562,21 @@ class Holidays(models.Model): return 'hr_holidays.mt_holidays_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 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='/hr_holidays_request/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='/hr_holidays_request/refuse') hr_actions += [{'url': ref_action, 'title': _('Refuse')}] new_group = ( @@ -602,9 +588,316 @@ 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 + return result + + +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') + + @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 + + _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.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 + + 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) + 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 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(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) + 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(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 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, + '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 _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 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): + self._check_security_action_refuse() + + 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 + + @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 + #################################################### + + @api.multi + def _track_subtype(self, init_values): + if 'state' in init_values and self.state == 'validate': + return 'hr_holidays.mt_holidays_approved' + elif 'state' in init_values and self.state == 'validate1': + return 'hr_holidays.mt_holidays_first_validated' + elif 'state' in init_values and self.state == 'confirm': + return 'hr_holidays.mt_holidays_confirmed' + elif 'state' in init_values and self.state == 'refuse': + return 'hr_holidays.mt_holidays_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='/hr_holidays_allocation/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_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 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..69b717e618b 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 report_all_leaves diff --git a/addons/hr_holidays/report/holidays_summary_report.py b/addons/hr_holidays/report/holidays_summary_report.py index 3b5df55d916..1520bffb99f 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,8 @@ 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) + # TODO: See if that is the correct type + 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_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 + hr.leave.report 20 - @@ -16,20 +15,29 @@ hr.holidays.report_graph - hr.holidays + hr.leave.report 20 - + + hr.holidays.report.calendar + hr.leave.report + + + + + + + Leaves Analysis - hr.holidays + hr.leave.report form graph,pivot,calendar,form @@ -52,8 +60,156 @@ + + 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')] + +
+ + + diff --git a/addons/hr_holidays/report/report_all_leaves.py b/addons/hr_holidays/report/report_all_leaves.py new file mode 100644 index 00000000000..acca821f58a --- /dev/null +++ b/addons/hr_holidays/report/report_all_leaves.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/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 index 3d08d494823..6a6f22ef263 100644 --- a/addons/hr_holidays/views/hr_holidays_views.xml +++ b/addons/hr_holidays/views/hr_holidays_views.xml @@ -4,7 +4,7 @@ hr.holidays.graph - hr.holidays + hr.leave @@ -15,16 +15,51 @@ + + hr.holidays.filter_allocations + hr.leave.allocation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hr.holidays.filter - hr.holidays + hr.leave - + @@ -36,12 +71,10 @@ + domain="[('holiday_status_id.active', '=', True)]" help="Active Leaves"/> - + - - @@ -63,7 +96,7 @@ Leave Request - hr.holidays + hr.leave @@ -71,14 +104,59 @@ - - + + Allocation Request + hr.leave.allocation + +
+ +
+
+ +
+

+
+ + + + + + + + + + + + + +
+
+ + +
+ +
+
+ + Leave Request - hr.holidays + hr.leave
-