import datetime import json import logging from operator import itemgetter from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.shortcuts import get_object_or_404 from web.models import Appointment, AppointmentTypeLink, Worker, Availability, Holiday from web.views import e500_error from web.views.notifications import get_filter_locations logger = logging.getLogger(__name__) RANDOM_COLORS = [ '#%02X%02X%02X' % (8, 218, 217), '#%02X%02X%02X' % (8, 8, 101), '#%02X%02X%02X' % (8, 218, 3), '#%02X%02X%02X' % (247, 137, 156), '#%02X%02X%02X' % (183, 96, 2), '#%02X%02X%02X' % (20, 106, 55), '#%02X%02X%02X' % (8, 119, 217), '#%02X%02X%02X' % (16, 76, 27), '#%02X%02X%02X' % (85, 7, 162), '#%02X%02X%02X' % (157, 7, 2), '#%02X%02X%02X' % (49, 65, 68), '#%02X%02X%02X' % (112, 124, 98), '#%02X%02X%02X' % (8, 8, 215), '#%02X%02X%02X' % (85, 7, 98), '#%02X%02X%02X' % (16, 76, 100), '#%02X%02X%02X' % (85, 7, 50), '#%02X%02X%02X' % (183, 5, 2), '#%02X%02X%02X' % (183, 156, 2), '#%02X%02X%02X' % (112, 82, 38), ] def build_duration(duration): number_of_hours = duration // 60 minutes = duration - number_of_hours * 60 return "{0:02d}:{1:02d}".format(number_of_hours, minutes) def get_holidays(worker, date): today_start = datetime.datetime.strptime(date, '%Y-%m-%d').replace(hour=0, minute=0) today_end = datetime.datetime.strptime(date, '%Y-%m-%d').replace(hour=23, minute=59) holidays = Holiday.objects.filter(person=worker, datetime_start__lte=today_end, datetime_end__gte=today_start) result = [] for holiday in holidays: minutes = int((holiday.datetime_end - holiday.datetime_start).total_seconds() / 60) # hack for time zones start_date = datetime.datetime.combine(today_start, holiday.datetime_start.time()) end_date = datetime.datetime.combine(today_start, holiday.datetime_end.time()) event = { 'duration': build_duration(minutes), 'link_when': start_date, 'link_who': worker.id, 'link_end': end_date, } result.append(event) return result def remove_holidays(availability, worker, date): today_start = datetime.datetime.strptime(date, '%Y-%m-%d').replace(hour=0, minute=0) today_end = datetime.datetime.strptime(date, '%Y-%m-%d').replace(hour=23, minute=59) holidays_starting_before = Holiday.objects.filter(person=worker, datetime_start__lte=today_start, datetime_end__gte=today_start) holidays_starting_today = Holiday.objects.filter(person=worker, datetime_start__lte=today_end, datetime_start__gte=today_start) holidays_ending_today = Holiday.objects.filter(person=worker, datetime_end__lte=today_end, datetime_end__gte=today_start) result = [] timestamps = [] timestamps.append({ "time": availability.available_from, "type": "start" }) timestamps.append({ "time": availability.available_till, "type": "stop" }) direction = -holidays_starting_before.count() for holiday in holidays_starting_today: timestamps.append({ "time": holiday.datetime_start.time(), "type": "stop" }) for holiday in holidays_ending_today: timestamps.append({ "time": holiday.datetime_end.time(), "type": "start" }) start_date = None stop_date = None sorted_array = sorted(timestamps, key=itemgetter('time')) for timestamp in sorted_array: type = timestamp.get("type", None) if type == "stop": direction -= 1 stop_date = timestamp["time"] if direction == 0: result.append(Availability(person=worker, day_number=availability.day_number, available_from=start_date, available_till=stop_date)) elif type == "start": direction += 1 start_date = timestamp["time"] else: raise TypeError("Unknown type: " + str(type)) if direction > 0: result.append(Availability(person=worker, day_number=availability.day_number, available_from=start_date, available_till=today_end.time())) return result def get_availabilities(worker, date): result = [] today = datetime.datetime.strptime(date, '%Y-%m-%d') weekday = today.weekday() + 1 availabilities = Availability.objects.filter(person=worker, day_number=weekday) for availability in availabilities: availabilities_without_holidays = remove_holidays(availability, worker, date) for availability_without_holiday in availabilities_without_holidays: start_date = datetime.datetime.combine(today, availability_without_holiday.available_from) end_date = datetime.datetime.combine(today, availability_without_holiday.available_till) delta = end_date - start_date minutes = int(delta.total_seconds() / 60) event = { 'duration': build_duration(minutes), 'link_when': start_date, 'link_who': worker.id, 'link_end': end_date, } result.append(event) return result def get_conflicts(worker, date): result = [] appointments = Appointment.objects.filter( datetime_when__date=date, location__in=get_filter_locations(worker), status=Appointment.APPOINTMENT_STATUS_SCHEDULED, visit__isnull=False).all() for i, appointment in enumerate(appointments): if appointment.worker_assigned is not None: link_when = appointment.datetime_when link_when = link_when.replace(tzinfo=None) link_end = link_when + datetime.timedelta(minutes=appointment.length) event = { 'title': appointment.title(), 'duration': appointment.length, 'appointment_id': appointment.id, 'link_when': link_when, 'link_who': appointment.worker_assigned.id, 'link_end': link_end, 'location': str(appointment.location), } result.append(event) links = AppointmentTypeLink.objects.filter(appointment=appointment).all() for j, link in enumerate(links): link_when = link.date_when if link_when is not None: link_when = link_when.replace(tzinfo=None) link_end = link_when + datetime.timedelta(minutes=link.appointment_type.default_duration) event = { 'title': link.appointment_type.description, 'duration': build_duration(link.appointment_type.default_duration), 'appointment_id': appointment.id, 'link_when': link_when, 'link_who': link.worker_id, 'link_end': link_end, 'location': str(appointment.location), } result.append(event) return result def get_generic_appointment_events(request, date): result = {} appointments = Appointment.objects.filter( datetime_when__date=date, location__in=get_filter_locations(request.user), status=Appointment.APPOINTMENT_STATUS_SCHEDULED, visit__isnull=True).all() for appointment in appointments: if appointment.location_id not in result: appointment_data = { 'name': "GENERAL", 'id': appointment.id, 'color': RANDOM_COLORS[len(RANDOM_COLORS) - 1], 'start': "", 'location': str(appointment.location), 'events': [] } result[appointment.location_id] = appointment_data link_when = appointment.datetime_when link_end = None if link_when is not None: link_when = link_when.replace(tzinfo=None) link_end = link_when + datetime.timedelta(minutes=appointment.length) event = { 'title': appointment.title(), 'short_title': appointment.title(), 'duration': build_duration(appointment.length), 'id': 'app-{}'.format(appointment.id), 'appointment_id': appointment.id, 'link_when': link_when, 'link_who': appointment.worker_assigned_id, 'link_end': link_end, 'location': str(appointment.location), 'flying_team_location': unicode(appointment.flying_team), 'appointment_start': appointment.datetime_when.replace(tzinfo=None, hour=7, minute=0), 'appointment_end': appointment.datetime_when.replace(tzinfo=None, hour=19, minute=0), } appointment_events = result[appointment.location_id]['events'] appointment_events.append(event) return result.values() @login_required def events(request, date): appointments = Appointment.objects.filter( datetime_when__date=date, location__in=get_filter_locations(request.user), status=Appointment.APPOINTMENT_STATUS_SCHEDULED, visit__isnull=False).all() subjects = {} for i, appointment in enumerate(appointments): appointment_subject = appointment.visit.subject if appointment_subject.id not in subjects: # create subject subject = { 'name': unicode(appointment_subject), 'id': appointment_subject.id, 'color': RANDOM_COLORS[i], 'start': appointment.datetime_when.replace(tzinfo=None).strftime("%H:%M:00"), # this indicates only location of the first appointment # (there is small chance to have two appointments in two different places at the same day) 'location': str(appointment.location), 'events': [] } subjects[appointment_subject.id] = subject links = AppointmentTypeLink.objects.filter(appointment=appointment).all() for j, link in enumerate(links): link_when = link.date_when link_end = None if link_when is not None: link_when = link_when.replace(tzinfo=None) link_end = link_when + datetime.timedelta(minutes=link.appointment_type.default_duration) event = { 'title': link.appointment_type.description, 'short_title': link.appointment_type.code, 'duration': build_duration(link.appointment_type.default_duration), 'subject': unicode(appointment_subject), 'id': '{}-{}'.format(i, j), 'link_id': link.id, 'link_when': link_when, 'link_who': link.worker_id, 'link_end': link_end, 'location': str(appointment.location), 'flying_team_location': unicode(appointment.flying_team), 'appointment_start': appointment.datetime_when.replace(tzinfo=None), 'appointment_end': appointment.datetime_when.replace(tzinfo=None, hour=19, minute=0), } subject_events = subjects[appointment_subject.id]['events'] subject_events.append(event) generic_appointment_events = get_generic_appointment_events(request, date) availabilities = [] holidays = [] workers = get_workers_for_daily_planning(request) for worker in workers: availabilities = availabilities + get_availabilities(worker, date) holidays = holidays + get_holidays(worker, date) return JsonResponse({ "data": subjects.values(), "generic": generic_appointment_events, 'availabilities': availabilities, 'holidays': holidays }) def get_workers_for_daily_planning(request): result = Worker.objects.filter(locations__in=get_filter_locations(request.user)). \ filter(user__is_active=True).exclude( role=Worker.ROLE_CHOICES_SECRETARY).distinct() return result def availabilities(request, date): availabilities = [] holidays = [] conflicts = [] workers = get_workers_for_daily_planning(request) for worker in workers: availabilities = availabilities + get_availabilities(worker, date) holidays = holidays + get_holidays(worker, date) conflicts = conflicts + get_conflicts(worker, date) return JsonResponse({ "conflicts": conflicts, 'availabilities': availabilities, 'holidays': holidays }) @login_required def events_persist(request): try: event_links = json.loads(request.POST.get('events_to_persist')) for event_link in event_links: try: when = datetime.datetime.strptime(event_link['start'], '%Y-%m-%dT%H:%M:00') except ValueError: when = datetime.datetime.strptime(event_link['start'][0:-6], '%Y-%m-%dT%H:%M:00') worker_id = event_link['link_who'] if 'link_id' in event_link: link_id = event_link['link_id'] appointment_type_link = get_object_or_404(AppointmentTypeLink, pk=link_id) appointment_type_link.worker_id = worker_id appointment_type_link.date_when = when appointment_type_link.save() else: logger.info(event_link) appointment_id = event_link['appointment_id'] appointment = get_object_or_404(Appointment, pk=appointment_id) appointment.worker_assigned_id = worker_id appointment.datetime_when = when appointment.save() events_to_clear = json.loads(request.POST.get('events_to_clear')) for link_id in events_to_clear: appointment_type_link = get_object_or_404(AppointmentTypeLink, pk=link_id) appointment_type_link.worker_id = None appointment_type_link.date_when = None appointment_type_link.save() appointments_to_clear = json.loads(request.POST.get('appointments_to_clear')) for appointment_id in appointments_to_clear: appointment = get_object_or_404(Appointment, pk=appointment_id) appointment.worker_assigned_id = None appointment.save() return JsonResponse({}, status=201) except Exception as e: logger.error(e, exc_info=True) return e500_error(request)