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/smash/web/forms.py b/smash/web/forms.py index 2522e94b9b48a244f7c10b8a9d9471c481e715a5..5b1d8c25a19162761a85cd8fd9ead0a5a2d07565 100644 --- a/smash/web/forms.py +++ b/smash/web/forms.py @@ -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 diff --git a/smash/web/models.py b/smash/web/models.py index c7227e240fb554c16a4f16c03590f8732aa7a05c..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( @@ -483,7 +494,6 @@ class Visit(models.Model): verbose_name='Has ended', default=False ) - BOOL_CHOICES = ((True, 'Yes'), (False, 'No')) post_mail_sent = models.BooleanField(choices=BOOL_CHOICES, verbose_name='Post mail sent', default=False 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/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/test_view_notifications.py b/smash/web/tests/test_view_notifications.py index 2447e4a2d68173f768729b78785357ae330b9b14..9a2fb73aad6adf009be43399f0640e2fb77e9131 100644 --- a/smash/web/tests/test_view_notifications.py +++ b/smash/web/tests/test_view_notifications.py @@ -253,3 +253,42 @@ class NotificationViewTests(TestCase): 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 ec7fc856eb4d46e854ea79286f0f0787bec91536..46500c6bd3a953d157ae7a4eab6d64265a2f8061 100644 --- a/smash/web/urls.py +++ b/smash/web/urls.py @@ -42,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'), diff --git a/smash/web/views.py b/smash/web/views.py index ba5eeae2301ec2f1224f824ae58c1f4189d4ab9e..d7306572359ff81326d4da4cdabb81bc329de271 100644 --- a/smash/web/views.py +++ b/smash/web/views.py @@ -120,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", @@ -251,6 +272,8 @@ def get_notifications(the_user): 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 @@ -411,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':