diff --git a/.gitignore b/.gitignore index e882e145fcec335b65eaeade223514c9ce1824a0..7cd94db8e4e3b989be1fb2f7f32a11f949533709 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ local_settings.py #tmp files appointment-import/testFiles/~* appointment-import/tmp.sql +.idea +*.iml diff --git a/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java b/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java index 3163eed9966d052bea816326d4ae3632ff2406bc..f0dc3de54d5f514defb6c882e3a20abb4d7dbd47 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java +++ b/appointment-import/src/main/java/smash/appointment/parse/LihControlMappingParser.java @@ -154,4 +154,9 @@ public class LihControlMappingParser extends SubjectParser { return false; } + @Override + protected boolean parsePostponed(Row row) { + return false; + } + } diff --git a/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java b/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java index c5b587f466ee3e91a4eceabf1236072d493512bf..a99ba461e07858392db5d4947a68e25a36aa8a1a 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java +++ b/appointment-import/src/main/java/smash/appointment/parse/LihControlParser.java @@ -5,7 +5,9 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.List; +import org.apache.poi.ss.usermodel.Color; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.xssf.usermodel.XSSFColor; public class LihControlParser extends SubjectParser { @@ -54,7 +56,7 @@ public class LihControlParser extends SubjectParser { return ""; } - private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd"); + private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd"); @Override protected String parseAddDate(Row row) { @@ -123,6 +125,7 @@ public class LihControlParser extends SubjectParser { remarks.add(getComments(row.getCell(14))); remarks.add(getComments(row.getCell(15))); remarks.add(getComments(row.getCell(16))); + remarks.add("Inclusion="+getString(row.getCell(17))); remarks.add(getString(row.getCell(18))); remarks.add(getString(row.getCell(19))); String result = ""; @@ -208,8 +211,51 @@ public class LihControlParser extends SubjectParser { return false; } + @Override + protected boolean parsePostponed(Row row) { + Color color = row.getCell(0).getCellStyle().getFillForegroundColorColor(); + if (color == null) { + return false; + } + + XSSFColor c = (XSSFColor) color; + String colorString = c.getARGBHex().substring(2); + switch (colorString) { + case ("FFC000"):// orange + return false; + case ("FFFF00"):// yellow + return false; + case ("FF0000"):// red + return false; + case ("FF3399"):// pink + return true; + case ("00B050"):// green + return false; + } + throw new RuntimeException(parseName(row) + " " + parseSurname(row) + ": Unknown color: " + colorString); + } + @Override protected boolean parseResigned(Row row) { - return false; + Color color = row.getCell(0).getCellStyle().getFillForegroundColorColor(); + if (color == null) { + return false; + } + + XSSFColor c = (XSSFColor) color; + String colorString = c.getARGBHex().substring(2); + switch (colorString) { + case ("FFC000"):// orange + return false; + case ("FFFF00"):// yellow + return false; + case ("FF0000"):// red + return true; + case ("FF3399"):// pink + return false; + case ("00B050"):// green + return false; + } + throw new RuntimeException(parseName(row) + " " + parseSurname(row) + ": Unknown color: " + colorString); } } diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java index e5802c237d7caf2c35655f27037bcafdbd151e87..7a7ec376543578b77ca3db1e2fb2206e79479f89 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java +++ b/appointment-import/src/main/java/smash/appointment/parse/PrcControlParser.java @@ -157,4 +157,9 @@ public class PrcControlParser extends SubjectParser { protected boolean parseResigned(Row row) { return false; } + + @Override + protected boolean parsePostponed(Row row) { + return false; + } } diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java index 168a3cfdd2d3443641e12d449d819fb6bcd439be..1ee25fa2de02e448063e86233b9bf137288c0a65 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java +++ b/appointment-import/src/main/java/smash/appointment/parse/PrcFlyingParser.java @@ -163,5 +163,9 @@ public class PrcFlyingParser extends SubjectParser { protected boolean parseResigned(Row row) { return false; } + @Override + protected boolean parsePostponed(Row row) { + return false; + } } diff --git a/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java b/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java index a06e088395e0e174ea3625507faf7affb26e1b3a..5badd40394a25d065d17796f3547427dc34c6d42 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java +++ b/appointment-import/src/main/java/smash/appointment/parse/PrcSubjectsParser.java @@ -205,4 +205,9 @@ public class PrcSubjectsParser extends SubjectParser { return false; } } + + @Override + protected boolean parsePostponed(Row row) { + return false; + } } diff --git a/appointment-import/src/main/java/smash/appointment/parse/Subject.java b/appointment-import/src/main/java/smash/appointment/parse/Subject.java index 893fbfd450461c84d13f68ebd85ccb4c3162e942..3921389cef0d569764e03ca6d4df4f0ec86cd4f7 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/Subject.java +++ b/appointment-import/src/main/java/smash/appointment/parse/Subject.java @@ -32,6 +32,7 @@ public class Subject { private String toBeSeenAt; private boolean dead = false; private boolean resigned = false; + private boolean postponed = false; private List<String> languages = new ArrayList<>(); @@ -476,6 +477,9 @@ public class Subject { setAddDate(getMergedValue("addDate", this.getAddDate(), subject.getAddDate(), errorPrefix)); setmPowerId(getMergedValue("mPowerId", this.getmPowerId(), subject.getmPowerId(), errorPrefix)); setType(getMergedValue("type", this.getType(), subject.getType(), errorPrefix)); + setResigned(this.isResigned()|| subject.isResigned()); + setDead(this.isDead()|| subject.isDead()); + setPostponed(this.isPostponed()|| subject.isPostponed()); // override only when to be seen by flying team if (subject.getToBeSeenAt().equals("F")) { setToBeSeenAt(subject.getToBeSeenAt()); @@ -566,4 +570,20 @@ public class Subject { } } + /** + * @return the postponed + * @see #postponed + */ + public boolean isPostponed() { + return postponed; + } + + /** + * @param postponed the postponed to set + * @see #postponed + */ + public void setPostponed(boolean postponed) { + this.postponed = postponed; + } + } diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java index 6292d3cb961547a986c42b59c3b0d665d2225bd9..f979012b76b8562a4757c6fa63ba1e6e3a408bbe 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java +++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectParser.java @@ -91,11 +91,13 @@ public abstract class SubjectParser { result.setToBeSeenAt(parseToBeSeenAt(row)); result.setDead(parseDead(row)); result.setResigned(parseResigned(row)); + result.setPostponed(parsePostponed(row)); return result; } protected abstract boolean parseDead(Row row); + protected abstract boolean parsePostponed(Row row); protected abstract boolean parseResigned(Row row); diff --git a/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java b/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java index ebbd5866b0fa122704b3a0ef8a160a234372147b..5c59412dfbd387afab381b8e4d824dc47a7baa4d 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java +++ b/appointment-import/src/main/java/smash/appointment/parse/SubjectSqlExporter.java @@ -30,6 +30,7 @@ public class SubjectSqlExporter extends SqlExporter { result.append("dead,"); result.append("default_written_communication_language_id,"); result.append("resigned,"); + result.append("postponed,"); result.append("date_born) "); result.append("values ("); @@ -74,6 +75,7 @@ public class SubjectSqlExporter extends SqlExporter { result.append("null,"); } result.append(subject.isResigned() + ","); + result.append(subject.isPostponed() + ","); result.append(getDateVal(subject.getBirthDate())); result.append(");\n"); diff --git a/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java b/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java index 6ffef703b3104abf444a31be9942ea409f59fa9b..7dbc577be9f30d938861ad6e5f4ff03d9e6e57ea 100644 --- a/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java +++ b/appointment-import/src/main/java/smash/appointment/parse/VisitSqlExporter.java @@ -12,12 +12,14 @@ public class VisitSqlExporter extends SqlExporter { result.append("subject_id, "); result.append("datetime_begin, "); result.append("datetime_end, "); + result.append("post_mail_sent, "); result.append("is_finished)"); result.append("values ("); result.append("(SELECT id from web_subject where screening_number = "+getStringVal(visit.getSubject().getScreeningNumber()) + "),"); result.append(getStringVal(visit.getStartDate()) + ","); result.append(getStringVal(visit.getEndDate()) + ","); + result.append("false,"); result.append(visit.isFinished()); result.append(");\n"); for (AppointmentEntry entry: visit.getAppointments()) { diff --git a/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java b/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java index bce5ee61b716e85c661cbae9db0ff0c792362469..05845e0558f5930907eaf9443d413a37d35762d8 100644 --- a/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java +++ b/appointment-import/src/test/java/smash/appointment/parse/LihControlParserTest.java @@ -1,6 +1,8 @@ package smash.appointment.parse; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.List; @@ -11,17 +13,16 @@ import org.junit.Before; import org.junit.Test; public class LihControlParserTest extends TestBase { - Logger logger = Logger.getLogger(LihControlParserTest.class); - - LihControlParser processor = new LihControlParser(); + Logger logger = Logger.getLogger(LihControlParserTest.class); + LihControlParser processor = new LihControlParser(); @AfterClass public static void tearDownAfterClass() throws Exception { } @Before - public void setUp() { + public void setUp() { super.setUp(); } @@ -31,8 +32,9 @@ public class LihControlParserTest extends TestBase { @Test public void testParseLang() throws Exception { - assertEquals("English",processor.getMappedLanguage("EN")); + assertEquals("English", processor.getMappedLanguage("EN")); } + @Test public void test() throws Exception { List<Subject> entries = processor.processExcel("testFiles/lihControlExample.xlsx"); @@ -43,7 +45,7 @@ public class LihControlParserTest extends TestBase { assertEquals("Name", subject.getName()); assertEquals("Surname", subject.getSurname()); assertTrue(subject.getRemarks().contains("001 rdv 01/09/2015 9h jyf")); - assertTrue(subject.getRemarks().contains("PD family relation=pd info")); + assertTrue(subject.getRemarks().contains("PD family relation=pd info")); assertEquals("11, Rue blabla", subject.getAddress()); assertEquals("L-3322", subject.getZipCode()); assertEquals("Luxembourg", subject.getCity()); @@ -58,11 +60,9 @@ public class LihControlParserTest extends TestBase { assertNotNull(subject.getAddDate()); assertEquals("", subject.getNdNumber()); assertEquals("1937-01-03", subject.getBirthDate()); - assertTrue(subject.getRemarks().contains("some other remark")); - assertTrue(subject.getRemarks().contains("at home: NMS + RFQ 1 + RFQ 2 + REM + PDSS: manque une page ds RFQ => Linda pr level b 09/09/15")); - assertTrue(subject.getLanguages().contains("French")); - assertTrue(subject.getLanguages().contains("German")); + assertTrue(subject.getRemarks().contains("some other remark")); + assertTrue(subject.getRemarks().contains("at home: NMS + RFQ 1 + RFQ 2 + REM + PDSS: manque une page ds RFQ => Linda pr level b 09/09/15")); + assertTrue(subject.getLanguages().contains("French")); + assertTrue(subject.getLanguages().contains("German")); } - - } diff --git a/readme.md b/readme.md index ebabb3fd11f567f25326f7af08f3fb79a0a438a3..3214f721cf79429cc6476a7ac6604496677cc83d 100644 --- a/readme.md +++ b/readme.md @@ -14,39 +14,8 @@ - `virtualenv env` to create new virtualenv (contains clean python working environment) - `. env/bin/activate` (to start using virtualenv) - `pip install -r requirements.txt` to install project's dependencies - - Create `local_settings.py` file in `(./scheduling-system)/smash/smash` directory (see template below), and change your database connection data: + - Create `local_settings.py` file in `(./scheduling-system)/smash/smash` directory by copying the template in `(./scheduling-system)/smash/smash/local_settings.template` and edit your local_setttings.py file to change your database connection data. -``` -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'Paste long random string here' # Insert long random string - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -WSGI_APPLICATION = 'smash.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'smashdb', # Insert your database's name - 'USER': 'postgresmashuser', # Insert your database's user - 'PASSWORD': 'thePOSTGRESpassword', # Insert your user's password - 'HOST': 'localhost', - 'PORT': '' # '' === default one # Empty string is OK - - # If to use sqlite - # 'ENGINE': 'django.db.backends.sqlite3', - # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - -STATIC_ROOT = '/tmp/static' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static -MEDIA_ROOT = '/tmp/media' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media -``` - Update migration db scrpit from file structure (`./scheduling-system/smash/manage.py makemigrations`) - Create the database by applying migrations (`./scheduling-system/smash/manage.py migrate`) - Create the first, administrative, user- (`./scheduling-system/smash/manage.py createsuperuser`) diff --git a/requirements.txt b/requirements.txt index 8ddce39523486fbd85b734c6cc1a11d671a2be22..57e916dd630ac9a2b6f598607e37f97050cce20b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ Django==1.10.3 gunicorn==19.6.0 Pillow==3.4.2 -pkg-resources==0.0.0 psycopg2==2.6.2 diff --git a/smash/smash/local_settings.py.template b/smash/smash/local_settings.py.template new file mode 100644 index 0000000000000000000000000000000000000000..b5c398c064b0b58992f6adf380bdb94b6ebc15a9 --- /dev/null +++ b/smash/smash/local_settings.py.template @@ -0,0 +1,29 @@ +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'Paste long random string here' # Insert long random string + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +WSGI_APPLICATION = 'smash.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'smashdb', # Insert your database's name + 'USER': 'postgresmashuser', # Insert your database's user + 'PASSWORD': 'thePOSTGRESpassword', # Insert your user's password + 'HOST': 'localhost', + 'PORT': '' # '' === default one # Empty string is OK + + # If to use sqlite + # 'ENGINE': 'django.db.backends.sqlite3', + # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +STATIC_ROOT = '/tmp/static' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static +MEDIA_ROOT = '/tmp/media' # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py index 15e088c588272d0656c5c81f770317c7421c8e24..f607052acc2eb332339cbdbcff5909822274f772 100644 --- a/smash/web/api_urls.py +++ b/smash/web/api_urls.py @@ -17,9 +17,10 @@ from django.conf.urls import url from web import api_views urlpatterns = [ - url(r'cities$', api_views.cities, name='web.api.cities'), - url(r'countries$', api_views.countries, name='web.api.countries'), - url(r'specializations$', api_views.specializations, name='web.api.specializations'), - url(r'units$', api_views.units, name='web.api.units'), - url(r'referrals$', api_views.referrals, name='web.api.referrals'), + url(r'^cities$', api_views.cities, name='web.api.cities'), + url(r'^countries$', api_views.countries, name='web.api.countries'), + url(r'^specializations$', api_views.specializations, name='web.api.specializations'), + url(r'^units$', api_views.units, name='web.api.units'), + url(r'^referrals$', api_views.referrals, name='web.api.referrals'), + url(r'^appointment_types$', api_views.appointment_types, name='web.api.appointment_types'), ] diff --git a/smash/web/api_views.py b/smash/web/api_views.py index b08f7c70d49118997adaaecace3f8279e9988271..d4234e9e8eb8f5807e7fcd5f9280cb528772a5ec 100644 --- a/smash/web/api_views.py +++ b/smash/web/api_views.py @@ -43,3 +43,18 @@ def units(request): return JsonResponse({ "units" : [x[0] for x in X] }) + +@login_required +def appointment_types(request): + appointments = AppointmentType.objects.filter().all() + result = [] + for appointment in appointments: + result.append({ + "id": appointment.id, + "type": appointment.code, + "default_duration": appointment.default_duration, + "can_be_parallelized": appointment.can_be_parallelized, + }) + return JsonResponse({ + "appointment_types" : result + }) diff --git a/smash/web/forms.py b/smash/web/forms.py index 5df96b139a5ce82909d37a17021ccff7dca13724..5b1d8c25a19162761a85cd8fd9ead0a5a2d07565 100644 --- a/smash/web/forms.py +++ b/smash/web/forms.py @@ -1,5 +1,5 @@ from django import forms -from django.forms import ModelForm +from django.forms import ModelForm, Form from .models import * from datetime import datetime @@ -35,6 +35,11 @@ class SubjectAddForm(ModelForm): required=False ) + datetime_contact_reminder = forms.DateField(label="Contact on", + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + required=False + ) + class Meta: model = Subject fields = '__all__' @@ -56,6 +61,11 @@ class SubjectDetailForm(ModelForm): class SubjectEditForm(ModelForm): + + datetime_contact_reminder = forms.DateField(label="Contact on", + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + required=False + ) date_born = forms.DateField(label="Date of birth", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), required=False @@ -135,6 +145,8 @@ class VisitDetailForm(ModelForm): widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d") ) + post_mail_sent = forms.RadioSelect() + class Meta: model = Visit exclude = ['is_finished'] @@ -156,3 +168,14 @@ class VisitAddForm(ModelForm): if (self.cleaned_data['datetime_begin']>=self.cleaned_data['datetime_end']): self.add_error('datetime_begin', "Start date must be before end date") self.add_error('datetime_end', "Start date must be before end date") + +class KitRequestForm(Form): + start_date = forms.DateField(label="From date", + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + required=False + ) + + end_date = forms.DateField(label="End date", + widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), + required=False + ) diff --git a/smash/web/models.py b/smash/web/models.py index 170690befbed6227eebfec5f2b8a61208187b113..71df4a06b74a8a6979d809b9b40aa8ef58ff273e 100644 --- a/smash/web/models.py +++ b/smash/web/models.py @@ -10,6 +10,8 @@ from datetime import timedelta def get_current_year(): return datetime.datetime.now().year +BOOL_CHOICES = ((True, 'Yes'), (False, 'No')) + class Location (models.Model): name = models.CharField(max_length=20) @@ -81,6 +83,15 @@ class Subject(models.Model): choices=SEX_CHOICES, verbose_name='Sex' ) + postponed = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Postponed', + default=False + ) + datetime_contact_reminder = models.DateField( + null=True, + blank=True, + verbose_name='Contact on', + ) type = models.CharField(max_length=1, choices=SUBJECT_TYPE_CHOICES, verbose_name='Type' @@ -122,17 +133,17 @@ class Subject(models.Model): ) phone_number_2 = models.CharField(max_length=20, null=True, - blank=True, + blank=True, verbose_name='Phone number 2' ) phone_number_3 = models.CharField(max_length=20, null=True, - blank=True, + blank=True, verbose_name='Phone number 3' ) email = models.EmailField( null=True, - blank=True, + blank=True, verbose_name='E-mail' ) date_born = models.DateField( @@ -214,6 +225,12 @@ class Item (models.Model): default=False, verbose_name='Is the item fixed?' ) + + disposable = models.BooleanField( + default=False, + verbose_name='Disposable set' + ) + name = models.CharField(max_length=255, verbose_name='Name' ) @@ -291,6 +308,10 @@ class AppointmentType (models.Model): verbose_name='Suggested rest time', default=0 ) + can_be_parallelized = models.BooleanField( + verbose_name='Can be parallelized', + default=False + ) REQ_ROLE_CHOICES = ( ('DOCTOR', 'Doctor'), ('NURSE', 'Nurse'), @@ -473,7 +494,10 @@ class Visit(models.Model): verbose_name='Has ended', default=False ) - + post_mail_sent = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Post mail sent', + default=False + ) appointment_types = models.ManyToManyField(AppointmentType, verbose_name='Requested appointments', blank=True, diff --git a/smash/web/static/js/appointment.js b/smash/web/static/js/appointment.js new file mode 100644 index 0000000000000000000000000000000000000000..a195413e582bf2a7569a3c57b140e9331164bf1b --- /dev/null +++ b/smash/web/static/js/appointment.js @@ -0,0 +1,44 @@ +function appointment_type_begaviour(selectObj, outObject,api_call) { + var appointment_types_data = null; + + function get_appointment_type_by_id(id) { + for (var i=0 ;i <appointment_types_data.length;i++) { + if (id == appointment_types_data[i].id) { + return appointment_types_data[i]; + } + } + return null; + } + function compute_time(object) { + var vals = object.val(); + var time = 0; + var max_paralel_time = 0; + for (var i=0;i<vals.length;i++) { + var appointment_type = get_appointment_type_by_id(vals[i]); + if (appointment_type== null) { + console.log("Cannot find appointment type with id: "+vals[i]); + } else { + if (appointment_type.can_be_parallelized) { + max_paralel_time = Math.max(max_paralel_time,appointment_type.default_duration); + } else { + time +=appointment_type.default_duration; + } + } + } + time = Math.max(time, max_paralel_time) + $(outObject).val(time+""); + } + $(selectObj ).change(function() { + var object = $(this) + if (appointment_types_data===null) { + $.get(api_call, function(data) { + appointment_types_data= data.appointment_types; + compute_time(object); + }); + } else { + compute_time(object); + } + + }); + +} diff --git a/smash/web/templates/appointments/add.html b/smash/web/templates/appointments/add.html index ea975643a3f0a4fe536878447e77c41fc709adf8..5b1bcef0884cf18657badba9c33d1ea6e5e61408 100644 --- a/smash/web/templates/appointments/add.html +++ b/smash/web/templates/appointments/add.html @@ -95,6 +95,7 @@ <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script> <script src="{% static 'AdminLTE/plugins/moment.js/moment.min.js' %}"></script> <script src="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.js' %}"></script> + <script src="{% static 'js/appointment.js' %}"></script> <script> $(function () { $('#table').DataTable({ @@ -152,6 +153,8 @@ }); }); + + appointment_type_begaviour($("[name='appointment_types']"), $("[name='length']"), "{% url 'web.api.appointment_types' %}"); </script> {% include "includes/datetimepicker.js.html" %} diff --git a/smash/web/templates/appointments/edit.html b/smash/web/templates/appointments/edit.html index 50d598d8065e3e7f6bf13f1889d0285fd5b762c0..8663014d7112a134ad02e617b655c0733fb68862 100644 --- a/smash/web/templates/appointments/edit.html +++ b/smash/web/templates/appointments/edit.html @@ -120,6 +120,7 @@ <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script> <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script> + <script src="{% static 'js/appointment.js' %}"></script> <script> $(function () { $('#table').DataTable({ @@ -131,6 +132,7 @@ "autoWidth": false }); }); + appointment_type_begaviour($("[name='appointment_types']"), $("[name='length']"), "{% url 'web.api.appointment_types' %}"); </script> {% include "includes/datetimepicker.js.html" %} diff --git a/smash/web/templates/appointments/index.html b/smash/web/templates/appointments/index.html index 3e35a34f8672051eaef93d2a2c22d3b68566d9a4..32897aeadf746587ad0387a02aa207d89e557d2c 100644 --- a/smash/web/templates/appointments/index.html +++ b/smash/web/templates/appointments/index.html @@ -134,7 +134,7 @@ "lengthChange": false, "searching": true, "ordering": true, - "order": [[ 2, "asc"]], + "order": [[ 3, "asc"]], "info": true, "autoWidth": false }); diff --git a/smash/web/templates/equipment_and_rooms/index.html b/smash/web/templates/equipment_and_rooms/index.html index 6c7a5f4fb99b8217171a0f4d0739cd09df2405e6..95d68215f8a6193d98efb7ef6bf61095c662c7fb 100644 --- a/smash/web/templates/equipment_and_rooms/index.html +++ b/smash/web/templates/equipment_and_rooms/index.html @@ -10,6 +10,7 @@ {% endblock breadcrumb %} {% block maincontent %} +{% comment %} <div class="box box-danger box-solid"> <div class="box-header with-border"> <h3 class="box-title">Not yet implemented</h3> @@ -27,8 +28,12 @@ <!-- /.box-body --> </div> +{% endcomment %} + <div class="row"> + {% comment %} <div class="col-md-3"> + <div class="bg-red-active small-box"> <div class="inner"> <h4>Types of equipment</h4> @@ -71,22 +76,24 @@ </a> </div> </div> + {% endcomment %} <div class="col-md-3"> <div class="bg-yellow-active small-box"> <div class="inner"> - <h4>Equipment requests</h4> + <h4>Kit requests</h4> <p> </p> </div> <div class="icon"> <i class="ion ion-compose"></i> </div> - <a href="#" class="small-box-footer"> - Edit <i class="fa fa-arrow-circle-right"></i> + <a href="{% url 'web.views.kit_requests' %}" class="small-box-footer"> + See <i class="fa fa-arrow-circle-right"></i> </a> </div> + {% comment %} <div class="bg-yellow small-box"> <div class="inner"> <h4>Equipment in rooms</h4> @@ -100,8 +107,10 @@ Edit <i class="fa fa-arrow-circle-right"></i> </a> </div> + {% endcomment %} </div> + {% comment %} <div class="col-md-3"> <div class="bg-green-active small-box"> <div class="inner"> @@ -145,5 +154,7 @@ </a> </div> </div> + {% endcomment %} + </div> {% endblock maincontent %} diff --git a/smash/web/templates/equipment_and_rooms/kit_requests.html b/smash/web/templates/equipment_and_rooms/kit_requests.html new file mode 100644 index 0000000000000000000000000000000000000000..b9881bd23f51782703c9782bd84cf63f094082e3 --- /dev/null +++ b/smash/web/templates/equipment_and_rooms/kit_requests.html @@ -0,0 +1,103 @@ +{% extends "_base.html" %} + +{% block ui_active_tab %}'equipment_and_rooms'{% endblock ui_active_tab %} +{% block page_header %} + Kits required between {{ start_date | date:"Y-m-d" }} and {% if end_date %} {{ end_date | date:"Y-m-d" }} {% else %} end of time {% endif %} +{% endblock page_header %} +{% block page_description %} +{% endblock page_description %} + +{% block styles %} +{{ block.super }} + {% include "includes/datepicker.css.html" %} +{% endblock styles %} + +{% block breadcrumb %} +{% include "equipment_and_rooms/breadcrumb.html" %} +{% endblock breadcrumb %} + +{% block maincontent %} + +{% block content %} +<div class="box box-info"> + <form method="post" action="" class="form-horizontal"> + {% csrf_token %} + + <div class="box-body"> + <div class="col-sm-6"> + {% for field in form %} + <div class="form-group {% if field.errors %}has-error{% endif %}"> + <label class="col-sm-4 control-label"> + {{ field.label }} + </label> + + <div class="col-sm-8"> + {{ field }} + </div> + + {% if field.errors %} + <span class="help-block"> + {{ field.errors }} + </span> + {% endif %} + </div> + {% endfor %} + </div> + </div> + + <div class="box-footer"> + <div class="col-sm-6"> + <button type="submit" class="btn btn-block btn-success">Search</button> + </div> + </div><!-- /.box-footer --> + + </form> + + <div class="box col-md-12"> + <table id="visit_table" class="table table-bordered table-striped"> + <thead> + <tr> + <th>Date</th> + <th>Kits</th> + </tr> + </thead> + <tbody> + {% for appointment in appointments %} + <tr> + <td>{{ appointment.datetime_when | date:"Y-m-d H:i"}} </td> + <td> + {% for type in appointment.appointment_types.all %} + {% for item in type.required_equipment.all %} + {% if item.disposable %} + {{ item.name }}, + {% endif %} + {% endfor %} + {% endfor %} + </td> + </tr> + {% endfor %} + </tbody> + </table> + + <div class="box-footer"> + <div class="col-sm-12"> + {% if end_date == None %} + <a href="{% url 'web.views.kit_requests_send_mail' start_date|date:"Y-m-d" %}" class="btn btn-block btn-default">Show email content</a> + {% else %} + <a href="{% url 'web.views.kit_requests_send_mail' start_date|date:"Y-m-d" end_date|date:"Y-m-d" %}" class="btn btn-block btn-default">Show email content</a> + {% endif %} + </div> + </div><!-- /.box-footer --> + + </div> + + +</div> +{% endblock %} +{% endblock maincontent %} + +{% block scripts %} + {{ block.super }} + + {% include "includes/datepicker.js.html" %} +{% endblock scripts %} diff --git a/smash/web/templates/equipment_and_rooms/kit_requests_send_mail.html b/smash/web/templates/equipment_and_rooms/kit_requests_send_mail.html new file mode 100644 index 0000000000000000000000000000000000000000..1c89a616bcb4c2e997d28b80d126e91908109cec --- /dev/null +++ b/smash/web/templates/equipment_and_rooms/kit_requests_send_mail.html @@ -0,0 +1,26 @@ +<h1>Kits required between {{ start_date }} and {% if end_date %} {{ end_date }} {% else %} end of time {% endif %}</h1> +<table> + <thead> + <tr> + <th>Date</th> + <th>Kits</th> + </tr> + </thead> + <tbody> + + {% for appointment in appointments %} + <tr> + <td>{{ appointment.datetime_when | date:"Y-m-d H:i"}} </td> + <td> + {% for type in appointment.appointment_types.all %} + {% for item in type.required_equipment.all %} + {% if item.disposable %} + {{ item.name }}, + {% endif %} + {% endfor %} + {% endfor %} + </td> + </tr> + {% endfor %} + </tbody> +</table> diff --git a/smash/web/templates/includes/tablesorter.tfoot.html b/smash/web/templates/includes/tablesorter.tfoot.html index d345ac6c9fce001a6ff774b90df66510581cdd28..b5b5536af2b6a4ecf9c852fc849fd0fb8cae96f7 100644 --- a/smash/web/templates/includes/tablesorter.tfoot.html +++ b/smash/web/templates/includes/tablesorter.tfoot.html @@ -1,6 +1,6 @@ <tfoot> <tr> - <th colspan="7" class="ts-pager form-inline"> + <th colspan="8" class="ts-pager form-inline"> <div class="btn-group btn-group-sm" role="group"> <button type="button" class="btn btn-default first"><span class="glyphicon glyphicon-step-backward"></span></button> <button type="button" class="btn btn-default prev"><span class="glyphicon glyphicon-backward"></span></button> @@ -19,4 +19,4 @@ <select class="form-control input-sm pagenum" title="Select page number"></select> </th> </tr> -</tfoot> \ No newline at end of file +</tfoot> diff --git a/smash/web/templates/login.html b/smash/web/templates/login.html index 1cdc90f5f1873ce649bce6ae8e710345fdb4838a..86b7238f0d148e5aa3ec9416c1a945840e9f0a5c 100644 --- a/smash/web/templates/login.html +++ b/smash/web/templates/login.html @@ -75,6 +75,9 @@ <form action="{% url 'web.views.login' %}" method="post"> {% csrf_token %} + {% if next %} + <input type="hidden" name="next" value="{{ next }}" /> + {% endif %} <div class="form-group has-feedback"> <input type="text" name="username" class="form-control" placeholder="Login"> diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html index 991e049ceeaeba1c2294375208eaa7440bfee197..889fb48da4794ed960c6470aeedd36fefb200d3c 100644 --- a/smash/web/templates/subjects/index.html +++ b/smash/web/templates/subjects/index.html @@ -34,11 +34,10 @@ <th>Screening</th> <th>First name</th> <th>Last name</th> - <th>Country</th> - <th data-sorter="false" data-filter="false">Languages</th> <th class="filter-select filter-exact" data-placeholder="Select location">Default location</th> <th>Dead</th> <th>Resigned</th> + <th>Postponed</th> <th>Edit</th> </tr> </thead> @@ -52,17 +51,10 @@ <td>{{ subject.screening_number }}</td> <td>{{ subject.first_name }}</td> <td>{{ subject.last_name }}</td> - <td>{{ subject.country }}</td> - <td> - {% autoescape off %} - {% for language in subject.languages.all %} - {{language.image_img}} - {% endfor %} - {% endautoescape %} - </td> <td>{{ subject.get_default_appointment_location_display }}</td> <td>{% if subject.dead %} YES {% else %} NO {% endif %} </td> <td>{% if subject.resigned %} YES {% else %} NO {% endif %} </td> + <td>{% if subject.postponed %} YES {% else %} NO {% endif %} </td> <td><a href="{% url 'web.views.subject_edit' subject.id %}" type="button" class="btn btn-block btn-default">Edit</a></td> </tr> {% endfor %} @@ -94,10 +86,7 @@ filter_cssFilter: "form-control", }, headers: { - 5: { sorter: false}, - 8: { sorter: false}, - 9: { sorter: false}, - 10: { sorter: false} + 0: { sorter: true}, } }).tablesorterPager({ container: $(".ts-pager"), diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index c8c748908ff4ba8ee947fa06dfcc513efdf5457e..520601b637e923bc3825a17787eccd68caf1a6c7 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -40,13 +40,17 @@ def create_worker(): email='jacob@bla', ) -def create_visit(subject): +def create_visit(subject = None): + if subject == None: + subject= create_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): +def create_appointment(visit= None): + if visit == None: + visit = create_visit() return Appointment.objects.create( visit = visit, length = 30, diff --git a/smash/web/tests/test_view_kit_request.py b/smash/web/tests/test_view_kit_request.py new file mode 100644 index 0000000000000000000000000000000000000000..6b32ea7e142954bb750a77cf80c40af4fcc39042 --- /dev/null +++ b/smash/web/tests/test_view_kit_request.py @@ -0,0 +1,76 @@ +from django.test import TestCase, RequestFactory +from django.urls import reverse + +from web.views import * + +from web.tests.functions import * + +class ViewFunctionsTests(TestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = create_user() + + def test_kit_requests(self): + request = self.factory.get(reverse('web.views.kit_requests')); + request.user = self.user + response = kit_requests(request); + self.assertEqual(response.status_code, 200) + + def test_kit_requests_2(self): + item_name = "Test item to be ordered" + item = Item.objects.create(disposable=True, name=item_name) + appointment_type = create_appointment_type() + appointment_type.required_equipment.add(item) + appointment_type.save() + + appointment = create_appointment() + appointment.datetime_when = get_today_midnight_date() + datetime.timedelta(days=2) + appointment.appointment_types.add(appointment_type) + appointment.save() + + request = self.factory.get(reverse('web.views.kit_requests')); + request.user = self.user + response = kit_requests(request); + self.assertEqual(response.status_code, 200) + + self.assertTrue(item_name in response.content) + + def test_kit_requests_4(self): + item_name = "Test item to be ordered" + item = Item.objects.create(disposable=True, name=item_name) + appointment_type = create_appointment_type() + appointment_type.required_equipment.add(item) + appointment_type.save() + + appointment = create_appointment() + appointment.datetime_when = get_today_midnight_date() + datetime.timedelta(days=2) + appointment.appointment_types.add(appointment_type) + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + + request = self.factory.get(reverse('web.views.kit_requests')); + request.user = self.user + response = kit_requests(request); + self.assertEqual(response.status_code, 200) + + self.assertFalse(item_name in response.content) + + def test_kit_requests_3(self): + item_name = "Test item to be ordered" + item = Item.objects.create(disposable=True, name=item_name) + appointment_type = create_appointment_type() + appointment_type.required_equipment.add(item) + appointment_type.save() + + appointment = create_appointment() + appointment.datetime_when = get_today_midnight_date() + datetime.timedelta(days=2) + appointment.appointment_types.add(appointment_type) + appointment.location = create_location("other_loc") + appointment.save() + + request = self.factory.get(reverse('web.views.kit_requests')); + request.user = self.user + response = kit_requests(request); + self.assertEqual(response.status_code, 200) + + self.assertFalse(not item_name in response.content) diff --git a/smash/web/tests/test_view_notifications.py b/smash/web/tests/test_view_notifications.py index 82868c2765e70250566fe6bf0782a0aba62f05b6..9a2fb73aad6adf009be43399f0640e2fb77e9131 100644 --- a/smash/web/tests/test_view_notifications.py +++ b/smash/web/tests/test_view_notifications.py @@ -206,3 +206,89 @@ class NotificationViewTests(TestCase): notification = get_subject_with_no_visit_notifications_count(worker) self.assertEquals(original_notification.count +1, notification.count) + + + def test_get_approaching_visits_for_mail_contact_count(self): + original_notification = get_approaching_visits_for_mail_contact_count(self.user) + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=100) + visit.save() + + notification = get_approaching_visits_for_mail_contact_count(self.user) + self.assertEquals(original_notification.count + 1, notification.count) + + def test_get_approaching_visits_for_mail_contact_count_2(self): + original_notification = get_approaching_visits_for_mail_contact_count(self.user) + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=100) + visit.save() + appointment = create_appointment(visit) + + notification = get_approaching_visits_for_mail_contact_count(self.user) + self.assertEquals(original_notification.count, notification.count) + + def test_get_approaching_visits_for_mail_contact_count_3(self): + original_notification = get_approaching_visits_for_mail_contact_count(self.user) + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=100) + visit.save() + appointment = create_appointment(visit) + + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + + notification = get_approaching_visits_for_mail_contact_count(self.user) + self.assertEquals(original_notification.count + 1, notification.count) + + def test_get_approaching_visits_for_mail_contact_count_4(self): + original_notification = get_approaching_visits_for_mail_contact_count(self.user) + subject = create_subject() + visit = create_visit(subject) + visit.datetime_begin = get_today_midnight_date()+datetime.timedelta(days=100) + visit.post_mail_sent = True + visit.save() + + notification = get_approaching_visits_for_mail_contact_count(self.user) + self.assertEquals(original_notification.count, notification.count) + + def test_get_subjects_with_reminder_count(self): + original_without_visit_notification = get_subject_with_no_visit_notifications_count(self.user) + original_notification = get_subjects_with_reminder_count(self.user) + subject = create_subject() + subject.datetime_contact_reminder = get_today_midnight_date()+datetime.timedelta(days=-1) + subject.save() + + notification = get_subjects_with_reminder_count(self.user) + self.assertEquals(original_notification.count + 1, notification.count) + + notification = get_subject_with_no_visit_notifications_count(self.user) + self.assertEquals(original_without_visit_notification.count, notification.count) + + def test_get_subjects_with_reminder_count_2(self): + original_without_visit_notification = get_subject_with_no_visit_notifications_count(self.user) + original_notification = get_subjects_with_reminder_count(self.user) + subject = create_subject() + subject.datetime_contact_reminder = get_today_midnight_date()+datetime.timedelta(hours=23) + subject.save() + + notification = get_subjects_with_reminder_count(self.user) + self.assertEquals(original_notification.count + 1, notification.count) + + notification = get_subject_with_no_visit_notifications_count(self.user) + self.assertEquals(original_without_visit_notification.count, notification.count) + + def test_get_subjects_with_reminder_count_3(self): + original_without_visit_notification = get_subject_with_no_visit_notifications_count(self.user) + original_notification = get_subjects_with_reminder_count(self.user) + subject = create_subject() + subject.datetime_contact_reminder = get_today_midnight_date()+datetime.timedelta(days=2) + subject.save() + + notification = get_subjects_with_reminder_count(self.user) + self.assertEquals(original_notification.count, notification.count) + + notification = get_subject_with_no_visit_notifications_count(self.user) + self.assertEquals(original_without_visit_notification.count, notification.count) diff --git a/smash/web/urls.py b/smash/web/urls.py index 2129f7b67af2873d70618d465c2a5329f09902e8..46500c6bd3a953d157ae7a4eab6d64265a2f8061 100644 --- a/smash/web/urls.py +++ b/smash/web/urls.py @@ -33,6 +33,7 @@ urlpatterns = [ 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/approaching_post_mail$', views.approaching_visits_for_mail_contact, name='web.views.approaching_visits_for_mail_contact'), url(r'^visits/missing_appointments$', views.visits_with_missing_appointments, name='web.views.visits_with_missing_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'), @@ -41,6 +42,7 @@ urlpatterns = [ url(r'^subjects$', views.subjects, name='web.views.subjects'), url(r'^subjects/no_visit$', views.subject_no_visits, name='web.views.subject_no_visits'), + url(r'^subjects/equire_contact$', views.subject_require_contact, name='web.views.subject_require_contact'), url(r'^subjects/add$', views.subject_add, name='web.views.subject_add'), url(r'^subjects/subject_visit_details/(?P<id>\d+)$', views.subject_visit_details, name='web.views.subject_visit_details'), url(r'^subjects/edit/(?P<id>\d+)$', views.subject_edit, name='web.views.subject_edit'), @@ -56,6 +58,10 @@ urlpatterns = [ url(r'^equipment_and_rooms$', views.equipment_and_rooms, name='web.views.equipment_and_rooms'), url(r'^equipment_and_rooms/eqdef$', views.equipment_def, name='web.views.equipment_def'), + url(r'^equipment_and_rooms/kit_requests$', views.kit_requests, name='web.views.kit_requests'), + url(r'^equipment_and_rooms/kit_requests/(?P<start_date>[\w-]+)/$', views.kit_requests_send_mail, name='web.views.kit_requests_send_mail'), + url(r'^equipment_and_rooms/kit_requests/(?P<start_date>[\w-]+)/(?P<end_date>[\w-]+)/$', views.kit_requests_send_mail, name='web.views.kit_requests_send_mail'), + url(r'^mail_templates$', views.mail_templates, name='web.views.mail_templates'), diff --git a/smash/web/views.py b/smash/web/views.py index 63b6bad87ee63ac5ab9cfbcce12b7a58279fb33e..d7306572359ff81326d4da4cdabb81bc329de271 100644 --- a/smash/web/views.py +++ b/smash/web/views.py @@ -15,6 +15,7 @@ import datetime from django.db.models import Count from django.db.models import Case from django.db.models import When +from django.utils.dateparse import parse_datetime import csv @@ -53,13 +54,18 @@ def login(request): if request.GET and request.GET.get('error'): context['state'] = request.GET.get('error') + if request.method == "GET" and request.GET: + context['next'] = request.GET.get('next') + if request.method == "POST" and request.POST: state, message = do_login(request) if state == True: - return redirect(appointments) + if request.POST.get('next'): + return redirect(request.POST.get('next')) + else: + return redirect(appointments) else: return redirect('/login?error=' + message) - return render(request, "login.html", context) class NotificationCount(object): @@ -76,12 +82,18 @@ class NotificationCount(object): def get_filter_locations(user): worker = None + if isinstance(user, User): workers = Worker.objects.filter(user=user) if len(workers)>0: worker = workers[0] - else: + elif isinstance(user, Worker): worker = user + elif isinstance(user, AnonymousUser): + # anonymous user shouldn't see anything + return Location.objects.filter(id=-1) + elif user!=None: + raise TypeError("Unknown class type: "+user.__class__.__name__) if worker==None or worker.locations.count() == 0: return Location.objects.all() @@ -108,10 +120,31 @@ def get_subjects_with_no_visit(user): dead=False, resigned=False, my_count=0, - default_location__in = get_filter_locations(user) + default_location__in = get_filter_locations(user), + postponed=False, + datetime_contact_reminder__isnull=True, + ) + return result + +def get_subjects_with_reminder(user): + tomorrow = get_today_midnight_date() + datetime.timedelta(days=1) + + result = Subject.objects.filter( + dead=False, + resigned=False, + default_location__in = get_filter_locations(user), + datetime_contact_reminder__lt=tomorrow, ) return result +def get_subjects_with_reminder_count(user): + notification = NotificationCount( + title = "subject required contact", + count = get_subjects_with_reminder(user).count(), + style = "fa fa-users text-aqua", + type = 'web.views.subject_require_contact') + return notification + def get_subject_with_no_visit_notifications_count(user): notification = NotificationCount( title = "subject without visit", @@ -181,9 +214,17 @@ def get_approaching_visits_without_appointments_count(user): type = 'web.views.approaching_visits_without_appointments') return notification +def get_approaching_visits_for_mail_contact_count(user): + notification = NotificationCount( + title = "post mail for approaching visits", + count = get_approaching_visits_for_mail_contact(user).count(), + style = "fa fa-users text-aqua", + type = 'web.views.approaching_visits_for_mail_contact') + return notification + def get_approaching_visits_without_appointments(user): today = get_today_midnight_date() - today_plus_two_months =today+datetime.timedelta(days=93) + today_plus_two_months =today+datetime.timedelta(days=62) return Visit.objects.annotate(my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter( datetime_begin__gt = today, datetime_begin__lt = today_plus_two_months, @@ -191,6 +232,18 @@ def get_approaching_visits_without_appointments(user): subject__default_location__in = get_filter_locations(user), my_count=0) +def get_approaching_visits_for_mail_contact(user): + today = get_today_midnight_date() + today_plus_three_months =today+datetime.timedelta(days=91) + today_plus_six_months =today+datetime.timedelta(days=183) + return Visit.objects.annotate(my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter( + datetime_begin__gt = today_plus_three_months, + datetime_begin__lt = today_plus_six_months, + is_finished = False, + post_mail_sent = False, + subject__default_location__in = get_filter_locations(user), + my_count=0) + def get_unfinished_appointments_count(user): return NotificationCount( title = "unfinished appointments ", @@ -218,6 +271,9 @@ def get_notifications(the_user): notifications.append(get_unfinished_appointments_count(person)) notifications.append(get_visits_with_missing_appointments_count(person)) notifications.append(get_subject_with_no_visit_notifications_count(person)) + notifications.append(get_approaching_visits_for_mail_contact_count(person)) + notifications.append(get_subjects_with_reminder_count(person)) + for notification in notifications: count += notification.count @@ -281,9 +337,16 @@ def approaching_visits_without_appointments(request): context = { 'visit_list': get_approaching_visits_without_appointments(request.user) } + return wrap_response(request, 'visits/index.html', context) +def approaching_visits_for_mail_contact(request): + context = { + 'visit_list': get_approaching_visits_for_mail_contact(request.user) + } return wrap_response(request, 'visits/index.html', context) + + def visit_details(request, id): displayedVisit = get_object_or_404(Visit, id=id) if request.method == 'POST': @@ -371,6 +434,14 @@ def subject_no_visits(request): return wrap_response(request, 'subjects/index.html', context) +def subject_require_contact(request): + subjects_list = get_subjects_with_reminder(request.user).order_by('-last_name') + context = { + 'subjects_list': subjects_list + } + + return wrap_response(request, 'subjects/index.html', context) + def subject_edit(request, id): the_subject = get_object_or_404(Subject, id=id) if request.method == 'POST': @@ -632,6 +703,7 @@ def appointment_edit_datetime(request, id): form = AppointmentEditForm(instance=the_appointment) return wrap_response(request, 'appointments/edit.html', {'form': form}) +#because we don't wrap_response we must force login required @login_required def export_to_csv2(request, type="subjects"): #Create the HttpResponse object with the appropriate CSV header. @@ -700,3 +772,52 @@ def write_appointments_to_csv(writer): def export(request): return wrap_response(request, 'export/index.html',{}) + +def get_kit_requests(user, start_date = None, end_date = None): + if start_date == None: + start_date = get_today_midnight_date() + datetime.timedelta(days=1) + end_date = start_date + datetime.timedelta(days=7) + else : + if isinstance(start_date, str): + start_date = parse_datetime(start_date) + if (end_date != None) and (isinstance(end_date, str)): + end_date = parse_datetime(end_date) + + appointment_types = AppointmentType.objects.filter(required_equipment__disposable=True) + + appointments = Appointment.objects.filter( + appointment_types__in = appointment_types, + datetime_when__gt = start_date, + location__in = get_filter_locations(user), + status = Appointment.APPOINTMENT_STATUS_SCHEDULED, + ) + if end_date!=None: + appointments = appointments.filter(datetime_when__lt = end_date) + + result = { + 'start_date' : start_date, + 'end_date' : end_date, + 'appointments' : appointments, + } + return result + +def get_kit_requests_data(request, start_date = None, end_date = None): + form = KitRequestForm() + if request.method=='POST': + form = KitRequestForm(request.POST) + if form.is_valid(): + form_data = form.cleaned_data + start_date = form_data.get('start_date') + end_date = form_data.get('end_date') + + params = get_kit_requests(request.user, start_date, end_date) + params.update({ + 'form': form + }) + return params + +def kit_requests(request): + return wrap_response(request, 'equipment_and_rooms/kit_requests.html', get_kit_requests_data(request)) + +def kit_requests_send_mail(request, start_date, end_date = None): + return wrap_response(request, 'equipment_and_rooms/kit_requests_send_mail.html', get_kit_requests_data(request, start_date, end_date)) diff --git a/todos.txt b/todos.txt deleted file mode 100644 index 261e8d71eccbefea0f53193c9a39530a6c980183..0000000000000000000000000000000000000000 --- a/todos.txt +++ /dev/null @@ -1,25 +0,0 @@ -* TODO List - -** Notice -Moved all the important stuff to gitlab issues - -** Important - - label for's => give them correct html id's (all html forms). - Currently it works, but it's not valid HTML, as the ID (for labels) are all the same. - It requires a hack, because django forms are not-so-flexible - - implement `on leave` button - - what about possible sunday in availabilities? - - in beta remember to disable Guest Access - - in beta remember to forbid not-logged users to browse the API - -** Visual - - None - -** Possible improvements -- breadcrumbs - make them stack. Currently, they are made "half-manually". - While it is not a great problem, it is not a flexible solution -- make some names sound/look more pythonic. Unfortunately, due to - programmistic experience of our team, instead of sticking to python convencies - (lower case methods, fields; using underscore to break words instead of - camelCase), using full names and not abbrieviations, the code sometimes - resembles a mess.