diff --git a/smash/web/api_views/daily_planning.py b/smash/web/api_views/daily_planning.py index a72f05e973e8b7d8c505b3cf2cbfc2e809d8bbe0..116b021e4fae2189287eaa7964b34d289dbdac7e 100644 --- a/smash/web/api_views/daily_planning.py +++ b/smash/web/api_views/daily_planning.py @@ -1,13 +1,17 @@ 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 ..models import Appointment, AppointmentTypeLink, Worker, Availability, Holiday -from ..views.notifications import get_filter_locations +from web.views import e500_error +from web.models import Appointment, AppointmentTypeLink, Worker, Availability, Holiday +from web.views.notifications import get_filter_locations + +logger = logging.getLogger(__name__) RANDOM_COLORS = [ '#%02X%02X%02X' % (8, 218, 217), @@ -184,6 +188,52 @@ def get_conflicts(worker, date): 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( @@ -233,6 +283,8 @@ def events(request, date): 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) @@ -243,6 +295,7 @@ def events(request, date): return JsonResponse({ "data": subjects.values(), + "generic": generic_appointment_events, 'availabilities': availabilities, 'holidays': holidays }) @@ -275,22 +328,39 @@ def availabilities(request, date): @login_required def events_persist(request): - 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'] - 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() - 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() - return JsonResponse({}, status=201) + 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) diff --git a/smash/web/models/appointment.py b/smash/web/models/appointment.py index 9db12586d0d83c0f61f9eba87d55260192eb5c8a..f4a6c043a3dc13da9bed30f999ae19cc42fa6caa 100644 --- a/smash/web/models/appointment.py +++ b/smash/web/models/appointment.py @@ -118,7 +118,12 @@ class Appointment(models.Model): def title(self): if self.visit is None: - return self.comment.replace("\n", ";").replace("\r", ";") + title = "N/A" + if self.comment is not None: + title = self.comment.replace("\n", ";").replace("\r", ";") + if title == "": + title = "N/A" + return title else: title = self.visit.subject.first_name + " " + self.visit.subject.last_name + " type: " for appointment_type in self.appointment_types.all(): diff --git a/smash/web/static/js/daily_planning.js b/smash/web/static/js/daily_planning.js index ec43541e6f3b96c601c2a5a48487afdc28e1d50f..55e0d1b34ffd017fbaff6eb0ac23018abb7eb9ce 100644 --- a/smash/web/static/js/daily_planning.js +++ b/smash/web/static/js/daily_planning.js @@ -27,8 +27,9 @@ var overlaps = (function () { })(); var eventsCleared = []; +var appointmentsCleared = []; -function add_event(event, color, subjectId, boxBody) { +function add_event(event, color, subjectId, locationId, boxBody) { var eventElement = $('<div class="fc-event ui-draggable ui-draggable-handle hidden-print">' + event.title + '<span style="float:right;padding-right:5px;">' + event.duration + '</span></div>' ) @@ -56,7 +57,9 @@ function add_event(event, color, subjectId, boxBody) { original_duration: event.duration, subject: event.subject, subject_id: subjectId, + location_id: locationId, id: event.id, + appointment_id: event.appointment_id, link_id: event.link_id, link_who: event.link_who, link_when: event.link_when, @@ -134,7 +137,7 @@ function get_subjects_events(day) { }; $('#calendar').fullCalendar('renderEvent', event, true); } else { - add_event(event, subject.color, subject.id, boxBody); + add_event(event, subject.color, subject.id, undefined, boxBody); } }); boxSubject.append(boxHeader); @@ -146,19 +149,65 @@ function get_subjects_events(day) { } }); }); + var location_based_general_appointments = data.generic; + $.each(location_based_general_appointments, function (index, location) { + var divSubject = $("<div class='col-md-4 col-lg-3 col-sm-12 subjects-events'/>"); + var boxSubject = $("<div class='box box-primary'/>").css('border-top-color', location.color); + var boxBody = $("<div class='box-body' id='location_" + location.id + "'>"); + var boxHeader = $("<div class='box-header with-border'/>"); + var title_location = $("<h4>" + location.name + " <span style='float:right;padding-right:5px;'>" + location.location + "</span></h4>"); + boxHeader.append(title_location); + $.each(location.events, function (index_event, event) { + if (event.link_who) { + event.title = event.short_title; + event.color = location.color + ' !important'; + event.start = $.fullCalendar.moment(event.link_when); + event.end = $.fullCalendar.moment(event.link_end); + event.resourceId = event.link_who.toString(); + event.location_id = location.id; + event.original_duration = event.duration; + event.constraint = { + start: $.fullCalendar.moment(event.appointment_start), + end: $.fullCalendar.moment(event.appointment_end) + }; + $('#calendar').fullCalendar('renderEvent', event, true); + } else { + add_event(event, location.color, undefined, location.id, boxBody); + } + }); + boxSubject.append(boxHeader); + boxSubject.append(boxBody); + divSubject.append(boxSubject); + $("#subjects").append(divSubject); + divSubject.droppable({ + drop: function (event, ui) { + } + }); + }); + }); } function remove_event(event) { $('#calendar').fullCalendar('removeEvents', event.id); - var selector = '#subject_' + event.subject_id; + var selector; + if (event.subject_id !== undefined) { + selector = '#subject_' + event.subject_id; + } else { + selector = '#location_' + event.location_id; + } + var boxBody = $(selector); event.duration = event.original_duration; event.removed = true; //remove !important event.color = event.color.substring(0, 7); - eventsCleared.push(event.link_id); - add_event(event, event.color, event.subject_id, boxBody); + if (event.link_id !== undefined) { + eventsCleared.push(event.link_id); + } else { + appointmentsCleared.push(event.appointment_id); + } + add_event(event, event.color, event.subject_id, event.location_id, boxBody); } $(document).ready(function () { @@ -199,20 +248,30 @@ $(document).ready(function () { if (calendar_event.rendering !== "background") { eventsToPersist.push({ 'link_id': calendar_event.link_id, + 'appointment_id': calendar_event.appointment_id, 'link_who': parseInt(calendar_event.resourceId), 'start': calendar_event.start.format() }); - var index = eventsCleared.indexOf(calendar_event.link_id); - if (index > -1) { - eventsCleared.splice(index, 1); + if (calendar_event.link_id !== undefined) { + var index = eventsCleared.indexOf(calendar_event.link_id); + if (index > -1) { + eventsCleared.splice(index, 1); + } + } else { + var index = appointmentsCleared.indexOf(calendar_event.appointment_id); + if (index > -1) { + appointmentsCleared.splice(index, 1); + } } + } }); $.post({ url: events_url, data: { events_to_persist: JSON.stringify(eventsToPersist), - events_to_clear: JSON.stringify(eventsCleared) + events_to_clear: JSON.stringify(eventsCleared), + appointments_to_clear: JSON.stringify(appointmentsCleared) }, dataType: "json" }).done(function (data) { @@ -281,19 +340,20 @@ $(document).ready(function () { events: [], eventRender: function (event, element) { if (event.rendering !== 'background') { - element.popover({ - title: event.short_title, - container: 'body', - placement: 'bottom', - trigger: 'click', - content: '<ul>' + - '<li>' + event.subject + '</li>' + - '<li>' + event.start.format(TIME_FORMAT) + ' - ' + event.end.format(TIME_FORMAT) + '</li>' + - '<li>Appointment starts: ' + event.constraint.start.format(TIME_FORMAT) + '</li>' + - '<li>Location: ' + (event.location !== FLYING_TEAM_LABEL ? event.location : event.flying_team_location + ' (FT)') + '</li>' + - '</ul>', - html: true - }); + var content = + element.popover({ + title: event.short_title, + container: 'body', + placement: 'bottom', + trigger: 'click', + content: '<ul>' + + '<li>' + (event.subject !== undefined ? event.subject : 'NO SUBJECT') + '</li>' + + '<li>' + event.start.format(TIME_FORMAT) + ' - ' + event.end.format(TIME_FORMAT) + '</li>' + + (event.subject !== undefined ? '<li>Appointment starts: ' + event.constraint.start.format(TIME_FORMAT) + '</li>' : '') + + '<li>Location: ' + (event.location !== FLYING_TEAM_LABEL ? event.location : event.flying_team_location + ' (FT)') + '</li>' + + '</ul>', + html: true + }); } else { } diff --git a/smash/web/tests/api_views/test_daily_planning.py b/smash/web/tests/api_views/test_daily_planning.py index d8c96868f541b4abcf09e40a45ea38d676b0cf96..257e4d7abdd681e9a03c028189879b05160b4622 100644 --- a/smash/web/tests/api_views/test_daily_planning.py +++ b/smash/web/tests/api_views/test_daily_planning.py @@ -7,7 +7,7 @@ from django.test import Client, RequestFactory from django.test import TestCase from django.urls import reverse -from web.api_views.daily_planning import get_workers_for_daily_planning +from web.api_views.daily_planning import get_workers_for_daily_planning, get_generic_appointment_events from web.models import Worker, Availability, Holiday, AppointmentTypeLink from web.models.constants import TUESDAY_AS_DAY_OF_WEEK from web.tests.functions import create_worker, create_subject, create_appointment, create_flying_team, create_visit, \ @@ -195,3 +195,19 @@ class TestApi(TestCase): workers = get_workers_for_daily_planning(request) self.assertFalse(invalid_worker in workers) + + def test_get_generic_appointment_events(self): + factory = RequestFactory() + request = factory.get('/api/workers') + request.user = self.user + + appointment = create_appointment() + appointment.datetime_when = datetime.datetime.now().replace(year=2017, month=9, day=5, + hour=12) + appointment.length = 30 + appointment.location = self.worker.locations.all()[0] + appointment.visit = None + appointment.save() + + events = get_generic_appointment_events(request, "2017-09-05") + self.assertEqual(1, len(events))