84 lines
4.1 KiB
Python
84 lines
4.1 KiB
Python
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
from dateutil.relativedelta import relativedelta
|
||
|
from pytz import UTC
|
||
|
|
||
|
from odoo import api, fields, models
|
||
|
|
||
|
from odoo.addons.resource.models.utils import Intervals, sum_intervals, timezone_datetime
|
||
|
|
||
|
|
||
|
class CalendarEvent(models.Model):
|
||
|
_inherit = "calendar.event"
|
||
|
|
||
|
unavailable_partner_ids = fields.Many2many('res.partner', compute='_compute_unavailable_partner_ids')
|
||
|
|
||
|
@api.depends('partner_ids', 'start', 'stop', 'allday')
|
||
|
def _compute_unavailable_partner_ids(self):
|
||
|
complete_events = self.filtered(
|
||
|
lambda event: event.start and event.stop and (event.stop > event.start or (event.stop >= event.start and event.allday)) and event.partner_ids)
|
||
|
incomplete_event = self - complete_events
|
||
|
incomplete_event.unavailable_partner_ids = []
|
||
|
if not complete_events:
|
||
|
return
|
||
|
event_intervals = complete_events._get_events_interval()
|
||
|
for event, event_interval in event_intervals.items():
|
||
|
# Event_interval is empty when an allday event contains at least one day where the company is closed
|
||
|
if not event_interval:
|
||
|
event.unavailable_partner_ids = event.partner_ids
|
||
|
continue
|
||
|
start = event_interval._items[0][0]
|
||
|
stop = event_interval._items[-1][1]
|
||
|
schedule_by_partner = event.partner_ids._get_schedule(start, stop, merge=False)
|
||
|
event.unavailable_partner_ids = event._check_employees_availability_for_event(
|
||
|
schedule_by_partner, event_interval)
|
||
|
|
||
|
@api.model
|
||
|
def get_unusual_days(self, date_from, date_to=None):
|
||
|
return self.env.user.employee_id._get_unusual_days(date_from, date_to)
|
||
|
|
||
|
def _get_events_interval(self):
|
||
|
"""
|
||
|
This method will returned an Intervals object that represent the event's interval based of its parameters.
|
||
|
|
||
|
If an event is scheduled for the entire day, its interval will correspond to the work interval defined by the
|
||
|
company's calendar.
|
||
|
If an allday event is scheduled on a day when the company is closed, the interval of this event will be empty.
|
||
|
"""
|
||
|
start = min(self.mapped('start')).replace(hour=0, minute=0, second=0, tzinfo=UTC)
|
||
|
stop = max(self.mapped('stop')).replace(hour=23, minute=59, second=59, tzinfo=UTC)
|
||
|
company_calendar = self.env.company.resource_calendar_id
|
||
|
global_interval = company_calendar._work_intervals_batch(start, stop)[False]
|
||
|
interval_by_event = {}
|
||
|
for event in self:
|
||
|
if event.allday:
|
||
|
# Avoid allday event with a duration of 0
|
||
|
allday_event_interval = Intervals([(
|
||
|
event.start.replace(hour=0, minute=0, second=0, tzinfo=UTC),
|
||
|
event.stop.replace(hour=23, minute=59, second=59, tzinfo=UTC),
|
||
|
self.env['resource.calendar']
|
||
|
)])
|
||
|
|
||
|
if any(not (Intervals([(
|
||
|
event.start.replace(hour=0, minute=0, second=0, tzinfo=UTC) + relativedelta(days=i),
|
||
|
event.start.replace(hour=23, minute=59, second=59, tzinfo=UTC) + relativedelta(days=i),
|
||
|
self.env['resource.calendar']
|
||
|
)]) & global_interval) for i in range(0, (event.stop_date - event.start_date).days + 1)):
|
||
|
interval_by_event[event] = Intervals([])
|
||
|
else:
|
||
|
interval_by_event[event] = allday_event_interval & global_interval
|
||
|
else:
|
||
|
interval_by_event[event] = Intervals([(
|
||
|
timezone_datetime(event.start),
|
||
|
timezone_datetime(event.stop),
|
||
|
self.env['resource.calendar']
|
||
|
)])
|
||
|
return interval_by_event
|
||
|
|
||
|
def _check_employees_availability_for_event(self, schedule_by_partner, event_interval):
|
||
|
unavailable_partners = []
|
||
|
for partner, schedule in schedule_by_partner.items():
|
||
|
common_interval = schedule & event_interval
|
||
|
if sum_intervals(common_interval) != sum_intervals(event_interval):
|
||
|
unavailable_partners.append(partner.id)
|
||
|
return unavailable_partners
|