[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) <yti@odoo.com>
Co-authored-by: Yannick Tivisse <yti@odoo.com>
This commit is contained in:
Xavier BOL (xbo)
2023-02-22 20:25:16 +01:00
committed by Yannick Tivisse
co-authored by Yannick Tivisse
parent c1e570afd0
commit 1648c25cc9
10 changed files with 33 additions and 29 deletions
+1 -1
View File
@@ -125,7 +125,7 @@
<field name="departure_date"/>
</group>
<group string="Schedule">
<field name="resource_calendar_id" required="1"/>
<field name="resource_calendar_id"/>
<field name="id" invisible="1"/>
<field name="tz" attrs="{'required': [('id', '!=', False)]}"/>
</group>
+4 -3
View File
@@ -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)]}
+5 -1
View File
@@ -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'})
+3 -2
View File
@@ -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):
@@ -23,14 +23,11 @@
<field name="calendar_mismatch" invisible="1"/>
<label for="resource_calendar_id"/>
<div class="d-flex align-items-center">
<field name="resource_calendar_id" required="1"/>
<field name="resource_calendar_id"/>
<widget name="contract_warning_tooltip"
attrs="{'invisible': [('calendar_mismatch', '=', False)]}"/>
</div>
</xpath>
<field name="resource_calendar_id" position="attributes">
<attribute name="attrs">{'readonly': [('contract_warning', '=', False)]}</attribute>
</field>
</data>
</field>
</record>
+8 -9
View File
@@ -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'):
@@ -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)
+2
View File
@@ -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
+1 -1
View File
@@ -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:
+5 -4
View File
@@ -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))