diff --git a/smash/web/models.py b/smash/web/models.py index df643b52c319b0776f61d4d975ba0baf286089b4..87bf48a9dc62c4c1dcf4953c69442650ef2f6d0b 100644 --- a/smash/web/models.py +++ b/smash/web/models.py @@ -353,12 +353,13 @@ class Worker (models.Model): email = models.EmailField( verbose_name='E-mail' ) + ROLE_CHOICES_SECRETARY = "SECRETARY" ROLE_CHOICES = ( ('DOCTOR', 'Doctor'), ('NURSE', 'Nurse'), ('PSYCHOLOGIST', 'Psychologist'), ('TECHNICIAN', 'Technician'), - ('SECRETARY', 'Secretary') + (ROLE_CHOICES_SECRETARY, 'Secretary') ) role = models.CharField(max_length=20, choices=ROLE_CHOICES, verbose_name='Role' diff --git a/smash/web/templates/_base.html b/smash/web/templates/_base.html index 3291f0ce7a57198e9098bf34a517d3250c971b01..1fedeafff2761c0411d1481976a380896033e59f 100644 --- a/smash/web/templates/_base.html +++ b/smash/web/templates/_base.html @@ -123,33 +123,27 @@ desired effect <!-- /.messages-menu --> <!-- Notifications Menu --> - <li class="dropdown notifications-menu" style="display: none"> + <li class="dropdown notifications-menu" > <!-- Menu toggle button --> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <i class="fa fa-bell-o"></i> - <span class="label label-warning"> - 0 - </span> + <span class="label label-warning"> {{notifications.0}}</span> </a> <ul class="dropdown-menu"> - <li class="header"> - You have 0 notifications - <hr /> - (Not implemented yet) - </li> - {% comment "TODO: Implement notifications" %} + <li class="header"> You have {{notification.0}} notifications </li> <li> <!-- Inner Menu: contains the notifications --> <ul class="menu"> - <li><!-- start notification --> - <a href="#"> - <i class="fa fa-users text-aqua"></i> 5 new members joined today - </a> - </li> + {% for notification in notifications.1 %} + <li><!-- start notification --> + <a href="{% url notification.type %}"> + <i class="fa {{notification.style}}"></i> {{ notification.title }}: {{ notification.count }} + </a> + </li> + {% endfor %} <!-- end notification --> </ul> </li> - {% endcomment %} <li class="footer"><a href="#">View all</a></li> </ul> </li> diff --git a/smash/web/templates/appointments/list.html b/smash/web/templates/appointments/list.html new file mode 100644 index 0000000000000000000000000000000000000000..1c7393d3a5c5307af237f5caf01b8c0e909aea65 --- /dev/null +++ b/smash/web/templates/appointments/list.html @@ -0,0 +1,70 @@ +{% extends "_base.html" %} +{% load static %} + +{% block styles %} +{{ block.super }} + <!-- DataTables --> + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> + + <!-- fullCalendar 2.2.5--> + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.css' %}"> + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.print.css' %}" media="print"> +{% endblock styles %} + +{% block ui_active_tab %}'appointments'{% endblock ui_active_tab %} +{% block page_header %}Appointments{% endblock page_header %} +{% block page_description %}{% endblock page_description %} + +{% block title %}{{ block.super }} - Appointments{% endblock %} + +{% block breadcrumb %} +{% include "appointments/breadcrumb.html" %} +{% endblock breadcrumb %} + +{% block maincontent %} +<div class="row"> + <div class="col-md-16"> + <table id="approaching_table" class="table table-bordered table-striped"> + <thead> + <tr> + <th>Subject</th> + <th>Visit</th> + <th>Type</th> + <th>Date</th> + <th>Details</th> + </tr> + </thead> + <tbody> + + {% for approach in appointment_list %} + <tr> + {% if approach.visit.subject.screening_number == "---" %} + <td> + </td> + <td> + </td> + {% else %} + <td> + {{ approach.visit.subject.first_name }} {{ approach.visit.subject.last_name }} ({{ approach.visit.subject.nd_number }}) + </td> + <td> + {{ approach.visit.follow_up_title }} + </td> + {% endif %} + <td> + {% for type in approach.appointment_types.all %} + {{ type.code }}, + {% endfor %} + </td> + <td>{{ approach.datetime_when | date:"Y-m-d H:i" }}</td> + <td> + <a href="{% url 'web.views.appointment_edit' approach.id %}" type="button" class="btn btn-block btn-default">Details</a> + </td> + </tr> + {% endfor %} + </tbody> + </table> + + </div> +</div> +{% endblock maincontent %} diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index 96a385732d537dd0b73d5bd15832b5ca3cdf03b7..d98dd27b06fa673d270e13084cd9e718133de193 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -1,12 +1,16 @@ from datetime import timedelta from web.models import * +from web.views import * def create_subject(): return Subject.objects.create(first_name="Piotr", last_name="Gawron", sex= Subject.SEX_CHOICES_MALE) def create_visit(subject): - return Visit.objects.create(datetime_begin="2017-01-01",datetime_end="2017-04-01", subject =subject) + return Visit.objects.create(datetime_begin=get_today_midnight_date()+datetime.timedelta(days=-31), + datetime_end=get_today_midnight_date()+datetime.timedelta(days=31), + subject =subject, + is_finished = False) def create_appointment(visit): return Appointment.objects.create(visit = visit, length = 30) diff --git a/smash/web/tests/test_view_notifications.py b/smash/web/tests/test_view_notifications.py new file mode 100644 index 0000000000000000000000000000000000000000..2c9113fbafd1fbeb47656f7d4a9bc449defa07a1 --- /dev/null +++ b/smash/web/tests/test_view_notifications.py @@ -0,0 +1,108 @@ +from django.contrib.auth.models import User +from django.test import TestCase, RequestFactory +from django.urls import reverse + +from web.views import * + +from web.models import * + +from web.tests.functions import * + +import datetime + + +class NotificationViewTests(TestCase): + def setUp(self): + # Every test needs access to the request factory. + self.factory = RequestFactory() + self.user = User.objects.create_user( + username='piotr', email='jacob@bla', password='top_secret') + + def test_get_exceeded_visit_notifications_count(self): + original_notification = get_visits_without_appointments_count() + + subject = create_subject() + visit = create_visit(subject) + visit.datetime_end = "2011-01-01" + visit.save() + appointment = create_appointment(visit) + + notification = get_exceeded_visit_notifications_count() + self.assertEquals(original_notification.count + 1, notification.count) + + def test_get_exceeded_visit_notifications_count_2(self): + original_notification = get_visits_without_appointments_count() + + subject = create_subject() + visit = create_visit(subject) + visit.datetime_end = "2011-01-01" + visit.is_finished = True + visit.save() + appointment = create_appointment(visit) + + notification = get_exceeded_visit_notifications_count() + self.assertEquals(original_notification.count, notification.count) + + def test_get_visits_without_appointments_count(self): + original_notification = get_visits_without_appointments_count() + subject = create_subject() + visit = create_visit(subject) + + notification = get_visits_without_appointments_count() + self.assertEquals(original_notification.count + 1, notification.count) + + def test_get_visits_without_appointments_count_2(self): + original_notification = get_visits_without_appointments_count() + subject = create_subject() + visit = create_visit(subject) + appointment = create_appointment(visit) + + notification = get_visits_without_appointments_count() + self.assertEquals(original_notification.count, notification.count) + + def test_get_visits_without_appointments_count_3(self): + original_notification = get_visits_without_appointments_count() + subject = create_subject() + visit = create_visit(subject) + appointment = create_appointment(visit) + + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + + notification = get_visits_without_appointments_count() + self.assertEquals(original_notification.count + 1, notification.count) + + def test_get_approaching_visits_without_appointments_count(self): + original_notification = get_approaching_visits_without_appointments_count() + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=2) + visit.save() + + notification = get_approaching_visits_without_appointments_count() + self.assertEquals(original_notification.count + 1, notification.count) + + def test_get_approaching_visits_without_appointments_count_2(self): + original_notification = get_approaching_visits_without_appointments_count() + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=2) + visit.save() + appointment = create_appointment(visit) + + notification = get_approaching_visits_without_appointments_count() + self.assertEquals(original_notification.count, notification.count) + + def test_get_approaching_visits_without_appointments_count_3(self): + original_notification = get_approaching_visits_without_appointments_count() + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=2) + visit.save() + appointment = create_appointment(visit) + + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + + notification = get_approaching_visits_without_appointments_count() + self.assertEquals(original_notification.count + 1, notification.count) diff --git a/smash/web/urls.py b/smash/web/urls.py index bf78ef96158d66d42d82d4f585b9269f60fad24f..1f5142ff85958ff21c9829df0732fbe47c4ac77b 100644 --- a/smash/web/urls.py +++ b/smash/web/urls.py @@ -18,6 +18,7 @@ from web import views urlpatterns = [ url(r'appointments$', views.appointments, name='web.views.appointments'), + url(r'appointments/unfinished$', views.unfinished_appointments, name='web.views.unfinished_appointments'), url(r'appointments/details/(?P<id>\d+)$', views.appointment_details, name='web.views.appointment_details'), url(r'appointments/add/(?P<id>\d+)$', views.appointment_add, name='web.views.appointment_add'), url(r'appointments/edit/(?P<id>\d+)$', views.appointment_edit, name='web.views.appointment_edit'), @@ -25,6 +26,9 @@ urlpatterns = [ url(r'appointments/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.appointment_mark, name='web.views.appointment_mark'), url(r'visits$', views.visits, name='web.views.visits'), + url(r'visits/exceeded$', views.exceeded_visits, name='web.views.exceeded_visits'), + url(r'visits/unfinished$', views.unfinished_visits, name='web.views.unfinished_visits'), + url(r'visits/approaching$', views.approaching_visits_without_appointments, name='web.views.approaching_visits_without_appointments'), url(r'visits/details/(?P<id>\d+)$', views.visit_details, name='web.views.visit_details'), url(r'visits/add$', views.visit_add, name='web.views.visit_add'), url(r'visit/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.visit_mark, name='web.views.visit_mark'), diff --git a/smash/web/views.py b/smash/web/views.py index ee8b80601052e98f272ae88d3fa3b33a88eeaad9..a605aef00da631c89a4d94371b11ccdcaa644622 100644 --- a/smash/web/views.py +++ b/smash/web/views.py @@ -56,6 +56,107 @@ def login(request): return render(request, "login.html", context) +class NotificationCount(object): + title = "" + count = 0 + style = "" + type = '' + + def __init__(self, title ="Unknown", count = 0, style = "fa fa-users text-aqua", type = 'web.views.appointments'): + self.title = title + self.count = count + self.style = style + self.type = type + +def get_exceeded_visit_notifications_count(): + notification = NotificationCount("exceeded visit time",0 , "fa fa-thermometer-4 text-red", 'web.views.exceeded_visits') + today = get_today_midnight_date() + count = Visit.objects.filter(datetime_end__lt = today, is_finished = False).count() + notification.count = count + return notification + +def get_exceeded_visits(): + return Visit.objects.filter(datetime_end__lt = get_today_midnight_date(), is_finished = False).order_by('datetime_begin') + +def get_visits_without_appointments_count(): + notification = NotificationCount("unfinished visits ",0 , "fa fa-user-times text-yellow", 'web.views.unfinished_visits') + today = get_today_midnight_date() + count = 0 + #visits in progress + visits = Visit.objects.filter(datetime_begin__lt = today, datetime_end__gt = today, is_finished = False) + for visit in visits: + app_count = Appointment.objects.filter(visit = visit, status = Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + if app_count==0: + count+=1 + + notification.count = count + return notification + +def get_unfinished_visits(): + today = get_today_midnight_date() + result = []; + #visits in progress + visits = Visit.objects.filter(datetime_begin__lt = today, datetime_end__gt = today, is_finished = False) + for visit in visits: + app_count = Appointment.objects.filter(visit = visit, status = Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + if app_count==0: + result.append(visit) + return result + +def get_approaching_visits_without_appointments_count(): + notification = NotificationCount("approaching visits ",0 , "fa fa-users text-aqua",'web.views.approaching_visits_without_appointments') + today = get_today_midnight_date() + today_plus_two_months =today+datetime.timedelta(days=62) + count = 0 + #visits in near future (2 months) + visits = Visit.objects.filter(datetime_begin__gt = today, datetime_begin__lt = today_plus_two_months, is_finished = False) + for visit in visits: + app_count = Appointment.objects.filter(visit = visit, status = Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + if app_count==0: + count+=1 + + notification.count = count + return notification + +def get_approaching_visits_without_appointments(): + today = get_today_midnight_date() + today_plus_two_months =today+datetime.timedelta(days=62) + result = []; + #visits in progress + visits = Visit.objects.filter(datetime_begin__gt = today, datetime_begin__lt = today_plus_two_months, is_finished = False) + for visit in visits: + app_count = Appointment.objects.filter(visit = visit, status = Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + if app_count==0: + result.append(visit) + return result + + +def get_unfinished_appointments_count(): + notification = NotificationCount("unfinished appointments ",0 , "fa fa-history text-yellow",'web.views.unfinished_appointments') + today = get_today_midnight_date() + + count = Appointment.objects.filter(datetime_when__lt = today, status = Appointment.APPOINTMENT_STATUS_SCHEDULED).count() + + notification.count = count + return notification + +def get_unfinished_appointments(): + return Appointment.objects.filter(datetime_when__lt = get_today_midnight_date(), status = Appointment.APPOINTMENT_STATUS_SCHEDULED) + +def get_notifications(the_user): + workers = Worker.objects.filter(user=the_user) + notifications = [] + count = 0; + if len(workers)>0: + person = workers[0] + if person.role == Worker.ROLE_CHOICES_SECRETARY: + notifications.append(get_exceeded_visit_notifications_count()) + notifications.append(get_visits_without_appointments_count()) + notifications.append(get_approaching_visits_without_appointments_count()) + notifications.append(get_unfinished_appointments_count()) + for notification in notifications: + count += notification.count + return (count, notifications) """ Saturates response with information about logged user @@ -64,10 +165,13 @@ Saturates response with information about logged user def wrap_response(request, template, params): person, role = Worker.get_details(request.user) + notifications = get_notifications(request.user) + final_params = params.copy() final_params.update({ 'person' : person, - 'role': role + 'role': role, + 'notifications': notifications }) return render(request, template, final_params) @@ -87,6 +191,25 @@ def visits(request): return wrap_response(request, 'visits/index.html', context) +def exceeded_visits(request): + context = { + 'visit_list': get_exceeded_visits() + } + return wrap_response(request, 'visits/index.html', context) + +def unfinished_visits(request): + context = { + 'visit_list': get_unfinished_visits() + } + + return wrap_response(request, 'visits/index.html', context) + +def approaching_visits_without_appointments(request): + context = { + 'visit_list': get_approaching_visits_without_appointments() + } + + return wrap_response(request, 'visits/index.html', context) def visit_details(request, id): displayedVisit = get_object_or_404(Visit, id=id) @@ -322,14 +445,17 @@ def suggest_details(Appointment appoint): """ +def get_today_midnight_date(): + today = datetime.datetime.now() + today_midnight = datetime.datetime(today.year,today.month,today.day) + return today_midnight def appointments(request): futureDate = datetime.datetime.now() + datetime.timedelta(days=93) planning_list = Appointment.objects.filter(datetime_when__isnull=True, visit__datetime_begin__lt = futureDate) - today = datetime.datetime.now() - today_midnight = datetime.datetime(today.year,today.month,today.day) - month_ago = today + datetime.timedelta(days=-31) + today_midnight = get_today_midnight_date() + month_ago = today_midnight + datetime.timedelta(days=-31) approaching_list = Appointment.objects.filter(datetime_when__gt = today_midnight, status = Appointment.APPOINTMENT_STATUS_SCHEDULED).order_by('datetime_when') full_list = Appointment.objects.filter(datetime_when__gt = month_ago).order_by('datetime_when') @@ -345,6 +471,14 @@ def appointments(request): return wrap_response(request, "appointments/index.html",context) +def unfinished_appointments(request): + appointments = get_unfinished_appointments() + context = { + 'appointment_list': appointments, + } + + return wrap_response(request, "appointments/list.html",context) + def appointment_details(request, id): the_appointment = get_object_or_404(Appointment, id=id) form = AppointmentDetailForm(instance=the_appointment)