From 4582cd2b9d199b0d7ea6c8b2cad11207f7fca574 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Wed, 6 Sep 2017 10:49:06 +0200
Subject: [PATCH] info about availabilities and holidays for assigning workers

---
 smash/web/api_urls.py                      |  1 +
 smash/web/api_views/daily_planning.py      | 62 ++++++++++++++++
 smash/web/static/css/appointment.css       | 19 +++++
 smash/web/static/js/appointment.js         | 85 ++++++++++++++++++++++
 smash/web/static/js/daily_planning.js      | 18 +++--
 smash/web/templates/appointments/add.html  |  4 -
 smash/web/templates/appointments/edit.html |  9 +--
 7 files changed, 181 insertions(+), 17 deletions(-)
 create mode 100644 smash/web/static/css/appointment.css

diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py
index a53d92e8..aafb4491 100644
--- a/smash/web/api_urls.py
+++ b/smash/web/api_urls.py
@@ -47,6 +47,7 @@ urlpatterns = [
 
     # daily planning events
     url(r'^events/(?P<date>\d{4}-\d{2}-\d{2})/$', daily_planning.events, name='web.api.events'),
+    url(r'^availabilities/(?P<date>\d{4}-\d{2}-\d{2})/$', daily_planning.availabilities, name='web.api.availabilities'),
     url(r'^events_persist$', daily_planning.events_persist, name='web.api.events_persist'),
 
 ]
diff --git a/smash/web/api_views/daily_planning.py b/smash/web/api_views/daily_planning.py
index e6417df2..d1fbfbc0 100644
--- a/smash/web/api_views/daily_planning.py
+++ b/smash/web/api_views/daily_planning.py
@@ -132,6 +132,49 @@ def get_availabilities(worker, date):
     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
+
+
 @login_required
 def events(request, date):
     appointments = Appointment.objects.filter(
@@ -192,6 +235,25 @@ def events(request, date):
     })
 
 
+def availabilities(request, date):
+    availabilities = []
+    holidays = []
+    conflicts = []
+    workers = Worker.objects.filter(locations__in=get_filter_locations(request.user)).exclude(
+        role=Worker.ROLE_CHOICES_SECRETARY).distinct()
+
+    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):
     event_links = json.loads(request.POST.get('events_to_persist'))
diff --git a/smash/web/static/css/appointment.css b/smash/web/static/css/appointment.css
new file mode 100644
index 00000000..3fb8456b
--- /dev/null
+++ b/smash/web/static/css/appointment.css
@@ -0,0 +1,19 @@
+.worker-option-holiday {
+    background-color: #FF0000;
+}
+
+.worker-option-conflict {
+    background-color: #ff851b;
+}
+
+.worker-option-match {
+    background-color: #00FF00;
+}
+
+.worker-option-partial-match {
+    background-color: #FFFF00;
+}
+
+.worker-option-no-match {
+    background-color: #FFFFFF;
+}
diff --git a/smash/web/static/js/appointment.js b/smash/web/static/js/appointment.js
index a9799661..8d9b5e83 100644
--- a/smash/web/static/js/appointment.js
+++ b/smash/web/static/js/appointment.js
@@ -108,6 +108,91 @@ function appointment_flying_team_place_behaviour(flying_team_select, location_se
     });
 }
 
