From 1648c25cc97d5cbcefa747c4b411c32641c76ecc Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Fri, 9 Sep 2022 08:46:55 +0000 Subject: [PATCH] [IMP] resource: make working schedule optional in resource model closes odoo/odoo#110548 Related: odoo/upgrade#4248 Related: odoo/enterprise#36101 Signed-off-by: Yannick Tivisse (yti) Co-authored-by: Yannick Tivisse --- addons/hr/views/hr_employee_views.xml | 2 +- addons/hr_attendance/models/hr_attendance.py | 7 ++++--- addons/hr_contract/models/hr_contract.py | 6 +++++- addons/hr_contract/models/hr_employee.py | 5 +++-- addons/hr_contract/views/hr_contract_views.xml | 5 +---- addons/hr_holidays/models/hr_leave.py | 17 ++++++++--------- .../hr_work_entry_holidays/models/hr_leave.py | 7 +++---- addons/mrp/models/mrp_workorder.py | 2 ++ addons/resource/models/resource_mixin.py | 2 +- addons/resource/models/resource_resource.py | 9 +++++---- 10 files changed, 33 insertions(+), 29 deletions(-) diff --git a/addons/hr/views/hr_employee_views.xml b/addons/hr/views/hr_employee_views.xml index 317f5304b46..a35ebfc6e11 100644 --- a/addons/hr/views/hr_employee_views.xml +++ b/addons/hr/views/hr_employee_views.xml @@ -125,7 +125,7 @@ - + diff --git a/addons/hr_attendance/models/hr_attendance.py b/addons/hr_attendance/models/hr_attendance.py index 5584833c98c..c2a17a96a73 100644 --- a/addons/hr_attendance/models/hr_attendance.py +++ b/addons/hr_attendance/models/hr_attendance.py @@ -48,7 +48,7 @@ class HrAttendance(models.Model): def _get_employee_calendar(self): self.ensure_one() - return self.employee_id.resource_calendar_id + return self.employee_id.resource_calendar_id or self.employee_id.company_id.resource_calendar_id @api.depends('check_in', 'check_out') def _compute_worked_hours(self): @@ -174,11 +174,12 @@ class HrAttendance(models.Model): stop = pytz.utc.localize(max(attendance_dates, key=itemgetter(0))[0] + timedelta(hours=24)) # Retrieve expected attendance intervals - expected_attendances = emp.resource_calendar_id._attendance_intervals_batch( + calendar = emp.resource_calendar_id or emp.company_id.resource_calendar_id + expected_attendances = calendar._attendance_intervals_batch( start, stop, emp.resource_id )[emp.resource_id.id] # Substract Global Leaves and Employee's Leaves - leave_intervals = emp.resource_calendar_id._leave_intervals_batch(start, stop, emp.resource_id, domain=[]) + leave_intervals = calendar._leave_intervals_batch(start, stop, emp.resource_id, domain=[]) expected_attendances -= leave_intervals[False] | leave_intervals[emp.resource_id.id] # working_times = {date: [(start, stop)]} diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py index cad63359d08..dec8fc6bbbe 100644 --- a/addons/hr_contract/models/hr_contract.py +++ b/addons/hr_contract/models/hr_contract.py @@ -254,7 +254,11 @@ class Contract(models.Model): calendar = vals.get('resource_calendar_id') if calendar: - self.filtered(lambda c: c.state == 'open' or (c.state == 'draft' and c.kanban_state == 'done')).mapped('employee_id').write({'resource_calendar_id': calendar}) + self.filtered( + lambda c: c.state == 'open' or (c.state == 'draft' and c.kanban_state == 'done') + ).mapped('employee_id').filtered( + lambda e: e.resource_calendar_id + ).write({'resource_calendar_id': calendar}) if 'state' in vals and 'kanban_state' not in vals: self.write({'kanban_state': 'normal'}) diff --git a/addons/hr_contract/models/hr_employee.py b/addons/hr_contract/models/hr_employee.py index 298fa1a76f8..e98d02100fc 100644 --- a/addons/hr_contract/models/hr_employee.py +++ b/addons/hr_contract/models/hr_employee.py @@ -95,11 +95,12 @@ class Employee(models.Model): return self.search(['|', ('active', '=', True), ('active', '=', False)])._get_contracts(date_from, date_to, states=states) def write(self, vals): - res = super(Employee, self).write(vals) + res = super().write(vals) if vals.get('contract_id'): for employee in self: employee.resource_calendar_id.transfer_leaves_to(employee.contract_id.resource_calendar_id, employee.resource_id) - employee.resource_calendar_id = employee.contract_id.resource_calendar_id + if employee.resource_calendar_id: + employee.resource_calendar_id = employee.contract_id.resource_calendar_id return res def action_open_contract(self): diff --git a/addons/hr_contract/views/hr_contract_views.xml b/addons/hr_contract/views/hr_contract_views.xml index 5fb302543a7..0510bb61577 100644 --- a/addons/hr_contract/views/hr_contract_views.xml +++ b/addons/hr_contract/views/hr_contract_views.xml @@ -23,14 +23,11 @@ diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index a62e237302f..a03bc41a9f7 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -509,7 +509,7 @@ class HolidaysRequest(models.Model): def _compute_number_of_days(self): for holiday in self: if holiday.date_from and holiday.date_to: - holiday.number_of_days = holiday._get_number_of_days(holiday.date_from, holiday.date_to, holiday.employee_id.id)['days'] + holiday.number_of_days = holiday._get_number_of_days(holiday.date_from, holiday.date_to, holiday.employee_id)['days'] else: holiday.number_of_days = 0 @@ -562,7 +562,7 @@ class HolidaysRequest(models.Model): - calendar._leave_intervals_batch(start_dt, end_dt, None)[False] # Substract Global Leaves number_of_hours = sum((stop - start).total_seconds() / 3600 for start, stop, dummy in intervals) else: - number_of_hours = holiday._get_number_of_days(holiday.date_from, holiday.date_to, holiday.employee_id.id)['hours'] + number_of_hours = holiday._get_number_of_days(holiday.date_from, holiday.date_to, holiday.employee_id)['hours'] holiday.number_of_hours_display = number_of_hours or (holiday.number_of_days * (calendar.hours_per_day or HOURS_PER_DAY)) else: holiday.number_of_hours_display = 0 @@ -735,22 +735,21 @@ class HolidaysRequest(models.Model): if holiday.state in ['cancel', 'refuse', 'validate1', 'validate']: raise ValidationError(_("This modification is not allowed in the current state.")) - def _get_number_of_days_batch(self, date_from, date_to, employee_ids): + def _get_number_of_days_batch(self, date_from, date_to, employees): """ Returns a float equals to the timedelta between two dates given as string.""" - employee = self.env['hr.employee'].browse(employee_ids) # We force the company in the domain as we are more than likely in a compute_sudo domain = [('company_id', 'in', self.env.company.ids + self.env.context.get('allowed_company_ids', []))] - result = employee._get_work_days_data_batch(date_from, date_to, domain=domain) + result = employees._get_work_days_data_batch(date_from, date_to, domain=domain) for employee_id in result: if self.request_unit_half and result[employee_id]['hours'] > 0: result[employee_id]['days'] = 0.5 return result - def _get_number_of_days(self, date_from, date_to, employee_id): + def _get_number_of_days(self, date_from, date_to, employee): """ Returns a float equals to the timedelta between two dates given as string.""" - if employee_id: - return self._get_number_of_days_batch(date_from, date_to, employee_id)[employee_id] + if employee.resource_calendar_id: + return self._get_number_of_days_batch(date_from, date_to, employee)[employee.id] today_hours = self.env.company.resource_calendar_id.get_work_hours_count( datetime.combine(date_from.date(), time.min), @@ -938,7 +937,7 @@ class HolidaysRequest(models.Model): if self._context.get('leave_compute_date_from_to') and employees: employee_leave_date_duration = defaultdict(dict) for (date_from, date_to), employee_ids in leave_date_employees.items(): - employee_leave_date_duration[(date_from, date_to)] = self._get_number_of_days_batch(date_from, date_to, employee_ids) + employee_leave_date_duration[(date_from, date_to)] = self._get_number_of_days_batch(date_from, date_to, self.env['hr.employee'].browse(employee_ids)) for values in vals_list: employee_id = values.get('employee_id') if employee_id and values.get('date_from') and values.get('date_to'): diff --git a/addons/hr_work_entry_holidays/models/hr_leave.py b/addons/hr_work_entry_holidays/models/hr_leave.py index 13d77c8a255..ad22c5e45b2 100644 --- a/addons/hr_work_entry_holidays/models/hr_leave.py +++ b/addons/hr_work_entry_holidays/models/hr_leave.py @@ -227,16 +227,15 @@ Contracts: vals_list += work_entry.contract_id._get_work_entries_values(work_entry.date_start, work_entry.date_stop) self.env['hr.work.entry'].create(vals_list) - def _get_number_of_days(self, date_from, date_to, employee_id): + def _get_number_of_days(self, date_from, date_to, employee): """ If an employee is currently working full time but asks for time off next month where he has a new contract working only 3 days/week. This should be taken into account when computing the number of days for the leave (2 weeks leave = 6 days). Override this method to get number of days according to the contract's calendar at the time of the leave. """ - days = super(HrLeave, self)._get_number_of_days(date_from, date_to, employee_id) - if employee_id: - employee = self.env['hr.employee'].browse(employee_id) + days = super()._get_number_of_days(date_from, date_to, employee) + if employee: # Use sudo otherwise base users can't compute number of days contracts = employee.sudo()._get_contracts(date_from, date_to, states=['open']) contracts |= employee.sudo()._get_incoming_contracts(date_from, date_to) diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 1e14616cbc9..929a0ad80dc 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -520,6 +520,8 @@ class MrpWorkorder(models.Model): best_finished_date = datetime.max vals = {} for workcenter in workcenters: + if not workcenter.resource_calendar_id: + raise UserError(_('There is no defined calendar on workcenter %s.', workcenter.name)) # Compute theoretical duration if self.workcenter_id == workcenter: duration_expected = self.duration_expected diff --git a/addons/resource/models/resource_mixin.py b/addons/resource/models/resource_mixin.py index 8b45c405442..122a682ece1 100644 --- a/addons/resource/models/resource_mixin.py +++ b/addons/resource/models/resource_mixin.py @@ -177,7 +177,7 @@ class ResourceMixin(models.AbstractModel): containing at least an attendance. """ resource = self.resource_id - calendar = calendar or self.resource_calendar_id + calendar = calendar or self.resource_calendar_id or self.company_id.resource_calendar_id # naive datetimes are made explicit in UTC if not from_datetime.tzinfo: diff --git a/addons/resource/models/resource_resource.py b/addons/resource/models/resource_resource.py index ed86c885564..bb4d547c3c4 100644 --- a/addons/resource/models/resource_resource.py +++ b/addons/resource/models/resource_resource.py @@ -40,7 +40,7 @@ class ResourceResource(models.Model): calendar_id = fields.Many2one( "resource.calendar", string='Working Time', default=lambda self: self.env.company.resource_calendar_id, - required=True, domain="[('company_id', '=', company_id)]") + domain="[('company_id', '=', company_id)]") tz = fields.Selection( _tz_get, string='Timezone', required=True, default=lambda self: self._context.get('tz') or self.env.user.tz or 'UTC') @@ -118,10 +118,11 @@ class ResourceResource(models.Model): start + relativedelta(hour=0, minute=0, second=0), end + relativedelta(days=1, hour=0, minute=0, second=0), ] - calendar_start = resource.calendar_id._get_closest_work_time(start, resource=resource, search_range=search_range, + calendar = resource.calendar_id or resource.company_id.resource_calendar_id or self.env.company.resource_calendar_id + calendar_start = calendar._get_closest_work_time(start, resource=resource, search_range=search_range, compute_leaves=compute_leaves) search_range[0] = start - calendar_end = resource.calendar_id._get_closest_work_time(end if end > start else start, match_end=True, + calendar_end = calendar._get_closest_work_time(max(start, end), match_end=True, resource=resource, search_range=search_range, compute_leaves=compute_leaves) result[resource] = ( @@ -140,7 +141,7 @@ class ResourceResource(models.Model): resource_mapping = {} calendar_mapping = defaultdict(lambda: self.env['resource.resource']) for resource in self: - calendar_mapping[resource.calendar_id] |= resource + calendar_mapping[resource.calendar_id or resource.company_id.resource_calendar_id] |= resource for calendar, resources in calendar_mapping.items(): resources_unavailable_intervals = calendar._unavailable_intervals_batch(start_datetime, end_datetime, resources, tz=timezone(calendar.tz))