diff --git a/addons/hr_holidays/i18n/hr_holidays.pot b/addons/hr_holidays/i18n/hr_holidays.pot index a696285504a..5c00fc18ecb 100644 --- a/addons/hr_holidays/i18n/hr_holidays.pot +++ b/addons/hr_holidays/i18n/hr_holidays.pot @@ -2534,6 +2534,14 @@ msgid "" "missing. Please make sure that your user login is linked to an employee." msgstr "" +#. module: hr_holidays +#: code:addons/hr_holidays/models/hr_leave.py:0 +#, python-format +msgid "" +"The following employees are not supposed to work during that period:\n" +" %s" +msgstr "" + #. module: hr_holidays #: model:ir.model.constraint,message:hr_holidays.constraint_hr_leave_allocation_interval_number_check msgid "The interval number should be greater than 0" diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 04dad838585..06d6dfad818 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -548,7 +548,13 @@ class HolidaysRequest(models.Model): else: holiday.can_approve = True - @api.constrains('date_from', 'date_to', 'state', 'employee_id') + @api.constrains('date_from', 'date_to') + def _check_number_of_days(self): + leaves = self.filtered(lambda l: l.employee_id and not l.number_of_days) + if leaves: + raise ValidationError(_('The following employees are not supposed to work during that period:\n %s') % ','.join(leaves.mapped('employee_id.name'))) + + @api.constrains('date_from', 'date_to', 'employee_id') def _check_date(self): for holiday in self.filtered('employee_id'): domain = [ @@ -923,7 +929,7 @@ class HolidaysRequest(models.Model): 'parent_id': self.id, 'employee_id': employee.id, 'state': 'validate', - } for employee in employees] + } for employee in employees if work_days_data[employee.id]['days']] def action_draft(self): if any(holiday.state not in ['confirm', 'refuse'] for holiday in self): diff --git a/addons/hr_holidays/tests/test_automatic_leave_dates.py b/addons/hr_holidays/tests/test_automatic_leave_dates.py index 3fb05d7f9e5..c19bf449ccc 100644 --- a/addons/hr_holidays/tests/test_automatic_leave_dates.py +++ b/addons/hr_holidays/tests/test_automatic_leave_dates.py @@ -4,6 +4,7 @@ from datetime import date, datetime from odoo.tests.common import Form from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon +from odoo.exceptions import ValidationError class TestAutomaticLeaveDates(TestHrHolidaysCommon): @@ -25,15 +26,16 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon): employee = self.employee_emp employee.resource_calendar_id = calendar - with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: - leave_form.holiday_status_id = self.leave_type - leave_form.request_date_from = date(2019, 9, 2) - leave_form.request_date_to = date(2019, 9, 2) - leave_form.request_unit_half = True - leave_form.request_date_from_period = 'am' + with self.assertRaises(ValidationError): + with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: + leave_form.holiday_status_id = self.leave_type + leave_form.request_date_from = date(2019, 9, 2) + leave_form.request_date_to = date(2019, 9, 2) + leave_form.request_unit_half = True + leave_form.request_date_from_period = 'am' - self.assertEqual(leave_form.number_of_days_display, 0) - self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') + self.assertEqual(leave_form.number_of_days_display, 0) + self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') def test_single_attendance_on_morning_and_afternoon(self): calendar = self.env['resource.calendar'].create({ @@ -164,18 +166,19 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon): employee = self.employee_emp employee.resource_calendar_id = calendar - with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: - leave_form.holiday_status_id = self.leave_type - leave_form.request_date_from = date(2019, 9, 2) - leave_form.request_date_to = date(2019, 9, 2) - leave_form.request_unit_half = True - leave_form.request_date_from_period = 'am' + with self.assertRaises(ValidationError): + with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: + leave_form.holiday_status_id = self.leave_type + leave_form.request_date_from = date(2019, 9, 2) + leave_form.request_date_to = date(2019, 9, 2) + leave_form.request_unit_half = True + leave_form.request_date_from_period = 'am' - self.assertEqual(leave_form.number_of_days_display, 0) - self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') - self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 6, 0, 0)) - self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0)) + self.assertEqual(leave_form.number_of_days_display, 0) + self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') + self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 6, 0, 0)) + self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0)) def test_attendance_previous_day(self): self.env.user.tz = 'Europe/Brussels' @@ -193,18 +196,19 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon): employee = self.employee_emp employee.resource_calendar_id = calendar - with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: - leave_form.holiday_status_id = self.leave_type - leave_form.request_date_from = date(2019, 9, 3) - leave_form.request_date_to = date(2019, 9, 3) - leave_form.request_unit_half = True - leave_form.request_date_from_period = 'am' + with self.assertRaises(ValidationError): + with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: + leave_form.holiday_status_id = self.leave_type + leave_form.request_date_from = date(2019, 9, 3) + leave_form.request_date_to = date(2019, 9, 3) + leave_form.request_unit_half = True + leave_form.request_date_from_period = 'am' - self.assertEqual(leave_form.number_of_days_display, 0) - self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') - self.assertEqual(leave_form.date_from, datetime(2019, 9, 3, 6, 0, 0)) - self.assertEqual(leave_form.date_to, datetime(2019, 9, 3, 10, 0, 0)) + self.assertEqual(leave_form.number_of_days_display, 0) + self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') + self.assertEqual(leave_form.date_from, datetime(2019, 9, 3, 6, 0, 0)) + self.assertEqual(leave_form.date_to, datetime(2019, 9, 3, 10, 0, 0)) def test_2weeks_calendar(self): self.env.user.tz = 'Europe/Brussels' @@ -276,15 +280,16 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon): employee = self.employee_emp employee.resource_calendar_id = calendar - with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: - leave_form.holiday_status_id = self.leave_type - # even week, does not work - leave_form.request_date_from = date(2019, 9, 2) - leave_form.request_date_to = date(2019, 9, 2) - leave_form.request_unit_half = True - leave_form.request_date_from_period = 'am' + with self.assertRaises(ValidationError): + with Form(self.env['hr.leave'].with_context(default_employee_id=employee.id)) as leave_form: + leave_form.holiday_status_id = self.leave_type + # even week, does not work + leave_form.request_date_from = date(2019, 9, 2) + leave_form.request_date_to = date(2019, 9, 2) + leave_form.request_unit_half = True + leave_form.request_date_from_period = 'am' - self.assertEqual(leave_form.number_of_days_display, 0) - self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') - self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 6, 0, 0)) - self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0)) + self.assertEqual(leave_form.number_of_days_display, 0) + self.assertEqual(leave_form.number_of_hours_text, '0.0 Hours') + self.assertEqual(leave_form.date_from, datetime(2019, 9, 2, 6, 0, 0)) + self.assertEqual(leave_form.date_to, datetime(2019, 9, 2, 10, 0, 0)) diff --git a/addons/hr_work_entry/security/ir.model.access.csv b/addons/hr_work_entry/security/ir.model.access.csv index 7350f08cb78..ac50de79e81 100644 --- a/addons/hr_work_entry/security/ir.model.access.csv +++ b/addons/hr_work_entry/security/ir.model.access.csv @@ -1,5 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_hr_work_entry_officer,access_hr_work_entry_officer,model_hr_work_entry,hr.group_hr_user,1,1,1,1 +access_hr_work_entry_officer,access_hr_work_entry_officer,model_hr_work_entry,hr.group_hr_user,1,1,1,0 +access_hr_work_entry_system,access_hr_work_entry_system,model_hr_work_entry,base.group_system,1,1,1,1 access_hr_work_entry_type_officer,access_hr_work_entry_type_officer,model_hr_work_entry_type,hr.group_hr_user,1,0,0,0 access_hr_work_entry_type_manager,access_hr_work_entry_type_manager,model_hr_work_entry_type,hr.group_hr_manager,1,1,1,1 access_hr_work_entry_employee,access_hr_work_entry_employee,model_hr_user_work_entry_employee,hr.group_hr_user,1,1,1,1 \ No newline at end of file diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index bd427edd891..85d550ff5e3 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -159,18 +159,33 @@ class ResourceCalendar(models.Model): if not res.get('name') and res.get('company_id'): res['name'] = _('Working Hours of %s', self.env['res.company'].browse(res['company_id']).name) if 'attendance_ids' in fields and not res.get('attendance_ids'): - res['attendance_ids'] = [ - (0, 0, {'name': _('Monday Morning'), 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), - (0, 0, {'name': _('Monday Afternoon'), 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), - (0, 0, {'name': _('Tuesday Morning'), 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), - (0, 0, {'name': _('Tuesday Afternoon'), 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), - (0, 0, {'name': _('Wednesday Morning'), 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), - (0, 0, {'name': _('Wednesday Afternoon'), 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), - (0, 0, {'name': _('Thursday Morning'), 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), - (0, 0, {'name': _('Thursday Afternoon'), 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), - (0, 0, {'name': _('Friday Morning'), 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), - (0, 0, {'name': _('Friday Afternoon'), 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}) - ] + company_id = res.get('company_id', self.env.company.id) + company = self.env['res.company'].browse(company_id) + company_attendance_ids = company.resource_calendar_id.attendance_ids + if company_attendance_ids: + res['attendance_ids'] = [ + (0, 0, { + 'name': attendance.name, + 'dayofweek': attendance.dayofweek, + 'hour_from': attendance.hour_from, + 'hour_to': attendance.hour_to, + 'day_period': attendance.day_period, + }) + for attendance in company_attendance_ids + ] + else: + res['attendance_ids'] = [ + (0, 0, {'name': _('Monday Morning'), 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), + (0, 0, {'name': _('Monday Afternoon'), 'dayofweek': '0', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), + (0, 0, {'name': _('Tuesday Morning'), 'dayofweek': '1', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), + (0, 0, {'name': _('Tuesday Afternoon'), 'dayofweek': '1', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), + (0, 0, {'name': _('Wednesday Morning'), 'dayofweek': '2', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), + (0, 0, {'name': _('Wednesday Afternoon'), 'dayofweek': '2', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), + (0, 0, {'name': _('Thursday Morning'), 'dayofweek': '3', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), + (0, 0, {'name': _('Thursday Afternoon'), 'dayofweek': '3', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}), + (0, 0, {'name': _('Friday Morning'), 'dayofweek': '4', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), + (0, 0, {'name': _('Friday Afternoon'), 'dayofweek': '4', 'hour_from': 13, 'hour_to': 17, 'day_period': 'afternoon'}) + ] return res name = fields.Char(required=True) @@ -181,13 +196,14 @@ class ResourceCalendar(models.Model): default=lambda self: self.env.company) attendance_ids = fields.One2many( 'resource.calendar.attendance', 'calendar_id', 'Working Time', - copy=True) + compute='_compute_attendance_ids', store=True, readonly=False, copy=True) leave_ids = fields.One2many( 'resource.calendar.leaves', 'calendar_id', 'Time Off') global_leave_ids = fields.One2many( 'resource.calendar.leaves', 'calendar_id', 'Global Time Off', + compute='_compute_global_leave_ids', store=True, readonly=False, domain=[('resource_id', '=', False)], copy=True, - ) + ) hours_per_day = fields.Float("Average Hour per Day", default=HOURS_PER_DAY, help="Average hours per day a resource is supposed to work with this calendar.") tz = fields.Selection( @@ -197,6 +213,42 @@ class ResourceCalendar(models.Model): two_weeks_calendar = fields.Boolean(string="Calendar in 2 weeks mode") two_weeks_explanation = fields.Char('Explanation', compute="_compute_two_weeks_explanation") + @api.depends('company_id') + def _compute_attendance_ids(self): + for calendar in self.filtered(lambda c: not c._origin or c._origin.company_id != c.company_id): + company_calendar = calendar.company_id.resource_calendar_id + calendar.write({ + 'two_weeks_calendar': company_calendar.two_weeks_calendar, + 'hours_per_day': company_calendar.hours_per_day, + 'tz': company_calendar.tz, + 'attendance_ids': [(5, 0, 0)] + [ + (0, 0, { + 'name': attendance.name, + 'dayofweek': attendance.dayofweek, + 'date_from': attendance.date_from, + 'date_to': attendance.date_to, + 'hour_from': attendance.hour_from, + 'hour_to': attendance.hour_to, + 'day_period': attendance.day_period, + 'week_type': attendance.week_type, + 'display_type': attendance.display_type, + 'sequence': attendance.sequence, + }) for attendance in company_calendar.attendance_ids if not attendance.resource_id] + }) + + @api.depends('company_id') + def _compute_global_leave_ids(self): + for calendar in self.filtered(lambda c: not c._origin or c._origin.company_id != c.company_id): + calendar.write({ + 'global_leave_ids': [(5, 0, 0)] + [ + (0, 0, { + 'name': leave.name, + 'date_from': leave.date_from, + 'date_to': leave.date_to, + 'time_type': leave.time_type, + }) for leave in calendar.company_id.resource_calendar_id.global_leave_ids] + }) + @api.returns('self', lambda value: value.id) def copy(self, default=None): self.ensure_one() @@ -744,6 +796,15 @@ class ResourceCalendar(models.Model): else: return revert(day_dt) + def _get_max_number_of_hours(self, start, end): + self.ensure_one() + if not self.attendance_ids: + return 0 + mapped_data = defaultdict(lambda: 0) + for attendance in self.attendance_ids.filtered(lambda a: (not a.date_from or not a.date_to) or (a.date_from <= end and a.date_to >= start)): + mapped_data[(attendance.week_type, attendance.dayofweek)] += attendance.hour_to - attendance.hour_from + return max(mapped_data.values()) + class ResourceCalendarAttendance(models.Model): _name = "resource.calendar.attendance" diff --git a/addons/web/static/src/scss/report.scss b/addons/web/static/src/scss/report.scss index 91500a689a7..0edc31005f4 100644 --- a/addons/web/static/src/scss/report.scss +++ b/addons/web/static/src/scss/report.scss @@ -60,6 +60,23 @@ div#total { td { border-top: 1px solid black !important; } + &.o_border_bottom { + td { + border-bottom: 1px solid black !important; + } + } + } + } + } +} + +table { + thead { + &.o_black_border { + tr { + th { + border-bottom: 2px solid black !important; + } } } }