[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:
committed by
Yannick Tivisse
co-authored by
Yannick Tivisse
parent
c1e570afd0
commit
1648c25cc9
@@ -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>
|
||||
|
||||
@@ -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)]}
|
||||
|
||||
@@ -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'})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user