def validate_time_limits(self, booking_start, booking_end): #localize UTC dates to be able to compare with hours in Working Time user_tz = pytz.timezone(self.env.context.get('tz') or 'UTC') start_dt = pytz.utc.localize(fields.Datetime.from_string(booking_start)).astimezone(user_tz) end_dt = pytz.utc.localize(fields.Datetime.from_string(booking_end)).astimezone(user_tz) for calendar in self: hours = calendar.get_working_accurate_hours(start_dt, end_dt) duration = seconds(end_dt - start_dt) / 3600.0 if round(hours, 2) != round(duration, 2): return False return True
def get_working_accurate_hours(self, start_dt=None, end_dt=None): """ Replacement of resource calendar method get_working_hours Allows to handle hour_to = 00:00 Takes in account minutes Adds public holidays time (resource leaves with reason = PH) """ leave_obj = self.env['resource.calendar.leaves'] for calendar in self: id = calendar.id hours = timedelta() for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, until=end_dt.replace(hour=23, minute=59, second=59), byweekday=calendar.get_weekdays()[0]): day_start_dt = day.replace(hour=0, minute=0, second=0) if start_dt and day.date() == start_dt.date(): day_start_dt = start_dt day_end_dt = day.replace(hour=23, minute=59, second=59) if end_dt and day.date() == end_dt.date(): day_end_dt = end_dt work_limits = [] work_limits.append((day_start_dt.replace(hour=0, minute=0, second=0), day_start_dt)) work_limits.append((day_end_dt, day_end_dt.replace(hour=23, minute=59, second=59))) intervals = [] work_dt = day_start_dt.replace(hour=0, minute=0, second=0) working_intervals = [] for calendar_working_day in calendar.get_attendances_for_weekdays([day_start_dt.weekday()])[0]: min_from = int((calendar_working_day.hour_from - int(calendar_working_day.hour_from)) * 60) min_to = int((calendar_working_day.hour_to - int(calendar_working_day.hour_to)) * 60) x = work_dt.replace(hour=int(calendar_working_day.hour_from), minute=min_from) if calendar_working_day.hour_to == 0: y = work_dt.replace(hour=0, minute=0) + timedelta(days=1) else: y = work_dt.replace(hour=int(calendar_working_day.hour_to), minute=min_to) working_interval = (x, y) working_intervals += calendar.interval_remove_leaves(working_interval, work_limits) for interval in working_intervals: hours += interval[1] - interval[0] # Add public holidays leaves = leave_obj.search([('name', '=', 'PH'), ('calendar_id', '=', calendar.id)]) leave_intervals = [] for l in leaves: leave_intervals.append((datetime.strptime(l.date_from, DTF), datetime.strptime(l.date_to, DTF) )) clean_intervals = calendar.interval_remove_leaves((start_dt, end_dt), leave_intervals) for interval in clean_intervals: hours += (end_dt - start_dt) - (interval[1] - interval[0]) return seconds(hours) / 3600.0
def get_working_accurate_hours(self, start_dt=None, end_dt=None): """ Replacement of resource calendar method get_working_hours Allows to handle hour_to = 00:00 Takes in account minutes Adds public holidays time (resource leaves with reason = PH) """ leave_obj = self.env['resource.calendar.leaves'] for calendar in self: id = calendar.id hours = timedelta() for day in rrule.rrule(rrule.DAILY, dtstart=start_dt, until=end_dt.replace(hour=23, minute=59, second=59), byweekday=calendar.get_weekdays()[0]): day_start_dt = day.replace(hour=0, minute=0, second=0) if start_dt and day.date() == start_dt.date(): day_start_dt = start_dt day_end_dt = day.replace(hour=23, minute=59, second=59) if end_dt and day.date() == end_dt.date(): day_end_dt = end_dt work_limits = [] work_limits.append((day_start_dt.replace(hour=0, minute=0, second=0), day_start_dt)) work_limits.append((day_end_dt, day_end_dt.replace(hour=23, minute=59, second=59))) intervals = [] work_dt = day_start_dt.replace(hour=0, minute=0, second=0) working_intervals = [] for calendar_working_day in calendar.get_attendances_for_weekdays([day_start_dt.weekday()])[0]: min_from = int((calendar_working_day.hour_from - int(calendar_working_day.hour_from)) * 60) min_to = int((calendar_working_day.hour_to - int(calendar_working_day.hour_to)) * 60) x = work_dt.replace(hour=int(calendar_working_day.hour_from), minute=min_from) if calendar_working_day.hour_to == 0: y = work_dt.replace(hour=0, minute=0)+timedelta(days=1) else: y = work_dt.replace(hour=int(calendar_working_day.hour_to), minute=min_to) working_interval = (x, y) working_intervals += calendar.interval_remove_leaves(working_interval, work_limits) for interval in working_intervals: hours += interval[1] - interval[0] #Add public holidays leaves = leave_obj.search([('name', '=', 'PH'), ('calendar_id', '=', calendar.id)]) leave_intervals = [] for l in leaves: leave_intervals.append((datetime.strptime(l.date_from, DTF), datetime.strptime(l.date_to, DTF) )) clean_intervals = calendar.interval_remove_leaves((start_dt, end_dt), leave_intervals) for interval in clean_intervals: hours += (end_dt - start_dt) - (interval[1] - interval[0]) return seconds(hours) / 3600.0
def validate_time_limits(self, booking_start, booking_end): # localize UTC dates to be able to compare with hours in Working Time tz_offset = self.env.context.get('tz_offset') if tz_offset: start_dt = datetime.strptime(booking_start, DTF) - timedelta(minutes=tz_offset) end_dt = datetime.strptime(booking_end, DTF) - timedelta(minutes=tz_offset) else: user_tz = pytz.timezone(self.env.context.get('tz') or 'UTC') start_dt = pytz.utc.localize(fields.Datetime.from_string(booking_start)).astimezone(user_tz) end_dt = pytz.utc.localize(fields.Datetime.from_string(booking_end)).astimezone(user_tz) for calendar in self: hours = calendar.get_working_accurate_hours(start_dt, end_dt) duration = seconds(end_dt - start_dt) / 3600.0 if round(hours, 2) != round(duration, 2): return False return True
def get_working_hours_of_date(self, cr, uid, id, start_dt=None, end_dt=None, leaves=None, compute_leaves=False, resource_id=None, default_interval=None, context=None): """ Get the working hours of the day based on calendar. This method uses get_working_intervals_of_day to have the work intervals of the day. It then calculates the number of hours contained in those intervals. """ date_holiday = start_dt.strftime('%Y-%m-%d') is_holiday = self.pool.get('hr.holidays.public.line').search( cr, uid, [('date', '=', date_holiday)]) if is_holiday: return 0.0 intervalx = super(resource_calendar, self).get_working_hours_of_date( cr, uid, id, start_dt=start_dt, end_dt=end_dt, leaves=leaves, compute_leaves=compute_leaves, resource_id=resource_id, default_interval=default_interval, context=context) res = datetime.timedelta() intervals = self.get_working_intervals_of_day( cr, uid, id, start_dt, end_dt, leaves, compute_leaves, resource_id, default_interval, context) for interval in intervals: res += interval[1] - interval[0] return seconds(res) / 3600.0
def events_to_bookings(self, events): calendar_obj = self.env['resource.calendar'] resource_obj = self.env['resource.resource'] lang_obj = self.env['res.lang'] lang = lang_obj.search([('code', '=', self.env.context.get('lang'))]) user_df = ('%s %s' % (lang.date_format, lang.time_format)) if lang else DTF products = self.env['product.product'].search([('calendar_id', '!=', False)]) bookings = {} for event in events: r = event['resource'] if not r in bookings: bookings[r] = {} start_dt = datetime.strptime(event['start'], DTF) end_dt = datetime.strptime(event['end'], DTF) #check products and its working calendars by every hour booked by user hour_dt = start_dt while hour_dt < end_dt: hour = hour_dt.strftime(DTF) if hour_dt < end_dt: bookings[r][hour] = { 'start': hour_dt, 'start_f': (hour_dt).strftime(user_df), 'end': (hour_dt + timedelta(hours=MIN_TIMESLOT_HOURS)), 'end_f': (hour_dt + timedelta(hours=MIN_TIMESLOT_HOURS) ).strftime(user_df), 'resource': resource_obj.browse(int(event['resource'])), 'products': {} } hour_end_dt = hour_dt + timedelta(hours=MIN_TIMESLOT_HOURS) duration = seconds(hour_end_dt - hour_dt) / 3600 for product in products: hours = product.calendar_id.get_working_accurate_hours( hour_dt, hour_end_dt) if hours == duration: bookings[r][hour]['products'][str(product.id)] = { 'id': product.id, 'name': product.name, 'price': product.lst_price or product.price, 'currency': product.company_id.currency_id.name } #join adjacent hour intervals to one SO position for h in bookings[r]: if h == hour or bookings[r][h]['products'].keys( ) != bookings[r][hour]['products'].keys(): continue adjacent = False if bookings[r][hour]['start'] == bookings[r][h]['end']: adjacent = True bookings[r][h].update({ 'end': bookings[r][hour]['end'], 'end_f': bookings[r][hour]['end_f'] }) elif bookings[r][hour]['end'] == bookings[r][h][ 'start']: adjacent = True bookings[r][h].update({ 'start': bookings[r][hour]['end'], 'start_f': bookings[r][hour]['start_f'] }) if adjacent: for id, p in bookings[r][h]['products'].iteritems( ): bookings[r][h]['products'][id][ 'price'] += bookings[r][hour]['products'][ id]['price'] del bookings[r][hour] break hour_dt += timedelta(hours=MIN_TIMESLOT_HOURS) res = [] for r in bookings.values(): res += r.values() return res
def events_to_bookings(self, events): calendar_obj = self.env['resource.calendar'] resource_obj = self.env['resource.resource'] lang_obj = self.env['res.lang'] lang = lang_obj.search([('code', '=', self.env.context.get('lang'))]) user_df = ('%s %s' % (lang.date_format, lang.time_format)) if lang else DTF products = self.env['product.product'].search([ ('calendar_id', '!=', False), ('website_published', '=', True) ]) bookings = {} partner = self.env.user.partner_id pricelist_id = partner.property_product_pricelist.id for event in events: r = event['resource'] if r not in bookings: bookings[r] = {} start_dt = datetime.strptime(event['start'], DTF) end_dt = datetime.strptime(event['end'], DTF) # check products and its working calendars by every hour booked by user hour_dt = start_dt while hour_dt < end_dt: hour = hour_dt.strftime(DTF) if hour_dt < end_dt: bookings[r][hour] = { 'start': hour_dt, 'start_f': (hour_dt).strftime(user_df), 'end': (hour_dt + timedelta(hours=MIN_TIMESLOT_HOURS)), 'end_f': (hour_dt + timedelta(hours=MIN_TIMESLOT_HOURS) ).strftime(user_df), 'resource': resource_obj.browse(int(event['resource'])), 'products': {} } hour_end_dt = hour_dt + timedelta(hours=MIN_TIMESLOT_HOURS) duration = seconds(hour_end_dt - hour_dt) / 3600 for product in self.get_booking_available_products( event, products): hours = product.calendar_id.get_working_accurate_hours( hour_dt, hour_end_dt) if hours == duration: bookings[r][hour]['products'][str(product.id)] = { 'id': product.id, 'name': product.name, 'quantity': 1, 'currency': product.company_id.currency_id.name } # join adjacent hour intervals to one SO position for h in bookings[r]: if h == hour or bookings[r][h]['products'].keys( ) != bookings[r][hour]['products'].keys(): continue adjacent = False if bookings[r][hour]['start'] == bookings[r][h]['end']: adjacent = True bookings[r][h].update({ 'end': bookings[r][hour]['end'], 'end_f': bookings[r][hour]['end_f'] }) elif bookings[r][hour]['end'] == bookings[r][h][ 'start']: adjacent = True bookings[r][h].update({ 'start': bookings[r][hour]['end'], 'start_f': bookings[r][hour]['start_f'] }) if adjacent: for id, p in bookings[r][h]['products'].iteritems( ): bookings[r][h]['products'][id][ 'quantity'] += bookings[r][hour][ 'products'][id]['quantity'] del bookings[r][hour] break hour_dt += timedelta(hours=MIN_TIMESLOT_HOURS) # calculate prices according to pricelists for k1, v1 in bookings.iteritems(): for k2, v2 in v1.iteritems(): for id, product in v2['products'].iteritems(): bookings[k1][k2]['products'][id]['price'] = self.env[ 'product.product'].browse(product['id']).with_context( { 'quantity': product['quantity'], 'pricelist': pricelist_id, 'partner': partner.id }).price * product['quantity'] res = [] for r in bookings.values(): res += r.values() return res
def get_working_accurate_hours(self, start_dt=None, end_dt=None): """ Replacement of resource calendar method get_working_hours Allows to handle hour_to = 00:00 Takes in account minutes Adds public holidays time (resource leaves with reason = PH) """ leave_obj = self.env['resource.calendar.leaves'] for calendar in self: product = self.env['product.template'].search([('calendar_id', '=', calendar.id)]) hours = timedelta() day_start_dt = start_dt day_end_dt = end_dt # sometimes one booking may be on two different weekdays # call get_working_accurate_hours recursively for that cases if day_end_dt.date() == day_start_dt.date() + timedelta(1) and \ day_end_dt != day_end_dt.replace(hour=0, minute=0, second=0): hours += timedelta(hours=self.get_working_accurate_hours(start_dt=day_end_dt.replace(hour=0, minute=0, second=0), end_dt=day_end_dt)) weekday = [day_start_dt.weekday()] if product and product[0].work_on_holidays and product[0].holidays_country_id and product[0].holidays_schedule == 'premium': if calendar.get_attendances_for_weekdays([5]): holidays = self.env['hr.holidays.public'].search([ ('country_id', '=', product[0].holidays_country_id.id), ('year', '=', start_dt.year), ], limit=1) for h in holidays[0].line_ids.filtered(lambda r: r.date == start_dt.strftime(DF)): weekday = [5] work_limits = [] work_limits.append((day_start_dt.replace(hour=0, minute=0, second=0), day_start_dt)) work_limits.append((day_end_dt, day_end_dt.replace(hour=23, minute=59, second=59))) work_dt = day_start_dt.replace(hour=0, minute=0, second=0) working_intervals = [] for calendar_working_day in calendar.get_attendances_for_weekdays(weekday)[0]: min_from = int((calendar_working_day.hour_from - int(calendar_working_day.hour_from)) * 60) min_to = int((calendar_working_day.hour_to - int(calendar_working_day.hour_to)) * 60) x = work_dt.replace(hour=int(calendar_working_day.hour_from), minute=min_from) if calendar_working_day.hour_to == 0: y = work_dt.replace(hour=0, minute=0) + timedelta(days=1) else: y = work_dt.replace(hour=int(calendar_working_day.hour_to), minute=min_to) working_interval = (x, y) leaves = self.get_leave_intervals() leaves = leaves and self.localize_time_intervals(leaves[0]) work_limits += leaves if product and not product[0].work_on_holidays and product[0].holidays_country_id: holidays = self.env['hr.holidays.public'].search([ ('country_id', '=', product[0].holidays_country_id.id), ('year', '=', start_dt.year), ], limit=1) for h in holidays[0].line_ids.filtered(lambda r: r.date == start_dt.strftime(DF)): holiday_interval = [(datetime.combine(datetime.strptime(h.date, DF), time(0, 0)), datetime.combine(datetime.strptime(h.date, DF) + timedelta(1), time(0, 0)))] holiday_interval = self.make_offset_aware(holiday_interval) work_limits += holiday_interval work_limits = self.make_offset_aware(work_limits) working_interval = self.make_offset_aware([working_interval])[0] working_intervals += calendar.interval_remove_leaves(working_interval, work_limits) for interval in working_intervals: hours += interval[1] - interval[0] # Add public holidays leaves = leave_obj.search([('name', '=', 'PH'), ('calendar_id', '=', calendar.id)]) leave_intervals = [] for l in leaves: leave_intervals.append((datetime.strptime(l.date_from, DTF), datetime.strptime(l.date_to, DTF) )) clean_intervals = calendar.interval_remove_leaves((start_dt, end_dt), leave_intervals) for interval in clean_intervals: hours += (end_dt - start_dt) - (interval[1] - interval[0]) return seconds(hours) / 3600.0