+
+function appointment_date_change_behaviour(datetime_picker, worker_select, minutes_input, appointment_id) {
+    function match_availability(id, availabilities, partialMatch, date_start, date_end) {
+        if (id === undefined) {
+            return false;
+        }
+        id = parseInt(id);
+        for (var i = 0; i < availabilities.length; i++) {
+            var availability = availabilities[i];
+            if (availability.link_who === id && appointment_id !== availability.appointment_id) {
+                var event_start = Date.parse(availability.link_when);
+                var event_end = Date.parse(availability.link_end);
+                if (partialMatch && event_start >= date_start && event_start <= date_end) {
+                    return true;
+                }
+                if (partialMatch && event_end >= date_start && event_end <= date_end) {
+                    return true;
+                }
+                if (partialMatch && date_start >= event_start && date_start <= event_end) {
+                    return true;
+                }
+                if (event_start <= date_start && date_end <= event_end) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    var getSelectedClass = function () {
+        return $(worker_select).find(":selected")[0].className;
+    };
+
+    var previousClass = getSelectedClass();
+
+    $(worker_select).addClass(previousClass);
+
+    $(worker_select).on('focus', function () {
+        // Store the current value on focus and on change
+        previousClass = getSelectedClass();
+    }).change(function () {
+        // Do something with the previous value after the change
+
+        $(worker_select).removeClass(previousClass);
+        // Make sure the previous value is updated
+        previousClass = getSelectedClass();
+        $(worker_select).addClass(previousClass);
+    });
+
+    $(datetime_picker).on("dp.change", function () {
+        if ($(this).val() != "") {
+            var datetime_start = Date.parse($(this).val());
+            var datetime_end = datetime_start + 60 * 1000 * parseInt(minutes_input.val());
+            var date = $(this).val().substr(0, 10);
+            $.get('/api/availabilities/' + date, function (data) {
+                var options = $("option", worker_select);
+                for (var i = 0; i < options.length; i++) {
+                    var option = options[i];
+                    if (match_availability(option.value, data.holidays, true, datetime_start, datetime_end)) {
+                        console.log(data.holidays);
+                        console.log("HOLIDAY");
+                        option.className = "worker-option-holiday";
+                    } else if (match_availability(option.value, data.conflicts, true, datetime_start, datetime_end)) {
+                        console.log(data.conflicts);
+                        console.log("CONFLICT");
+                        option.className = "worker-option-conflict";
+                    } else if (match_availability(option.value, data.availabilities, false, datetime_start, datetime_end)) {
+                        console.log(data.availabilities);
+                        console.log("MATCH");
+                        option.className = "worker-option-match";
+                    } else if (match_availability(option.value, data.availabilities, true, datetime_start, datetime_end)) {
+                        console.log(data.availabilities);
+                        console.log("PARTIAL_MATCH");
+                        option.className = "worker-option-partial-match";
+                    } else {
+                        option.className = "worker-option-no-match";
+                    }
+                }
+                $(worker_select).trigger("change");
+            });
+        }
+    });
+
+}
+
 function get_calendar_events_function(source, allow_url_redirection) {
     if (allow_url_redirection === undefined) {
         allow_url_redirection = false;
diff --git a/smash/web/static/js/daily_planning.js b/smash/web/static/js/daily_planning.js
index 68f6b33b..4b608d6f 100644
--- a/smash/web/static/js/daily_planning.js
+++ b/smash/web/static/js/daily_planning.js
@@ -188,14 +188,16 @@ $(document).ready(function () {
                     var saveButton = $(".fc-save-button");
                     var currentBorder = saveButton.css('border-color');
                     $.each(calendarEvents, function (i, calendar_event) {
-                        eventsToPersist.push({
-                            'link_id': calendar_event.link_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.rendering!=="background") {
+                            eventsToPersist.push({
+                                'link_id': calendar_event.link_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);
+                            }
                         }
                     });
                     $.post({
diff --git a/smash/web/templates/appointments/add.html b/smash/web/templates/appointments/add.html
index 707facd6..e54ad96b 100644
--- a/smash/web/templates/appointments/add.html
+++ b/smash/web/templates/appointments/add.html
@@ -32,10 +32,6 @@
                 <a href="{% url 'web.views.appointments' %}" class="btn btn-block btn-default">Cancel</a>
             </div>
 
-            {% comment %} <div class="box-header with-border">
-		<h3 class="box-title">Adding an appointment</h3>
-	</div>{% endcomment %}
-
             <form method="post" action="" class="form-horizontal">
                 {% csrf_token %}
                 <div class="box-body">
diff --git a/smash/web/templates/appointments/edit.html b/smash/web/templates/appointments/edit.html
index 2c1c323f..54a95306 100644
--- a/smash/web/templates/appointments/edit.html
+++ b/smash/web/templates/appointments/edit.html
@@ -8,6 +8,7 @@
     <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
 
     {% include "includes/datetimepicker.css.html" %}
+    <link rel="stylesheet" href="{% static 'css/appointment.css' %}">
 {% endblock styles %}
 
 {% block ui_active_tab %}'appointments'{% endblock ui_active_tab %}
@@ -24,7 +25,7 @@
 
     {% block content %}
         <div class="row">
-           
+
             {% if appointment.visit %}
                 <p class="col-md-2 pull-right">
                     <a href="{% url 'web.views.visit_details' appointment.visit.id %}" type="button"
@@ -34,10 +35,6 @@
         </div>
         <div class="box box-info">
 
-            {% comment %} <div class="box-header with-border">
-		<h3 class="box-title">Details of appointment</h3>
-	</div>{% endcomment %}
-
             <form method="post" action="" class="form-horizontal">
                 {% csrf_token %}
                 <fieldset>
@@ -134,6 +131,8 @@
         });
         appointment_type_behaviour($("input[name='appointment-appointment_types']"), $("input[name='appointment-length']"), "{% url 'web.api.appointment_types' %}");
         appointment_flying_team_place_behaviour($("select[name='appointment-flying_team']"), $("select[name='appointment-location']"));
+        appointment_date_change_behaviour($("input[name='appointment-datetime_when']"), $("select[name='appointment-worker_assigned']"), $("input[name='appointment-length']"), {{ appointment.id }});
+
     </script>
 
     {% include "includes/datetimepicker.js.html" %}
-- 
GitLab